From 0d0b6bf37c8a9c4494071973103a89b4aa82574a Mon Sep 17 00:00:00 2001 From: hgn Date: Mon, 2 May 2022 01:29:06 +0100 Subject: [PATCH] largely loadable model assets --- __init__.py | 191 ++++++++++-- cxr/cxr.h | 138 --------- cxr/cxr_io.h | 58 +++- cxr/cxr_valve_bin.h | 693 ++++++++++++++++++++++++++++++++++++-------- cxr/test.c | 26 +- 5 files changed, 805 insertions(+), 301 deletions(-) diff --git a/__init__.py b/__init__.py index 9eba53c..8d9254e 100644 --- a/__init__.py +++ b/__init__.py @@ -53,6 +53,13 @@ cxr_jobs_batch = None cxr_jobs_inf = [] cxr_error_inf = None +cxr_asset_lib = \ +{ + "models": {}, + "materials": {}, + "textures": {} +} + # Shaders cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') @@ -96,7 +103,7 @@ void main() vec3 worldPos = pWorldPos.xyz; gl_Position = viewProjectionMatrix * pWorldPos; - lNormal = aNormal; //mat3(transpose(inverse(modelMatrix))) * aNormal; + lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal); lPos = worldPos; } """,""" @@ -419,6 +426,33 @@ class cxr_vmf_context(Structure): ("entity_count",c_int32), ("face_count",c_int32)] +# Valve wrapper types +class fs_locator(Structure): + _fields_ = [("vpk_entry",c_void_p), + ("path",c_char_p*1024)] + +class valve_material(Structure): + _fields_ = [("basetexture",c_char_p), + ("bumpmap",c_char_p)] + +class valve_model_batch(Structure): + _fields_ = [("material",c_uint32), + ("ibstart",c_uint32), + ("ibcount",c_uint32)] + +class valve_model(Structure): + _fields_ = [("vertex_data",POINTER(c_float)), + ("indices",POINTER(c_uint32)), + ("indices_count",c_uint32), + ("vertex_count",c_uint32), + ("part_count",c_uint32), + ("material_count",c_uint32), + ("materials",POINTER(c_char_p)), + ("parts",POINTER(valve_model_batch)), + ("studiohdr",c_void_p), + ("vtxhdr",c_void_p), + ("vvdhdr",c_void_p)] + # Convert blenders mesh format into CXR's static format (they are very similar) # def mesh_cxr_format(obj): @@ -593,17 +627,30 @@ libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None ) # Binary file formats and FS libcxr_fs_set_gameinfo = extern( "cxr_fs_set_gameinfo", [c_char_p], c_int32 ) libcxr_fs_exit = extern( "cxr_fs_exit", [], None ) -libcxr_fs_get = extern( "cxr_fs_get", [c_char_p], c_char_p ) -libcxr_load_mdl = extern( "cxr_load_mdl", [c_char_p], POINTER(cxr_tri_mesh) ) +libcxr_fs_get = extern( "cxr_fs_get", [c_char_p, c_int32], c_char_p ) +libcxr_fs_find = extern( "cxr_fs_find", [c_char_p, POINTER(fs_locator)],\ + c_int32 ) + +libcxr_valve_load_model = extern( "valve_load_model", [c_char_p], \ + POINTER(valve_model) ) +libcxr_valve_free_model = extern( "valve_free_model", [POINTER(valve_model)],\ + None ) + +libcxr_valve_load_material = extern( "valve_load_material", [c_char_p], \ + POINTER(valve_material) ) +libcxr_valve_free_material = extern( "valve_free_material", \ + [POINTER(valve_material)], None ) libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \ libcxr_vmf_begin_entities, libcxr_push_world_vmf, \ libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \ - libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, + libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, \ libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\ libcxr_world_preview, libcxr_free_tri_mesh, \ libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \ - libcxr_load_mdl ] + libcxr_fs_find,\ + libcxr_valve_load_model, libcxr_valve_free_model,\ + libcxr_valve_load_material, libcxr_valve_free_material ] # Callbacks def libcxr_log_callback(logStr): @@ -634,7 +681,8 @@ def libcxr_line_callback( p0,p1,colour ): cxr_line_colours += [(colour[0],colour[1],colour[2],colour[3])] def cxr_reset_all(): - global cxr_jobs_inf, cxr_jobs_batch, cxr_error_inf, cxr_view_mesh + global cxr_jobs_inf, cxr_jobs_batch, cxr_error_inf, cxr_view_mesh, \ + cxr_asset_lib cxr_jobs_inf = None cxr_jobs_batch = None cxr_error_inf = None @@ -643,6 +691,10 @@ def cxr_reset_all(): cxr_batch_lines() cxr_view_mesh = None + cxr_asset_lib['models'] = {} + cxr_asset_lib['materials'] = {} + cxr_asset_lib['textures'] = {} + scene_redraw() # libnbvtf @@ -1718,40 +1770,115 @@ class CXR_INIT_FS_OPERATOR(bpy.types.Operator): return {'FINISHED'} -class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator): - bl_idname="convexer.model_load" - bl_label="Load model" +def cxr_load_texture( path ): + global cxr_asset_lib - def execute(_,context): - global cxr_mdl_mesh, cxr_mdl_shader + if path in cxr_asset_lib['textures']: + return cxr_asset_lib['textures'][path] - mdlpath = bpy.context.scene.cxr_data.dev_mdl.encode('utf-8') - pmesh = libcxr_load_mdl.call( mdlpath ) + print( F"cxr_load_texture( '{path}' )" ) - if not pmesh: - print( "Failed to load model" ) - return {'FINISHED'} - - mesh = pmesh[0] + # TODO - #TODO: remove code dupe - vertices = mesh.vertices[:mesh.vertex_count] - vertices = [(_[0],_[1],_[2]) for _ in vertices] + tex = cxr_asset_lib['textures'][path] = None + return tex + +def cxr_load_material( path ): + global cxr_asset_lib + + if path in cxr_asset_lib['materials']: + return cxr_asset_lib['materials'][path] + + print( F"cxr_load_material( '{path}' )" ) + + pvmt = libcxr_valve_load_material.call( path.encode( 'utf-8') ) + vmt = pvmt[0] + + mat = cxr_asset_lib['materials'][path] = {} + + if vmt.basetexture: + mat['basetexture'] = cxr_load_texture( vmt.basetexture.decode('utf-8') ) + + if vmt.bumpmap: + mat['bumpmap'] = cxr_load_texture( vmt.bumpmap.decode('utf-8') ) + + libcxr_valve_free_material.call( pvmt ) + + return mat + +def cxr_load_model_full( path ): + global cxr_asset_lib, cxr_mdl_shader + + if path in cxr_asset_lib['models']: + return cxr_asset_lib['models'][path] + + pmdl = libcxr_valve_load_model.call( path.encode( 'utf-8' ) ) + + print( F"cxr_load_model_full( '{path}' )" ) + + if not pmdl: + print( "Failed to load model" ) + cxr_asset_lib['models'][path] = None + return None + + mdl = pmdl[0] + + # Convert our lovely interleaved vertex stream into, whatever this is. + positions = [ (mdl.vertex_data[i*8+0], \ + mdl.vertex_data[i*8+1], \ + mdl.vertex_data[i*8+2]) for i in range(mdl.vertex_count) ] + + normals = [ (mdl.vertex_data[i*8+3], \ + mdl.vertex_data[i*8+4], \ + mdl.vertex_data[i*8+5]) for i in range(mdl.vertex_count) ] - normals = mesh.normals[:mesh.vertex_count] - normals = [(_[0],_[1],_[2]) for _ in normals] + uvs = [ (mdl.vertex_data[i*8+6], \ + mdl.vertex_data[i*8+7]) for i in range(mdl.vertex_count) ] - indices = mesh.indices[:mesh.indices_count] + fmt = gpu.types.GPUVertFormat() + fmt.attr_add(id="aPos", comp_type='F32', len=3, fetch_mode='FLOAT') + fmt.attr_add(id="aNormal", comp_type='F32', len=3, fetch_mode='FLOAT') + fmt.attr_add(id="aUv", comp_type='F32', len=2, fetch_mode='FLOAT') + + vbo = gpu.types.GPUVertBuf(len=mdl.vertex_count, format=fmt) + vbo.attr_fill(id="aPos", data=positions ) + vbo.attr_fill(id="aNormal", data=normals ) + vbo.attr_fill(id="aUv", data=uvs ) + + batches = cxr_asset_lib['models'][path] = [] + + for p in range(mdl.part_count): + part = mdl.parts[p] + indices = mdl.indices[part.ibstart:part.ibstart+part.ibcount] indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \ - for i in range(int(mesh.indices_count/3)) ] - - cxr_mdl_mesh = batch_for_shader( - cxr_mdl_shader, 'TRIS', - { "aPos": vertices, "aNormal": normals }, - indices = indices, - ) + for i in range(part.ibcount//3) ] - libcxr_free_tri_mesh.call( pmesh ) + ibo = gpu.types.GPUIndexBuf( type='TRIS', seq=indices ) + + batch = gpu.types.GPUBatch( type='TRIS', buf=vbo, elem=ibo ) + batch.program_set( cxr_mdl_shader ) + + mat_str = cast( mdl.materials[ part.material ], c_char_p ) + batches += [( cxr_load_material( mat_str.value.decode('utf-8') ), batch )] + + libcxr_valve_free_model.call( pmdl ) + + return batches + +class CXR_LOAD_MODEL_OPERATOR(bpy.types.Operator): + bl_idname="convexer.model_load" + bl_label="Load model" + + def execute(_,context): + global cxr_mdl_mesh, cxr_mdl_shader, cxr_asset_lib + + test_mdl = cxr_load_model_full( bpy.context.scene.cxr_data.dev_mdl ) + + if test_mdl != None: + # just draw first batch part for now + cxr_mdl_mesh = test_mdl[0][1] + else: + cxr_mdl_mesh = None scene_redraw() return {'FINISHED'} diff --git a/cxr/cxr.h b/cxr/cxr.h index 2a4267f..644c8ed 100644 --- a/cxr/cxr.h +++ b/cxr/cxr.h @@ -3510,143 +3510,5 @@ CXR_API int cxr_lightpatch_bsp( const char *path ) return 1; } -#ifdef CXR_VALVE_BIN - -CXR_API cxr_tri_mesh *cxr_load_mdl( const char *mdlname ) -{ - char path[1024]; - strcpy( path, mdlname ); - cxr_stripext( path ); - strcat( path, ".dx90.vtx" ); - VTXFileHeader_t *pVtxHdr = (VTXFileHeader_t *)cxr_fs_get( path ); - - if( !pVtxHdr ) - return NULL; - - /* .VVD */ - strcpy( path, mdlname ); - cxr_stripext( path ); - strcat( path, ".vvd" ); - vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)cxr_fs_get( path ); - - if( !pVvdHdr ) - { - free( pVtxHdr ); - return 0; - } - - /* .MDL */ - strcpy( path, mdlname ); - cxr_stripext( path ); - strcat( path, ".mdl" ); - studiohdr_t *pMdl = (studiohdr_t *)cxr_fs_get( path ); - - if( !pMdl ) - { - free( pVtxHdr ); - free( pVvdHdr ); - return 0; - } - - cxr_tri_mesh *mesh = malloc( sizeof(cxr_tri_mesh) ); - mesh->colours = NULL; - - mesh->indices_count = vtx_count_indices( pVtxHdr, pMdl ); - mesh->indices = malloc( mesh->indices_count * sizeof( u32 ) ); - - mesh->vertex_count = pVvdHdr->numLodVertexes[0]; - mesh->vertices = malloc( mesh->vertex_count * sizeof(v3f) ); - mesh->uvs = malloc( mesh->vertex_count * sizeof(v2f) ); - mesh->normals = malloc( mesh->vertex_count * sizeof(v3f) ); - -#if 0 - mesh->bounds[0][0] = pMdl->hull_min[0]; - mesh->bounds[0][1] = pMdl->hull_min[1]; - mesh->bounds[0][2] = pMdl->hull_min[2]; - mesh->bounds[0][0] = pMdl->hull_max[0]; - mesh->bounds[0][1] = pMdl->hull_max[1]; - mesh->bounds[0][2] = pMdl->hull_max[2]; -#endif - - mesh->indices_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 ) - { - /* indices */ - for ( int i = 0; i < pStrip->numIndices; i ++ ) - { - u16 i1 = *pIndexVTX( pStripGroup, - pStrip->indexOffset + i ); - - mesh->indices[ mesh->indices_count ++ ] = - pVertexVTX( pStripGroup, i1 )->origMeshVertID + - pMesh->vertexoffset; - } - } - } - } - } - } - } - - mstudiovertex_t *vertexData = GetVertexData( pVvdHdr ); - - for( int i = 0; i < mesh->vertex_count; i ++ ) - { - mstudiovertex_t *vert = vertexData + i; - - mesh->vertices[i][0] = vert->pos[0]; - mesh->vertices[i][1] = vert->pos[1]; - mesh->vertices[i][2] = vert->pos[2]; - - mesh->normals[i][0] = vert->norm[0]; - mesh->normals[i][1] = vert->norm[1]; - mesh->normals[i][2] = vert->norm[2]; - - mesh->uvs[i][0] = vert->uv[0]; - mesh->uvs[i][1] = vert->uv[1]; - } - - free( pVtxHdr ); - free( pVvdHdr ); - free( pMdl ); - - return mesh; -} -#endif /* CXR_VALVE_BIN */ - #endif /* CXR_VALVE_MAP_FILE */ #endif /* CXR_IMPLEMENTATION */ diff --git a/cxr/cxr_io.h b/cxr/cxr_io.h index ed28bb8..2e944aa 100644 --- a/cxr/cxr_io.h +++ b/cxr/cxr_io.h @@ -19,7 +19,7 @@ static i64 cxr_file_size( FILE *fileptr ); static char *cxr_findext( char *path, char const delim ); static char *cxr_findsep( char *path ); -static void cxr_stripext( char *path ); +static char *cxr_stripext( char *path ); static int cxr_path_is_abs( char const *path ); static char *cxr_filename( char *path ); @@ -41,11 +41,25 @@ static i64 cxr_file_size( FILE *fileptr ) return fsize; } +static int cxr_file_exists( const char *path ) +{ + FILE *fp; + if( (fp=fopen( path, "rb" )) ) + { + fclose(fp); + return 1; + } + + return 0; +} + static void *fs_disk_open_read( const char *path, int reserve_end, i64 *size ) { FILE *f = fopen( path, "rb" ); if( f ) { + printf( "fs_open_read (%s)\n", path ); + i64 fsize = cxr_file_size( f ); void *buf = malloc( fsize + reserve_end ); @@ -147,11 +161,11 @@ static char *cxr_findsep( char *path ) return ptr; } -static void cxr_stripext( char *path ) +static char *cxr_stripext( char *path ) { - char *point, *start; + char *point, *start, *ret = NULL; - // Skip folders + /* Skip any folders */ if( !(start = cxr_findsep( path )) ) { start = path; @@ -161,9 +175,12 @@ static void cxr_stripext( char *path ) { if( point > path ) { - *(point-1) = 0x00; + ret = point-1; + *(ret) = 0x00; } } + + return ret; } static void cxr_downlvl( char *path ) @@ -204,8 +221,39 @@ static int cxr_path_is_abs( char const *path ) #endif } +static void cxr_lowercase( char *str ) +{ + char *c = str; + while( *c ) + { + int val = (int)*c; + if( val >= (int)'A' && val <= (int)'Z' ) + { + val = (int)'a' + (val - (int)'A'); + *c = (char)val; + } + + c ++; + } +} + +static void cxr_unixpath( char *path ) +{ + char *c = path; + while( *c ) + { + if( *c == '\\' ) + *c = '/'; + + c ++; + } +} + static char *cxr_str_clone( char const *str, int extra ) { + if( !str ) + return NULL; + char *newstr = malloc(strlen(str)+1+extra); strcpy( newstr, str ); 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 */ diff --git a/cxr/test.c b/cxr/test.c index 3903b96..7d82c1e 100644 --- a/cxr/test.c +++ b/cxr/test.c @@ -7,12 +7,30 @@ int main(int arc, const char *argv[]) "/home/harry/.steam/steam/steamapps/common/" "Counter-Strike Global Offensive/csgo/gameinfo.txt" ) ) { - cxr_tri_mesh *mesh_test = cxr_load_mdl( "models/pigeon.mdl" ); - - if( mesh_test ) + valve_model *model_test = valve_load_model( "models/pigeon.mdl" ); + if( model_test ) { cxr_log( "Mesh loaded\n" ); - cxr_free_tri_mesh( mesh_test ); + + for( int i=0; imaterial_count; i++ ) + { + cxr_log( " material: %s\n", model_test->materials[i] ); + + valve_material *material = + valve_load_material( model_test->materials[i] ); + + if( material ) + { + cxr_log( " $basetexture: %s\n" + " $bumpmap: %s\n", + material->basetexture, + material->bumpmap ); + + valve_free_material( material ); + } + } + + valve_free_model( model_test ); } else cxr_log( "Mesh failed to load\n" ); -- 2.25.1