1 #include "vg/vg_engine.h"
3 #include "vg/vg_loader.h"
5 #include "addon_types.h"
11 struct addon_system addon_system
;
13 u32
addon_count( enum addon_type type
, u32 ignoreflags
)
16 u32 typecount
= 0, count
= 0;
17 for( u32 i
=0; typecount
<addon_count( type
, 0 ); i
++ ){
18 addon_reg
*reg
= &addon_system
.registry
[i
];
19 if( reg
->alias
.type
== type
){
22 if( reg
->flags
& ignoreflags
)
32 return addon_system
.registry_type_counts
[ type
];
36 /* these kind of suck, oh well. */
37 addon_reg
*get_addon_from_index( enum addon_type type
, u32 index
,
40 u32 typecount
= 0, count
= 0;
41 for( u32 i
=0; typecount
<addon_count(type
,0); i
++ ){
42 addon_reg
*reg
= &addon_system
.registry
[i
];
43 if( reg
->alias
.type
== type
){
46 if( reg
->flags
& ignoreflags
)
59 u32
get_index_from_addon( enum addon_type type
, addon_reg
*a
)
62 for( u32 i
=0; count
<addon_system
.registry_type_counts
[type
]; i
++ ){
63 addon_reg
*reg
= &addon_system
.registry
[i
];
64 if( reg
->alias
.type
== type
){
75 u32
addon_match( addon_alias
*alias
)
77 if( alias
->type
== k_addon_type_none
) return 0xffffffff;
79 u32 foldername_djb2
= 0;
80 if( !alias
->workshop_id
)
81 foldername_djb2
= vg_strdjb2( alias
->foldername
);
84 for( u32 i
=0; count
<addon_system
.registry_type_counts
[alias
->type
]; i
++ ){
85 addon_reg
*reg
= &addon_system
.registry
[i
];
86 if( reg
->alias
.type
== alias
->type
){
88 if( alias
->workshop_id
){
89 if( alias
->workshop_id
== reg
->alias
.workshop_id
)
93 if( reg
->foldername_hash
== foldername_djb2
){
94 if( !strcmp( reg
->alias
.foldername
, alias
->foldername
) ){
108 * Create a string version of addon alias in buf
110 void addon_alias_uid( addon_alias
*alias
, char buf
[ADDON_UID_MAX
] )
112 if( alias
->workshop_id
){
113 snprintf( buf
, 128, "sr%03d-steam-"PRINTF_U64
,
114 alias
->type
, alias
->workshop_id
);
117 snprintf( buf
, 128, "sr%03d-local-%s",
118 alias
->type
, alias
->foldername
);
125 int addon_alias_eq( addon_alias
*a
, addon_alias
*b
)
127 if( a
->type
== b
->type
){
128 if( a
->workshop_id
== b
->workshop_id
){
132 return !strcmp( a
->foldername
, b
->foldername
);
141 * make alias represent NULL.
143 void invalidate_addon_alias( addon_alias
*alias
)
145 alias
->type
= k_addon_type_none
;
146 alias
->workshop_id
= 0;
147 alias
->foldername
[0] = '\0';
151 * parse uid to alias. returns 1 if successful
153 int addon_uid_to_alias( const char *uid
, addon_alias
*alias
)
163 if( strlen(uid
) < 13 ){
164 invalidate_addon_alias( alias
);
167 if( !((uid
[0] == 's') && (uid
[1] == 'r')) ){
168 invalidate_addon_alias( alias
);
173 memcpy( type
, uid
+2, 3 );
175 alias
->type
= atoi(type
);
178 memcpy( location
, uid
+6, 5 );
181 if( !strcmp(location
,"steam") )
182 alias
->workshop_id
= atoll( uid
+12 );
183 else if( !strcmp(location
,"local") ){
184 alias
->workshop_id
= 0;
185 vg_strncpy( uid
+12, alias
->foldername
, 64, k_strncpy_always_add_null
);
188 invalidate_addon_alias( alias
);
195 void addon_system_init( void )
197 u32 reg_size
= sizeof(addon_reg
)*ADDON_MOUNTED_MAX
;
198 addon_system
.registry
= vg_linear_alloc( vg_mem
.rtmemory
, reg_size
);
200 for( u32 type
=0; type
<k_addon_type_max
; type
++ ){
201 struct addon_type_info
*inf
= &addon_type_infos
[type
];
202 struct addon_cache
*cache
= &addon_system
.cache
[type
];
204 if( inf
->cache_count
){
205 /* create the allocations pool */
206 u32 alloc_size
= sizeof(struct addon_cache_entry
)*inf
->cache_count
;
207 cache
->allocs
= vg_linear_alloc( vg_mem
.rtmemory
, alloc_size
);
208 memset( cache
->allocs
, 0, alloc_size
);
210 cache
->pool
.buffer
= cache
->allocs
;
211 cache
->pool
.count
= inf
->cache_count
;
212 cache
->pool
.stride
= sizeof( struct addon_cache_entry
);
213 cache
->pool
.offset
= offsetof( struct addon_cache_entry
, poolnode
);
214 vg_pool_init( &cache
->pool
);
216 /* create the real memory */
217 u32 cache_size
= inf
->cache_stride
*inf
->cache_count
;
218 cache
->items
= vg_linear_alloc( vg_mem
.rtmemory
, cache_size
);
219 cache
->stride
= inf
->cache_stride
;
220 memset( cache
->items
, 0, cache_size
);
222 for( i32 j
=0; j
<inf
->cache_count
; j
++ ){
223 struct addon_cache_entry
*alloc
= &cache
->allocs
[j
];
224 alloc
->reg_ptr
= NULL
;
225 alloc
->reg_index
= 0xffffffff;
233 * -----------------------------------------------------------------------------
237 * Reciever for scan completion. copies the registry counts back into main fred
239 void async_addon_reg_update( void *data
, u32 size
)
241 vg_info( "Registry update notify\n" );
243 for( u32 i
=0; i
<k_addon_type_max
; i
++ ){
244 addon_system
.registry_type_counts
[i
] = 0;
247 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
248 enum addon_type type
= addon_system
.registry
[i
].alias
.type
;
249 addon_system
.registry_type_counts
[ type
] ++;
253 static void addon_set_foldername( addon_reg
*reg
, const char name
[64] ){
254 vg_strncpy( name
, reg
->alias
.foldername
, 64, k_strncpy_always_add_null
);
255 reg
->foldername_hash
= vg_strdjb2( reg
->alias
.foldername
);
259 * Create a new registry
261 static addon_reg
*addon_alloc_reg( PublishedFileId_t workshop_id
,
262 enum addon_type type
){
263 if( addon_system
.registry_count
== ADDON_MOUNTED_MAX
){
264 vg_error( "You have too many addons installed!\n" );
268 addon_reg
*reg
= &addon_system
.registry
[ addon_system
.registry_count
];
270 reg
->metadata_len
= 0;
272 reg
->state
= k_addon_state_indexed
;
273 reg
->alias
.workshop_id
= workshop_id
;
274 reg
->alias
.foldername
[0] = '\0';
275 reg
->alias
.type
= type
;
279 snprintf( foldername
, 64, PRINTF_U64
, workshop_id
);
280 addon_set_foldername( reg
, foldername
);
286 * If the addon.inf exists int the folder, load into the reg
288 static int addon_try_load_metadata( addon_reg
*reg
, vg_str folder_path
){
289 vg_str meta_path
= folder_path
;
290 vg_strcat( &meta_path
, "/addon.inf" );
291 if( !vg_strgood( &meta_path
) ){
292 vg_error( "The metadata path is too long\n" );
296 FILE *fp
= fopen( meta_path
.buffer
, "rb" );
298 vg_error( "Could not open the '%s'\n", meta_path
.buffer
);
302 reg
->metadata_len
= fread( reg
->metadata
, 1, 512, fp
);
303 if( reg
->metadata_len
!= 512 ){
306 vg_error( "unknown error codition" );
307 reg
->metadata_len
= 0;
315 static void addon_print_info( addon_reg
*reg
){
316 vg_info( "addon_reg #%u{\n", addon_system
.registry_count
);
317 vg_info( " type: %d\n", reg
->alias
.type
);
318 vg_info( " workshop_id: " PRINTF_U64
"\n", reg
->alias
.workshop_id
);
319 vg_info( " folder: [%u]%s\n", reg
->foldername_hash
, reg
->alias
.foldername
);
320 vg_info( " metadata_len: %u\n", reg
->metadata_len
);
321 vg_info( " cache_id: %hu\n", reg
->cache_id
);
325 static void addon_mount_finish( addon_reg
*reg
){
327 addon_print_info( reg
);
329 addon_system
.registry_count
++;
333 * Mount a fully packaged addon, one that certainly has a addon.inf
335 static addon_reg
*addon_mount_workshop_folder( PublishedFileId_t workshop_id
,
338 addon_reg
*reg
= addon_alloc_reg( workshop_id
, k_addon_type_none
);
339 if( !reg
) return NULL
;
341 if( !addon_try_load_metadata( reg
, folder_path
) ){
345 enum addon_type type
= k_addon_type_none
;
347 vg_msg_init( &msg
, reg
->metadata
, reg
->metadata_len
);
349 if( vg_msg_seekframe( &msg
, "workshop" ))
351 vg_msg_getkvintg( &msg
, "type", k_vg_msg_u32
, &type
, NULL
);
354 if( type
== k_addon_type_none
)
356 vg_error( "Cannot determine addon type\n" );
360 reg
->alias
.type
= type
;
361 addon_mount_finish( reg
);
366 * Mount a local folder. may or may not have addon.inf
368 addon_reg
*addon_mount_local_addon( const char *folder
,
369 enum addon_type type
,
370 const char *content_ext
)
372 char folder_path_buf
[4096];
374 vg_strnull( &folder_path
, folder_path_buf
, 4096 );
375 vg_strcat( &folder_path
, folder
);
377 const char *folder_name
= vg_strch( &folder_path
, '/' )+1;
378 u32 folder_hash
= vg_strdjb2(folder_name
);
379 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
380 addon_reg
*reg
= &addon_system
.registry
[i
];
382 if( (reg
->alias
.type
== type
) && (reg
->foldername_hash
== folder_hash
) ){
383 if( !strcmp( reg
->alias
.foldername
, folder_name
) ){
384 reg
->state
= k_addon_state_indexed
;
390 addon_reg
*reg
= addon_alloc_reg( 0, type
);
391 if( !reg
) return NULL
;
392 addon_set_foldername( reg
, folder_name
);
393 addon_try_load_metadata( reg
, folder_path
);
395 if( reg
->metadata_len
== 0 ){
396 /* create our own content commands */
398 vg_msg_init( &msg
, reg
->metadata
, sizeof(reg
->metadata
) );
400 u32 content_count
= 0;
402 vg_strcat( &folder_path
, "" );
403 vg_warn( "Creating own metadata for: %s\n", folder_path
.buffer
);
406 if( !vg_dir_open(&subdir
, folder_path
.buffer
) ){
407 vg_error( "Failed to open '%s'\n", folder_path
.buffer
);
411 while( vg_dir_next_entry(&subdir
) ){
412 if( vg_dir_entry_type(&subdir
) == k_vg_entry_type_file
){
413 const char *fname
= vg_dir_entry_name(&subdir
);
414 vg_str file
= folder_path
;
415 vg_strcat( &file
, "/" );
416 vg_strcat( &file
, fname
);
417 if( !vg_strgood( &file
) ) continue;
419 char *ext
= vg_strch( &file
, '.' );
421 if( strcmp(ext
,content_ext
) ) continue;
423 vg_msg_wkvstr( &msg
, "content", fname
);
427 vg_dir_close(&subdir
);
429 if( !content_count
) return NULL
;
430 if( msg
.error
== k_vg_msg_error_OK
)
431 reg
->metadata_len
= msg
.cur
.co
;
433 vg_error( "Error creating metadata: %d\n", msg
.error
);
438 addon_mount_finish( reg
);
443 * Check all subscribed items
445 void addon_mount_workshop_items(void)
447 if( skaterift
.demo_mode
){
448 vg_info( "Won't load workshop items in demo mode\n" );
451 if( !steam_ready
) return;
454 * Steam workshop scan
456 vg_info( "Mounting steam workshop subscriptions\n" );
457 PublishedFileId_t workshop_ids
[ ADDON_MOUNTED_MAX
];
458 u32 workshop_count
= ADDON_MOUNTED_MAX
;
460 vg_async_item
*call
= vg_async_alloc(
461 sizeof(struct async_workshop_installed_files_info
));
462 struct async_workshop_installed_files_info
*info
= call
->payload
;
463 info
->buffer
= workshop_ids
;
464 info
->len
= &workshop_count
;
465 vg_async_dispatch( call
, async_workshop_get_installed_files
);
468 for( u32 j
=0; j
<workshop_count
; j
++ ){
469 /* check for existance in both our caches
470 * ----------------------------------------------------------*/
471 PublishedFileId_t id
= workshop_ids
[j
];
472 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
473 addon_reg
*reg
= &addon_system
.registry
[i
];
475 if( reg
->alias
.workshop_id
== id
){
476 reg
->state
= k_addon_state_indexed
;
477 goto next_file_workshop
;
481 vg_async_item
*call1
=
482 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
486 struct async_workshop_filepath_info
*info
= call1
->payload
;
489 info
->len
= vg_list_size(path
);
490 vg_async_dispatch( call1
, async_workshop_get_filepath
);
491 vg_async_stall(); /* too bad! */
493 vg_str folder
= {.buffer
= path
, .i
=strlen(path
), .len
=4096};
494 addon_mount_workshop_folder( id
, folder
);
500 * Scan a local content folder for addons. It must find at least one file with
501 * the specified content_ext to be considered.
503 void addon_mount_content_folder( enum addon_type type
,
504 const char *base_folder
,
505 const char *content_ext
)
507 vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
508 type
, base_folder
, content_ext
);
512 vg_strnull( &path
, path_buf
, 4096 );
513 vg_strcat( &path
, base_folder
);
516 if( !vg_dir_open(&dir
,path
.buffer
) ){
517 vg_error( "vg_dir_open('%s') failed\n", path
.buffer
);
521 vg_strcat(&path
,"/");
523 while( vg_dir_next_entry(&dir
) ){
524 if( vg_dir_entry_type(&dir
) == k_vg_entry_type_dir
){
525 const char *d_name
= vg_dir_entry_name(&dir
);
527 vg_str folder
= path
;
528 if( strlen( d_name
) > ADDON_FOLDERNAME_MAX
){
529 vg_warn( "folder too long: %s\n", d_name
);
533 vg_strcat( &folder
, d_name
);
534 if( !vg_strgood( &folder
) ) continue;
536 addon_mount_local_addon( folder
.buffer
, type
, content_ext
);
543 * write the full path of the addon's folder into the vg_str
545 int addon_get_content_folder( addon_reg
*reg
, vg_str
*folder
, int async
)
547 if( reg
->alias
.workshop_id
){
548 struct async_workshop_filepath_info
*info
= NULL
;
549 vg_async_item
*call
= NULL
;
552 call
= vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
553 info
= call
->payload
;
556 info
= alloca( sizeof(struct async_workshop_filepath_info
) );
558 info
->buf
= folder
->buffer
;
559 info
->id
= reg
->alias
.workshop_id
;
560 info
->len
= folder
->len
;
563 vg_async_dispatch( call
, async_workshop_get_filepath
);
564 vg_async_stall(); /* too bad! */
567 async_workshop_get_filepath( info
, 0 );
570 if( info
->buf
[0] == '\0' ){
571 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
572 reg
->alias
.workshop_id
);
575 folder
->i
= strlen( folder
->buffer
);
581 const char *local_folder
=
582 addon_type_infos
[reg
->alias
.type
].local_content_folder
;
584 if( !local_folder
) return 0;
585 vg_strcat( folder
, local_folder
);
586 vg_strcat( folder
, reg
->alias
.foldername
);
592 * Return existing cache id if reg_index points to a registry with its cache
595 u16
addon_cache_fetch( enum addon_type type
, u32 reg_index
)
597 addon_reg
*reg
= NULL
;
599 if( reg_index
< addon_count( type
, 0 ) ){
600 reg
= get_addon_from_index( type
, reg_index
, 0 );
602 return reg
->cache_id
;
609 * Allocate a new cache item from the pool
611 u16
addon_cache_alloc( enum addon_type type
, u32 reg_index
)
613 struct addon_cache
*cache
= &addon_system
.cache
[ type
];
615 u16 new_id
= vg_pool_lru( &cache
->pool
);
616 struct addon_cache_entry
*new_entry
= vg_pool_item( &cache
->pool
, new_id
);
618 addon_reg
*reg
= NULL
;
619 if( reg_index
< addon_count( type
, 0 ) )
620 reg
= get_addon_from_index( type
, reg_index
, 0 );
623 if( new_entry
->reg_ptr
)
624 new_entry
->reg_ptr
->cache_id
= 0;
627 reg
->cache_id
= new_id
;
629 new_entry
->reg_ptr
= reg
;
630 new_entry
->reg_index
= reg_index
;
634 vg_error( "cache full (type: %u)!\n", type
);
640 * Get the real item data for cache id
642 void *addon_cache_item( enum addon_type type
, u16 id
)
644 if( !id
) return NULL
;
646 struct addon_cache
*cache
= &addon_system
.cache
[type
];
647 return cache
->items
+ ((size_t)(id
-1) * cache
->stride
);
651 * Get the real item data for cache id ONLY if the item is completely loaded.
653 void *addon_cache_item_if_loaded( enum addon_type type
, u16 id
)
655 if( !id
) return NULL
;
657 struct addon_cache
*cache
= &addon_system
.cache
[type
];
658 struct addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
660 if( entry
->state
== k_addon_cache_state_loaded
)
661 return addon_cache_item( type
, id
);
666 * Updates the item state from the main thread
668 void async_addon_setstate( void *_entry
, u32 _state
)
670 addon_cache_entry
*entry
= _entry
;
671 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
672 entry
->state
= _state
;
673 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
674 vg_success( " loaded (%s)\n", entry
->reg_ptr
->alias
.foldername
);
678 * Handles the loading of an individual item
680 static int addon_cache_load_request( enum addon_type type
, u16 id
,
681 addon_reg
*reg
, vg_str folder
){
683 /* load content files
684 * --------------------------------- */
685 vg_str content_path
= folder
;
688 vg_msg_init( &msg
, reg
->metadata
, reg
->metadata_len
);
690 const char *kv_content
= vg_msg_getkvstr( &msg
, "content" );
692 vg_strcat( &content_path
, "/" );
693 vg_strcat( &content_path
, kv_content
);
696 vg_error( " No content paths in metadata\n" );
700 if( !vg_strgood( &content_path
) ) {
701 vg_error( " Metadata path too long\n" );
705 if( type
== k_addon_type_board
){
706 struct player_board
*board
= addon_cache_item( type
, id
);
707 player_board_load( board
, content_path
.buffer
);
710 else if( type
== k_addon_type_player
){
711 struct player_model
*model
= addon_cache_item( type
, id
);
712 player_model_load( model
, content_path
.buffer
);
722 static void addon_cache_free_item( enum addon_type type
, u16 id
){
723 if( type
== k_addon_type_board
){
724 struct player_board
*board
= addon_cache_item( type
, id
);
725 player_board_unload( board
);
727 else if( type
== k_addon_type_player
){
728 struct player_model
*model
= addon_cache_item( type
, id
);
729 player_model_unload( model
);
734 * Goes over cache item load requests and calls the above ^
736 static void T1_addon_cache_load_loop(void *_
)
738 vg_info( "Running load loop\n" );
741 for( u32 type
=0; type
<k_addon_type_max
; type
++ )
743 struct addon_cache
*cache
= &addon_system
.cache
[type
];
745 for( u32 id
=1; id
<=cache
->pool
.count
; id
++ )
747 addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
749 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
750 if( entry
->state
== k_addon_cache_state_load_request
)
752 vg_info( "process cache load request (%u#%u, reg:%u)\n",
753 type
, id
, entry
->reg_index
);
755 if( entry
->reg_index
>= addon_count(type
,0) )
757 /* should maybe have a different value for this case */
758 entry
->state
= k_addon_cache_state_none
;
759 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
763 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
765 /* continue with the request */
766 addon_reg
*reg
= get_addon_from_index( type
, entry
->reg_index
, 0 );
767 entry
->reg_ptr
= reg
;
770 vg_strnull( &folder
, path_buf
, 4096 );
771 if( addon_get_content_folder( reg
, &folder
, 1 ) )
773 if( addon_cache_load_request( type
, id
, reg
, folder
) )
775 vg_async_call( async_addon_setstate
,
776 entry
, k_addon_cache_state_loaded
);
781 vg_warn( "cache item did not load (%u#%u)\n", type
, id
);
782 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
783 entry
->state
= k_addon_cache_state_none
;
784 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
787 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
792 void addon_system_pre_update(void)
794 if( !vg_loader_availible() ) return;
796 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
797 for( u32 type
=0; type
<k_addon_type_max
; type
++ )
799 struct addon_cache
*cache
= &addon_system
.cache
[type
];
801 for( u32 id
=1; id
<=cache
->pool
.count
; id
++ )
803 addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
804 if( entry
->state
== k_addon_cache_state_load_request
)
806 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
807 vg_loader_start( T1_addon_cache_load_loop
, NULL
);
812 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
816 * Perform the cache interactions required to create a viewslot which will
817 * eventually be loaded by other parts of the system.
819 u16
addon_cache_create_viewer( enum addon_type type
, u16 reg_id
)
821 struct addon_cache
*cache
= &addon_system
.cache
[type
];
822 vg_pool
*pool
= &cache
->pool
;
824 u16 cache_id
= addon_cache_fetch( type
, reg_id
);
826 cache_id
= addon_cache_alloc( type
, reg_id
);
829 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
830 addon_cache_entry
*entry
= vg_pool_item( pool
, cache_id
);
832 if( entry
->state
== k_addon_cache_state_loaded
){
833 addon_cache_free_item( type
, cache_id
);
836 entry
->state
= k_addon_cache_state_load_request
;
837 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
842 vg_pool_watch( pool
, cache_id
);
847 u16
addon_cache_create_viewer_from_uid( enum addon_type type
,
848 char uid
[ADDON_UID_MAX
] )
851 if( !addon_uid_to_alias( uid
, &q
) ) return 0;
852 if( q
.type
!= type
) return 0;
854 u32 reg_id
= addon_match( &q
);
856 if( reg_id
== 0xffffffff ){
857 vg_warn( "We dont have the addon '%s' installed.\n", uid
);
861 return addon_cache_create_viewer( type
, reg_id
);
865 void addon_cache_watch( enum addon_type type
, u16 cache_id
)
867 if( !cache_id
) return;
869 struct addon_cache
*cache
= &addon_system
.cache
[type
];
870 vg_pool
*pool
= &cache
->pool
;
871 vg_pool_watch( pool
, cache_id
);
874 void addon_cache_unwatch( enum addon_type type
, u16 cache_id
)
876 if( !cache_id
) return;
878 struct addon_cache
*cache
= &addon_system
.cache
[type
];
879 vg_pool
*pool
= &cache
->pool
;
880 vg_pool_unwatch( pool
, cache_id
);