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=b11f71f5d7f1cd00b067fcd9a4af5851d3b0828e;hp=da6c84d6e11f81c12e00fe7e7081f897babfba90;hb=0d0b6bf37c8a9c4494071973103a89b4aa82574a;hpb=dbd379f76bcb2139fdb5740232511fa789018e10 diff --git a/cxr/cxr_valve_bin.h b/cxr/cxr_valve_bin.h index da6c84d..b11f71f 100644 --- a/cxr/cxr_valve_bin.h +++ b/cxr/cxr_valve_bin.h @@ -24,19 +24,66 @@ typedef struct VPKDirectoryEntry VPKDirectoryEntry; typedef struct vdf_kv vdf_kv; typedef struct vdf_node vdf_node; typedef struct vdf_ctx vdf_ctx; +typedef struct fs_locator fs_locator; + +/* + * These are 'unofficial' representations of the original formats, more C + * friendly + */ typedef struct valve_file_system valve_file_system; +typedef struct valve_model valve_model; +typedef struct valve_model_batch valve_model_batch; +typedef struct valve_material valve_material; + +/* Api */ + +CXR_API i32 cxr_fs_set_gameinfo( const char *path ); /* Setup system */ +CXR_API void cxr_fs_exit(void); /* Clean up */ +CXR_API void *cxr_fs_get( const char *path, i32 stringbuffer ); /* Get a file */ +CXR_API i32 cxr_fs_find( const char *path, fs_locator *locator ); + +CXR_API valve_model *valve_load_model( const char *relpath ); +CXR_API void valve_free_model( valve_model *model ); +CXR_API valve_material *valve_load_material( const char *path ); +CXR_API void valve_free_material( valve_material *material ); /* - * File system + * File system implementation */ +#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 struct valve_file_system { char *gamedir, *exedir; /* Runtime */ - VPKHeader *vpk; + VPKHeader vpk; + char *directory_tree; cxr_abuffer searchpaths; @@ -47,16 +94,17 @@ static struct valve_file_system } 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 */ +struct fs_locator +{ + VPKDirectoryEntry *vpk_entry; + char path[ 1024 ]; +}; /* * VPK reading */ -static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset ); -static void vpk_free( VPKHeader *self ); +static VPKDirectoryEntry *vpk_find( const char *asset ); /* * VDF reading @@ -97,39 +145,13 @@ while( (AS = vdf_next( NODE, STR, &__vdf_it_##AS )) ) int __kv_it_##AS = 0; \ const char * AS;\ while( (AS = kv_next( NODE, STR, &__kv_it_##AS )) ) -#pragma pack(push, 1) -struct VPKHeader +static VPKDirectoryEntry *vpk_find( const char *asset ) { - 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 ); -} + valve_file_system *fs = &fs_global; -static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset ) -{ - if( !self ) - return NULL; + if( !fs->directory_tree ) + return NULL; char wbuf[ 512 ]; strcpy( wbuf, asset ); @@ -151,7 +173,7 @@ static VPKDirectoryEntry *vpk_find( VPKHeader *self, const char *asset ) *(fn-1) = 0x00; char *dir = wbuf; - char *pCur = ((char *)self) + sizeof( VPKHeader ); + char *pCur = fs->directory_tree; while( 1 ) { @@ -540,6 +562,7 @@ static void vdf_parse_string( vdf_ctx *ctx ) if( vdf_line_control( ctx ) ) { /* Unexpected end of line */ + cxr_log( "vdf: unexpected EOL\n" ); return; } @@ -554,6 +577,7 @@ static int vdf_parse_structure( vdf_ctx *ctx ) if( ctx->st.tokens[0] || !ctx->st.expect_decl ) { /* Unexpected token '{' */ + cxr_log( "vdf: Unexpected token '{'\n" ); ctx->errors ++; } @@ -569,6 +593,7 @@ static int vdf_parse_structure( vdf_ctx *ctx ) if( !ctx->st.pnode->parent ) { /* Unexpected token '}' */ + cxr_log( "vdf: Unexpected token '}'\n" ); ctx->errors ++; } else @@ -590,6 +615,7 @@ static void vdf_parse_begin_token( vdf_ctx *ctx, char *ptr ) if( ctx->st.expect_decl ) { /* Unexpected token 'name' */ + cxr_log( "vdf: unexpected token 'name'\n" ); ctx->errors ++; } } @@ -657,37 +683,68 @@ static void vdf_parse_feedbuffer( vdf_ctx *ctx, char *buf ) } } -static int vdf_load_into( const char *fn, vdf_node *node ) +static void vdf_debug_indent( int level ) { - char *text_src = cxr_textasset_read( fn ); - - if( !text_src ) - { - return 0; - } - + for(int i=0; iname ); + + vdf_debug_indent(level); + cxr_log( "{\n" ); + + for( int i=0; iabpairs.count; i++ ) + { + vdf_kv *kv = cxr_ab_ptr( &node->abpairs, i ); + + vdf_debug_indent(level+1); + cxr_log( "vdf_kv(%p, k: '%s', v: '%s')\n", + kv, kv->key, kv->value ); + } + + for( int i=0; iabnodes.count; i++ ) + { + vdf_node **child = cxr_ab_ptr( &node->abnodes, i ); + vdf_debug_r( *child, level+1 ); + } + + vdf_debug_indent(level); + cxr_log( "}\n" ); +} + +/* This will wreck the original buffer, but must stay alive! */ +static vdf_node *vdf_from_buffer( char *buffer ) +{ + vdf_node *root = vdf_create_node( NULL, NULL ); + vdf_ctx ctx = {0}; - ctx.root = ctx.st.pnode = node; + ctx.root = ctx.st.pnode = root; vdf_newln( &ctx ); - vdf_parse_feedbuffer( &ctx, text_src ); - free( text_src ); - - return 1; + vdf_parse_feedbuffer( &ctx, buffer ); + +#if 0 + vdf_debug_r( root, 0 ); +#endif + + return root; } 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 ); + char *text_src = cxr_textasset_read( fn ); + + if( !text_src ) return NULL; - } + + vdf_node *root = vdf_from_buffer( text_src ); + free( text_src ); + + return root; } /* @@ -761,18 +818,31 @@ CXR_API i32 cxr_fs_set_gameinfo( const char *path ) /* Find pack diretory */ char pack_path[512]; + fs->current_archive = NULL; + fs->current_idx = 0x7fff; + 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; + + fs->current_archive = fopen( pack_path, "rb" ); + + /* Read vpk directory */ + if( fs->current_archive ) + { + fread( &fs->vpk, sizeof(VPKHeader), 1, fs->current_archive ); + + fs->directory_tree = malloc( fs->vpk.TreeSize ); + fread( fs->directory_tree, fs->vpk.TreeSize, 1, fs->current_archive ); + + break; + } } - if( !fs->vpk ) + if( !fs->current_archive ) { cxr_log( "Could not locate pak01_dir.vpk in %i searchpaths. " "Stock models will not load!\n", fs->searchpaths.count ); @@ -794,10 +864,10 @@ CXR_API void cxr_fs_exit(void) cxr_ab_free( &fs->searchpaths ); - if( fs->vpk ) + if( fs->directory_tree ) { - vpk_free( fs->vpk ); - fs->vpk = NULL; + free( fs->directory_tree ); + fs->directory_tree = NULL; } if( fs->current_archive ) @@ -812,77 +882,148 @@ CXR_API void cxr_fs_exit(void) memset( fs, 0, sizeof( valve_file_system ) ); } -CXR_API char *cxr_fs_get( const char *path ) +static char *cxr_vpk_read( VPKDirectoryEntry *entry, int stringbuffer ) { valve_file_system *fs = &fs_global; if( !fs->initialized ) return NULL; + + char pak[1024]; + + /* Check if we need to change file handle */ + if( entry->ArchiveIndex != fs->current_idx ) + { + if( fs->current_archive ) + fclose( fs->current_archive ); + + fs->current_archive = NULL; + fs->current_idx = entry->ArchiveIndex; + + if( entry->ArchiveIndex == 0x7fff ) + { + snprintf( pak, 1023, "%scsgo/pak01_dir.vpk", fs->exedir ); + } + else + { + snprintf( pak, 1023, "%scsgo/pak01_%03hu.vpk", + fs->exedir, entry->ArchiveIndex ); + } + + fs->current_archive = fopen( pak, "rb" ); + + if( !fs->current_archive ) + cxr_log( "Warning: could not locate %s\n", pak ); + } + + if( !fs->current_archive ) + return NULL; + + size_t offset = entry->EntryOffset, + length = entry->EntryLength; + + /* + * File is stored in the index, after the tree + */ + if( entry->ArchiveIndex == 0x7fff ) + offset += fs->vpk.TreeSize + sizeof(VPKHeader); + + /* + * Entire file is stored in the preload bytes; + * Backtrack offset from directory to get absolute offset + */ + if( length == 0 ) + { + offset = (char *)entry - (char *)fs->directory_tree; + offset += sizeof( VPKHeader ); + offset += sizeof( VPKDirectoryEntry ); + + length = entry->PreloadBytes; + } + else + length += entry->PreloadBytes; + + fseek( fs->current_archive, offset, SEEK_SET ); + + size_t alloc_size = stringbuffer? length+1: length; + char *filebuf = malloc( alloc_size ); + + if( stringbuffer ) + filebuf[length] = 0x00; + + if( fread( filebuf, 1, length, fs->current_archive ) == length ) + return filebuf; + else + { + /* Invalid read */ + free( filebuf ); + return NULL; + } +} + +CXR_API i32 cxr_fs_find( const char *path, fs_locator *locator ) +{ + valve_file_system *fs = &fs_global; + + if( !fs->initialized ) + return 0; VPKDirectoryEntry *entry; - char pak[ 533 ]; - if( fs->vpk ) + if( fs->directory_tree ) { - if( (entry = vpk_find( fs->vpk, path )) ) + if( (entry = vpk_find( 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; - } + locator->vpk_entry = entry; + locator->path[0] = 0x00; + return 1; } } - + + locator->vpk_entry = 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; + snprintf( locator->path, 1023, "%s%s", *sp, path ); + + if( cxr_file_exists( locator->path ) ) + return 1; } /* File not found */ - return NULL; + return 0; +} + +CXR_API void *cxr_fs_get( const char *path, i32 stringbuffer ) +{ + valve_file_system *fs = &fs_global; + + if( !fs->initialized ) + return NULL; + + fs_locator locator; + + if( cxr_fs_find( path, &locator ) ) + { + if( locator.vpk_entry ) + { + return cxr_vpk_read( locator.vpk_entry, stringbuffer ); + } + else + { + char *filebuf; + + if( stringbuffer ) + return cxr_textasset_read( locator.path ); + else + return cxr_asset_read( locator.path ); + } + } + + return NULL; } /* @@ -1264,16 +1405,44 @@ typedef struct } studiohdr_t; +static char *studiohdr_pCdtexture( studiohdr_t *t, int i ) +{ + return (((char *)t) + *((int *)(((u8 *)t) + t->cdtextureindex) + i)); +} + static mstudiobodyparts_t *studiohdr_pBodypart( studiohdr_t *t, int i ) { return (mstudiobodyparts_t *)(((char *)t) + t->bodypartindex) + i; } +typedef struct +{ + int sznameindex; + int flags; + int used; + + /* There is some extra unused stuff that was here... + * Luckily since byte offsets are used, structure size doesn't matter */ +} +mstudiotexture_t; + +static char *mstudiotexture_pszName( mstudiotexture_t *t ) +{ + return ((char *)t) + t->sznameindex; +} + +static mstudiotexture_t *studiohdr_pTexture( studiohdr_t *t, int i ) +{ + return (mstudiotexture_t *)(((u8 *)t) + t->textureindex) + i; +} + #pragma pack(pop) -static u32 vtx_count_indices( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl ) +static void vtx_resource_counts( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl, + u32 *indices, u32 *meshes ) { - int indice_count = 0; + *indices = 0; + *meshes = 0; for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID ) { @@ -1297,6 +1466,8 @@ static u32 vtx_count_indices( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl ) VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh ); mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh ); + (*meshes)++; + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) { /* groups */ @@ -1310,15 +1481,293 @@ static u32 vtx_count_indices( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl ) if ( pStrip->flags & STRIP_IS_TRILIST ) { - indice_count += pStrip->numIndices; + *indices += pStrip->numIndices; + } + } + } + } + } + } +} + +/* + * The following section is the wrappers for the underlying types + */ + +struct valve_material +{ + char *basetexture, + *bumpmap; +}; + +struct valve_model +{ + float *vertex_data; /* pos xyz, norm xyz, uv xy */ + + u32 *indices, + indices_count, + vertex_count, + part_count, + material_count; + + char **materials; + + struct valve_model_batch + { + u32 material, + ibstart, + ibcount; + } + *parts; + + /* Internal valve data */ + studiohdr_t *studiohdr; + VTXFileHeader_t *vtxhdr; + vertexFileHeader_t *vvdhdr; +}; + +CXR_API valve_model *valve_load_model( const char *relpath ) +{ + char path[1024]; + strcpy( path, relpath ); + + char *ext = cxr_stripext( path ); + + if( !ext ) + return NULL; + + /* Load data files */ + valve_model *model = malloc( sizeof( valve_model ) ); + model->studiohdr = NULL; + model->vtxhdr = NULL; + model->vvdhdr = NULL; + + strcpy( ext, ".dx90.vtx" ); + model->vtxhdr = cxr_fs_get( path, 0 ); + + strcpy( ext, ".vvd" ); + model->vvdhdr = cxr_fs_get( path, 0 ); + + strcpy( ext, ".mdl" ); + model->studiohdr = cxr_fs_get( path, 0 ); + + if( !model->vvdhdr || !model->studiohdr || !model->vtxhdr ) + { + cxr_log( "Error, failed to load: (%s)\n", relpath ); + + free( model->studiohdr ); + free( model->vvdhdr ); + free( model->studiohdr ); + free( model ); + + return NULL; + } + + /* allocate resources */ + vtx_resource_counts( model->vtxhdr, model->studiohdr, + &model->indices_count, &model->part_count ); + model->vertex_count = model->vvdhdr->numLodVertexes[0]; + model->material_count = model->studiohdr->numtextures; + + model->materials = malloc( model->material_count * sizeof(char *) ); + model->parts = malloc( sizeof( valve_model_batch ) * model->part_count ); + model->indices = malloc( sizeof( u32 ) * model->indices_count ); + model->vertex_data = malloc( sizeof( float ) * 8 * model->vertex_count ); + + /* Find materials */ + for( int i=0; istudiohdr->numtextures; i++ ) + { + char material_path[ 1024 ]; + fs_locator locator; + + mstudiotexture_t *tex = studiohdr_pTexture( model->studiohdr, i ); + const char *name = mstudiotexture_pszName( tex ); + + model->materials[i] = NULL; + + for( int j=0; jstudiohdr->numcdtextures; j++ ) + { + char *cdpath = studiohdr_pCdtexture( model->studiohdr, j ); + snprintf( material_path, 1023, "materials/%s%s.vmt", cdpath, name ); + cxr_unixpath( material_path ); + + if( cxr_fs_find( material_path, &locator )) + { + model->materials[i] = cxr_str_clone( material_path, 0 ); + break; + } + } + } + + u32 i_index = 0, + i_mesh = 0; + + /* Extract meshes */ + for( int bodyID = 0; bodyID < model->studiohdr->numbodyparts; ++bodyID ) + { + /* Body parts */ + VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( model->vtxhdr, bodyID ); + mstudiobodyparts_t *pBodyPart = + studiohdr_pBodypart( model->studiohdr, 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, each of these creates a new draw CMD */ + VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh ); + mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh ); + + valve_model_batch *curBatch = &model->parts[ i_mesh ++ ]; + curBatch->material = pMesh->material; + curBatch->ibstart = i_index; + curBatch->ibcount = 0; + + 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 ) + { + /* indices */ + for( int i = 0; i < pStrip->numIndices; i ++ ) + { + u16 i1 = *pIndexVTX( pStripGroup, + pStrip->indexOffset + i ); + + model->indices[ i_index ++ ] = + pVertexVTX( pStripGroup, i1 )->origMeshVertID + + pMesh->vertexoffset; + + curBatch->ibcount ++; + } } + else + { + /* This is unused? */ + } } } } } } + + mstudiovertex_t *vertexData = GetVertexData( model->vvdhdr ); + + for( int i = 0; i < model->vertex_count; i ++ ) + { + mstudiovertex_t *vert = &vertexData[i]; + + float *dest = &model->vertex_data[ i*8 ]; + + dest[0] = vert->pos[0]; + dest[1] = vert->pos[1]; + dest[2] = vert->pos[2]; - return indice_count; + dest[3] = vert->norm[0]; + dest[4] = vert->norm[1]; + dest[5] = vert->norm[2]; + + dest[6] = vert->uv[0]; + dest[7] = vert->uv[1]; + } + + return model; +} + +CXR_API void valve_free_model( valve_model *model ) +{ + for( int i=0; imaterial_count; i++ ) + free( model->materials[i] ); + + free( model->materials ); + free( model->parts ); + free( model->indices ); + free( model->vertex_data ); + + free( model->studiohdr ); + free( model->vtxhdr ); + free( model->vvdhdr ); + free( model ); +} + +static char *valve_texture_path( const char *path ) +{ + if( !path ) + return NULL; + + char *buf = cxr_str_clone( path, 4 ); + + strcat( buf, ".vtf" ); + cxr_unixpath( buf ); + cxr_lowercase( buf ); + + return buf; +} + +CXR_API valve_material *valve_load_material( const char *path ) +{ + char *vmt = cxr_fs_get( path, 1 ); + + if( vmt ) + { + valve_material *material = malloc( sizeof(valve_material) ); + vdf_node *vmt_root = vdf_from_buffer( vmt ); + + if( vmt_root->abnodes.count == 0 ) + { + cxr_log( "Error: vmt has no nodes\n" ); + free( vmt ); + vdf_free_r( vmt_root ); + return 0; + } + + vdf_node **body = cxr_ab_ptr( &vmt_root->abnodes, 0 ); + + /* Path semantics here are inconsistent + * I believe they should all just be converted to lowercase, though */ + + for( int i=0; i<(*body)->abpairs.count; i++ ) + { + vdf_kv *kv = cxr_ab_ptr( &(*body)->abpairs, i ); + cxr_lowercase( kv->key ); + } + + const char *basetexture = kv_get( *body, "$basetexture", NULL ), + *bumpmap = kv_get( *body, "$bumpmap", NULL ); + + /* TODO: other shader parameters */ + material->basetexture = valve_texture_path( basetexture ); + material->bumpmap = valve_texture_path( bumpmap ); + + vdf_free_r( vmt_root ); + free(vmt); + + return material; + } + + return NULL; +} + +CXR_API void valve_free_material( valve_material *material ) +{ + free( material->basetexture ); + free( material->bumpmap ); } #endif /* CXR_VALVE_BIN_H */