What follows is a technical description of the SKD version 5 and the SKC version 13 formats (the ones used by the vanilla Allied Assault). The programming language used is C. It probably won't tell you much unless you have some programming knowledge.
The files are little-endian, binary. The data types used are signed 32-bit integers (int), 32-bit IEEE floats (float), 3D-space vectors (typedef float vec3_t[3]), signed 8-bit integers (char), unsigned 8-bit integers (typedef unsigned char byte) and fixed-length strings/byte arrays (char[x]).
Note that this is still a work in progress and not everything has yet been reverse-engineered (forward kinematics in SKC, face morphs in SKD, also something which looks like some kind of hitbox data, but so far I wasn't able to produce anything meaningful out of it). All struct elements named dummy* I obviously do not know the meaning of yet. The comments mostly come from the sources of Quake III and skl2md4, as I was using them as a reference.
The SKD 5 format - revision 1 (02.02.2008)
Let's take a look at the structs:
Code: Select all
/*
==============================================================================
SKD file format
==============================================================================
*/
#define SKD_IDENT (('D'<<24)+('M'<<16)+('K'<<8)+'S')
#define SKD_VERSION 5
#define SKD_MAX_BONES 128
#define SKD_SURFACE_IDENT ((' '<<24)+('L'<<16)+('K'<<8)+'S')
typedef struct {
int dummy;
} skdHitbox_t;
typedef struct {
int boneIndex; // these are indexes into the boneReferences,
float boneWeight; // not the global per-frame bone list
vec3_t offset;
} skdWeight_t;
typedef struct {
int dummy[4];
} skdMorph_t;
typedef struct {
vec3_t normal;
vec2_t texCoords;
int numWeights;
int numMorphs;
/*skdWeight_t weights[1]; // variable sized
skdMorph_t morphs[1];*/
} skdVertex_t;
typedef struct {
int indices[3];
} skdTriangle_t;
typedef struct {
int ident;
char name[MAX_QPATH]; // polyset name
int numTriangles;
int numVerts;
int dummy1;
int ofsTriangles;
int ofsVerts;
int dummy2;
int ofsEnd; // next surface follows
int dummy3;
} skdSurface_t;
typedef enum {
JT_24BYTES1,
JT_POSROT_SKC,
JT_40BYTES1,
JT_24BYTES2,
JT_24BYTES3,
JT_40BYTES2,
JT_28BYTES
} skdJointType_t;
typedef struct {
char name[32];
char parent[32];
int jointType;
int ofsValues;
int ofsChannels;
int ofsRefs;
int ofsEnd;
} skdBone_t;
typedef struct {
int ident;
int version;
char name[MAX_QPATH]; // model name
int numSurfaces;
int numBones;
int ofsBones; // char name[ MAX_QPATH ]
int ofsSurfaces; // skdFrame_t[numFrames]
// each level of detail has completely separate sets of surfaces
int numLODs;
int ofsLODs;
int ofsEnd; // end of file
int lodIndex[8];
int dummy1;
int dummy2;
int numMorphTargets;
int ofsMorphTargets;
} skdHeader_t;- skdHeader_t: at the beginning of the file,
- skdBone_t: at offset skdHeader_t->ofsBones, skdHeader_t->numBones times; the next skdBone_t follows after skdBone_t->ofsEnd + skdBone_t->ofsValues,
- either 24, 12, 40 or 28 bytes of unknown data depending on skdBone_t->jointType (see skdJointType_t): at offset skdBone_t->ofsEnd + skdBone_t->ofsValues,
- skdSurface_t: at offset skdHeader_t->ofsSurfaces, skdHeader_t->numSurfaces times; the next skdSurface_t follows after skdSurface_t->ofsEnd,
- skdTriangle_t: at offset skdSurface_t->ofsTriangles, skdSurface_t->numTriangles times,
- skdVertex_t: at offset skdSurface_t->ofsVerts, skdSurface_t->numVerts times; the struct is variable-sized depending on the bone weight and (what looks like) morph data,
- skdWeight_t: immediately after a skdVertex_t struct, skdVertex_t->numWeights times,
- skdMorphs_t: immediately after the last skdWeight_t struct, sdkVertex_t->numMorphs times.
The SKC 13 format - revision 1 (02.02.2008)
Code: Select all
/*
==============================================================================
SKC file format
==============================================================================
*/
#define SKC_IDENT (('N'<<24)+('A'<<16)+('K'<<8)+'S')
#define SKC_VERSION 13
#define SKC_MAX_CHANNEL_CHARS 32
typedef struct {
float floatVal[4];
} skcBone_t;
typedef struct {
vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame
float radius; // dist from localOrigin to corner
vec3_t delta;
int dummy; // this actually looks like a float
int numChannels;
} skcFrame_t;
typedef struct {
int ident;
int version;
int type;
int ofsEnd;
float frameTime;
int dummy1;
int dummy2;
int dummy3;
int dummy4;
int numChannels;
int ofsChannels;
int numFrames;
} skcHeader_t;The data appears in the following order:
- skcHeader_t: beginning of the file,
- skcFrame_t: first one immediately after skcHeader_t; skcHeader->numFrames times; the next frame starts at offset skcFrame_t->ofsEnd from the previous one,
- skcBone_t: immediately after skcFrame_t; skcHeader_t->numChannels times,
- a list of channel names; fixed-length strings of SKC_MAX_CHANNEL_CHARS; at offset skcHeader_t->ofsChannels, skcHeader_t->numChannels times.
Here's hoping someone actually finds this information useful.





