refactor(1)
[carveJwlIkooP6JGAAIwe30JlM.git] / addon.c
1 #ifndef ADDON_C
2 #define ADDON_C
3
4 #include "addon.h"
5 #include "vg/vg_msg.h"
6 #include "steam.h"
7 #include "workshop_types.h"
8 #include "workshop.h"
9
10 static u32 addon_count( enum workshop_file_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 workshop_file_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->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 workshop_file_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->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( enum workshop_file_type type,
46 u64 workshop_id, const char *foldername ){
47 u32 foldername_djb2 = vg_strdjb2( foldername );
48
49 u32 count = 0;
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 ){
53
54 if( workshop_id ){
55 if( workshop_id == reg->workshop_id )
56 return count;
57 }
58 else{
59 if( reg->foldername_hash == foldername_djb2 ){
60 if( !strcmp( reg->foldername, foldername ) ){
61 return count;
62 }
63 }
64 }
65
66 count ++;
67 }
68 }
69
70 return 0xffffffff;
71 }
72
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 );
76
77 struct cache_inf {
78 size_t stride;
79 u32 count;
80 }
81 static cache_inf[] = {
82 [k_workshop_file_type_board] = {
83 .stride = sizeof(struct player_board),
84 .count = CACHE_BOARD_MAX
85 },
86 [k_workshop_file_type_player] = {
87 .stride = sizeof(struct player_model),
88 .count = CACHE_PLAYER_MAX
89 }
90 };
91
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];
95
96 if( inf->count ){
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 );
101
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 );
107
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 );
113
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;
118 }
119 }
120 }
121 }
122
123 /*
124 * Scanning routines
125 * -----------------------------------------------------------------------------
126 */
127
128 /*
129 * Reciever for scan completion. copies the registry counts back into main fred
130 */
131 VG_STATIC void async_addon_reg_update( void *data, u32 size )
132 {
133 vg_info( "Registry update notify\n" );
134
135 for( u32 i=0; i<k_workshop_file_type_max; i++ ){
136 addon_system.registry_type_counts[i] = 0;
137 }
138
139 for( u32 i=0; i<addon_system.registry_count; i++ ){
140 addon_system.registry_type_counts[ addon_system.registry[i].type ] ++;
141 }
142 }
143
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 );
147 }
148
149 /*
150 * Create a new registry
151 */
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" );
156 return NULL;
157 }
158
159 addon_reg *reg = &addon_system.registry[ addon_system.registry_count ];
160 reg->metadata_len = 0;
161 reg->cache_id = 0;
162 reg->state = k_addon_state_indexed;
163 reg->workshop_id = workshop_id;
164 reg->foldername[0] = '\0';
165 reg->type = type;
166
167 if( workshop_id ){
168 char foldername[64];
169 snprintf( foldername, 64, PRINTF_U64, workshop_id );
170 addon_set_foldername( reg, foldername );
171 }
172 return reg;
173 }
174
175 /*
176 * If the addon.inf exists int the folder, load into the reg
177 */
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" );
183 return 0;
184 }
185
186 FILE *fp = fopen( meta_path.buffer, "rb" );
187 if( !fp ){
188 vg_error( "Could not open the '%s'\n", meta_path.buffer );
189 return 0;
190 }
191
192 reg->metadata_len = fread( reg->metadata, 1, 512, fp );
193 if( reg->metadata_len != 512 ){
194 if( !feof(fp) ){
195 fclose(fp);
196 vg_error( "unknown error codition" );
197 reg->metadata_len = 0;
198 return 0;
199 }
200 }
201 fclose(fp);
202 return 1;
203 }
204
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 );
212 vg_info( "}\n" );
213 }
214
215 VG_STATIC void addon_mount_finish( addon_reg *reg ){
216 addon_print_info( reg );
217 addon_system.registry_count ++;
218 }
219
220 /*
221 * Mount a fully packaged addon, one that certainly has a addon.inf
222 */
223 VG_STATIC addon_reg *addon_mount_workshop_folder( PublishedFileId_t workshop_id,
224 vg_str folder_path )
225 {
226 addon_reg *reg = addon_alloc_reg( workshop_id, k_workshop_file_type_none );
227 if( !reg ) return NULL;
228
229 if( !addon_try_load_metadata( reg, folder_path ) ){
230 return NULL;
231 }
232
233 enum workshop_file_type type = k_workshop_file_type_none;
234 vg_msg root = {0};
235 root.buf = reg->metadata;
236 root.len = reg->metadata_len;
237 root.max = sizeof(reg->metadata);
238
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 );
242 }
243
244 if( type == k_workshop_file_type_none ){
245 vg_error( "Cannot determine addon type\n" );
246 return NULL;
247 }
248
249 reg->type = type;
250 addon_mount_finish( reg );
251 return reg;
252 }
253
254 /*
255 * Mount a local folder. may or may not have addon.inf
256 */
257 VG_STATIC addon_reg *addon_mount_local_addon( const char *folder,
258 enum workshop_file_type type,
259 const char *content_ext )
260 {
261 char folder_path_buf[4096];
262 vg_str folder_path;
263 vg_strnull( &folder_path, folder_path_buf, 4096 );
264 vg_strcat( &folder_path, folder );
265
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];
270
271 if( (reg->type == type) && (reg->foldername_hash == folder_hash) ){
272 if( !strcmp( reg->foldername, folder_name ) ){
273 reg->state = k_addon_state_indexed;
274 return NULL;
275 }
276 }
277 }
278
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 );
283
284 if( reg->metadata_len == 0 ){
285 /* create our own content commands */
286 vg_msg msg = {0};
287 msg.buf = reg->metadata;
288 msg.len = 0;
289 msg.max = sizeof(reg->metadata);
290
291 u32 content_count = 0;
292
293 vg_strcat( &folder_path, "" );
294 vg_warn( "Creating own metadata for: %s\n", folder_path.buffer );
295
296 vg_dir subdir;
297 if( !vg_dir_open(&subdir, folder_path.buffer) ){
298 vg_error( "Failed to open '%s'\n", folder_path.buffer );
299 return NULL;
300 }
301
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;
309
310 char *ext = vg_strch( &file, '.' );
311 if( !ext ) continue;
312 if( strcmp(ext,content_ext) ) continue;
313
314 vg_msg_wkvstr( &msg, "content", fname );
315 content_count ++;
316 }
317 }
318 vg_dir_close(&subdir);
319
320 if( !content_count ) return NULL;
321 if( msg.error == k_vg_msg_error_OK )
322 reg->metadata_len = msg.cur;
323 else{
324 vg_error( "Error creating metadata: %d\n", msg.error );
325 return NULL;
326 }
327 }
328
329 addon_mount_finish( reg );
330 return reg;
331 }
332
333 /*
334 * Check all subscribed items
335 */
336 VG_STATIC void addon_mount_workshop_items(void){
337 if( !steam_ready ) return;
338 /*
339 * Steam workshop scan
340 */
341 vg_info( "Mounting steam workshop subscriptions\n" );
342 PublishedFileId_t workshop_ids[ ADDON_MOUNTED_MAX ];
343 u32 workshop_count = ADDON_MOUNTED_MAX;
344
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 );
351 vg_async_stall();
352
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];
359
360 if( reg->workshop_id == id ){
361 reg->state = k_addon_state_indexed;
362 goto next_file_workshop;
363 }
364 }
365
366 vg_async_item *call1 =
367 vg_async_alloc( sizeof(struct async_workshop_filepath_info) );
368
369 char path[ 4096 ];
370
371 struct async_workshop_filepath_info *info = call1->payload;
372 info->buf = path;
373 info->id = id;
374 info->len = vg_list_size(path);
375 vg_async_dispatch( call1, async_workshop_get_filepath );
376 vg_async_stall(); /* too bad! */
377
378 vg_str folder = {.buffer = path, .i=strlen(path), .len=4096};
379 addon_mount_workshop_folder( id, folder );
380 next_file_workshop:;
381 }
382 }
383
384 /*
385 * Scan a local content folder for addons. It must find at least one file with
386 * the specified content_ext to be considered.
387 */
388 VG_STATIC void addon_mount_content_folder( enum workshop_file_type type,
389 const char *base_folder,
390 const char *content_ext )
391 {
392 vg_info( "Mounting addons(type:%d) matching skaterift/%s/*/*%s\n",
393 type, base_folder, content_ext );
394
395 char path_buf[4096];
396 vg_str path;
397 vg_strnull( &path, path_buf, 4096 );
398 vg_strcat( &path, base_folder );
399
400 vg_dir dir;
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 );
404 return;
405 }
406
407 vg_strcat(&path,"/");
408
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);
412
413 vg_str folder = path;
414 if( strlen( d_name ) > ADDON_FOLDERNAME_MAX ){
415 vg_warn( "folder too long: %s\n", d_name );
416 continue;
417 }
418
419 vg_strcat( &folder, d_name );
420 if( !vg_strgood( &folder ) ) continue;
421
422 addon_mount_local_addon( folder.buffer, type, content_ext );
423 }
424 }
425 vg_dir_close(&dir);
426 }
427
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",
440 reg->workshop_id );
441 return 0;
442 }
443 folder->i = strlen( folder->buffer );
444 return 1;
445 }
446 else{
447 folder->i = 0;
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/" );
452 else return 0;
453
454 vg_strcat( folder, reg->foldername );
455 return 1;
456 }
457 }
458
459 static u16 addon_cache_fetch( enum workshop_file_type type, u32 reg_index ){
460 vg_assert_thread( k_thread_purpose_main );
461
462 addon_reg *reg = NULL;
463
464 if( reg_index < addon_count( type ) ){
465 reg = get_addon_from_index( type, reg_index );
466 if( reg->cache_id )
467 return reg->cache_id;
468 }
469
470 return 0;
471 }
472
473 static u16 addon_cache_alloc( enum workshop_file_type type, u32 reg_index ){
474 struct addon_cache *cache = &addon_system.cache[ type ];
475
476 u16 new_id = vg_pool_lru( &cache->pool );
477 struct addon_cache_entry *new_entry = vg_pool_item( &cache->pool, new_id );
478
479 addon_reg *reg = NULL;
480 if( reg_index < addon_count( type ) )
481 reg = get_addon_from_index( type, reg_index );
482
483 if( new_entry ){
484 if( new_entry->reg_ptr )
485 new_entry->reg_ptr->cache_id = 0;
486
487 if( reg )
488 reg->cache_id = new_id;
489
490 new_entry->reg_ptr = reg;
491 new_entry->reg_index = reg_index;
492 return new_id;
493 }
494 else{
495 vg_error( "cache full (type: %u)!\n", type );
496 return 0;
497 }
498 }
499
500 static void *addon_cache_item( enum workshop_file_type type, u16 id ){
501 if( !id ) return NULL;
502
503 struct addon_cache *cache = &addon_system.cache[type];
504 return cache->items + ((size_t)(id-1) * cache->stride);
505 }
506
507 static void *addon_cache_item_if_loaded( enum workshop_file_type type, u16 id ){
508 if( !id ) return NULL;
509
510 struct addon_cache *cache = &addon_system.cache[type];
511 struct addon_cache_entry *entry = vg_pool_item( &cache->pool, id );
512
513 if( entry->state == k_addon_cache_state_loaded )
514 return addon_cache_item( type, id );
515 else return NULL;
516 }
517
518 #endif /* ADDON_C */