X-Git-Url: https://harrygodden.com/git/?p=convexer.git;a=blobdiff_plain;f=cxr%2Fcxr_valve_bin.h;fp=cxr%2Fcxr_valve_bin.h;h=da6c84d6e11f81c12e00fe7e7081f897babfba90;hp=0000000000000000000000000000000000000000;hb=dbd379f76bcb2139fdb5740232511fa789018e10;hpb=8e1b6889db7ce10f8d594539ef74dc4390e8e891 diff --git a/cxr/cxr_valve_bin.h b/cxr/cxr_valve_bin.h new file mode 100644 index 0000000..da6c84d --- /dev/null +++ b/cxr/cxr_valve_bin.h @@ -0,0 +1,1324 @@ +/* + * The following is used to read the existing content from CS:GO compiled assets + * + * Supported formats: + * vdf + * vpk + * mdl + */ + +#ifndef CXR_VALVE_BIN_H +#define CXR_VALVE_BIN_H + +#include +#include + +#include "cxr_types.h" +#include "cxr_math.h" +#include "cxr_io.h" +#include "cxr_mem.h" +#include "cxr_log.h" + +typedef struct VPKHeader VPKHeader; +typedef struct VPKDirectoryEntry VPKDirectoryEntry; +typedef struct vdf_kv vdf_kv; +typedef struct vdf_node vdf_node; +typedef struct vdf_ctx vdf_ctx; +typedef struct valve_file_system valve_file_system; + +/* + * File system + */ + +static struct valve_file_system +{ + char *gamedir, + *exedir; + + /* Runtime */ + VPKHeader *vpk; + + cxr_abuffer searchpaths; + + FILE *current_archive; + u16 current_idx; + + int initialized; +} +fs_global = { .initialized = 0 }; + +CXR_API int cxr_fs_set_gameinfo( const char *path ); /* Setup system */ +CXR_API void cxr_fs_exit(void); /* Clean up */ +CXR_API char *cxr_fs_get( const char *path ); /* Get a file */ + +/* + * VPK reading + */ + +static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset ); +static void vpk_free( VPKHeader *self ); + +/* + * VDF reading + */ + +static vdf_node *vdf_open_file( const char *fn ); +static void vdf_free_r( vdf_node *p ); + +/* Starting from *it, get next child with matching name from node. */ +static vdf_node *vdf_next( vdf_node *node, const char *name, int *it ); + +/* Create new empty node attached to parent. name can be NULL */ +static vdf_node *vdf_create_node( vdf_node *parent, const char *name ); + +/* KV access */ +static const char *kv_get( vdf_node *node, const char *key, + const char *value_defalt ); + +/* Iterate each keyvalue starting from *it until key is matched */ +static char *kv_next( vdf_node *node, const char *key, int *it ); + +static int kv_get_int( vdf_node *node, const char *key, const int fallback ); +static float kv_get_float( vdf_node *node, const char *key, float fallback ); + +static void kv_int_array( vdf_node *node, const char *key, u32 count, + int *arr ); +static void kv_float_array( vdf_node *node, const char *key, u32 count, + float *arr ); +static void kv_double_array( vdf_node *node, const char *key, u32 count, + double *arr ); + +#define vdf_foreach( NODE, STR, AS ) \ +int __vdf_it_##AS = 0; \ +vdf_node * AS;\ +while( (AS = vdf_next( NODE, STR, &__vdf_it_##AS )) ) + +#define kv_foreach( NODE, STR, AS ) \ +int __kv_it_##AS = 0; \ +const char * AS;\ +while( (AS = kv_next( NODE, STR, &__kv_it_##AS )) ) +#pragma pack(push, 1) + +struct VPKHeader +{ + u32 Signature; + u32 Version; + u32 TreeSize; + u32 FileDataSectionSize; + u32 ArchiveMD5SectionSize; + u32 OtherMD5SectionSize; + u32 SignatureSectionSize; +}; + +struct VPKDirectoryEntry +{ + u32 CRC; + u16 PreloadBytes; + u16 ArchiveIndex; + u32 EntryOffset; + u32 EntryLength; + u16 Terminator; +}; +#pragma pack(pop) + +static void vpk_free( VPKHeader *self ) +{ + free( self ); +} + +static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset ) +{ + if( !self ) + return NULL; + + char wbuf[ 512 ]; + strcpy( wbuf, asset ); + + /* + * This currently fails if the filename doesn't have a file extension, or + * if it is located at the root path. I'm not sure if this is defined + * behaviour or not. TODO: allow it to work anyway + */ + + char *ext = cxr_findext( wbuf, '.' ); + + if( !ext ) return NULL; + *(ext-1) = 0x00; + + char *fn = cxr_findext( wbuf, '/' ); + + if( !fn ) return NULL; + *(fn-1) = 0x00; + + char *dir = wbuf; + char *pCur = ((char *)self) + sizeof( VPKHeader ); + + while( 1 ) + { + if( !*pCur ) break; + + int bExt = !strcmp( ext, pCur ); + + while( *( pCur ++ ) ) {}; + while( 1 ) + { + if( !*pCur ) { pCur ++; break; } + + int bDir = !strcmp( dir, pCur ); + + while( *( pCur ++ ) ) {}; + while( 1 ) + { + if( !*pCur ) { pCur ++; break; } + + const char *vpk_fn = pCur; + + while( *( pCur ++ ) ) {}; + VPKDirectoryEntry *entry = (VPKDirectoryEntry *)pCur; + + if( !strcmp( vpk_fn, fn ) && bExt && bDir ) + { + return entry; + } + + pCur += entry->PreloadBytes + sizeof( VPKDirectoryEntry ); + } + + if( bDir && bExt ) return NULL; + } + + if( bExt ) return NULL; + } + + return NULL; +} + +/* + * VDF + */ + +struct vdf_kv +{ + char *key; + char *value; +}; + +struct vdf_node +{ + char *name; + + vdf_node *parent; + + cxr_abuffer abnodes, + abpairs; + + u32 user; + u32 user1; +}; + +static vdf_node *vdf_next( vdf_node *node, const char *name, int *it ) +{ + if( !node ) + return NULL; + + for( int i = it? *it: 0; i < node->abnodes.count; i ++ ) + { + vdf_node **ptr_child = cxr_ab_ptr( &node->abnodes, i ), + *child = *ptr_child; + + if( !name || !strcmp( name, child->name )) + { + if( it ) *it = i+1; + return child; + } + } + + return NULL; +} + +static char *kv_next( vdf_node *node, const char *key, int *it ) +{ + char *val; + + if( node ) + { + while( *it < node->abpairs.count ) + { + vdf_kv *kv = cxr_ab_ptr( &node->abpairs, *it ); + + if( !strcmp( kv->key, key ) ) + { + val = kv->value; + *it = *it + 1; + return val; + } + + *it = *it + 1; + } + } + + return NULL; +} + +static const char *kv_get( vdf_node *node, const char *key, + const char *value_defalt ) +{ + int it = 0; + char *val = kv_next( node, key, &it ); + + if( val ) + return val; + + return value_defalt; +} + +static void vdf_str_to_int( const char *src, void *dest ) +{ + *((int *)dest) = atoi( src ); +} + +static void vdf_str_to_float( const char *src, void *dest ) +{ + *((float *)dest) = atof( src ); +} + +static void vdf_str_to_double( const char *src, void *dest ) +{ + *((double *)dest) = atof( src ); +} + +static void kv_parse_array( const char *source, u32 esize, u32 count, + void(*interp_func)(const char *src, void *dest), void *arr ) +{ + if( !source ) + return; + + char value_buf[ 64 ]; + int token = 0; + + u32 i = 0; + u32 k = 0; + + char const *c = source; + + while( *c ) + { + if( *c==' ' || *c=='\t' || *c=='[' || *c==']' || *c=='(' || *c==')' ) + { + if( token ) + { + value_buf[ k ] = 0x00; + token = 0; + + interp_func( value_buf, ((u8 *)arr) + i*esize ); + i ++; + + if( i >= count ) + { + break; + } + } + } + else + { + if( !token ) + { + token = 1; + k = 0; + } + + if( token ) + { + if( k < sizeof( value_buf ) - 1 ) + { + value_buf[ k ++ ] = *c; + } + } + } + + c ++; + } + + /* Add remaining case if we hit null */ + if( token && (i < count) ) + { + value_buf[ k ] = 0x00; + interp_func( value_buf, ((u8 *)arr) + i*esize ); + } +} + +static void kv_int_array( vdf_node *node, const char *key, u32 count, int *arr ) +{ + kv_parse_array( kv_get( node, key, NULL ), sizeof(int), count, + vdf_str_to_int, arr ); +} + +static void kv_float_array( vdf_node *node, const char *key, u32 count, + float *arr ) +{ + kv_parse_array( kv_get( node, key, NULL ), sizeof(float), count, + vdf_str_to_float, arr ); +} + +static void kv_double_array( vdf_node *node, const char *key, u32 count, + double *arr ) +{ + kv_parse_array( kv_get( node, key, NULL ), sizeof(double), count, + vdf_str_to_double, arr ); +} + +static int kv_get_int( vdf_node *node, const char *key, + const int default_value ) +{ + const char *v = kv_get( node, key, NULL ); + return v? atoi(v): default_value; +} + +static float kv_get_float( vdf_node *node, const char *key, + float default_value ) +{ + const char *v = kv_get( node, key, NULL ); + return v? atof( v ): default_value; +} + +static vdf_node *vdf_create_node( vdf_node *parent, const char *name ) +{ + vdf_node *node = calloc( 1, sizeof( vdf_node ) ); + + /* init buffers */ + cxr_ab_init( &node->abnodes, sizeof( vdf_node* ), 0 ); + cxr_ab_init( &node->abpairs, sizeof( vdf_kv ), 0 ); + + if( name ) + { + node->name = cxr_str_clone(name, 0); + } + + if( parent ) + { + node->parent = parent; + + vdf_node **child = cxr_ab_empty( &parent->abnodes ); + *child = node; + } + + return node; +} + +static void vdf_kv_append( vdf_node *p, const char *k, const char *v ) +{ + vdf_kv *kv = cxr_ab_empty( &p->abpairs ); + + u32 sv = strlen(v)+1; + u32 sk = strlen(k)+1; + + kv->key = malloc( sv+sk ); + kv->value = kv->key+sk; + + memcpy( kv->key, k, sk ); + memcpy( kv->value, v, sv ); +} + +static void vdf_free_r( vdf_node *p ) +{ + for( int i = 0; i < p->abpairs.count; i ++ ) + { + vdf_kv *kv = cxr_ab_ptr( &p->abpairs, i ); + free( kv->key ); /* key and value are allocated in the same buffer */ + } + + for( int i = 0; i < p->abnodes.count; i ++ ) + { + vdf_node **ptr_node = cxr_ab_ptr( &p->abnodes, i ); + vdf_free_r( *ptr_node ); + } + + cxr_ab_free( &p->abpairs ); + cxr_ab_free( &p->abnodes ); + + free( p->name ); + free( p ); +} + +/* + * Parsing + */ + +struct vdf_ctx +{ + char name[1024]; + + vdf_node *root; + + u32 lines; + u32 errors; + + struct + { + int wait; + int expect_decl; + + char *tokens[2]; + int i; + + char *ptr_read; + + vdf_node *pnode; + } + st; +}; + +static void vdf_newln( vdf_ctx *ctx ) +{ + ctx->lines ++; + + ctx->st.tokens[0] = NULL; + ctx->st.tokens[1] = NULL; + ctx->st.i = 0; +} + +static void vdf_endl( vdf_ctx *ctx ) +{ + if( ctx->st.tokens[0] ) + { + /* Keypair */ + if( ctx->st.tokens[1] ) + { + vdf_kv_append( ctx->st.pnode, ctx->st.tokens[0], ctx->st.tokens[1] ); + } + else + { + /* decl */ + strcpy( ctx->name, ctx->st.tokens[0] ); + ctx->st.expect_decl = 1; + } + } + + vdf_newln( ctx ); +} + +static int vdf_line_control( vdf_ctx *ctx ) +{ + if( *ctx->st.ptr_read == '\r' ) + { + *ctx->st.ptr_read = 0x00; + return 1; + } + if( *ctx->st.ptr_read == '\n' ) + { + *ctx->st.ptr_read = 0x00; + vdf_endl( ctx ); + return 2; + } + + return 0; +} + +static void vdf_wait_endl( vdf_ctx *ctx ) +{ + while( (*ctx->st.ptr_read) && (*ctx->st.ptr_read != '\n') ) + { + if( vdf_line_control( ctx ) == 2 ) + { + return; + } + + ctx->st.ptr_read ++; + } +} + +static void vdf_parse_string( vdf_ctx *ctx ) +{ + while( *ctx->st.ptr_read ) + { + if( *ctx->st.ptr_read == '"' ) + { + *ctx->st.ptr_read = 0x00; + return; + } + + if( vdf_line_control( ctx ) ) + { + /* Unexpected end of line */ + return; + } + + ctx->st.ptr_read ++; + } +} + +static int vdf_parse_structure( vdf_ctx *ctx ) +{ + if( *ctx->st.ptr_read == '{' ) + { + if( ctx->st.tokens[0] || !ctx->st.expect_decl ) + { + /* Unexpected token '{' */ + ctx->errors ++; + } + + ctx->st.expect_decl = 0; + ctx->st.pnode = vdf_create_node( ctx->st.pnode, ctx->name ); + + vdf_wait_endl( ctx ); + return 1; + } + + if( *ctx->st.ptr_read == '}' ) + { + if( !ctx->st.pnode->parent ) + { + /* Unexpected token '}' */ + ctx->errors ++; + } + else + { + ctx->st.pnode = ctx->st.pnode->parent; + } + + vdf_wait_endl( ctx ); + return 1; + } + + return 0; +} + +static void vdf_parse_begin_token( vdf_ctx *ctx, char *ptr ) +{ + ctx->st.tokens[ ctx->st.i ] = ptr; + + if( ctx->st.expect_decl ) + { + /* Unexpected token 'name' */ + ctx->errors ++; + } +} + +static void vdf_parse_feedbuffer( vdf_ctx *ctx, char *buf ) +{ + ctx->st.ptr_read = buf; + + while( *ctx->st.ptr_read ) + { + if( !vdf_line_control( ctx ) ) + { + if( (*ctx->st.ptr_read == '/') && (ctx->st.ptr_read[1] == '/') ) + { + *ctx->st.ptr_read = 0x00; + ctx->st.ptr_read += 2; + + vdf_endl( ctx ); + vdf_wait_endl( ctx ); + } + else + { + if( !vdf_parse_structure( ctx ) ) + { + if( *ctx->st.ptr_read == ' ' || *ctx->st.ptr_read == '\t' ) + { + *ctx->st.ptr_read = 0x00; + + if( ctx->st.tokens[ ctx->st.i ] ) + { + ctx->st.i ++; + + if( ctx->st.i == 2 ) + { + vdf_wait_endl( ctx ); + } + } + } + + /* New entry */ + else if( !ctx->st.tokens[ ctx->st.i ] ) + { + if( *ctx->st.ptr_read == '"' ) + { + *ctx->st.ptr_read = 0x00; + ctx->st.ptr_read ++; + + vdf_parse_begin_token( ctx, ctx->st.ptr_read ); + vdf_parse_string( ctx ); + } + else + { + if( !( *ctx->st.ptr_read == '/' && + *(ctx->st.ptr_read + 1) == *ctx->st.ptr_read ) ) + { + vdf_parse_begin_token( ctx, ctx->st.ptr_read ); + } + } + } + } + } + } + + ctx->st.ptr_read ++; + } +} + +static int vdf_load_into( const char *fn, vdf_node *node ) +{ + char *text_src = cxr_textasset_read( fn ); + + if( !text_src ) + { + return 0; + } + + vdf_ctx ctx = {0}; + ctx.root = ctx.st.pnode = node; + + vdf_newln( &ctx ); + vdf_parse_feedbuffer( &ctx, text_src ); + free( text_src ); + + return 1; +} + +static vdf_node *vdf_open_file( const char *fn ) +{ + vdf_node *root = vdf_create_node( NULL, NULL ); + if( vdf_load_into( fn, root ) ) + { + return root; + } + else + { + vdf_free_r( root ); + return NULL; + } +} + +/* + * File system + */ + +CXR_API i32 cxr_fs_set_gameinfo( const char *path ) +{ + valve_file_system *fs = &fs_global; + + if( fs->initialized ) + cxr_fs_exit(); + + vdf_node *info = vdf_open_file( path ); + if( !info ) + return 0; + + fs->gamedir = cxr_str_clone( path, 0 ); + cxr_downlvl( fs->gamedir ); + + fs->exedir = cxr_str_clone( fs->gamedir, 0 ); + cxr_downlvl( fs->exedir ); + + cxr_log( "Setting up file system:\n" + "gameinfo: %s\n" + "gamedir: %s\n" + "exedir: %s\n", + + path, fs->gamedir, fs->exedir ); + + /* get search paths */ + vdf_node *search_paths = + vdf_next + ( + vdf_next + ( + vdf_next( info, "GameInfo", NULL ), + "FileSystem", + NULL + ), + "SearchPaths", + NULL + ); + + cxr_ab_init( &fs->searchpaths, sizeof( char *), 0 ); + + kv_foreach( search_paths, "Game", kv_game ) + { + cxr_log( "Game %s\n", kv_game ); + + if( kv_game[0] == '|' ) continue; + + char *buf; + if( cxr_path_is_abs( kv_game ) ) + { + buf = cxr_str_clone( kv_game, 1 ); + strcat( buf, "/" ); + } + else + { + buf = cxr_str_clone( fs->exedir, strlen(kv_game)+1 ); + strcat( buf, kv_game ); + strcat( buf, "/" ); + } + + char **sp = cxr_ab_empty( &fs->searchpaths ); + *sp = buf; + } + + vdf_free_r( info ); + + /* Find pack diretory */ + char pack_path[512]; + for( int i = 0; i < fs->searchpaths.count; i ++ ) + { + char **sp = cxr_ab_ptr( &fs->searchpaths, i ); + + strcpy( pack_path, *sp ); + strcat( pack_path, "pak01_dir.vpk" ); + + if( (fs->vpk = (VPKHeader *)cxr_asset_read( pack_path )) ) + break; + } + + if( !fs->vpk ) + { + cxr_log( "Could not locate pak01_dir.vpk in %i searchpaths. " + "Stock models will not load!\n", fs->searchpaths.count ); + } + + fs->initialized = 1; + return 1; +} + +CXR_API void cxr_fs_exit(void) +{ + valve_file_system *fs = &fs_global; + + for( int i = 0; i < fs->searchpaths.count; i ++ ) + { + char **sp = cxr_ab_ptr( &fs->searchpaths, i ); + free( *sp ); + } + + cxr_ab_free( &fs->searchpaths ); + + if( fs->vpk ) + { + vpk_free( fs->vpk ); + fs->vpk = NULL; + } + + if( fs->current_archive ) + { + fclose( fs->current_archive ); + fs->current_archive = NULL; + } + + free( fs->gamedir ); + free( fs->exedir ); + + memset( fs, 0, sizeof( valve_file_system ) ); +} + +CXR_API char *cxr_fs_get( const char *path ) +{ + valve_file_system *fs = &fs_global; + + if( !fs->initialized ) + return NULL; + + VPKDirectoryEntry *entry; + char pak[ 533 ]; + + if( fs->vpk ) + { + if( (entry = vpk_find( fs->vpk, path )) ) + { + if( entry->ArchiveIndex != fs->current_idx ) + { + if( fs->current_archive ) + { + fclose( fs->current_archive ); + fs->current_archive = NULL; + } + + fs->current_idx = entry->ArchiveIndex; + } + + if( !fs->current_archive ) + { + sprintf( pak, "%scsgo/pak01_%03hu.vpk", fs->exedir, + fs->current_idx ); + fs->current_archive = fopen( pak, "rb" ); + + if( !fs->current_archive ) + { + cxr_log( "Could not locate %s\n", pak ); + return NULL; + } + } + + char *filebuf = malloc( entry->EntryLength ); + + fseek( fs->current_archive, entry->EntryOffset, SEEK_SET ); + if( fread( filebuf, 1, entry->EntryLength, fs->current_archive ) + == entry->EntryLength ) + { + return filebuf; + } + else + { + free( filebuf ); + return NULL; + } + } + } + + /* Use physical search paths */ + char path_buf[ 512 ]; + + for( int i = 0; i < fs->searchpaths.count; i ++ ) + { + char **sp = cxr_ab_ptr( &fs->searchpaths, i ); + + strcpy( path_buf, *sp ); + strcat( path_buf, path ); + + char *filebuf; + if( (filebuf = cxr_asset_read( path_buf )) ) + return filebuf; + } + + /* File not found */ + return NULL; +} + +/* + * VMDL interface + * + * This software is not affiliated with Valve Corporation + * We are not affiliated, associated, authorized, endorsed by, or in any way + * officially connected with Valve Corporation, or any of its subsidiaries or + * its affiliates. + * + * All trademarks are property of their respective owners + */ + +#define MAX_NUM_LODS 8 +#define MAX_NUM_BONES_PER_VERT 3 + +#pragma pack(push, 1) +typedef struct +{ + float weight[MAX_NUM_BONES_PER_VERT]; + char bone[MAX_NUM_BONES_PER_VERT]; + char numbones; +} +boneWeight_t; + +typedef struct +{ + boneWeight_t boneweights; + float pos[3]; + float norm[3]; + float uv[2]; +} +mstudiovertex_t; + +typedef struct +{ + int id; + int version; + int checksum; + int numLods; + int numLodVertexes[MAX_NUM_LODS]; + int numFixups; + int fixupTableStart; + int vertexDataStart; + int tangentDataStart; +} +vertexFileHeader_t; + +#pragma pack(pop) + +static mstudiovertex_t *GetVertexData( vertexFileHeader_t *t ) +{ + return (mstudiovertex_t *) ( (char *)t + t->vertexDataStart ); +} + +/* + * VTX file format + */ + +#pragma pack(push, 1) + +typedef struct +{ + unsigned char boneWeightIndex[3]; + unsigned char numBones; + + unsigned short origMeshVertID; + char boneID[3]; +} +VTXVertex_t; + +enum StripGroupFlags +{ + STRIPGROUP_IS_FLEXED = 0x01, + STRIPGROUP_IS_HWSKINNED = 0x02, + STRIPGROUP_IS_DELTA_FLEXED = 0x04, + STRIPGROUP_SUPPRESS_HW_MORPH = 0x08 +}; + +enum StripHeaderFlags_t { + STRIP_IS_TRILIST = 0x01, + STRIP_IS_TRISTRIP = 0x02 /* Unused by studiomdl 2015? */ +}; + +typedef struct +{ + int numIndices; + int indexOffset; + + int numVerts; + int vertOffset; + + short numBones; + + unsigned char flags; + + int numBoneStateChanges; + int boneStateChangeOffset; +} +VTXStripHeader_t; + +typedef struct +{ + int numVerts; + int vertOffset; + + int numIndices; + int indexOffset; + + int numStrips; + int stripOffset; + + unsigned char flags; +} +VTXStripGroupHeader_t; + +static VTXVertex_t *pVertexVTX( VTXStripGroupHeader_t *t, int i ) +{ + return (VTXVertex_t *)(((char *)t) + t->vertOffset) + i; +} + +static unsigned short *pIndexVTX( VTXStripGroupHeader_t *t, int i ) +{ + return (unsigned short *)(((char *)t) + t->indexOffset) + i; +} + +static VTXStripHeader_t *pStripVTX( VTXStripGroupHeader_t *t, int i ) +{ + return (VTXStripHeader_t *)(((char *)t) + t->stripOffset) + i; +} + +typedef struct +{ + int numStripGroups; + int stripGroupHeaderOffset; + + unsigned char flags; +} +VTXMeshHeader_t; +static VTXStripGroupHeader_t *pStripGroupVTX( VTXMeshHeader_t *t, int i ) +{ + return (VTXStripGroupHeader_t *)(((char *)t) + t->stripGroupHeaderOffset) +i; +} + +typedef struct +{ + int numMeshes; + int meshOffset; + + float switchPoint; +} +VTXModelLODHeader_t; +static VTXMeshHeader_t *pMeshVTX( VTXModelLODHeader_t *t, int i ) +{ + return (VTXMeshHeader_t *)(((char *)t) + t->meshOffset) + i; +} + +typedef struct +{ + int numLODs; + int lodOffset; +} +VTXModelHeader_t; +static VTXModelLODHeader_t *pLODVTX( VTXModelHeader_t *t, int i ) +{ + return (VTXModelLODHeader_t *)(((char *)t) + t->lodOffset) + i; +} + +typedef struct +{ + int numModels; + int modelOffset; +} +VTXBodyPartHeader_t; +static VTXModelHeader_t *pModelVTX( VTXBodyPartHeader_t *t, int i ) +{ + return (VTXModelHeader_t *)(((char *)t) + t->modelOffset) + i; +} + +typedef struct +{ + int version; /* 7 */ + + /* hardware params that affect how the model is to be optimized. */ + int vertCacheSize; + unsigned short maxBonesPerStrip; + unsigned short maxBonesPerTri; + int maxBonesPerVert; + + int checkSum; + int numLODs; + int materialReplacementListOffset; + int numBodyParts; + int bodyPartOffset; +} +VTXFileHeader_t; +static VTXBodyPartHeader_t *pBodyPartVTX( VTXFileHeader_t *t, int i ) +{ + return (VTXBodyPartHeader_t *)(((char *)t) + t->bodyPartOffset) + i; +} + +/* + .VTX file structure + ============================================= + + FileHeader + L BodyParts:: + L Models:: + L LODS:: + L Meshes:: + L StripGroups:: + L VerticesTable[StudioMDL.Vertex] + L IndicesTable[UINT16] + | + L Strips:: + L Vertices[UINT16] + L Indices[UINT16] +*/ + +#pragma pack(pop) + +/* + * mdl format + */ + +#pragma pack(push, 1) + +typedef struct +{ + void *pVertexData; + void *pTangentData; +} +mstudio_modelvertexdata_t; + +typedef struct +{ + int unused_modelvertexdata; + int numLODVertexes[MAX_NUM_LODS]; + + mstudio_modelvertexdata_t *_the_death_ptr; +} +mstudio_meshvertexdata_t; + +typedef struct mstudiomodel_t mstudiomodel_t; +typedef struct +{ + int material; + int modelindex; + int numvertices; + int vertexoffset; + int numflexes; + int flexindex; + int materialtype; + int materialparam; + int meshid; + float center[3]; + mstudio_meshvertexdata_t vertexdata; + int unused[6]; +} +mstudiomesh_t; + +struct mstudiomodel_t +{ + char name[64]; + int type; + float boundingradius; + + int nummeshes; + int meshindex; + + int numvertices; + int vertexindex; + int tangentsindex; + + int numattachments; + int attachmentindex; + + int numeyeballs; + int eyeballindex; + + mstudio_modelvertexdata_t vertexdata; + + int unused[8]; +}; +static mstudiomesh_t *studiomodel_pMesh( mstudiomodel_t *t, int i ) +{ + return (mstudiomesh_t *)(((char *)t) + t->meshindex) + i; +} + +typedef struct +{ + int sznameindex; + int nummodels; + int base; + int modelindex; +} mstudiobodyparts_t; + +static mstudiomodel_t *mstudiobodyparts_pModel( mstudiobodyparts_t *t, int i ) +{ + return (mstudiomodel_t *)(((char *)t) + t->modelindex) + i; +} + +typedef struct +{ + int id; + int version; + int checksum; + char name[64]; + int length; + float eyeposition[3]; + float illumposition[3]; + float hull_min[3]; + float hull_max[3]; + float view_bbmin[3]; + float view_bbmax[3]; + int flags; + int numbones; + int boneindex; + int numbonecontrollers; + int bonecontrollerindex; + int numhitboxsets; + int hitboxsetindex; + int numlocalanim; + int localanimindex; + int numlocalseq; + int localseqindex; + int activitylistversion; + int eventsindexed; + int numtextures; + int textureindex; + int numcdtextures; + int cdtextureindex; + int numskinref; + int numskinfamilies; + int skinindex; + int numbodyparts; + int bodypartindex; + int numlocalattachments; + int localattachmentindex; + int numlocalnodes; + int localnodeindex; + int localnodenameindex; + int numflexdesc; + int flexdescindex; + int numflexcontrollers; + int flexcontrollerindex; + int numflexrules; + int flexruleindex; + int numikchains; + int ikchainindex; + int nummouths; + int mouthindex; + int numlocalposeparameters; + int localposeparamindex; + int surfacepropindex; + int keyvalueindex; + int keyvaluesize; + int numlocalikautoplaylocks; + int localikautoplaylockindex; + float mass; + int contents; + int numincludemodels; + int includemodelindex; + int szanimblocknameindex; + int numanimblocks; + int animblockindex; + int bonetablebynameindex; + char constdirectionallightdot; + char rootLOD; + char numAllowedRootLODs; + char unused[1]; + int unused4; + int numflexcontrollerui; + int flexcontrolleruiindex; + float flVertAnimFixedPointScale; + int unused3[1]; + int studiohdr2index; + int unused2[1]; +} +studiohdr_t; + +static mstudiobodyparts_t *studiohdr_pBodypart( studiohdr_t *t, int i ) +{ + return (mstudiobodyparts_t *)(((char *)t) + t->bodypartindex) + i; +} + +#pragma pack(pop) + +static u32 vtx_count_indices( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl ) +{ + int indice_count = 0; + + for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID ) + { + /* Body parts */ + VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( pVtxHdr, bodyID ); + mstudiobodyparts_t *pBodyPart = studiohdr_pBodypart( pMdl, bodyID ); + + for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + /* models */ + VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID ); + mstudiomodel_t *pStudioModel = + mstudiobodyparts_pModel( pBodyPart, modelID ); + + int nLod = 0; + VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod ); + + for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + /* meshes */ + VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh ); + mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh ); + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + /* groups */ + VTXStripGroupHeader_t* pStripGroup = + pStripGroupVTX( pVtxMesh, nGroup ); + + for ( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + /* strips */ + VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip ); + + if ( pStrip->flags & STRIP_IS_TRILIST ) + { + indice_count += pStrip->numIndices; + } + } + } + } + } + } + + return indice_count; +} + +#endif /* CXR_VALVE_BIN_H */