Ripping Models From Directx9 Games Part 3 – Making Use Of Exported Data

[Intro]

If you read part 2 of this series, you have yourself a bunch of indices and vertices for 3d geometry in a game.  The question now is, how are you going to do anything with them?  That’s where this final part of the tutorial comes in.  Here we turn the geometry vertex data into a usable format- Wavefront OBJ, which is a format that is readily understood by most 3D modeling software.

slender_textured

Slenderman Re-Textured Model

[OBJ Format]

OBJ format is composed of a string representation of the various vertex coordinates of the geometry.  At its most basic form, all you need to have to have a conforming obj file is a series of vertices represented with the letter “v” followed by the vertex coordinates, and a series of faces represented by the letter “f” followed by two or three indices which identify the vertices which compose that particular face.  It is worth noting that for obj format purposes you start counting the vertices from 1 as opposed to 0.  Thus, the following obj file:

v -1 0 1
v 3 0 1
v 1 0 1
f 1 2 3

represents a triangle composed of vertices (-1,0,1), (3,0,1), (1,0,1).

[Turning Intercepted Buffer Data Into OBJ]

In part 2 of this article we talked about getting access to the raw index and vertex data needed to capture the game’s 3d geometry.  Turning that data into a usable obj file is actually quite simple if we keep in mind a couple of considerations.  First, the index values you get from the game’s index buffers might not start from 1, and the values may not have any logical relationship to each other.  Theoretically you could have index_buffer[0] = 300, index_buffer[1] = 50, etc.  If we were to use these values to populate the faces in our OBJ file, we would end up with either a file with invalid indices, or a file that does not accurately represent our geometry.  For this reason, the first thing you should do is create a mapping between the in game index values, and index values starting from 1.

[TriangleLists v. TriangleStrips]

As you can see, obj format is basically a triangle list representation of the geometry.  So if you have a model that is being rendered using a triangle list, all that is required is that you dump each vertex preceded by a “v” character, and all the faces preceded by a “f” character like so:

HRESULT WINAPI PopulateTriangleList(UINT PrimCount, string filename, UINT StartIndex, UINT MinIndex, INT BaseVertexIndex, UINT NumVertices, WORD * indices, void * verts)
{
    int f = 0;
 
    for(int i = StartIndex; i < StartIndex+PrimCount*3; i++)
    {
        vertex v = GetVertex(indices[i]+BaseVertexIndex, verts);
        if(!VertContains(v))
        {
            vertex_vec.push_back(v);
            out << "v " << v.x << " " << v.y << " " << v.z << endl;
        }
 
        vmap[indices[i]+BaseVertexIndex] = GetIndex(v);
    }
 
    for(int i = StartIndex; i < StartIndex+PrimCount*3; i++)
    {
        if(f % 3 == 0)
            out << endl << "f ";
 
        out << vmap[indices[i]+BaseVertexIndex] << " ";
        f++;
    }
    return 0;
}

If your model is being rendered from a triangle strip, then you have to rebuild the missing vertices and faces. The code looks like this:

HRESULT WINAPI PopulateTriangleStrip(UINT PrimCount, string filename, UINT StartIndex, UINT MinIndex, UINT BaseVertexIndex, UINT NumVertices, WORD * indices, void * verts)
{
    int f = 0;
    int count =0;
 
    for(int i = StartIndex; i < StartIndex+PrimCount; i++)
    {
        vertex v = GetVertex(indices[i]+BaseVertexIndex, verts);
 
        if(!VertContains(v))
        {
            vertex_vec.push_back(v);
            out << "v " << v.x << " " << v.y << " " << v.z << endl;
        }
 
        vmap[indices[i]+BaseVertexIndex] = GetIndex(v);
    }
 
    //build initial face
    out << "f " << vmap[indices[StartIndex]+BaseVertexIndex] << " " << vmap[indices[StartIndex+1]+BaseVertexIndex]
    << " " << vmap[indices[StartIndex+2]+BaseVertexIndex] << endl;
 
    //skip the first 3vertices since we already used them to lay the first face
    //Go from the fourth vertice until primitiveCount+2 (since we skipped the first two vertices, we need to add
    //to the primcount to get all the faces in the model
    for(int i = StartIndex+3; i < StartIndex+PrimCount+2; i++)
    {	
        out << "f ";
        out << vmap[indices[i-2]+BaseVertexIndex] << " ";
        out << vmap[indices[i-1]+BaseVertexIndex] << " ";
        out << vmap[indices[i]+BaseVertexIndex] << endl;
    }
 
    return 0;
}

And that’s pretty much it, if you run into a model rendered from a triangle fan, or a line, or point or whatever you’re on your own. I haven’t had to deal with any of that yet, but I am sure it should not be too hard to deal with.

[Conclusion]

This series pretty much covers all there is to implementing a selective 3d model ripper.  In the future I might write an article on how to dump an entire frame with all the models in it, as opposed to dumping selective models.  If you want to see a working project with source that implements what has been discussed here, checkout this project.  I might also put together something for directx10 and 11, and OpenGL in the near future.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>