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