From dbd379f76bcb2139fdb5740232511fa789018e10 Mon Sep 17 00:00:00 2001 From: hgn Date: Sun, 1 May 2022 01:03:32 +0100 Subject: [PATCH] model view prototype --- __init__.py | 189 +++++- build.sh | 13 +- cxr/cxr.h | 193 +++++-- cxr/cxr_io.h | 215 +++++++ cxr/cxr_log.h | 28 + cxr/cxr_math.h | 7 + cxr/cxr_mem.h | 21 +- cxr/cxr_types.h | 26 + cxr/cxr_valve_bin.h | 1324 +++++++++++++++++++++++++++++++++++++++++++ cxr/test.c | 30 +- 10 files changed, 1989 insertions(+), 57 deletions(-) create mode 100644 cxr/cxr_io.h create mode 100644 cxr/cxr_log.h create mode 100644 cxr/cxr_types.h create mode 100644 cxr/cxr_valve_bin.h diff --git a/__init__.py b/__init__.py index 3ce19bd..9eba53c 100644 --- a/__init__.py +++ b/__init__.py @@ -48,12 +48,14 @@ cxr_ui_draw_handler = None # Batches cxr_view_lines = None cxr_view_mesh = None +cxr_mdl_mesh = None cxr_jobs_batch = None cxr_jobs_inf = [] cxr_error_inf = None # Shaders cxr_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') + cxr_ui_shader = gpu.types.GPUShader(""" uniform mat4 ModelViewProjectionMatrix; uniform float scale; @@ -78,6 +80,88 @@ void main() } """) +cxr_mdl_shader = gpu.types.GPUShader(""" +uniform mat4 modelMatrix; +uniform mat4 viewProjectionMatrix; + +in vec3 aPos; +in vec3 aNormal; + +out vec3 lPos; +out vec3 lNormal; + +void main() +{ + vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0); + vec3 worldPos = pWorldPos.xyz; + + gl_Position = viewProjectionMatrix * pWorldPos; + lNormal = aNormal; //mat3(transpose(inverse(modelMatrix))) * aNormal; + lPos = worldPos; +} +""",""" + +uniform vec4 colour; +uniform vec3 testLightDir; + +in vec3 lNormal; +in vec3 lPos; + +out vec4 FragColor; + +float SoftenCosineTerm( float flDot ) +{ + return ( flDot + ( flDot * flDot ) ) * 0.5; +} + +vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir ) +{ + float fResult = 0.0; + float NDotL = dot( worldNormal, lightDir ); + + fResult = clamp( NDotL, 0.0, 1.0 ); + fResult = SoftenCosineTerm( fResult ); + + vec3 fOut = vec3( fResult, fResult, fResult ); + return fOut; +} + +vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal ) +{ + vec3 linearColor = vec3(0.0,0.0,0.0); + linearColor += DiffuseTerm( worldNormal, testLightDir ); + + return linearColor; +} + +vec3 LinearToGamma( vec3 f3linear ) +{ + return pow( f3linear, vec3(1.0 / 2.2) ); +} + +void main() +{ + vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 ); + vec4 normalTexel = vec4(1.0,1.0,1.0,1.0); + vec4 baseColor = colour; + + //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord ); + //tangentSpaceNormal = 2.0 * normalTexel - 1.0; + + vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 ); + + vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 ); + diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal ); + + // multiply by .5 since we want a 50% (in gamma space) reflective surface) + diffuseLighting *= pow( 0.5, 2.2 ); + + vec3 result = diffuseLighting * baseColor.xyz; + + FragColor = vec4( LinearToGamma(result), 1.0 ); +} +""") + # Render functions # def cxr_ui(_,context): @@ -137,7 +221,8 @@ def cxr_ui(_,context): CXR_COMPILER_CHAIN.WAIT_REDRAW = False def cxr_draw(): - global cxr_view_shader, cxr_view_mesh, cxr_view_lines + global cxr_view_shader, cxr_view_mesh, cxr_view_lines, cxr_mdl_shader,\ + cxr_mdl_mesh cxr_view_shader.bind() @@ -150,11 +235,33 @@ def cxr_draw(): if cxr_view_lines != None: cxr_view_lines.draw( cxr_view_shader ) - gpu.state.depth_test_set('LESS_EQUAL') - gpu.state.blend_set('ADDITIVE') if cxr_view_mesh != None: + gpu.state.depth_test_set('LESS_EQUAL') + gpu.state.blend_set('ADDITIVE') + cxr_view_mesh.draw( cxr_view_shader ) + if cxr_mdl_mesh != None: + gpu.state.depth_mask_set(True) + gpu.state.depth_test_set('LESS_EQUAL') + gpu.state.face_culling_set('FRONT') + gpu.state.blend_set('NONE') + cxr_mdl_shader.bind() + cxr_mdl_shader.uniform_float('colour',(0.5,0.5,0.5,1.0)) + cxr_mdl_shader.uniform_float("viewProjectionMatrix", \ + bpy.context.region_data.perspective_matrix) + + testmdl = bpy.context.scene.objects['target'] + light = bpy.context.scene.objects['point'] + relative = light.location - testmdl.location + relative.normalize() + + cxr_mdl_shader.uniform_float("modelMatrix", testmdl.matrix_world) + cxr_mdl_shader.uniform_float("testLightDir", relative) + + + cxr_mdl_mesh.draw( cxr_mdl_shader ) + def cxr_jobs_update_graph(jobs): global cxr_jobs_batch, cxr_ui_shader, cxr_jobs_inf @@ -287,6 +394,8 @@ class cxr_static_mesh(Structure): class cxr_tri_mesh(Structure): _fields_ = [("vertices",POINTER(c_double *3)), + ("normals",POINTER(c_double *3)), + ("uvs",POINTER(c_double *2)), ("colours",POINTER(c_double *4)), ("indices",POINTER(c_int32)), ("indices_count",c_int32), @@ -481,15 +590,22 @@ class vdf_structure(): # Other 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_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_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\ - libcxr_world_preview, libcxr_free_tri_mesh ] + libcxr_world_preview, libcxr_free_tri_mesh, \ + libcxr_fs_set_gameinfo, libcxr_fs_exit, libcxr_fs_get, \ + libcxr_load_mdl ] # Callbacks - def libcxr_log_callback(logStr): print( F"{logStr.decode('utf-8')}",end='' ) @@ -829,7 +945,7 @@ def cxr_decompose_globalerr( mesh_src ): err = c_int32(0) world = libcxr_decompose.call( mesh_src, pointer(err) ) - if world == None: + if not world: cxr_view_mesh = None cxr_batch_lines() @@ -1588,6 +1704,58 @@ class CXR_DEV_OPERATOR(bpy.types.Operator): libcxr_write_test_data.call( pointer(mesh_src) ) return {'FINISHED'} +class CXR_INIT_FS_OPERATOR(bpy.types.Operator): + bl_idname="convexer.fs_init" + bl_label="Initialize filesystem" + + def execute(_,context): + gameinfo = F'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt' + + if libcxr_fs_set_gameinfo.call( gameinfo.encode('utf-8') ) == 1: + print( "File system ready" ) + else: + print( "File system failed to initialize" ) + + return {'FINISHED'} + +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 + + mdlpath = bpy.context.scene.cxr_data.dev_mdl.encode('utf-8') + pmesh = libcxr_load_mdl.call( mdlpath ) + + if not pmesh: + print( "Failed to load model" ) + return {'FINISHED'} + + mesh = pmesh[0] + + #TODO: remove code dupe + vertices = mesh.vertices[:mesh.vertex_count] + vertices = [(_[0],_[1],_[2]) for _ in vertices] + + normals = mesh.normals[:mesh.vertex_count] + normals = [(_[0],_[1],_[2]) for _ in normals] + + indices = mesh.indices[:mesh.indices_count] + 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, + ) + + libcxr_free_tri_mesh.call( pmesh ) + + scene_redraw() + return {'FINISHED'} + # UI: Preview how the brushes will looks in 3D view # class CXR_PREVIEW_OPERATOR(bpy.types.Operator): @@ -2153,6 +2321,9 @@ class CXR_VIEW3D( bpy.types.Panel ): row.scale_y = 2 row.operator("convexer.reset") + layout.prop( bpy.context.scene.cxr_data, "dev_mdl" ) + layout.operator( "convexer.model_load" ) + # Main scene properties interface, where all the settings go # class CXR_INTERFACE(bpy.types.Panel): @@ -2166,6 +2337,7 @@ class CXR_INTERFACE(bpy.types.Panel): if CXR_GNU_LINUX==1: _.layout.operator("convexer.reload") _.layout.operator("convexer.dev_test") + _.layout.operator("convexer.fs_init") _.layout.operator("convexer.hash_reset") settings = context.scene.cxr_data @@ -2560,13 +2732,16 @@ class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup): comp_compile: bpy.props.BoolProperty(name="Compile",default=True) comp_pack: bpy.props.BoolProperty(name="Pack",default=False) + dev_mdl: bpy.props.StringProperty(name="Model",default="") + classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \ CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\ CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\ CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\ CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR,\ CXR_VIEW3D, CXR_COMPILER_CHAIN, CXR_RESET_HASHES,\ - CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET ] + CXR_COMPILE_MATERIAL, CXR_COLLECTION_PANEL, CXR_RESET, \ + CXR_INIT_FS_OPERATOR, CXR_LOAD_MODEL_OPERATOR ] vmt_param_dynamic_class = None diff --git a/build.sh b/build.sh index 6cf104c..c5a1a51 100755 --- a/build.sh +++ b/build.sh @@ -65,7 +65,7 @@ fi mkdir -p nbvtf/obj compile $C -O1 -ggdb -fPIC -shared \ -Wall -Wno-unused-variable -Wno-unused-function -std=c99 -pedantic \ - -DCXR_SO -DCXR_DEBUG -DCXR_VALVE_MAP_FILE \ + -DCXR_SO -DCXR_DEBUG -DCXR_VALVE_MAP_FILE -DCXR_VALVE_BIN \ -xc cxr/cxr.h \ -o libcxr$lib_ext \ -lm @@ -96,8 +96,9 @@ compile $C -O3 \ -lm # This is for testing with asan on linux -# compile gcc -ggdb -O1 -Wall \ -# -Wno-unused-variable -Wno-unused-function $asan -Werror=vla \ -# cxr/test.c \ -# -o test$exe_ext \ -# -lm +compile $C -ggdb -O1 -Wall \ + -Wno-unused-variable -Wno-unused-function $asan -Werror=vla \ + -DCXR_SO -DCXR_DEBUG -DCXR_VALVE_MAP_FILE \ + cxr/test.c \ + -o test$exe_ext \ + -lm diff --git a/cxr/cxr.h b/cxr/cxr.h index e1c1679..2a4267f 100644 --- a/cxr/cxr.h +++ b/cxr/cxr.h @@ -48,7 +48,6 @@ LICENSE: GPLv3.0, please see COPYING and LICENSE for more information IMPLEMENTATION */ -#define CXR_API #define CXR_EPSILON 0.001 #define CXR_PLANE_SIMILARITY_MAX 0.998 #define CXR_BIG_NUMBER 1e300 @@ -60,31 +59,17 @@ LICENSE: GPLv3.0, please see COPYING and LICENSE for more information #include #include -#include -#include #include #include -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; -typedef int8_t i8; -typedef int16_t i16; -typedef int32_t i32; -typedef int64_t i64; - -typedef unsigned int uint; - -typedef double v2f[2]; -typedef double v3f[3]; -typedef double v4f[4]; -typedef v3f m3x3f[3]; -typedef v3f m4x3f[4]; -typedef v3f boxf[2]; - +#include "cxr_types.h" #include "cxr_math.h" #include "cxr_mem.h" +#include "cxr_log.h" + +#ifdef CXR_VALVE_BIN + #include "cxr_valve_bin.h" +#endif typedef struct cxr_world cxr_world; typedef struct cxr_solid cxr_solid; @@ -232,8 +217,11 @@ struct cxr_mesh /* Simple mesh type mainly for debugging */ struct cxr_tri_mesh { - v3f *vertices; + v3f *vertices, + *normals; + v2f *uvs; v4f *colours; + i32 *indices, indices_count, vertex_count; @@ -314,9 +302,6 @@ enum cxr_soliderr #endif #endif -static void (*cxr_log_func)(const char *str); -static void (*cxr_line_func)( v3f p0, v3f p1, v4f colour ); - static int cxr_range(int x, int bound) { if( x < 0 ) @@ -391,21 +376,6 @@ static void colour_random_brush(int n, v4f colour) #ifdef CXR_DEBUG -static void cxr_log( const char *fmt, ... ) -{ - char buf[512]; - - va_list args; - va_start( args, fmt ); - vsnprintf( buf, sizeof(buf)-1, fmt, args ); - va_end(args); - - if( cxr_log_func ) - cxr_log_func( buf ); - - fputs(buf,stdout); -} - static void cxr_debug_line( v3f p0, v3f p1, v4f colour ) { if( cxr_line_func ) @@ -2218,6 +2188,8 @@ CXR_API cxr_tri_mesh *cxr_world_preview( cxr_world *world ) out->colours = malloc( sizeof(v4f)*out->vertex_count ); out->vertices = malloc( sizeof(v3f)*out->vertex_count ); out->indices = malloc( sizeof(i32)*out->indices_count ); + out->uvs = NULL; + out->normals = NULL; v3f *overts = out->vertices; v4f *colours = out->colours; @@ -2299,6 +2271,8 @@ CXR_API void cxr_free_tri_mesh( cxr_tri_mesh *mesh ) free( mesh->colours ); free( mesh->indices ); free( mesh->vertices ); + free( mesh->normals ); + free( mesh->uvs ); free( mesh ); } @@ -3535,5 +3509,144 @@ 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 new file mode 100644 index 0000000..ed28bb8 --- /dev/null +++ b/cxr/cxr_io.h @@ -0,0 +1,215 @@ +#ifndef CXR_IO_H +#define CXR_IO_H + +#include +#include +#include +#include "cxr_types.h" + +/* Read binary or text assets in full from file */ +static void *cxr_asset_read_s( const char *path, i64 *size ); +static void *cxr_asset_read( const char *path ); +static char *cxr_textasset_read_s( const char *path, i64 *size ); +static char *cxr_textasset_read( const char *name ); + +static i64 cxr_file_size( FILE *fileptr ); + +/* Path utilities */ +/* Returns pointer to the extension in path */ +static char *cxr_findext( char *path, char const delim ); +static char *cxr_findsep( char *path ); + +static void cxr_stripext( char *path ); +static int cxr_path_is_abs( char const *path ); +static char *cxr_filename( char *path ); + +/* Remove one level (nop if can't) eg: /home/harry/test.file -> /home/harry/ */ +static void cxr_downlvl( char *path ); + +#ifdef _WIN32 + #define CXR_FOLDER_CHAR '\\' +#else + #define CXR_FOLDER_CHAR '/' +#endif + +static i64 cxr_file_size( FILE *fileptr ) +{ + fseek( fileptr, 0, SEEK_END ); + i64 fsize = ftell( fileptr ); + fseek( fileptr, 0, SEEK_SET ); + + return fsize; +} + +static void *fs_disk_open_read( const char *path, int reserve_end, i64 *size ) +{ + FILE *f = fopen( path, "rb" ); + if( f ) + { + i64 fsize = cxr_file_size( f ); + void *buf = malloc( fsize + reserve_end ); + + if( buf ) + { + if( fread( buf, 1, fsize, f ) != fsize ) + { + free( buf ); + buf = NULL; + } + } + + *size = fsize; + + fclose( f ); + return buf; + } + else + { + return NULL; + } +} + +static char *fs_disk_load_text( const char *path, i64 *size ) +{ + char *buf; + i64 fsize; + + if( (buf = fs_disk_open_read( path, 1, &fsize )) ) + { + buf[ fsize ] = 0x00; + *size = fsize +1; + + return buf; + } + + return NULL; +} + +static void *cxr_asset_read_s( const char *path, i64 *size ) +{ + return fs_disk_open_read( path, 0, size ); +} + +static void *cxr_asset_read( const char *path ) +{ + i64 size; + return fs_disk_open_read( path, 0, &size ); +} + +static char *cxr_textasset_read_s( const char *path, i64 *size ) +{ + return fs_disk_load_text( path, size ); +} + +static char *cxr_textasset_read( const char *name ) +{ + i64 size; + return fs_disk_load_text( name, &size ); +} + +static char *cxr_findext( char *path, char const delim ) +{ + char *c, *ptr; + + c = path; + ptr = NULL; + + while( *c ) + { + if( *c == delim ) + { + ptr = c + 1; + } + + c ++; + } + + return ptr; +} + +static char *cxr_findsep( char *path ) +{ + char *c, *ptr; + + c = path; + ptr = NULL; + + while( *c ) + { + if( *c == '/' || *c == '\\' ) + { + ptr = c + 1; + } + + c ++; + } + + return ptr; +} + +static void cxr_stripext( char *path ) +{ + char *point, *start; + + // Skip folders + if( !(start = cxr_findsep( path )) ) + { + start = path; + } + + if( (point = cxr_findext( start, '.' )) ) + { + if( point > path ) + { + *(point-1) = 0x00; + } + } +} + +static void cxr_downlvl( char *path ) +{ + char *start_name, *c; + + c = path; + while( *c ) + c ++; + int len = c - path; + + if( len ) + path[ len -1 ] = 0x00; + + if( (start_name = cxr_findsep( path ) )) + *start_name = 0x00; + else + path[0] = 0x00; +} + +static char *cxr_filename( char *path ) +{ + char *base_name; + if( (base_name = cxr_findsep( path ) )) + return base_name; + + return path; +} + +static int cxr_path_is_abs( char const *path ) +{ +#ifdef _WIN32 + if( strlen( path ) < 2 ) return 0; + return path[1] == ':'; +#else + if( strlen( path ) < 1 ) return 0; + return path[0] == '/'; +#endif +} + +static char *cxr_str_clone( char const *str, int extra ) +{ + char *newstr = malloc(strlen(str)+1+extra); + strcpy( newstr, str ); + + return newstr; +} + +#endif /* CXR_IO_H */ diff --git a/cxr/cxr_log.h b/cxr/cxr_log.h new file mode 100644 index 0000000..48ebadf --- /dev/null +++ b/cxr/cxr_log.h @@ -0,0 +1,28 @@ +#ifndef CXR_LOG_H +#define CXR_LOG_H + +#include +#include + +#include "cxr_types.h" + +static void (*cxr_log_func)(const char *str); +static void (*cxr_line_func)( v3f p0, v3f p1, v4f colour ); + +static void cxr_log( const char *fmt, ... ) +{ + char buf[512]; + + va_list args; + va_start( args, fmt ); + vsnprintf( buf, sizeof(buf)-1, fmt, args ); + va_end(args); + + if( cxr_log_func ) + cxr_log_func( buf ); + + fputs(buf,stdout); +} + + +#endif /* CXR_LOG_H */ diff --git a/cxr/cxr_math.h b/cxr/cxr_math.h index 68f32ff..dcaaab5 100644 --- a/cxr/cxr_math.h +++ b/cxr/cxr_math.h @@ -7,6 +7,11 @@ * Other useful geometric functions */ +#ifndef CXR_MATH_H +#define CXR_MATH_H + +#include "cxr_types.h" + #define CXR_INLINE static inline #define CXR_PIf 3.14159265358979323846264338327950288f @@ -617,3 +622,5 @@ CXR_INLINE double segment_segment_dist( v3f a0, v3f a1, v3f b0, v3f b1, return v3_dist( a, b ); } + +#endif /* CXR_MATH_H */ diff --git a/cxr/cxr_mem.h b/cxr/cxr_mem.h index 4378314..38e7a3f 100644 --- a/cxr/cxr_mem.h +++ b/cxr/cxr_mem.h @@ -1,3 +1,12 @@ +#ifndef CXR_MEM_H +#define CXR_MEM_H + +#include +#include + +#include "cxr_types.h" +#include "cxr_math.h" + typedef struct cxr_abuffer cxr_abuffer; struct cxr_abuffer @@ -28,7 +37,8 @@ static void *__cxr_ab_ptr( struct cxr_abuffer *buffer, u32 index #ifdef CXR_DEBUG_ALLOCATORS if( index >= buffer->capacity || index < 0 ) { - printf( "index out of capactity (%d /: [0->%d (cap)]) (%s)\n", index, buffer->capacity, debug_str ); + printf( "index out of capactity (%d /: [0->%d (cap)]) (%s)\n", index, + buffer->capacity, debug_str ); exit(1); } #endif @@ -84,10 +94,13 @@ static void cxr_ab_push( struct cxr_abuffer *buffer, void *em ) static void cxr_ab_init( struct cxr_abuffer *buffer, u32 esize, u32 cap ) { buffer->esize = esize; - buffer->capacity = cxr_max(1,cap); + buffer->capacity = cap; buffer->count = 0; - buffer->arr = malloc( buffer->esize*buffer->capacity ); + if( cap ) + buffer->arr = malloc( buffer->esize*buffer->capacity ); + else + buffer->arr = NULL; } static void cxr_ab_clear( struct cxr_abuffer *buffer ) @@ -99,3 +112,5 @@ static void cxr_ab_free( struct cxr_abuffer *buffer ) { free( buffer->arr ); } + +#endif /* CXR_TYPES_H */ diff --git a/cxr/cxr_types.h b/cxr/cxr_types.h new file mode 100644 index 0000000..98229dd --- /dev/null +++ b/cxr/cxr_types.h @@ -0,0 +1,26 @@ +#ifndef CXR_TYPES_H +#define CXR_TYPES_H + +#include + +#define CXR_API + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef unsigned int uint; + +typedef double v2f[2]; +typedef double v3f[3]; +typedef double v4f[4]; +typedef v3f m3x3f[3]; +typedef v3f m4x3f[4]; +typedef v3f boxf[2]; + +#endif 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 */ diff --git a/cxr/test.c b/cxr/test.c index b8d91d8..3903b96 100644 --- a/cxr/test.c +++ b/cxr/test.c @@ -1,4 +1,31 @@ -#define CXR_IMPLEMENTATION +#define CXR_VALVE_BIN +#include "cxr.h" + +int main(int arc, const char *argv[]) +{ + if( cxr_fs_set_gameinfo( + "/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 ) + { + cxr_log( "Mesh loaded\n" ); + cxr_free_tri_mesh( mesh_test ); + } + else + cxr_log( "Mesh failed to load\n" ); + } + + cxr_fs_exit(); + return 0; +} + + + + +#if 0 #define CXR_VALVE_MAP_FILE #define CXR_DEBUG 1 #include "cxr.h" @@ -36,3 +63,4 @@ int main(int arc, const char *argv[]) cxr_vdf_close(vdo); return 0; } +#endif -- 2.25.1