8d0bf9c33c6702daa9ee5c5c7f0a91813c3b4f9e
[convexer.git] / cxr / cxr_valve_bin.h
1 /*
2 * The following is used to read the existing content from CS:GO compiled assets
3 *
4 * Supported formats:
5 * vdf
6 * vpk
7 * mdl
8 */
9
10 #ifndef CXR_VALVE_BIN_H
11 #define CXR_VALVE_BIN_H
12
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "cxr_types.h"
17 #include "cxr_math.h"
18 #include "cxr_io.h"
19 #include "cxr_mem.h"
20 #include "cxr_log.h"
21
22 typedef struct VPKHeader VPKHeader;
23 typedef struct VPKDirectoryEntry VPKDirectoryEntry;
24 typedef struct vdf_kv vdf_kv;
25 typedef struct vdf_node vdf_node;
26 typedef struct vdf_ctx vdf_ctx;
27 typedef struct fs_locator fs_locator;
28
29 /*
30 * These are 'unofficial' representations of the original formats, more C
31 * friendly
32 */
33 typedef struct valve_file_system valve_file_system;
34 typedef struct valve_model valve_model;
35 typedef struct valve_model_batch valve_model_batch;
36 typedef struct valve_material valve_material;
37
38 /* Api */
39
40 CXR_API i32 cxr_fs_set_gameinfo( const char *path ); /* Setup system */
41 CXR_API void cxr_fs_exit(void); /* Clean up */
42 CXR_API void *cxr_fs_get( const char *path, i32 stringbuffer ); /* Get a file */
43 CXR_API void cxr_fs_free( void *data );
44 CXR_API i32 cxr_fs_find( const char *path, fs_locator *locator );
45
46 CXR_API valve_model *valve_load_model( const char *relpath );
47 CXR_API void valve_free_model( valve_model *model );
48 CXR_API valve_material *valve_load_material( const char *path );
49 CXR_API void valve_free_material( valve_material *material );
50
51 /*
52 * File system implementation
53 */
54
55 #pragma pack(push, 1)
56
57 struct VPKHeader
58 {
59 u32 Signature;
60 u32 Version;
61 u32 TreeSize;
62 u32 FileDataSectionSize;
63 u32 ArchiveMD5SectionSize;
64 u32 OtherMD5SectionSize;
65 u32 SignatureSectionSize;
66 };
67
68 struct VPKDirectoryEntry
69 {
70 u32 CRC;
71 u16 PreloadBytes;
72 u16 ArchiveIndex;
73 u32 EntryOffset;
74 u32 EntryLength;
75 u16 Terminator;
76 };
77
78 #pragma pack(pop)
79
80 static struct valve_file_system
81 {
82 char *gamedir,
83 *exedir;
84
85 /* Runtime */
86 VPKHeader vpk;
87 char *directory_tree;
88
89 cxr_abuffer searchpaths;
90
91 FILE *current_archive;
92 u16 current_idx;
93
94 int initialized;
95 }
96 fs_global = { .initialized = 0 };
97
98 struct fs_locator
99 {
100 VPKDirectoryEntry *vpk_entry;
101 char path[ 1024 ];
102 };
103
104 /*
105 * VPK reading
106 */
107
108 static VPKDirectoryEntry *vpk_find( const char *asset );
109
110 /*
111 * VDF reading
112 */
113
114 static vdf_node *vdf_open_file( const char *fn );
115 static void vdf_free_r( vdf_node *p );
116
117 /* Starting from *it, get next child with matching name from node. */
118 static vdf_node *vdf_next( vdf_node *node, const char *name, int *it );
119
120 /* Create new empty node attached to parent. name can be NULL */
121 static vdf_node *vdf_create_node( vdf_node *parent, const char *name );
122
123 /* KV access */
124 static const char *kv_get( vdf_node *node, const char *key,
125 const char *value_defalt );
126
127 /* Iterate each keyvalue starting from *it until key is matched */
128 static char *kv_next( vdf_node *node, const char *key, int *it );
129
130 static int kv_get_int( vdf_node *node, const char *key, const int fallback );
131 static float kv_get_float( vdf_node *node, const char *key, float fallback );
132
133 static void kv_int_array( vdf_node *node, const char *key, u32 count,
134 int *arr );
135 static void kv_float_array( vdf_node *node, const char *key, u32 count,
136 float *arr );
137 static void kv_double_array( vdf_node *node, const char *key, u32 count,
138 double *arr );
139
140 #define vdf_foreach( NODE, STR, AS ) \
141 int __vdf_it_##AS = 0; \
142 vdf_node * AS;\
143 while( (AS = vdf_next( NODE, STR, &__vdf_it_##AS )) )
144
145 #define kv_foreach( NODE, STR, AS ) \
146 int __kv_it_##AS = 0; \
147 const char * AS;\
148 while( (AS = kv_next( NODE, STR, &__kv_it_##AS )) )
149
150 static VPKDirectoryEntry *vpk_find( const char *asset )
151 {
152 valve_file_system *fs = &fs_global;
153
154 if( !fs->directory_tree )
155 return NULL;
156
157 char wbuf[ 512 ];
158 strcpy( wbuf, asset );
159
160 /*
161 * This currently fails if the filename doesn't have a file extension, or
162 * if it is located at the root path. I'm not sure if this is defined
163 * behaviour or not. TODO: allow it to work anyway
164 */
165
166 char *ext = cxr_findext( wbuf, '.' );
167
168 if( !ext ) return NULL;
169 *(ext-1) = 0x00;
170
171 char *fn = cxr_findext( wbuf, '/' );
172
173 if( !fn ) return NULL;
174 *(fn-1) = 0x00;
175
176 char *dir = wbuf;
177 char *pCur = fs->directory_tree;
178
179 while( 1 )
180 {
181 if( !*pCur ) break;
182
183 int bExt = !strcmp( ext, pCur );
184
185 while( *( pCur ++ ) ) {};
186 while( 1 )
187 {
188 if( !*pCur ) { pCur ++; break; }
189
190 int bDir = !strcmp( dir, pCur );
191
192 while( *( pCur ++ ) ) {};
193 while( 1 )
194 {
195 if( !*pCur ) { pCur ++; break; }
196
197 const char *vpk_fn = pCur;
198
199 while( *( pCur ++ ) ) {};
200 VPKDirectoryEntry *entry = (VPKDirectoryEntry *)pCur;
201
202 if( !strcmp( vpk_fn, fn ) && bExt && bDir )
203 {
204 return entry;
205 }
206
207 pCur += entry->PreloadBytes + sizeof( VPKDirectoryEntry );
208 }
209
210 if( bDir && bExt ) return NULL;
211 }
212
213 if( bExt ) return NULL;
214 }
215
216 return NULL;
217 }
218
219 /*
220 * VDF
221 */
222
223 struct vdf_kv
224 {
225 char *key;
226 char *value;
227 };
228
229 struct vdf_node
230 {
231 char *name;
232
233 vdf_node *parent;
234
235 cxr_abuffer abnodes,
236 abpairs;
237
238 u32 user;
239 u32 user1;
240 };
241
242 static vdf_node *vdf_next( vdf_node *node, const char *name, int *it )
243 {
244 if( !node )
245 return NULL;
246
247 for( int i = it? *it: 0; i < node->abnodes.count; i ++ )
248 {
249 vdf_node **ptr_child = cxr_ab_ptr( &node->abnodes, i ),
250 *child = *ptr_child;
251
252 if( !name || !strcmp( name, child->name ))
253 {
254 if( it ) *it = i+1;
255 return child;
256 }
257 }
258
259 return NULL;
260 }
261
262 static char *kv_next( vdf_node *node, const char *key, int *it )
263 {
264 char *val;
265
266 if( node )
267 {
268 while( *it < node->abpairs.count )
269 {
270 vdf_kv *kv = cxr_ab_ptr( &node->abpairs, *it );
271
272 if( !strcmp( kv->key, key ) )
273 {
274 val = kv->value;
275 *it = *it + 1;
276 return val;
277 }
278
279 *it = *it + 1;
280 }
281 }
282
283 return NULL;
284 }
285
286 static const char *kv_get( vdf_node *node, const char *key,
287 const char *value_defalt )
288 {
289 int it = 0;
290 char *val = kv_next( node, key, &it );
291
292 if( val )
293 return val;
294
295 return value_defalt;
296 }
297
298 static void vdf_str_to_int( const char *src, void *dest )
299 {
300 *((int *)dest) = atoi( src );
301 }
302
303 static void vdf_str_to_float( const char *src, void *dest )
304 {
305 *((float *)dest) = atof( src );
306 }
307
308 static void vdf_str_to_double( const char *src, void *dest )
309 {
310 *((double *)dest) = atof( src );
311 }
312
313 static void kv_parse_array( const char *source, u32 esize, u32 count,
314 void(*interp_func)(const char *src, void *dest), void *arr )
315 {
316 if( !source )
317 return;
318
319 char value_buf[ 64 ];
320 int token = 0;
321
322 u32 i = 0;
323 u32 k = 0;
324
325 char const *c = source;
326
327 while( *c )
328 {
329 if( *c==' ' || *c=='\t' || *c=='[' || *c==']' || *c=='(' || *c==')' )
330 {
331 if( token )
332 {
333 value_buf[ k ] = 0x00;
334 token = 0;
335
336 interp_func( value_buf, ((u8 *)arr) + i*esize );
337 i ++;
338
339 if( i >= count )
340 {
341 break;
342 }
343 }
344 }
345 else
346 {
347 if( !token )
348 {
349 token = 1;
350 k = 0;
351 }
352
353 if( token )
354 {
355 if( k < sizeof( value_buf ) - 1 )
356 {
357 value_buf[ k ++ ] = *c;
358 }
359 }
360 }
361
362 c ++;
363 }
364
365 /* Add remaining case if we hit null */
366 if( token && (i < count) )
367 {
368 value_buf[ k ] = 0x00;
369 interp_func( value_buf, ((u8 *)arr) + i*esize );
370 }
371 }
372
373 static void kv_int_array( vdf_node *node, const char *key, u32 count, int *arr )
374 {
375 kv_parse_array( kv_get( node, key, NULL ), sizeof(int), count,
376 vdf_str_to_int, arr );
377 }
378
379 static void kv_float_array( vdf_node *node, const char *key, u32 count,
380 float *arr )
381 {
382 kv_parse_array( kv_get( node, key, NULL ), sizeof(float), count,
383 vdf_str_to_float, arr );
384 }
385
386 static void kv_double_array( vdf_node *node, const char *key, u32 count,
387 double *arr )
388 {
389 kv_parse_array( kv_get( node, key, NULL ), sizeof(double), count,
390 vdf_str_to_double, arr );
391 }
392
393 static int kv_get_int( vdf_node *node, const char *key,
394 const int default_value )
395 {
396 const char *v = kv_get( node, key, NULL );
397 return v? atoi(v): default_value;
398 }
399
400 static float kv_get_float( vdf_node *node, const char *key,
401 float default_value )
402 {
403 const char *v = kv_get( node, key, NULL );
404 return v? atof( v ): default_value;
405 }
406
407 static vdf_node *vdf_create_node( vdf_node *parent, const char *name )
408 {
409 vdf_node *node = calloc( 1, sizeof( vdf_node ) );
410
411 /* init buffers */
412 cxr_ab_init( &node->abnodes, sizeof( vdf_node* ), 0 );
413 cxr_ab_init( &node->abpairs, sizeof( vdf_kv ), 0 );
414
415 if( name )
416 {
417 node->name = cxr_str_clone(name, 0);
418 }
419
420 if( parent )
421 {
422 node->parent = parent;
423
424 vdf_node **child = cxr_ab_empty( &parent->abnodes );
425 *child = node;
426 }
427
428 return node;
429 }
430
431 static void vdf_kv_append( vdf_node *p, const char *k, const char *v )
432 {
433 vdf_kv *kv = cxr_ab_empty( &p->abpairs );
434
435 u32 sv = strlen(v)+1;
436 u32 sk = strlen(k)+1;
437
438 kv->key = malloc( sv+sk );
439 kv->value = kv->key+sk;
440
441 memcpy( kv->key, k, sk );
442 memcpy( kv->value, v, sv );
443 }
444
445 static void vdf_free_r( vdf_node *p )
446 {
447 for( int i = 0; i < p->abpairs.count; i ++ )
448 {
449 vdf_kv *kv = cxr_ab_ptr( &p->abpairs, i );
450 free( kv->key ); /* key and value are allocated in the same buffer */
451 }
452
453 for( int i = 0; i < p->abnodes.count; i ++ )
454 {
455 vdf_node **ptr_node = cxr_ab_ptr( &p->abnodes, i );
456 vdf_free_r( *ptr_node );
457 }
458
459 cxr_ab_free( &p->abpairs );
460 cxr_ab_free( &p->abnodes );
461
462 free( p->name );
463 free( p );
464 }
465
466 /*
467 * Parsing
468 */
469
470 struct vdf_ctx
471 {
472 char name[1024];
473
474 vdf_node *root;
475
476 u32 lines;
477 u32 errors;
478
479 struct
480 {
481 int wait;
482 int expect_decl;
483
484 char *tokens[2];
485 int i;
486
487 char *ptr_read;
488
489 vdf_node *pnode;
490 }
491 st;
492 };
493
494 static void vdf_newln( vdf_ctx *ctx )
495 {
496 ctx->lines ++;
497
498 ctx->st.tokens[0] = NULL;
499 ctx->st.tokens[1] = NULL;
500 ctx->st.i = 0;
501 }
502
503 static void vdf_endl( vdf_ctx *ctx )
504 {
505 if( ctx->st.tokens[0] )
506 {
507 /* Keypair */
508 if( ctx->st.tokens[1] )
509 {
510 vdf_kv_append( ctx->st.pnode, ctx->st.tokens[0], ctx->st.tokens[1] );
511 }
512 else
513 {
514 /* decl */
515 strcpy( ctx->name, ctx->st.tokens[0] );
516 ctx->st.expect_decl = 1;
517 }
518 }
519
520 vdf_newln( ctx );
521 }
522
523 static int vdf_line_control( vdf_ctx *ctx )
524 {
525 if( *ctx->st.ptr_read == '\r' )
526 {
527 *ctx->st.ptr_read = 0x00;
528 return 1;
529 }
530 if( *ctx->st.ptr_read == '\n' )
531 {
532 *ctx->st.ptr_read = 0x00;
533 vdf_endl( ctx );
534 return 2;
535 }
536
537 return 0;
538 }
539
540 static void vdf_wait_endl( vdf_ctx *ctx )
541 {
542 while( (*ctx->st.ptr_read) && (*ctx->st.ptr_read != '\n') )
543 {
544 if( vdf_line_control( ctx ) == 2 )
545 {
546 return;
547 }
548
549 ctx->st.ptr_read ++;
550 }
551 }
552
553 static void vdf_parse_string( vdf_ctx *ctx )
554 {
555 while( *ctx->st.ptr_read )
556 {
557 if( *ctx->st.ptr_read == '"' )
558 {
559 *ctx->st.ptr_read = 0x00;
560 return;
561 }
562
563 if( vdf_line_control( ctx ) )
564 {
565 /* Unexpected end of line */
566 cxr_log( "vdf: unexpected EOL\n" );
567 return;
568 }
569
570 ctx->st.ptr_read ++;
571 }
572 }
573
574 static int vdf_parse_structure( vdf_ctx *ctx )
575 {
576 if( *ctx->st.ptr_read == '{' )
577 {
578 if( ctx->st.tokens[0] || !ctx->st.expect_decl )
579 {
580 /* Unexpected token '{' */
581 cxr_log( "vdf: Unexpected token '{'\n" );
582 ctx->errors ++;
583 }
584
585 ctx->st.expect_decl = 0;
586 ctx->st.pnode = vdf_create_node( ctx->st.pnode, ctx->name );
587
588 vdf_wait_endl( ctx );
589 return 1;
590 }
591
592 if( *ctx->st.ptr_read == '}' )
593 {
594 if( !ctx->st.pnode->parent )
595 {
596 /* Unexpected token '}' */
597 cxr_log( "vdf: Unexpected token '}'\n" );
598 ctx->errors ++;
599 }
600 else
601 {
602 ctx->st.pnode = ctx->st.pnode->parent;
603 }
604
605 vdf_wait_endl( ctx );
606 return 1;
607 }
608
609 return 0;
610 }
611
612 static void vdf_parse_begin_token( vdf_ctx *ctx, char *ptr )
613 {
614 ctx->st.tokens[ ctx->st.i ] = ptr;
615
616 if( ctx->st.expect_decl )
617 {
618 /* Unexpected token 'name' */
619 cxr_log( "vdf: unexpected token 'name'\n" );
620 ctx->errors ++;
621 }
622 }
623
624 static void vdf_parse_feedbuffer( vdf_ctx *ctx, char *buf )
625 {
626 ctx->st.ptr_read = buf;
627
628 while( *ctx->st.ptr_read )
629 {
630 if( !vdf_line_control( ctx ) )
631 {
632 if( (*ctx->st.ptr_read == '/') && (ctx->st.ptr_read[1] == '/') )
633 {
634 *ctx->st.ptr_read = 0x00;
635 ctx->st.ptr_read += 2;
636
637 vdf_endl( ctx );
638 vdf_wait_endl( ctx );
639 }
640 else
641 {
642 if( !vdf_parse_structure( ctx ) )
643 {
644 if( *ctx->st.ptr_read == ' ' || *ctx->st.ptr_read == '\t' )
645 {
646 *ctx->st.ptr_read = 0x00;
647
648 if( ctx->st.tokens[ ctx->st.i ] )
649 {
650 ctx->st.i ++;
651
652 if( ctx->st.i == 2 )
653 {
654 vdf_wait_endl( ctx );
655 }
656 }
657 }
658
659 /* New entry */
660 else if( !ctx->st.tokens[ ctx->st.i ] )
661 {
662 if( *ctx->st.ptr_read == '"' )
663 {
664 *ctx->st.ptr_read = 0x00;
665 ctx->st.ptr_read ++;
666
667 vdf_parse_begin_token( ctx, ctx->st.ptr_read );
668 vdf_parse_string( ctx );
669 }
670 else
671 {
672 if( !( *ctx->st.ptr_read == '/' &&
673 *(ctx->st.ptr_read + 1) == *ctx->st.ptr_read ) )
674 {
675 vdf_parse_begin_token( ctx, ctx->st.ptr_read );
676 }
677 }
678 }
679 }
680 }
681 }
682
683 ctx->st.ptr_read ++;
684 }
685 }
686
687 static void vdf_debug_indent( int level )
688 {
689 for(int i=0; i<level; i++)
690 cxr_log(" ");
691 }
692
693 static void vdf_debug_r( vdf_node *node, int level )
694 {
695 vdf_debug_indent(level);
696 cxr_log( "vdf_node(%p, name: '%s')\n", node, node->name );
697
698 vdf_debug_indent(level);
699 cxr_log( "{\n" );
700
701 for( int i=0; i<node->abpairs.count; i++ )
702 {
703 vdf_kv *kv = cxr_ab_ptr( &node->abpairs, i );
704
705 vdf_debug_indent(level+1);
706 cxr_log( "vdf_kv(%p, k: '%s', v: '%s')\n",
707 kv, kv->key, kv->value );
708 }
709
710 for( int i=0; i<node->abnodes.count; i++ )
711 {
712 vdf_node **child = cxr_ab_ptr( &node->abnodes, i );
713 vdf_debug_r( *child, level+1 );
714 }
715
716 vdf_debug_indent(level);
717 cxr_log( "}\n" );
718 }
719
720 /* This will wreck the original buffer, but must stay alive! */
721 static vdf_node *vdf_from_buffer( char *buffer )
722 {
723 vdf_node *root = vdf_create_node( NULL, NULL );
724
725 vdf_ctx ctx = {0};
726 ctx.root = ctx.st.pnode = root;
727
728 vdf_newln( &ctx );
729 vdf_parse_feedbuffer( &ctx, buffer );
730
731 #if 0
732 vdf_debug_r( root, 0 );
733 #endif
734
735 return root;
736 }
737
738 static vdf_node *vdf_open_file( const char *fn )
739 {
740 char *text_src = cxr_textasset_read( fn );
741
742 if( !text_src )
743 return NULL;
744
745 vdf_node *root = vdf_from_buffer( text_src );
746 free( text_src );
747
748 return root;
749 }
750
751 /*
752 * File system
753 */
754
755 CXR_API i32 cxr_fs_set_gameinfo( const char *path )
756 {
757 valve_file_system *fs = &fs_global;
758
759 if( fs->initialized )
760 cxr_fs_exit();
761
762 vdf_node *info = vdf_open_file( path );
763 if( !info )
764 return 0;
765
766 fs->gamedir = cxr_str_clone( path, 0 );
767 cxr_downlvl( fs->gamedir );
768
769 fs->exedir = cxr_str_clone( fs->gamedir, 0 );
770 cxr_downlvl( fs->exedir );
771
772 cxr_log( "Setting up file system:\n"
773 "gameinfo: %s\n"
774 "gamedir: %s\n"
775 "exedir: %s\n",
776
777 path, fs->gamedir, fs->exedir );
778
779 /* get search paths */
780 vdf_node *search_paths =
781 vdf_next
782 (
783 vdf_next
784 (
785 vdf_next( info, "GameInfo", NULL ),
786 "FileSystem",
787 NULL
788 ),
789 "SearchPaths",
790 NULL
791 );
792
793 cxr_ab_init( &fs->searchpaths, sizeof( char *), 0 );
794
795 kv_foreach( search_paths, "Game", kv_game )
796 {
797 cxr_log( "Game %s\n", kv_game );
798
799 if( kv_game[0] == '|' ) continue;
800
801 char *buf;
802 if( cxr_path_is_abs( kv_game ) )
803 {
804 buf = cxr_str_clone( kv_game, 1 );
805 strcat( buf, "/" );
806 }
807 else
808 {
809 buf = cxr_str_clone( fs->exedir, strlen(kv_game)+1 );
810 strcat( buf, kv_game );
811 strcat( buf, "/" );
812 }
813
814 char **sp = cxr_ab_empty( &fs->searchpaths );
815 *sp = buf;
816 }
817
818 vdf_free_r( info );
819
820 /* Find pack diretory */
821 char pack_path[512];
822 fs->current_archive = NULL;
823 fs->current_idx = 0x7fff;
824
825 for( int i = 0; i < fs->searchpaths.count; i ++ )
826 {
827 char **sp = cxr_ab_ptr( &fs->searchpaths, i );
828
829 strcpy( pack_path, *sp );
830 strcat( pack_path, "pak01_dir.vpk" );
831
832 fs->current_archive = fopen( pack_path, "rb" );
833
834 /* Read vpk directory */
835 if( fs->current_archive )
836 {
837 fread( &fs->vpk, sizeof(VPKHeader), 1, fs->current_archive );
838
839 fs->directory_tree = malloc( fs->vpk.TreeSize );
840 fread( fs->directory_tree, fs->vpk.TreeSize, 1, fs->current_archive );
841
842 break;
843 }
844 }
845
846 if( !fs->current_archive )
847 {
848 cxr_log( "Could not locate pak01_dir.vpk in %i searchpaths. "
849 "Stock models will not load!\n", fs->searchpaths.count );
850 }
851
852 fs->initialized = 1;
853 return 1;
854 }
855
856 CXR_API void cxr_fs_exit(void)
857 {
858 valve_file_system *fs = &fs_global;
859
860 for( int i = 0; i < fs->searchpaths.count; i ++ )
861 {
862 char **sp = cxr_ab_ptr( &fs->searchpaths, i );
863 free( *sp );
864 }
865
866 cxr_ab_free( &fs->searchpaths );
867
868 if( fs->directory_tree )
869 {
870 free( fs->directory_tree );
871 fs->directory_tree = NULL;
872 }
873
874 if( fs->current_archive )
875 {
876 fclose( fs->current_archive );
877 fs->current_archive = NULL;
878 }
879
880 free( fs->gamedir );
881 free( fs->exedir );
882
883 memset( fs, 0, sizeof( valve_file_system ) );
884 }
885
886 static char *cxr_vpk_read( VPKDirectoryEntry *entry, int stringbuffer )
887 {
888 valve_file_system *fs = &fs_global;
889
890 if( !fs->initialized )
891 return NULL;
892
893 char pak[1024];
894
895 /* Check if we need to change file handle */
896 if( entry->ArchiveIndex != fs->current_idx )
897 {
898 if( fs->current_archive )
899 fclose( fs->current_archive );
900
901 fs->current_archive = NULL;
902 fs->current_idx = entry->ArchiveIndex;
903
904 if( entry->ArchiveIndex == 0x7fff )
905 {
906 snprintf( pak, 1023, "%scsgo/pak01_dir.vpk", fs->exedir );
907 }
908 else
909 {
910 snprintf( pak, 1023, "%scsgo/pak01_%03hu.vpk",
911 fs->exedir, entry->ArchiveIndex );
912 }
913
914 fs->current_archive = fopen( pak, "rb" );
915
916 if( !fs->current_archive )
917 cxr_log( "Warning: could not locate %s\n", pak );
918 }
919
920 if( !fs->current_archive )
921 return NULL;
922
923 size_t offset = entry->EntryOffset,
924 length = entry->EntryLength;
925
926 /*
927 * File is stored in the index, after the tree
928 */
929 if( entry->ArchiveIndex == 0x7fff )
930 offset += fs->vpk.TreeSize + sizeof(VPKHeader);
931
932 /*
933 * Entire file is stored in the preload bytes;
934 * Backtrack offset from directory to get absolute offset
935 */
936 if( length == 0 )
937 {
938 offset = (char *)entry - (char *)fs->directory_tree;
939 offset += sizeof( VPKHeader );
940 offset += sizeof( VPKDirectoryEntry );
941
942 length = entry->PreloadBytes;
943 }
944 else
945 length += entry->PreloadBytes;
946
947 fseek( fs->current_archive, offset, SEEK_SET );
948
949 size_t alloc_size = stringbuffer? length+1: length;
950 char *filebuf = malloc( alloc_size );
951
952 if( stringbuffer )
953 filebuf[length] = 0x00;
954
955 if( fread( filebuf, 1, length, fs->current_archive ) == length )
956 return filebuf;
957 else
958 {
959 /* Invalid read */
960 free( filebuf );
961 return NULL;
962 }
963 }
964
965 CXR_API i32 cxr_fs_find( const char *path, fs_locator *locator )
966 {
967 valve_file_system *fs = &fs_global;
968
969 if( !fs->initialized )
970 return 0;
971
972 VPKDirectoryEntry *entry;
973
974 if( fs->directory_tree )
975 {
976 if( (entry = vpk_find( path )) )
977 {
978 locator->vpk_entry = entry;
979 locator->path[0] = 0x00;
980 return 1;
981 }
982 }
983
984 locator->vpk_entry = NULL;
985
986 /* Use physical search paths */
987 for( int i = 0; i < fs->searchpaths.count; i ++ )
988 {
989 char **sp = cxr_ab_ptr( &fs->searchpaths, i );
990
991 snprintf( locator->path, 1023, "%s%s", *sp, path );
992
993 if( cxr_file_exists( locator->path ) )
994 return 1;
995 }
996
997 /* File not found */
998 return 0;
999 }
1000
1001 CXR_API void cxr_fs_free( void *data )
1002 {
1003 free( data );
1004 }
1005
1006 CXR_API void *cxr_fs_get( const char *path, i32 stringbuffer )
1007 {
1008 valve_file_system *fs = &fs_global;
1009
1010 if( !fs->initialized )
1011 return NULL;
1012
1013 fs_locator locator;
1014
1015 if( cxr_fs_find( path, &locator ) )
1016 {
1017 if( locator.vpk_entry )
1018 {
1019 return cxr_vpk_read( locator.vpk_entry, stringbuffer );
1020 }
1021 else
1022 {
1023 char *filebuf;
1024
1025 if( stringbuffer )
1026 return cxr_textasset_read( locator.path );
1027 else
1028 return cxr_asset_read( locator.path );
1029 }
1030 }
1031
1032 return NULL;
1033 }
1034
1035 /*
1036 * VMDL interface
1037 *
1038 * This software is not affiliated with Valve Corporation
1039 * We are not affiliated, associated, authorized, endorsed by, or in any way
1040 * officially connected with Valve Corporation, or any of its subsidiaries or
1041 * its affiliates.
1042 *
1043 * All trademarks are property of their respective owners
1044 */
1045
1046 #define MAX_NUM_LODS 8
1047 #define MAX_NUM_BONES_PER_VERT 3
1048
1049 #pragma pack(push, 1)
1050 typedef struct
1051 {
1052 float weight[MAX_NUM_BONES_PER_VERT];
1053 char bone[MAX_NUM_BONES_PER_VERT];
1054 char numbones;
1055 }
1056 boneWeight_t;
1057
1058 typedef struct
1059 {
1060 boneWeight_t boneweights;
1061 float pos[3];
1062 float norm[3];
1063 float uv[2];
1064 }
1065 mstudiovertex_t;
1066
1067 typedef struct
1068 {
1069 int id;
1070 int version;
1071 int checksum;
1072 int numLods;
1073 int numLodVertexes[MAX_NUM_LODS];
1074 int numFixups;
1075 int fixupTableStart;
1076 int vertexDataStart;
1077 int tangentDataStart;
1078 }
1079 vertexFileHeader_t;
1080
1081 #pragma pack(pop)
1082
1083 static mstudiovertex_t *GetVertexData( vertexFileHeader_t *t )
1084 {
1085 return (mstudiovertex_t *) ( (char *)t + t->vertexDataStart );
1086 }
1087
1088 /*
1089 * VTX file format
1090 */
1091
1092 #pragma pack(push, 1)
1093
1094 typedef struct
1095 {
1096 unsigned char boneWeightIndex[3];
1097 unsigned char numBones;
1098
1099 unsigned short origMeshVertID;
1100 char boneID[3];
1101 }
1102 VTXVertex_t;
1103
1104 enum StripGroupFlags
1105 {
1106 STRIPGROUP_IS_FLEXED = 0x01,
1107 STRIPGROUP_IS_HWSKINNED = 0x02,
1108 STRIPGROUP_IS_DELTA_FLEXED = 0x04,
1109 STRIPGROUP_SUPPRESS_HW_MORPH = 0x08
1110 };
1111
1112 enum StripHeaderFlags_t {
1113 STRIP_IS_TRILIST = 0x01,
1114 STRIP_IS_TRISTRIP = 0x02 /* Unused by studiomdl 2015? */
1115 };
1116
1117 typedef struct
1118 {
1119 int numIndices;
1120 int indexOffset;
1121
1122 int numVerts;
1123 int vertOffset;
1124
1125 short numBones;
1126
1127 unsigned char flags;
1128
1129 int numBoneStateChanges;
1130 int boneStateChangeOffset;
1131 }
1132 VTXStripHeader_t;
1133
1134 typedef struct
1135 {
1136 int numVerts;
1137 int vertOffset;
1138
1139 int numIndices;
1140 int indexOffset;
1141
1142 int numStrips;
1143 int stripOffset;
1144
1145 unsigned char flags;
1146 }
1147 VTXStripGroupHeader_t;
1148
1149 static VTXVertex_t *pVertexVTX( VTXStripGroupHeader_t *t, int i )
1150 {
1151 return (VTXVertex_t *)(((char *)t) + t->vertOffset) + i;
1152 }
1153
1154 static unsigned short *pIndexVTX( VTXStripGroupHeader_t *t, int i )
1155 {
1156 return (unsigned short *)(((char *)t) + t->indexOffset) + i;
1157 }
1158
1159 static VTXStripHeader_t *pStripVTX( VTXStripGroupHeader_t *t, int i )
1160 {
1161 return (VTXStripHeader_t *)(((char *)t) + t->stripOffset) + i;
1162 }
1163
1164 typedef struct
1165 {
1166 int numStripGroups;
1167 int stripGroupHeaderOffset;
1168
1169 unsigned char flags;
1170 }
1171 VTXMeshHeader_t;
1172 static VTXStripGroupHeader_t *pStripGroupVTX( VTXMeshHeader_t *t, int i )
1173 {
1174 return (VTXStripGroupHeader_t *)(((char *)t) + t->stripGroupHeaderOffset) +i;
1175 }
1176
1177 typedef struct
1178 {
1179 int numMeshes;
1180 int meshOffset;
1181
1182 float switchPoint;
1183 }
1184 VTXModelLODHeader_t;
1185 static VTXMeshHeader_t *pMeshVTX( VTXModelLODHeader_t *t, int i )
1186 {
1187 return (VTXMeshHeader_t *)(((char *)t) + t->meshOffset) + i;
1188 }
1189
1190 typedef struct
1191 {
1192 int numLODs;
1193 int lodOffset;
1194 }
1195 VTXModelHeader_t;
1196 static VTXModelLODHeader_t *pLODVTX( VTXModelHeader_t *t, int i )
1197 {
1198 return (VTXModelLODHeader_t *)(((char *)t) + t->lodOffset) + i;
1199 }
1200
1201 typedef struct
1202 {
1203 int numModels;
1204 int modelOffset;
1205 }
1206 VTXBodyPartHeader_t;
1207 static VTXModelHeader_t *pModelVTX( VTXBodyPartHeader_t *t, int i )
1208 {
1209 return (VTXModelHeader_t *)(((char *)t) + t->modelOffset) + i;
1210 }
1211
1212 typedef struct
1213 {
1214 int version; /* 7 */
1215
1216 /* hardware params that affect how the model is to be optimized. */
1217 int vertCacheSize;
1218 unsigned short maxBonesPerStrip;
1219 unsigned short maxBonesPerTri;
1220 int maxBonesPerVert;
1221
1222 int checkSum;
1223 int numLODs;
1224 int materialReplacementListOffset;
1225 int numBodyParts;
1226 int bodyPartOffset;
1227 }
1228 VTXFileHeader_t;
1229 static VTXBodyPartHeader_t *pBodyPartVTX( VTXFileHeader_t *t, int i )
1230 {
1231 return (VTXBodyPartHeader_t *)(((char *)t) + t->bodyPartOffset) + i;
1232 }
1233
1234 /*
1235 .VTX file structure
1236 =============================================
1237
1238 FileHeader
1239 L BodyParts::
1240 L Models::
1241 L LODS::
1242 L Meshes::
1243 L StripGroups::
1244 L VerticesTable[StudioMDL.Vertex]
1245 L IndicesTable[UINT16]
1246 |
1247 L Strips::
1248 L Vertices[UINT16]
1249 L Indices[UINT16]
1250 */
1251
1252 #pragma pack(pop)
1253
1254 /*
1255 * mdl format
1256 */
1257
1258 #pragma pack(push, 1)
1259
1260 typedef struct
1261 {
1262 void *pVertexData;
1263 void *pTangentData;
1264 }
1265 mstudio_modelvertexdata_t;
1266
1267 typedef struct
1268 {
1269 int unused_modelvertexdata;
1270 int numLODVertexes[MAX_NUM_LODS];
1271
1272 mstudio_modelvertexdata_t *_the_death_ptr;
1273 }
1274 mstudio_meshvertexdata_t;
1275
1276 typedef struct mstudiomodel_t mstudiomodel_t;
1277 typedef struct
1278 {
1279 int material;
1280 int modelindex;
1281 int numvertices;
1282 int vertexoffset;
1283 int numflexes;
1284 int flexindex;
1285 int materialtype;
1286 int materialparam;
1287 int meshid;
1288 float center[3];
1289 mstudio_meshvertexdata_t vertexdata;
1290 int unused[6];
1291 }
1292 mstudiomesh_t;
1293
1294 struct mstudiomodel_t
1295 {
1296 char name[64];
1297 int type;
1298 float boundingradius;
1299
1300 int nummeshes;
1301 int meshindex;
1302
1303 int numvertices;
1304 int vertexindex;
1305 int tangentsindex;
1306
1307 int numattachments;
1308 int attachmentindex;
1309
1310 int numeyeballs;
1311 int eyeballindex;
1312
1313 mstudio_modelvertexdata_t vertexdata;
1314
1315 int unused[8];
1316 };
1317 static mstudiomesh_t *studiomodel_pMesh( mstudiomodel_t *t, int i )
1318 {
1319 return (mstudiomesh_t *)(((char *)t) + t->meshindex) + i;
1320 }
1321
1322 typedef struct
1323 {
1324 int sznameindex;
1325 int nummodels;
1326 int base;
1327 int modelindex;
1328 } mstudiobodyparts_t;
1329
1330 static mstudiomodel_t *mstudiobodyparts_pModel( mstudiobodyparts_t *t, int i )
1331 {
1332 return (mstudiomodel_t *)(((char *)t) + t->modelindex) + i;
1333 }
1334
1335 typedef struct
1336 {
1337 int id;
1338 int version;
1339 int checksum;
1340 char name[64];
1341 int length;
1342 float eyeposition[3];
1343 float illumposition[3];
1344 float hull_min[3];
1345 float hull_max[3];
1346 float view_bbmin[3];
1347 float view_bbmax[3];
1348 int flags;
1349 int numbones;
1350 int boneindex;
1351 int numbonecontrollers;
1352 int bonecontrollerindex;
1353 int numhitboxsets;
1354 int hitboxsetindex;
1355 int numlocalanim;
1356 int localanimindex;
1357 int numlocalseq;
1358 int localseqindex;
1359 int activitylistversion;
1360 int eventsindexed;
1361 int numtextures;
1362 int textureindex;
1363 int numcdtextures;
1364 int cdtextureindex;
1365 int numskinref;
1366 int numskinfamilies;
1367 int skinindex;
1368 int numbodyparts;
1369 int bodypartindex;
1370 int numlocalattachments;
1371 int localattachmentindex;
1372 int numlocalnodes;
1373 int localnodeindex;
1374 int localnodenameindex;
1375 int numflexdesc;
1376 int flexdescindex;
1377 int numflexcontrollers;
1378 int flexcontrollerindex;
1379 int numflexrules;
1380 int flexruleindex;
1381 int numikchains;
1382 int ikchainindex;
1383 int nummouths;
1384 int mouthindex;
1385 int numlocalposeparameters;
1386 int localposeparamindex;
1387 int surfacepropindex;
1388 int keyvalueindex;
1389 int keyvaluesize;
1390 int numlocalikautoplaylocks;
1391 int localikautoplaylockindex;
1392 float mass;
1393 int contents;
1394 int numincludemodels;
1395 int includemodelindex;
1396 int szanimblocknameindex;
1397 int numanimblocks;
1398 int animblockindex;
1399 int bonetablebynameindex;
1400 char constdirectionallightdot;
1401 char rootLOD;
1402 char numAllowedRootLODs;
1403 char unused[1];
1404 int unused4;
1405 int numflexcontrollerui;
1406 int flexcontrolleruiindex;
1407 float flVertAnimFixedPointScale;
1408 int unused3[1];
1409 int studiohdr2index;
1410 int unused2[1];
1411 }
1412 studiohdr_t;
1413
1414 static char *studiohdr_pCdtexture( studiohdr_t *t, int i )
1415 {
1416 return (((char *)t) + *((int *)(((u8 *)t) + t->cdtextureindex) + i));
1417 }
1418
1419 static mstudiobodyparts_t *studiohdr_pBodypart( studiohdr_t *t, int i )
1420 {
1421 return (mstudiobodyparts_t *)(((char *)t) + t->bodypartindex) + i;
1422 }
1423
1424 typedef struct
1425 {
1426 int sznameindex;
1427 int flags;
1428 int used;
1429
1430 /* There is some extra unused stuff that was here...
1431 * Luckily since byte offsets are used, structure size doesn't matter */
1432 }
1433 mstudiotexture_t;
1434
1435 static char *mstudiotexture_pszName( mstudiotexture_t *t )
1436 {
1437 return ((char *)t) + t->sznameindex;
1438 }
1439
1440 static mstudiotexture_t *studiohdr_pTexture( studiohdr_t *t, int i )
1441 {
1442 return (mstudiotexture_t *)(((u8 *)t) + t->textureindex) + i;
1443 }
1444
1445 #pragma pack(pop)
1446
1447 static void vtx_resource_counts( VTXFileHeader_t *pVtxHdr, studiohdr_t *pMdl,
1448 u32 *indices, u32 *meshes )
1449 {
1450 *indices = 0;
1451 *meshes = 0;
1452
1453 for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID )
1454 {
1455 /* Body parts */
1456 VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( pVtxHdr, bodyID );
1457 mstudiobodyparts_t *pBodyPart = studiohdr_pBodypart( pMdl, bodyID );
1458
1459 for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
1460 {
1461 /* models */
1462 VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID );
1463 mstudiomodel_t *pStudioModel =
1464 mstudiobodyparts_pModel( pBodyPart, modelID );
1465
1466 int nLod = 0;
1467 VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod );
1468
1469 for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh )
1470 {
1471 /* meshes */
1472 VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
1473 mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
1474
1475 (*meshes)++;
1476
1477 for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
1478 {
1479 /* groups */
1480 VTXStripGroupHeader_t* pStripGroup =
1481 pStripGroupVTX( pVtxMesh, nGroup );
1482
1483 for ( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ )
1484 {
1485 /* strips */
1486 VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip );
1487
1488 if ( pStrip->flags & STRIP_IS_TRILIST )
1489 {
1490 *indices += pStrip->numIndices;
1491 }
1492 }
1493 }
1494 }
1495 }
1496 }
1497 }
1498
1499 /*
1500 * The following section is the wrappers for the underlying types
1501 */
1502
1503 struct valve_material
1504 {
1505 char *basetexture,
1506 *bumpmap;
1507 };
1508
1509 struct valve_model
1510 {
1511 float *vertex_data; /* pos xyz, norm xyz, uv xy */
1512
1513 u32 *indices,
1514 indices_count,
1515 vertex_count,
1516 part_count,
1517 material_count;
1518
1519 char **materials;
1520
1521 struct valve_model_batch
1522 {
1523 u32 material,
1524 ibstart,
1525 ibcount;
1526 }
1527 *parts;
1528
1529 /* Internal valve data */
1530 studiohdr_t *studiohdr;
1531 VTXFileHeader_t *vtxhdr;
1532 vertexFileHeader_t *vvdhdr;
1533 };
1534
1535 CXR_API valve_model *valve_load_model( const char *relpath )
1536 {
1537 char path[1024];
1538 strcpy( path, relpath );
1539
1540 char *ext = cxr_stripext( path );
1541
1542 if( !ext )
1543 return NULL;
1544
1545 /* Load data files */
1546 valve_model *model = malloc( sizeof( valve_model ) );
1547 model->studiohdr = NULL;
1548 model->vtxhdr = NULL;
1549 model->vvdhdr = NULL;
1550
1551 strcpy( ext, ".dx90.vtx" );
1552 model->vtxhdr = cxr_fs_get( path, 0 );
1553
1554 strcpy( ext, ".vvd" );
1555 model->vvdhdr = cxr_fs_get( path, 0 );
1556
1557 strcpy( ext, ".mdl" );
1558 model->studiohdr = cxr_fs_get( path, 0 );
1559
1560 if( !model->vvdhdr || !model->studiohdr || !model->vtxhdr )
1561 {
1562 cxr_log( "Error, failed to load: (%s)\n", relpath );
1563
1564 free( model->studiohdr );
1565 free( model->vvdhdr );
1566 free( model->studiohdr );
1567 free( model );
1568
1569 return NULL;
1570 }
1571
1572 /* allocate resources */
1573 vtx_resource_counts( model->vtxhdr, model->studiohdr,
1574 &model->indices_count, &model->part_count );
1575 model->vertex_count = model->vvdhdr->numLodVertexes[0];
1576 model->material_count = model->studiohdr->numtextures;
1577
1578 model->materials = malloc( model->material_count * sizeof(char *) );
1579 model->parts = malloc( sizeof( valve_model_batch ) * model->part_count );
1580 model->indices = malloc( sizeof( u32 ) * model->indices_count );
1581 model->vertex_data = malloc( sizeof( float ) * 8 * model->vertex_count );
1582
1583 /* Find materials */
1584 for( int i=0; i<model->studiohdr->numtextures; i++ )
1585 {
1586 char material_path[ 1024 ];
1587 fs_locator locator;
1588
1589 mstudiotexture_t *tex = studiohdr_pTexture( model->studiohdr, i );
1590 const char *name = mstudiotexture_pszName( tex );
1591
1592 model->materials[i] = NULL;
1593
1594 for( int j=0; j<model->studiohdr->numcdtextures; j++ )
1595 {
1596 char *cdpath = studiohdr_pCdtexture( model->studiohdr, j );
1597 snprintf( material_path, 1023, "materials/%s%s.vmt", cdpath, name );
1598 cxr_unixpath( material_path );
1599
1600 if( cxr_fs_find( material_path, &locator ))
1601 {
1602 model->materials[i] = cxr_str_clone( material_path, 0 );
1603 break;
1604 }
1605 }
1606 }
1607
1608 u32 i_index = 0,
1609 i_mesh = 0;
1610
1611 /* Extract meshes */
1612 for( int bodyID = 0; bodyID < model->studiohdr->numbodyparts; ++bodyID )
1613 {
1614 /* Body parts */
1615 VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( model->vtxhdr, bodyID );
1616 mstudiobodyparts_t *pBodyPart =
1617 studiohdr_pBodypart( model->studiohdr, bodyID );
1618
1619 for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
1620 {
1621 /* models */
1622 VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID );
1623 mstudiomodel_t *pStudioModel =
1624 mstudiobodyparts_pModel( pBodyPart, modelID );
1625
1626 int nLod = 0;
1627 VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod );
1628
1629 for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh )
1630 {
1631 /* meshes, each of these creates a new draw CMD */
1632 VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
1633 mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
1634
1635 valve_model_batch *curBatch = &model->parts[ i_mesh ++ ];
1636 curBatch->material = pMesh->material;
1637 curBatch->ibstart = i_index;
1638 curBatch->ibcount = 0;
1639
1640 for( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
1641 {
1642 /* groups */
1643 VTXStripGroupHeader_t* pStripGroup =
1644 pStripGroupVTX( pVtxMesh, nGroup );
1645
1646 for( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ )
1647 {
1648 /* strips */
1649 VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip );
1650
1651 if( pStrip->flags & STRIP_IS_TRILIST )
1652 {
1653 /* indices */
1654 for( int i = 0; i < pStrip->numIndices; i ++ )
1655 {
1656 u16 i1 = *pIndexVTX( pStripGroup,
1657 pStrip->indexOffset + i );
1658
1659 model->indices[ i_index ++ ] =
1660 pVertexVTX( pStripGroup, i1 )->origMeshVertID +
1661 pMesh->vertexoffset;
1662
1663 curBatch->ibcount ++;
1664 }
1665 }
1666 else
1667 {
1668 /* This is unused? */
1669 }
1670 }
1671 }
1672 }
1673 }
1674 }
1675
1676 mstudiovertex_t *vertexData = GetVertexData( model->vvdhdr );
1677
1678 for( int i = 0; i < model->vertex_count; i ++ )
1679 {
1680 mstudiovertex_t *vert = &vertexData[i];
1681
1682 float *dest = &model->vertex_data[ i*8 ];
1683
1684 dest[0] = vert->pos[0];
1685 dest[1] = vert->pos[1];
1686 dest[2] = vert->pos[2];
1687
1688 dest[3] = vert->norm[0];
1689 dest[4] = vert->norm[1];
1690 dest[5] = vert->norm[2];
1691
1692 dest[6] = vert->uv[0];
1693 dest[7] = vert->uv[1];
1694 }
1695
1696 return model;
1697 }
1698
1699 CXR_API void valve_free_model( valve_model *model )
1700 {
1701 for( int i=0; i<model->material_count; i++ )
1702 free( model->materials[i] );
1703
1704 free( model->materials );
1705 free( model->parts );
1706 free( model->indices );
1707 free( model->vertex_data );
1708
1709 free( model->studiohdr );
1710 free( model->vtxhdr );
1711 free( model->vvdhdr );
1712 free( model );
1713 }
1714
1715 static char *valve_texture_path( const char *path )
1716 {
1717 if( !path )
1718 return NULL;
1719
1720 char *buf =
1721 malloc( strlen( path ) + strlen(".vtf") + strlen("materials/") +1 );
1722 strcpy( buf, "materials/" );
1723 strcat( buf, path );
1724 strcat( buf, ".vtf" );
1725
1726 cxr_unixpath( buf );
1727 cxr_lowercase( buf );
1728
1729 return buf;
1730 }
1731
1732 CXR_API valve_material *valve_load_material( const char *path )
1733 {
1734 char *vmt = cxr_fs_get( path, 1 );
1735
1736 if( vmt )
1737 {
1738 valve_material *material = malloc( sizeof(valve_material) );
1739 vdf_node *vmt_root = vdf_from_buffer( vmt );
1740
1741 if( vmt_root->abnodes.count == 0 )
1742 {
1743 cxr_log( "Error: vmt has no nodes\n" );
1744 free( vmt );
1745 vdf_free_r( vmt_root );
1746 return 0;
1747 }
1748
1749 vdf_node **body = cxr_ab_ptr( &vmt_root->abnodes, 0 );
1750
1751 /* Path semantics here are inconsistent
1752 * I believe they should all just be converted to lowercase, though */
1753
1754 for( int i=0; i<(*body)->abpairs.count; i++ )
1755 {
1756 vdf_kv *kv = cxr_ab_ptr( &(*body)->abpairs, i );
1757 cxr_lowercase( kv->key );
1758 }
1759
1760 const char *basetexture = kv_get( *body, "$basetexture", NULL ),
1761 *bumpmap = kv_get( *body, "$bumpmap", NULL );
1762
1763 /* TODO: other shader parameters */
1764 material->basetexture = valve_texture_path( basetexture );
1765 material->bumpmap = valve_texture_path( bumpmap );
1766
1767 vdf_free_r( vmt_root );
1768 free(vmt);
1769
1770 return material;
1771 }
1772
1773 return NULL;
1774 }
1775
1776 CXR_API void valve_free_material( valve_material *material )
1777 {
1778 free( material->basetexture );
1779 free( material->bumpmap );
1780 }
1781
1782 #endif /* CXR_VALVE_BIN_H */