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;
}
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
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 );
*(fn-1) = 0x00;
char *dir = wbuf;
- char *pCur = ((char *)self) + sizeof( VPKHeader );
+ char *pCur = fs->directory_tree;
while( 1 )
{
if( vdf_line_control( ctx ) )
{
/* Unexpected end of line */
+ cxr_log( "vdf: unexpected EOL\n" );
return;
}
if( ctx->st.tokens[0] || !ctx->st.expect_decl )
{
/* Unexpected token '{' */
+ cxr_log( "vdf: Unexpected token '{'\n" );
ctx->errors ++;
}
if( !ctx->st.pnode->parent )
{
/* Unexpected token '}' */
+ cxr_log( "vdf: Unexpected token '}'\n" );
ctx->errors ++;
}
else
if( ctx->st.expect_decl )
{
/* Unexpected token 'name' */
+ cxr_log( "vdf: unexpected token 'name'\n" );
ctx->errors ++;
}
}
}
}
-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; i<level; i++)
+ cxr_log(" ");
+}
+
+static void vdf_debug_r( vdf_node *node, int level )
+{
+ vdf_debug_indent(level);
+ cxr_log( "vdf_node(%p, name: '%s')\n", node, node->name );
+
+ vdf_debug_indent(level);
+ cxr_log( "{\n" );
+
+ for( int i=0; i<node->abpairs.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; i<node->abnodes.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;
}
/*
/* 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 );
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 )
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;
}
/*
}
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 )
{
VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
+ (*meshes)++;
+
for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
{
/* groups */
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; i<model->studiohdr->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; j<model->studiohdr->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; i<model->material_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 */