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