7 #include "workshop_types.h"
10 static u32
addon_count( enum workshop_file_type type
){
11 return addon_system
.registry_type_counts
[ type
];
14 /* these kind of suck, oh well. */
15 static addon_reg
*get_addon_from_index(enum workshop_file_type type
, u32 index
){
17 for( u32 i
=0; count
<addon_count(type
); i
++ ){
18 addon_reg
*reg
= &addon_system
.registry
[i
];
19 if( reg
->type
== type
){
30 static u32
get_index_from_addon( enum workshop_file_type type
, addon_reg
*a
){
32 for( u32 i
=0; count
<addon_system
.registry_type_counts
[type
]; i
++ ){
33 addon_reg
*reg
= &addon_system
.registry
[i
];
34 if( reg
->type
== type
){
45 static u32
addon_match( enum workshop_file_type type
,
46 u64 workshop_id
, const char *foldername
){
47 u32 foldername_djb2
= vg_strdjb2( foldername
);
50 for( u32 i
=0; count
<addon_system
.registry_type_counts
[type
]; i
++ ){
51 addon_reg
*reg
= &addon_system
.registry
[i
];
52 if( reg
->type
== type
){
55 if( workshop_id
== reg
->workshop_id
)
59 if( reg
->foldername_hash
== foldername_djb2
){
60 if( !strcmp( reg
->foldername
, foldername
) ){
73 static void addon_system_init( void ){
74 u32 reg_size
= sizeof(addon_reg
)*ADDON_MOUNTED_MAX
;
75 addon_system
.registry
= vg_linear_alloc( vg_mem
.rtmemory
, reg_size
);
81 static cache_inf
[] = {
82 [k_workshop_file_type_board
] = {
83 .stride
= sizeof(struct player_board
),
84 .count
= CACHE_BOARD_MAX
86 [k_workshop_file_type_player
] = {
87 .stride
= sizeof(struct player_model
),
88 .count
= CACHE_PLAYER_MAX
92 for( u32 i
=0; i
<vg_list_size(cache_inf
); i
++ ){
93 struct cache_inf
*inf
= &cache_inf
[i
];
94 struct addon_cache
*cache
= &addon_system
.cache
[i
];
97 /* create the allocations pool */
98 u32 alloc_size
= sizeof(struct addon_cache_entry
)*inf
->count
;
99 cache
->allocs
= vg_linear_alloc( vg_mem
.rtmemory
, alloc_size
);
100 memset( cache
->allocs
, 0, alloc_size
);
102 cache
->pool
.buffer
= cache
->allocs
;
103 cache
->pool
.count
= inf
->count
;
104 cache
->pool
.stride
= sizeof( struct addon_cache_entry
);
105 cache
->pool
.offset
= offsetof( struct addon_cache_entry
, poolnode
);
106 vg_pool_init( &cache
->pool
);
108 /* create the real memory */
109 u32 cache_size
= inf
->stride
*inf
->count
;
110 cache
->items
= vg_linear_alloc( vg_mem
.rtmemory
, cache_size
);
111 cache
->stride
= inf
->stride
;
112 memset( cache
->items
, 0, cache_size
);
114 for( i32 j
=0; j
<inf
->count
; j
++ ){
115 struct addon_cache_entry
*alloc
= &cache
->allocs
[j
];
116 alloc
->reg_ptr
= NULL
;
117 alloc
->reg_index
= 0xffffffff;
125 * -----------------------------------------------------------------------------
129 * Reciever for scan completion. copies the registry counts back into main fred
131 VG_STATIC
void async_addon_reg_update( void *data
, u32 size
)
133 vg_info( "Registry update notify\n" );
135 for( u32 i
=0; i
<k_workshop_file_type_max
; i
++ ){
136 addon_system
.registry_type_counts
[i
] = 0;
139 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
140 addon_system
.registry_type_counts
[ addon_system
.registry
[i
].type
] ++;
144 VG_STATIC
void addon_set_foldername( addon_reg
*reg
, const char name
[64] ){
145 vg_strncpy( name
, reg
->foldername
, 64, k_strncpy_always_add_null
);
146 reg
->foldername_hash
= vg_strdjb2( reg
->foldername
);
150 * Create a new registry
152 VG_STATIC addon_reg
*addon_alloc_reg( PublishedFileId_t workshop_id
,
153 enum workshop_file_type type
){
154 if( addon_system
.registry_count
== ADDON_MOUNTED_MAX
){
155 vg_error( "You have too many addons installed!\n" );
159 addon_reg
*reg
= &addon_system
.registry
[ addon_system
.registry_count
];
160 reg
->metadata_len
= 0;
162 reg
->state
= k_addon_state_indexed
;
163 reg
->workshop_id
= workshop_id
;
164 reg
->foldername
[0] = '\0';
169 snprintf( foldername
, 64, PRINTF_U64
, workshop_id
);
170 addon_set_foldername( reg
, foldername
);
176 * If the addon.inf exists int the folder, load into the reg
178 VG_STATIC
int addon_try_load_metadata( addon_reg
*reg
, vg_str folder_path
){
179 vg_str meta_path
= folder_path
;
180 vg_strcat( &meta_path
, "/addon.inf" );
181 if( !vg_strgood( &meta_path
) ){
182 vg_error( "The metadata path is too long\n" );
186 FILE *fp
= fopen( meta_path
.buffer
, "rb" );
188 vg_error( "Could not open the '%s'\n", meta_path
.buffer
);
192 reg
->metadata_len
= fread( reg
->metadata
, 1, 512, fp
);
193 if( reg
->metadata_len
!= 512 ){
196 vg_error( "unknown error codition" );
197 reg
->metadata_len
= 0;
205 VG_STATIC
void addon_print_info( addon_reg
*reg
){
206 vg_info( "addon_reg #%u{\n", addon_system
.registry_count
);
207 vg_info( " type: %d\n", reg
->type
);
208 vg_info( " workshop_id: " PRINTF_U64
"\n", reg
->workshop_id
);
209 vg_info( " folder: [%u]%s\n", reg
->foldername_hash
, reg
->foldername
);
210 vg_info( " metadata_len: %u\n", reg
->metadata_len
);
211 vg_info( " cache_id: %hu\n", reg
->cache_id
);
215 VG_STATIC
void addon_mount_finish( addon_reg
*reg
){
216 addon_print_info( reg
);
217 addon_system
.registry_count
++;
221 * Mount a fully packaged addon, one that certainly has a addon.inf
223 VG_STATIC addon_reg
*addon_mount_workshop_folder( PublishedFileId_t workshop_id
,
226 addon_reg
*reg
= addon_alloc_reg( workshop_id
, k_workshop_file_type_none
);
227 if( !reg
) return NULL
;
229 if( !addon_try_load_metadata( reg
, folder_path
) ){
233 enum workshop_file_type type
= k_workshop_file_type_none
;
235 root
.buf
= reg
->metadata
;
236 root
.len
= reg
->metadata_len
;
237 root
.max
= sizeof(reg
->metadata
);
239 vg_msg workshop
= root
;
240 if( vg_msg_seekframe( &workshop
, "workshop", k_vg_msg_first
)){
241 type
= vg_msg_seekkvu32( &workshop
, "type", k_vg_msg_first
);
244 if( type
== k_workshop_file_type_none
){
245 vg_error( "Cannot determine addon type\n" );
250 addon_mount_finish( reg
);
255 * Mount a local folder. may or may not have addon.inf
257 VG_STATIC addon_reg
*addon_mount_local_addon( const char *folder
,
258 enum workshop_file_type type
,
259 const char *content_ext
)
261 char folder_path_buf
[4096];
263 vg_strnull( &folder_path
, folder_path_buf
, 4096 );
264 vg_strcat( &folder_path
, folder
);
266 const char *folder_name
= vg_strch( &folder_path
, '/' )+1;
267 u32 folder_hash
= vg_strdjb2(folder_name
);
268 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
269 addon_reg
*reg
= &addon_system
.registry
[i
];
271 if( (reg
->type
== type
) && (reg
->foldername_hash
== folder_hash
) ){
272 if( !strcmp( reg
->foldername
, folder_name
) ){
273 reg
->state
= k_addon_state_indexed
;
279 addon_reg
*reg
= addon_alloc_reg( 0, type
);
280 if( !reg
) return NULL
;
281 addon_set_foldername( reg
, folder_name
);
282 addon_try_load_metadata( reg
, folder_path
);
284 if( reg
->metadata_len
== 0 ){
285 /* create our own content commands */
287 msg
.buf
= reg
->metadata
;
289 msg
.max
= sizeof(reg
->metadata
);
291 u32 content_count
= 0;
293 vg_strcat( &folder_path
, "" );
294 vg_warn( "Creating own metadata for: %s\n", folder_path
.buffer
);
297 if( !vg_dir_open(&subdir
, folder_path
.buffer
) ){
298 vg_error( "Failed to open '%s'\n", folder_path
.buffer
);
302 while( vg_dir_next_entry(&subdir
) ){
303 if( vg_dir_entry_type(&subdir
) == k_vg_entry_type_file
){
304 const char *fname
= vg_dir_entry_name(&subdir
);
305 vg_str file
= folder_path
;
306 vg_strcat( &file
, "/" );
307 vg_strcat( &file
, fname
);
308 if( !vg_strgood( &file
) ) continue;
310 char *ext
= vg_strch( &file
, '.' );
312 if( strcmp(ext
,content_ext
) ) continue;
314 vg_msg_wkvstr( &msg
, "content", fname
);
318 vg_dir_close(&subdir
);
320 if( !content_count
) return NULL
;
321 if( msg
.error
== k_vg_msg_error_OK
)
322 reg
->metadata_len
= msg
.cur
;
324 vg_error( "Error creating metadata: %d\n", msg
.error
);
329 addon_mount_finish( reg
);
334 * Check all subscribed items
336 VG_STATIC
void addon_mount_workshop_items(void){
337 if( !steam_ready
) return;
339 * Steam workshop scan
341 vg_info( "Mounting steam workshop subscriptions\n" );
342 PublishedFileId_t workshop_ids
[ ADDON_MOUNTED_MAX
];
343 u32 workshop_count
= ADDON_MOUNTED_MAX
;
345 vg_async_item
*call
= vg_async_alloc(
346 sizeof(struct async_workshop_installed_files_info
));
347 struct async_workshop_installed_files_info
*info
= call
->payload
;
348 info
->buffer
= workshop_ids
;
349 info
->len
= &workshop_count
;
350 vg_async_dispatch( call
, async_workshop_get_installed_files
);
353 for( u32 j
=0; j
<workshop_count
; j
++ ){
354 /* check for existance in both our caches
355 * ----------------------------------------------------------*/
356 PublishedFileId_t id
= workshop_ids
[j
];
357 for( u32 i
=0; i
<addon_system
.registry_count
; i
++ ){
358 addon_reg
*reg
= &addon_system
.registry
[i
];
360 if( reg
->workshop_id
== id
){
361 reg
->state
= k_addon_state_indexed
;
362 goto next_file_workshop
;
366 vg_async_item
*call1
=
367 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
371 struct async_workshop_filepath_info
*info
= call1
->payload
;
374 info
->len
= vg_list_size(path
);
375 vg_async_dispatch( call1
, async_workshop_get_filepath
);
376 vg_async_stall(); /* too bad! */
378 vg_str folder
= {.buffer
= path
, .i
=strlen(path
), .len
=4096};
379 addon_mount_workshop_folder( id
, folder
);
385 * Scan a local content folder for addons. It must find at least one file with
386 * the specified content_ext to be considered.
388 VG_STATIC
void addon_mount_content_folder( enum workshop_file_type type
,
389 const char *base_folder
,
390 const char *content_ext
)
392 vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
393 type
, base_folder
, content_ext
);
397 vg_strnull( &path
, path_buf
, 4096 );
398 vg_strcat( &path
, base_folder
);
401 if( !vg_dir_open(&dir
,path
.buffer
) ){
402 vg_error( "vg_dir_open('%s') failed\n", path
.buffer
);
403 vg_async_call( workshop_async_any_complete
, NULL
, 0 );
407 vg_strcat(&path
,"/");
409 while( vg_dir_next_entry(&dir
) ){
410 if( vg_dir_entry_type(&dir
) == k_vg_entry_type_dir
){
411 const char *d_name
= vg_dir_entry_name(&dir
);
413 vg_str folder
= path
;
414 if( strlen( d_name
) > ADDON_FOLDERNAME_MAX
){
415 vg_warn( "folder too long: %s\n", d_name
);
419 vg_strcat( &folder
, d_name
);
420 if( !vg_strgood( &folder
) ) continue;
422 addon_mount_local_addon( folder
.buffer
, type
, content_ext
);
428 static int addon_get_content_folder( addon_reg
*reg
, vg_str
*folder
){
429 if( reg
->workshop_id
){
430 vg_async_item
*call
=
431 vg_async_alloc( sizeof(struct async_workshop_filepath_info
) );
432 struct async_workshop_filepath_info
*info
= call
->payload
;
433 info
->buf
= folder
->buffer
;
434 info
->id
= reg
->workshop_id
;
435 info
->len
= folder
->len
;
436 vg_async_dispatch( call
, async_workshop_get_filepath
);
437 vg_async_stall(); /* too bad! */
438 if( info
->buf
[0] == '\0' ){
439 vg_error( "Failed SteamAPI_GetItemInstallInfo(" PRINTF_U64
")\n",
443 folder
->i
= strlen( folder
->buffer
);
448 if( reg
->type
== k_workshop_file_type_board
)
449 vg_strcat( folder
, "boards/" );
450 else if( reg
->type
== k_workshop_file_type_world
)
451 vg_strcat( folder
, "maps/" );
454 vg_strcat( folder
, reg
->foldername
);
459 static u16
addon_cache_fetch( enum workshop_file_type type
, u32 reg_index
){
460 vg_assert_thread( k_thread_purpose_main
);
462 addon_reg
*reg
= NULL
;
464 if( reg_index
< addon_count( type
) ){
465 reg
= get_addon_from_index( type
, reg_index
);
467 return reg
->cache_id
;
473 static u16
addon_cache_alloc( enum workshop_file_type type
, u32 reg_index
){
474 struct addon_cache
*cache
= &addon_system
.cache
[ type
];
476 u16 new_id
= vg_pool_lru( &cache
->pool
);
477 struct addon_cache_entry
*new_entry
= vg_pool_item( &cache
->pool
, new_id
);
479 addon_reg
*reg
= NULL
;
480 if( reg_index
< addon_count( type
) )
481 reg
= get_addon_from_index( type
, reg_index
);
484 if( new_entry
->reg_ptr
)
485 new_entry
->reg_ptr
->cache_id
= 0;
488 reg
->cache_id
= new_id
;
490 new_entry
->reg_ptr
= reg
;
491 new_entry
->reg_index
= reg_index
;
495 vg_error( "cache full (type: %u)!\n", type
);
500 static void *addon_cache_item( enum workshop_file_type type
, u16 id
){
501 if( !id
) return NULL
;
503 struct addon_cache
*cache
= &addon_system
.cache
[type
];
504 return cache
->items
+ ((size_t)(id
-1) * cache
->stride
);
507 static void *addon_cache_item_if_loaded( enum workshop_file_type type
, u16 id
){
508 if( !id
) return NULL
;
510 struct addon_cache
*cache
= &addon_system
.cache
[type
];
511 struct addon_cache_entry
*entry
= vg_pool_item( &cache
->pool
, id
);
513 if( entry
->state
== k_addon_cache_state_loaded
)
514 return addon_cache_item( type
, id
);