Decoding the Saints Row 3 mesh format.

This thread will be a discussion of how the Saints Row 3 models work. Me and CJay are working on the format of the files and it's a continuation of the original work done by zoum.

The Saints Row 3 model files are broken up into several different files that work together. The main file are the ccmesh_pc or csmesh_pc files. They are the header files for the models and contain information that the other file types use. The ccmesh_pc is for rigged (or deformable) models and csmesh_pc is for static models.

The actual geometry (the shape of the object) is stored in the gcmesh_pc or gsmesh_pc files. Again gcmesh_pc for rigged meshes and gsmesh_pc for static meshes.

Rigged model files can also have the following files associated with them:

rig_pc - This contains the skeletal animation information.
cmorph_pc - This contains any morphing information.
matlib_pc - This contains material information i.e. how to render the model.

The next few pages will contain information about each of the file types. I've done this to help organise the layout and simplify editing.

This thread is a continuation of this thread.
 
The ccmesh_pc / csmesh_pc format.

The following is a pseudo c header file but with conditional statements in it.
Any Align16 or Align8 statements mean that the next piece of data is aligned to either 16 or 8 bytes.
A char array with no size parameter i.e. char Foo[]; indicates that the data is a null terminated c type string.

Code:
// This section defines the structure used by the file.
 
struct FVECTOR {
    float x;
    float y;
    float z;
};
 
struct FVECTOR4 {
    float x,y,z,w;
};
 
struct FVECTOR44 {
    float _11,_12,_13,_14;
    float _21,_22,_23,_24;
    float _31,_32,_33,_34;
    float _41,_41,_43,_44;
};
 
struct TEXTURENAME {
    char Name[];
};
 
struct TEXTURENAME2 {
    char Unknown[];
    char Name[];
};
 
struct POINTSOFINTEREST {
    char Name[64];
    int unknown;
    FVECTOR Position;
    FVECTOR4 Orientation;
};
 
struct THING1BLOCK {
    int unknown;
    int unknown2;
    FVECTOR unknown3;
    float unknown4;
};
 
struct THING2BLOCK {
    int unknown;
    int unknown2;
    FVECTOR normal;
    FVECTOR position;
    float float1;
    float float2;
};
 
struct MODELDATA {
    int Nine;    // Always 9
    unsigned int CRC;
    int SizeOfModelData;
    unsigned int SizeOfgmesh_pc;
    unsigned int Flags;
    int unknown2[7];
    int IndicesCount;
    int Unknown3[3];
    unsigned char IndexStride;
    char unknown4;
    short unknown5;
    int unknown6;
    int BonesInRigCount;
    int unknown7[7];
    FVECTOR scale;
    int unknown8[3];
    int VertexCount;
    unsigned char VertexStride;
    unsigned char Unknown9;
    short unknown10;
    unsigned char unknown11;
};
 
struct BLOCK2 {
    int Magic;
    int Unknown1;
};
 
struct BONES {
    int BoneData[file.NumberOfBones];
};
 
struct TEXTUREBLOCK {
    int SizeOfTextureNameList;
    int unknown1;
    int TextureNameListCount;
    int unknown2[4];
    TEXTURENAME TextureNames[TextureNameListCount];
    ALIGN16;
};
 
struct BLOCK3 {
    int Unknown1;
    float Unknown2[11];
};
 
// This is the actual start of the file format.
 
struct FILE {
    char Magic[2];    // Always T8
    short Version;      // Always 4
    TEXTUREBLOCK TextureInfo;
    int Block1Magic;    // Always 0x424BD00D
    int unknown3;
    short PointsOfInterestCount;
    short NumberOfBones;
    short unknown4[2];
    short Block3Count;
    short unknown4a;
    int unknown5[5];
    FVECTOR BoundingSphereCentre;
    float BoundingSphereRadius;
    short Thing1Count;
    short Thing2Count;
    short unknown6[2];
    int unknown7[18];
    int TextureNames2Size;
    int unknown8;
    char TextureNames2[TextureNames2Size];
    char NullTerminator;
    if(Thing1Count>0)
        ALIGN16;
    else
        ALIGN8;
    POINTSOFINTEREST PointsOfInterest[PointsOfInterestCount];
    if (PointsOfInterestCount>0)
        ALIGN16;
    THING1BLOCK Thing1Data[Thing1Count];
    if (Thing1Count>0)
        ALIGN16;
    THING2BLOCK Thing2Data[Thing2Count];
    if (Thing2Count>0)
        ALIGN16;
    int BoneData[NumberOfBones];
    if (NumberOfBones>0)
        ALIGN8;
    MODELDATA ModelData;
        ALIGN8;
    unsigned int Unknown9;
    unsigned int Unknown10;
    unsigned char BoneIndexes[ModelData.BonesInRigCount];
    if (ModelData.BonesInRigCount>0)
    {
        unsigned char BonesInRigCheck;
    }
        ALIGN16;
    BLOCK3 Unknown11[Block3Count];
}file;

There is more data in the file that I haven't decoded yet. I think the last few bytes is the material properties of the mesh

This is the format of the gcmesh_pc / gsmesh_pc files:

Any references to data held in the corresponding ccmesh_pc / gcmesh_pc file is prefixed with ccmesh_pc.

Code:
struct FVERTEX {
    float x,y,z;
};
 
struct NORMAL {
    unsigned char x,y,z;    // Normal co-ords are (data-128)/128 I think
    unsigned char Unknown;
};
 
struct VERTEXDATA {
    FVERTEX Position;
    NORMAL    Normal;
    char OtherData[ccmesh_pc.ModelData.VertexStride-16];
};
 
int CRCofFile;
ALIGN16;
short IndexData[ccmesh_pc.ModelData.IndicesCount];
ALIGN16;
VERTEXDATA Vertices[ccmesh_pc.ModelData.VertexCount];
ALIGN16;
int CopyOfCRCofFile;

The vertex data is different according to the shaders that the model uses. It's basically the raw data that the graphics card sends to the Vertex Shader.

It can also contain UV information. The UV is stored as 2 shorts with 0 being the minimum value of the mapping and 2048 being the maximum. The vertex data can have up to 3 sets of UV data in it.
 
Any information on LOD? As I saw in CJay's model viewer you figured out the order of faces and as I understood ccmesh should contain pointers to modelparts, in your file structure you have only one model structure or I am missing something?
 
Any information on LOD? As I saw in CJay's model viewer you figured out the order of faces and as I understood ccmesh should contain pointers to modelparts, in your file structure you have only one model structure or I am missing something?

I haven't got that far into decoding the format. I'm working on working out the structure of the header file first. Hopefully CJay will share his knowledge as he seems to be a bit further in decoding the actual model geometry data and has a tool to visualise the data. It looks like he has a better knowledge of the format than I do.

Just looking at the data either the index list is a tri-strip list and/or the levels of detail are interlaced together in the index list. (It's definitely not a simple tri-face list as the data isn't always dividable by 3 and you end up with faces where all 3 points point to the same index co-ordinate). However I would have to write a model viewer like CJay has to give a definitive answer.
 
I haven't got that far into decoding the format. I'm working on working out the structure of the header file first. Hopefully CJay will share his knowledge as he seems to be a bit further in decoding the actual model geometry data and has a tool to visualise the data. It looks like he has a better knowledge of the format than I do.

Just looking at the data either the index list is a tri-strip list and/or the levels of detail are interlaced together in the index list. (It's definitely not a simple tri-face list as the data isn't always dividable by 3 and you end up with faces where all 3 points point to the same index co-ordinate). However I would have to write a model viewer like CJay has to give a definitive answer.

not a problem with 3 points with same coords, its a degenerate strip, is not that hard to read, just need to skip the Findex1 == Findex2 || Findex2 == Findex3 || Findex 3 == Findex1
 
Is anyone working on this any more? I would love to see some progress. It seems way over my head, I just have basic 3D modeling knowledge and only know a bit of Python/Javascript...
 
Bumping this old topic with some news which some of you might like.

I have been fiddling with the model format descriptions I have found here and have been building additions/improvements to the maxscript importer made by mariokart64n. So far I have managed to correctly import texture vertices, normals, material id's and LOD-data.

SRIII_importer.jpg


I made use of the LOD data found in the ccmesh file to determine which part of the model use which faces of the Indexdata, so you don't get any more degenerate faces. If you take shaundy's ccmesh file for example, the LOD data is found at the entry 800x. There are 28 entry's of each 5 integers. The layout is like this: [material id, startindice, number of indices, unknown, unknown]. I'm still hoping to figure out those last two numbers. Also it appears shaundy has 4 lod levels, so the first 7 entries are for the highest level of lod. Her hair however is found at the end of the LOD entry's so that's still a bit of a mystery.

The normal data is correctly presented as: (data-128)/128. So that's confirmed. Unfortunately 3ds max has a little trouble keeping the normal information as soon as you collapse objects, but the initial import is looking great. I'll figure out what that is all about.

The script is now a rudimentary importer, but it should be re-written properly.
I also having trouble with the textures I managed to extract using ripper.exe (not sure who made that). The textures seem to be having a corrupt header, so I cannot load or view them apart from the thumbnail. I'm also still looking where to find the head data. Any clues on that? The lod head is present, but that's clearly not enough. :)

I will be working on rewriting the script from what I have now. As soon as that is done I will post it here. After that I will dig deeper into the model format hoping to figure out how to setup the bone and vertex-weight data. Who knows where this will end. There is still so much unknown data to uncover ;)
 
Last edited:
I've added automatic loading of the textures and normal maps. Turned out I didn't have the dds plugin for photoshop so at first I thought the textures were broken. Silly me...

script is still a mess unfortunately, as I haven't given it any time last week apart from this small update. I also had some issues with other models so I haven't quite gotten the final thing on the model reading part. Stay tuned!

SRIII_importer_V2.jpg


Can anybody tell me where I can find the high res face models?
 
Last edited:
Back
Top