bugs
[carveJwlIkooP6JGAAIwe30JlM.git] / addon.c
1 #ifndef ADDON_C
2 #define ADDON_C
3
4 #include "addon.h"
5 #include "addon_types.h"
6 #include "vg/vg_msg.h"
7 #include "steam.h"
8 #include "workshop.h"
9
10 static u32 addon_count( enum addon_type type ){
11 return addon_system.registry_type_counts[ type ];
12 }
13
14 /* these kind of suck, oh well. */
15 static addon_reg *get_addon_from_index(enum addon_type type, u32 index){
16 u32 count = 0;
17 for( u32 i=0; count<addon_count(type); i++ ){
18 addon_reg *reg = &addon_system.registry[i];
19 if( reg->alias.type == type ){
20 if( index == count )
21 return reg;
22
23 count ++;
24 }
25 }
26
27 return NULL;
28 }
29
30 static u32 get_index_from_addon( enum addon_type type, addon_reg *a ){
31 u32 count = 0;
32 for( u32 i=0; count<addon_system.registry_type_counts[type]; i++ ){
33 addon_reg *reg = &addon_system.registry[i];
34 if( reg->alias.type == type ){
35 if( reg == a )
36 return count;
37
38 count ++;
39 }
40 }
41
42 return 0xffffffff;
43 }
44
45 static u32 addon_match( addon_alias *alias ){
46 u32 foldername_djb2 = vg_strdjb2( alias->foldername );
47
48 u32 count = 0;
49 for( u32 i=0; count<addon_system.registry_type_counts[alias->type]; i++ ){
50 addon_reg *reg = &addon_system.registry[i];
51 if( reg->alias.type == alias->type ){
52
53 if( alias->workshop_id ){
54 if( alias->workshop_id == reg->alias.workshop_id )
55 return count;
56 }
57 else{
58 if( reg->foldername_hash == foldername_djb2 ){
59 if( !strcmp( reg->alias.foldername, alias->foldername ) ){
60 return count;
61 }
62 }
63 }
64
65 count ++;
66 }
67 }
68
69 return 0xffffffff;
70 }
71
72 /*
73 * Create a string version of addon alias in buf
74 */
75 static void addon_alias_uid( addon_alias *alias, char buf[ADDON_UID_MAX] ){
76 if( alias->workshop_id ){
77 snprintf( buf, 128, "sr%03d-steam-"PRINTF_U64,
78 alias->type, alias->workshop_id );
79 }
80 else {
81 snprintf( buf, 128, "sr%03d-local-%s",
82 alias->type, alias->foldername );
83 }
84 }
85
86 /*
87 * parse uid to alias. returns 1 if successful
88 */
89 static int addon_uid_to_alias( char uid[ADDON_UID_MAX], addon_alias *alias ){
90 /* 1
91 * 01234567890123
92 * sr&&&-@@@@@-#*
93 * | | |
94 * type | id
95 * |
96 * location
97 */
98 if( strlen(uid) < 13 ) return 0;
99 if( !((uid[0] == 's') && (uid[1] == 'r')) ) return 0;
100
101 char type[4];
102 memcpy( type, uid+2, 3 );
103 type[3] = '\0';
104 alias->type = atoi(type);
105
106 char location[6];
107 memcpy( location, uid+6, 5 );
108 location[5] = '\0';
109
110 if( !strcmp(location,"steam") )
111 alias->workshop_id = atoll( uid+12 );
112 else if( !strcmp(location,"local") ){
113 alias->workshop_id = 0;
114 vg_strncpy( uid+12, alias->foldername, 64, k_strncpy_always_add_null );
115 }
116 else
117 return 0;
118
119 return 1;
120 }
121
122 static void addon_system_init( void ){
123 u32 reg_size = sizeof(addon_reg)*ADDON_MOUNTED_MAX;
124 addon_system.registry = vg_linear_alloc( vg_mem.rtmemory, reg_size );
125
126 for( u32 type=0; type<k_addon_type_max; type++ ){
127 struct addon_type_info *inf = &addon_type_infos[type];
128 struct addon_cache *cache = &addon_system.cache[type];
129
130 if( inf->cache_count ){
131 /* create the allocations pool */
132 u32 alloc_size = sizeof(struct addon_cache_entry)*inf->cache_count;
133 cache->allocs = vg_linear_alloc( vg_mem.rtmemory, alloc_size );
134 memset( cache->allocs, 0, alloc_size );
135
136 cache->pool.buffer = cache->allocs;
137 cache->pool.count = inf->cache_count;
138 cache->pool.stride = sizeof( struct addon_cache_entry );
139 cache->pool.offset = offsetof( struct addon_cache_entry, poolnode );
140 vg_pool_init( &cache->pool );
141
142 /* create the real memory */
143 u32 cache_size = inf->cache_stride*inf->cache_count;
144 cache->items = vg_linear_alloc( vg_mem.rtmemory, cache_size );
145 cache->stride = inf->cache_stride;
146 memset( cache->items, 0, cache_size );
147
148 for( i32 j=0; j<inf->cache_count; j++ ){
149 struct addon_cache_entry *alloc = &cache->allocs[j];
150 alloc->reg_ptr = NULL;
151 alloc->reg_index = 0xffffffff;
152 }
153 }
154 }
155 }
156
157 /*
158 * Scanning routines
159 * -----------------------------------------------------------------------------
160 */
161
162 /*
163 * Reciever for scan completion. copies the registry counts back into main fred
164 */
165 static void async_addon_reg_update( void *data, u32 size )
166 {
167 vg_info( "Registry update notify\n" );
168
169 for( u32 i=0; i<k_addon_type_max; i++ ){
170 addon_system.registry_type_counts[i] = 0;
171 }
172
173 for( u32 i=0; i<addon_system.registry_count; i++ ){
174 enum addon_type type = addon_system.registry[i].alias.type;
175 addon_system.registry_type_counts[ type ] ++;
176 }
177 }
178
179 static void addon_set_foldername( addon_reg *reg, const char name[64] ){
180 vg_strncpy( name, reg->alias.foldername, 64, k_strncpy_always_add_null );
181 reg->foldername_hash = vg_strdjb2( reg->alias.foldername );
182 }
183
184 /*
185 * Create a new registry
186 */
187 static addon_reg *addon_alloc_reg( PublishedFileId_t workshop_id,
188 enum addon_type type ){
189 if( addon_system.registry_count == ADDON_MOUNTED_MAX ){
190 vg_error( "You have too many addons installed!\n" );
191 return NULL;
192 }
193
194 addon_reg *reg = &addon_system.registry[ addon_system.registry_count ];
195 reg->metadata_len = 0;
196 reg->cache_id = 0;
197 reg->state = k_addon_state_indexed;
198 reg->alias.workshop_id = workshop_id;
199 reg->alias.foldername[0] = '\0';
200 reg->alias.type = type;
201
202 if( workshop_id ){
203 char foldername[64];
204 snprintf( foldername, 64, PRINTF_U64, workshop_id );
205 addon_set_foldername( reg, foldername );
206 }
207 return reg;
208 }
209
210 /*
211 * If the addon.inf exists int the folder, load into the reg
212 */
213 static int addon_try_load_metadata( addon_reg *reg, vg_str folder_path ){
214 vg_str meta_path = folder_path;
215 vg_strcat( &meta_path, "/addon.inf" );
216 if( !vg_strgood( &meta_path ) ){
217 vg_error( "The metadata path is too long\n" );
218 return 0;
219 }
220
221 FILE *fp = fopen( meta_path.buffer, "rb" );
222 if( !fp ){
223 vg_error( "Could not open the '%s'\n", meta_path.buffer );
224 return 0;
225 }
226
227 reg->metadata_len = fread( reg->metadata, 1, 512, fp );
228 if( reg->metadata_len != 512 ){
229 if( !feof(fp) ){
230 fclose(fp);
231 vg_error( "unknown error codition" );
232 reg->metadata_len = 0;
233 return 0;
234 }
235 }
236 fclose(fp);
237 return 1;
238 }
239
240 static void addon_print_info( addon_reg *reg ){
241 vg_info( "addon_reg #%u{\n", addon_system.registry_count );
242 vg_info( " type: %d\n", reg->alias.type );
243 vg_info( " workshop_id: " PRINTF_U64 "\n", reg->alias.workshop_id );
244 vg_info( " folder: [%u]%s\n", reg->foldername_hash, reg->alias.foldername );
245 vg_info( " metadata_len: %u\n", reg->metadata_len );
246 vg_info( " cache_id: %hu\n", reg->cache_id );
247 vg_info( "}\n" );
248 }
249
250 static void addon_mount_finish( addon_reg *reg ){
251 #if 0
252 addon_print_info( reg );
253 #endif
254 addon_system.registry_count ++;
255 }
256
257 /*
258 * Mount a fully packaged addon, one that certainly has a addon.inf
259 */
260 static addon_reg *addon_mount_workshop_folder( PublishedFileId_t workshop_id,
261 vg_str folder_path )
262 {
263 addon_reg *reg = addon_alloc_reg( workshop_id, k_addon_type_none );
264 if( !reg ) return NULL;
265
266 if( !addon_try_load_metadata( reg, folder_path ) ){
267 return NULL;
268 }
269
270 enum addon_type type = k_addon_type_none;
271 vg_msg root = {0};
272 root.buf = reg->metadata;
273 root.len = reg->metadata_len;
274 root.max = sizeof(reg->metadata);
275
276 vg_msg workshop = root;
277 if( vg_msg_seekframe( &workshop, "workshop", k_vg_msg_first )){
278 type = vg_msg_seekkvu32( &workshop, "type", k_vg_msg_first );
279 }
280
281 if( type == k_addon_type_none ){
282 vg_error( "Cannot determine addon type\n" );
283 return NULL;
284 }
285
286 reg->alias.type = type;
287 addon_mount_finish( reg );
288 return reg;
289 }
290
291 /*
292 * Mount a local folder. may or may not have addon.inf
293 */
294 static addon_reg *addon_mount_local_addon( const char *folder,
295 enum addon_type type,
296 const char *content_ext )
297 {
298 char folder_path_buf[4096];
299 vg_str folder_path;
300 vg_strnull( &folder_path, folder_path_buf, 4096 );
301 vg_strcat( &folder_path, folder );
302
303 const char *folder_name = vg_strch( &folder_path, '/' )+1;
304 u32 folder_hash = vg_strdjb2(folder_name);
305 for( u32 i=0; i<addon_system.registry_count; i++ ){
306 addon_reg *reg = &addon_system.registry[i];
307
308 if( (reg->alias.type == type) && (reg->foldername_hash == folder_hash) ){
309 if( !strcmp( reg->alias.foldername, folder_name ) ){
310 reg->state = k_addon_state_indexed;
311 return NULL;
312 }
313 }
314 }
315
316 addon_reg *reg = addon_alloc_reg( 0, type );
317 if( !reg ) return NULL;
318 addon_set_foldername( reg, folder_name );
319 addon_try_load_metadata( reg, folder_path );
320
321 if( reg->metadata_len == 0 ){
322 /* create our own content commands */
323 vg_msg msg = {0};
324 msg.buf = reg->metadata;
325 msg.len = 0;
326 msg.max = sizeof(reg->metadata);
327
328 u32 content_count = 0;
329
330 vg_strcat( &folder_path, "" );
331 vg_warn( "Creating own metadata for: %s\n", folder_path.buffer );
332
333 vg_dir subdir;
334 if( !vg_dir_open(&subdir, folder_path.buffer) ){
335 vg_error( "Failed to open '%s'\n", folder_path.buffer );
336 return NULL;
337 }
338
339 while( vg_dir_next_entry(&subdir) ){
340 if( vg_dir_entry_type(&subdir) == k_vg_entry_type_file ){
341 const char *fname = vg_dir_entry_name(&subdir);
342 vg_str file = folder_path;
343 vg_strcat( &file, "/" );
344 vg_strcat( &file, fname );
345 if( !vg_strgood( &file ) ) continue;
346
347 char *ext = vg_strch( &file, '.' );
348 if( !ext ) continue;
349 if( strcmp(ext,content_ext) ) continue;
350
351 vg_msg_wkvstr( &msg, "content", fname );
352 content_count ++;
353 }
354 }
355 vg_dir_close(&subdir);
356
357 if( !content_count ) return NULL;
358 if( msg.error == k_vg_msg_error_OK )
359 reg->metadata_len = msg.cur;
360 else{
361 vg_error( "Error creating metadata: %d\n", msg.error );
362 return NULL;
363 }
364 }
365
366 addon_mount_finish( reg );
367 return reg;
368 }
369
370 /*
371 * Check all subscribed items
372 */
373 static void addon_mount_workshop_items(void){
374 if( !steam_ready ) return;
375 /*
376 * Steam workshop scan
377 */
378 vg_info( "Mounting steam workshop subscriptions\n" );
379 PublishedFileId_t workshop_ids[ ADDON_MOUNTED_MAX ];
380 u32 workshop_count = ADDON_MOUNTED_MAX;
381
382 vg_async_item *call = vg_async_alloc(
383 sizeof(struct async_workshop_installed_files_info));
384 struct async_workshop_installed_files_info *info = call->payload;
385 info->buffer = workshop_ids;
386 info->len = &workshop_count;
387 vg_async_dispatch( call, async_workshop_get_installed_files );
388 vg_async_stall();
389
390 for( u32 j=0; j<workshop_count; j++ ){
391 /* check for existance in both our caches
392 * ----------------------------------------------------------*/
393 PublishedFileId_t id = workshop_ids[j];
394 for( u32 i=0; i<addon_system.registry_count; i++ ){
395 addon_reg *reg = &addon_system.registry[i];
396
397 if( reg->alias.workshop_id == id ){
398 reg->state = k_addon_state_indexed;
399 goto next_file_workshop;
400 }
401 }
402
403 vg_async_item *call1 =
404 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
405
406 char path[ 4096 ];
407
408 struct async_workshop_filepath_info *info = call1->payload;
409 info->buf = path;
410 info->id = id;
411 info->len = vg_list_size(path);
412 vg_async_dispatch( call1, async_workshop_get_filepath );
413 vg_async_stall(); /* too bad! */
414
415 vg_str folder = {.buffer = path, .i=strlen(path), .len=4096};
416 addon_mount_workshop_folder( id, folder );
417 next_file_workshop:;
418 }
419 }
420
421 /*
422 * Scan a local content folder for addons. It must find at least one file with
423 * the specified content_ext to be considered.
424 */
425 static void addon_mount_content_folder( enum addon_type type,
426 const char *base_folder,
427 const char *content_ext )
428 {
429 vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
430 type, base_folder, content_ext );
431
432 char path_buf[4096];
433 vg_str path;
434 vg_strnull( &path, path_buf, 4096 );
435 vg_strcat( &path, base_folder );
436
437 vg_dir dir;
438 if( !vg_dir_open(&dir,path.buffer) ){
439 vg_error( "vg_dir_open('%s') failed\n", path.buffer );
440 return;
441 }
442
443 vg_strcat(&path,"/");
444
445 while( vg_dir_next_entry(&dir) ){
446 if( vg_dir_entry_type(&dir) == k_vg_entry_type_dir ){
447 const char *d_name = vg_dir_entry_name(&dir);
448
449 vg_str folder = path;
450 if( strlen( d_name ) > ADDON_FOLDERNAME_MAX ){
451 vg_warn( "folder too long: %s\n", d_name );
452 continue;
453 }
454
455 vg_strcat( &folder, d_name );
456 if( !vg_strgood( &folder ) ) continue;
457
458 addon_mount_local_addon( folder.buffer, type, content_ext );
459 }
460 }
461 vg_dir_close(&dir);
462 }
463
464 /*
465 * write the full path of the addon's folder into the vg_str
466 */
467 static int addon_get_content_folder( addon_reg *reg, vg_str *folder ){
468 if( reg->alias.workshop_id ){
469 vg_async_item *call =
470 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
471 struct async_workshop_filepath_info *info = call->payload;
472 info->buf = folder->buffer;
473 info->id = reg->alias.workshop_id;
474 info->len = folder->len;
475 vg_async_dispatch( call, async_workshop_get_filepath );
476 vg_async_stall(); /* too bad! */
477 if( info->buf[0] == '\0' ){
478 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64 ")\n",
479 reg->alias.workshop_id );
480 return 0;
481 }
482 folder->i = strlen( folder->buffer );
483 return 1;
484 }
485 else{
486 folder->i = 0;
487
488 const char *local_folder =
489 addon_type_infos[reg->alias.type].local_content_folder;
490
491 if( !local_folder ) return 0;
492 vg_strcat( folder, local_folder );
493 vg_strcat( folder, reg->alias.foldername );
494 return 1;
495 }
496 }
497
498 /*
499 * Return existing cache id if reg_index points to a registry with its cache
500 * already set.
501 */
502 static u16 addon_cache_fetch( enum addon_type type, u32 reg_index ){
503 addon_reg *reg = NULL;
504
505 if( reg_index < addon_count( type ) ){
506 reg = get_addon_from_index( type, reg_index );
507 if( reg->cache_id )
508 return reg->cache_id;
509 }
510
511 return 0;
512 }
513
514 /*
515 * Allocate a new cache item from the pool
516 */
517 static u16 addon_cache_alloc( enum addon_type type, u32 reg_index ){
518 struct addon_cache *cache = &addon_system.cache[ type ];
519
520 u16 new_id = vg_pool_lru( &cache->pool );
521 struct addon_cache_entry *new_entry = vg_pool_item( &cache->pool, new_id );
522
523 addon_reg *reg = NULL;
524 if( reg_index < addon_count( type ) )
525 reg = get_addon_from_index( type, reg_index );
526
527 if( new_entry ){
528 if( new_entry->reg_ptr )
529 new_entry->reg_ptr->cache_id = 0;
530
531 if( reg )
532 reg->cache_id = new_id;
533
534 new_entry->reg_ptr = reg;
535 new_entry->reg_index = reg_index;
536 return new_id;
537 }
538 else{
539 vg_error( "cache full (type: %u)!\n", type );
540 return 0;
541 }
542 }
543
544 /*
545 * Get the real item data for cache id
546 */
547 static void *addon_cache_item( enum addon_type type, u16 id ){
548 if( !id ) return NULL;
549
550 struct addon_cache *cache = &addon_system.cache[type];
551 return cache->items + ((size_t)(id-1) * cache->stride);
552 }
553
554 /*
555 * Get the real item data for cache id ONLY if the item is completely loaded.
556 */
557 static void *addon_cache_item_if_loaded( enum addon_type type, u16 id ){
558 if( !id ) return NULL;
559
560 struct addon_cache *cache = &addon_system.cache[type];
561 struct addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
562
563 if( entry->state == k_addon_cache_state_loaded )
564 return addon_cache_item( type, id );
565 else return NULL;
566 }
567
568 /*
569 * Updates the item state from the main thread
570 */
571 static void async_addon_setstate( void *_entry, u32 _state ){
572 addon_cache_entry *entry = _entry;
573 SDL_AtomicLock( &addon_system.sl_cache_using_resources );
574 entry->state = _state;
575 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
576 vg_success( " loaded (%s)\n", entry->reg_ptr->alias.foldername );
577 }
578
579 /*
580 * Handles the loading of an individual item
581 */
582 static int addon_cache_load_request( enum addon_type type, u16 id,
583 addon_reg *reg, vg_str folder ){
584
585 /* load content files
586 * --------------------------------- */
587 vg_str content_path = folder;
588
589 vg_msg root = {0};
590 root.buf = reg->metadata;
591 root.len = reg->metadata_len;
592 root.max = sizeof(reg->metadata);
593
594 const char *kv_content = vg_msg_seekkvstr( &root, "content", 0 );
595 if( kv_content ){
596 vg_strcat( &content_path, "/" );
597 vg_strcat( &content_path, kv_content );
598 }
599 else{
600 vg_error( " No content paths in metadata\n" );
601 return 0;
602 }
603
604 if( !vg_strgood( &content_path ) ) {
605 vg_error( " Metadata path too long\n" );
606 return 0;
607 }
608
609 if( type == k_addon_type_board ){
610 struct player_board *board = addon_cache_item( type, id );
611 player_board_load( board, content_path.buffer );
612 return 1;
613 }
614 else if( type == k_addon_type_player ){
615 struct player_model *model = addon_cache_item( type, id );
616 player_model_load( model, content_path.buffer );
617 return 1;
618 }
619 else {
620 return 0;
621 }
622
623 return 0;
624 }
625
626 static void addon_cache_free_item( enum addon_type type, u16 id ){
627 if( type == k_addon_type_board ){
628 struct player_board *board = addon_cache_item( type, id );
629 player_board_unload( board );
630 }
631 else if( type == k_addon_type_player ){
632 struct player_model *model = addon_cache_item( type, id );
633 player_model_unload( model );
634 }
635 }
636
637 /*
638 * Goes over cache item load requests and calls the above ^
639 */
640 static void addon_cache_load_loop(void){
641 vg_info( "Running load loop\n" );
642 char path_buf[4096];
643
644 for( u32 type=0; type<k_addon_type_max; type++ ){
645 struct addon_cache *cache = &addon_system.cache[type];
646
647 for( u32 id=1; id<=cache->pool.count; id++ ){
648 addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
649
650 SDL_AtomicLock( &addon_system.sl_cache_using_resources );
651 if( entry->state == k_addon_cache_state_load_request ){
652 vg_info( "process cache load request (%u#%u, reg:%u)\n",
653 type, id, entry->reg_index );
654
655 if( entry->reg_index >= addon_count(type) ){
656 /* should maybe have a different value for this case */
657 entry->state = k_addon_cache_state_none;
658 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
659 continue;
660 }
661
662 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
663
664 /* continue with the request */
665 addon_reg *reg = get_addon_from_index( type, entry->reg_index );
666 entry->reg_ptr = reg;
667
668 vg_str folder;
669 vg_strnull( &folder, path_buf, 4096 );
670 if( addon_get_content_folder( reg, &folder ) ){
671 if( addon_cache_load_request( type, id, reg, folder ) ){
672 vg_async_call( async_addon_setstate,
673 entry, k_addon_cache_state_loaded );
674 continue;
675 }
676 }
677
678 vg_warn( "cache item did not load (%u#%u)\n", type, id );
679 SDL_AtomicLock( &addon_system.sl_cache_using_resources );
680 entry->state = k_addon_cache_state_none;
681 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
682 }
683 else
684 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
685 }
686 }
687 }
688
689 /*
690 * Perform the cache interactions required to create a viewslot which will
691 * eventually be loaded by other parts of the system.
692 */
693 static u16 addon_cache_create_viewer( enum addon_type type, u16 reg_id ){
694 struct addon_cache *cache = &addon_system.cache[type];
695 vg_pool *pool = &cache->pool;
696
697 u16 cache_id = addon_cache_fetch( type, reg_id );
698 if( !cache_id ){
699 cache_id = addon_cache_alloc( type, reg_id );
700
701 if( cache_id ){
702 SDL_AtomicLock( &addon_system.sl_cache_using_resources );
703 addon_cache_entry *entry = vg_pool_item( pool, cache_id );
704
705 if( entry->state == k_addon_cache_state_loaded ){
706 addon_cache_free_item( type, cache_id );
707 }
708
709 entry->state = k_addon_cache_state_load_request;
710 SDL_AtomicUnlock( &addon_system.sl_cache_using_resources );
711 }
712 }
713
714 if( cache_id )
715 vg_pool_watch( pool, cache_id );
716
717 return cache_id;
718 }
719
720 static u16 addon_cache_create_viewer_from_uid( enum addon_type type,
721 char uid[ADDON_UID_MAX] ){
722 addon_alias q;
723 if( !addon_uid_to_alias( uid, &q ) ) return 0;
724 if( q.type != type ) return 0;
725
726 u32 reg_id = addon_match( &q );
727
728 if( reg_id == 0xffffffff ){
729 vg_warn( "We dont have the addon '%s' installed.\n", uid );
730 return 0;
731 }
732 else {
733 return addon_cache_create_viewer( type, reg_id );
734 }
735 }
736
737 static void addon_cache_watch( enum addon_type type, u16 cache_id ){
738 if( !cache_id ) return;
739
740 struct addon_cache *cache = &addon_system.cache[type];
741 vg_pool *pool = &cache->pool;
742 vg_pool_watch( pool, cache_id );
743 }
744
745 static void addon_cache_unwatch( enum addon_type type, u16 cache_id ){
746 if( !cache_id ) return;
747
748 struct addon_cache *cache = &addon_system.cache[type];
749 vg_pool *pool = &cache->pool;
750 vg_pool_unwatch( pool, cache_id );
751 }
752
753 #endif /* ADDON_C */