1
2 /***************************************************************************
3 * __ __ _ ___________ *
4 * \ \ / /| |____ ____| *
5 * \ \ / / | | | | *
6 * \ \ /\ / / | | | | *
7 * \ \/ \/ / | | | | *
8 * \ /\ / | | | | *
9 * \/ \/ |_| |_| *
10 * *
11 * Wiimms ISO Tools *
12 * http://wit.wiimm.de/ *
13 * *
14 ***************************************************************************
15 * *
16 * This file is part of the WIT project. *
17 * Visit http://wit.wiimm.de/ for project details and sources. *
18 * *
19 * Copyright (c) 2009-2013 by Dirk Clemens <wiimm@wiimm.de> *
20 * *
21 ***************************************************************************
22 * *
23 * This program is free software; you can redistribute it and/or modify *
24 * it under the terms of the GNU General Public License as published by *
25 * the Free Software Foundation; either version 2 of the License, or *
26 * (at your option) any later version. *
27 * *
28 * This program is distributed in the hope that it will be useful, *
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
31 * GNU General Public License for more details. *
32 * *
33 * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
34 * *
35 ***************************************************************************/
36
37 #define _GNU_SOURCE 1
38
39 #include <sys/types.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <string.h>
43 #include <ctype.h>
44 #include <dirent.h>
45
46 #include "debug.h"
47 #include "lib-std.h"
48 #include "wbfs-interface.h"
49 #include "titles.h"
50 #include "dclib-utf8.h"
51
52 //
53 ///////////////////////////////////////////////////////////////////////////////
54 /////////////// variables ///////////////
55 ///////////////////////////////////////////////////////////////////////////////
56
57 int title_mode = 1;
58
59 StringList_t * first_title_fname = 0;
60 StringList_t ** append_title_fname = &first_title_fname;
61
62 ID_DB_t title_db = {0,0,0}; // title database
63
64 static bool load_default_titles = true;
65 const int tdb_grow_size = 1000;
66
67 //
68 ///////////////////////////////////////////////////////////////////////////////
69 /////////////// titles interface ///////////////
70 ///////////////////////////////////////////////////////////////////////////////
71
AddTitleFile(ccp arg,int unused)72 int AddTitleFile ( ccp arg, int unused )
73 {
74 if ( arg && *arg )
75 {
76 if ( !arg[1] && arg[0] >= '0' && arg[0] <= '9' )
77 {
78 TRACE("#T# set title mode: %d -> %d\n",title_mode,arg[0]-'0');
79 title_mode = arg[0] - '0';
80 }
81 else if ( !arg[1] && arg[0] == '/' )
82 {
83 TRACE("#T# disable default titles\n");
84 load_default_titles = false;
85 while (first_title_fname)
86 {
87 StringList_t * sl = first_title_fname;
88 first_title_fname = sl->next;
89 FREE((char*)sl->str);
90 FREE(sl);
91 }
92 }
93 else
94 {
95 TRACE("#T# append title file: %s\n",arg);
96 StringList_t * sl = CALLOC(1,sizeof(StringList_t));
97 sl->str = STRDUP(arg);
98
99 *append_title_fname = sl;
100 append_title_fname = &sl->next;
101 ASSERT(!sl->next);
102 }
103 }
104 return ERR_OK;
105 }
106
107 ///////////////////////////////////////////////////////////////////////////////
108
LoadTitleFile(ccp fname,bool warn)109 static int LoadTitleFile ( ccp fname, bool warn )
110 {
111 ASSERT( fname && *fname );
112 TRACE("#T# LoadTitleFile(%s)\n",fname);
113
114 const int max_title_size = 200; // not a exact value
115 char buf[PATH_MAX+max_title_size], title_buf[max_title_size+10];
116 buf[0] = 0;
117
118 FILE * f = 0;
119 const bool use_stdin = fname[0] == '-' && !fname[1];
120 if (use_stdin)
121 {
122 f = stdin;
123 TRACE("#T# - use stdin, f=%p\n",f);
124 }
125 else if (strchr(fname,'/'))
126 {
127 #ifdef __CYGWIN__
128 NormalizeFilenameCygwin(buf,sizeof(buf),fname);
129 f = fopen(buf,"r");
130 TRACE("#T# - f=%p: %s\n",f,buf);
131 buf[0] = 0;
132 #else
133 f = fopen(fname,"r");
134 TRACE("#T# - f=%p: %s\n",f,fname);
135 #endif
136 }
137 else
138 {
139 // no path found ==> use search_path[]
140 ccp * sp;
141 for ( sp = search_path; *sp && !f; sp++ )
142 {
143 snprintf(buf,sizeof(buf),"%s%s",*sp,fname);
144 f = fopen(buf,"r");
145 TRACE("#T# - f=%p: %s\n",f,buf);
146 }
147 }
148
149 if (!f)
150 {
151 if ( warn || verbose > 3 )
152 ERROR0(ERR_WARNING,"Title file not found: %s\n",fname);
153 return ERR_READ_FAILED;
154 }
155
156 if ( verbose > 3 )
157 printf("SCAN TITLE FILE %s\n", *buf ? buf : fname );
158
159 while (fgets(buf,sizeof(buf),f))
160 {
161 ccp ptr = buf;
162 noTRACE("LINE: %s\n",ptr);
163
164 // skip blanks
165 while ( *ptr > 0 && *ptr <= ' ' )
166 ptr++;
167
168 const int idtype = CountIDChars(ptr,false,false);
169 if ( idtype != 4 && idtype != 6 )
170 continue;
171
172 char * id = (char*)ptr;
173 ptr += idtype;
174
175 // skip blanks and find '='
176 while ( *ptr > 0 && *ptr <= ' ' )
177 ptr++;
178 if ( *ptr != '=' )
179 continue;
180 ptr++;
181
182 // title found, skip blanks
183 id[idtype] = 0;
184 while ( *ptr > 0 && *ptr <= ' ' )
185 ptr++;
186
187 char *dest = title_buf;
188 char *dend = dest + sizeof(title_buf) - 6; // enough for SPACE + UTF8 + NULL
189
190 bool have_blank = false;
191 while ( dest < dend && *ptr )
192 {
193 ulong ch = ScanUTF8AnsiChar(&ptr);
194 if ( ch <= ' ' )
195 have_blank = true;
196 else
197 {
198 // real char found
199 if (have_blank)
200 {
201 have_blank = false;
202 *dest++ = ' ';
203 }
204
205 if ( ch >= 0x100 )
206 {
207 const dcUnicodeTripel * trip = DecomposeUnicode(ch);
208 if (trip)
209 ch = trip->code2;
210 }
211
212 if (use_utf8)
213 dest = PrintUTF8Char(dest,ch);
214 else
215 *dest++ = ch < 0xff ? ch : '?';
216 }
217 }
218 *dest = 0;
219 if (*title_buf)
220 InsertID(&title_db,id,title_buf);
221 }
222
223 fclose(f);
224 return ERR_OK;
225 }
226
227 ///////////////////////////////////////////////////////////////////////////////
228
InitializeTDB()229 void InitializeTDB()
230 {
231 static bool tdb_initialized = false;
232 if (!tdb_initialized)
233 {
234 tdb_initialized = true;
235
236 title_db.list = 0;
237 title_db.used = 0;
238 title_db.size = 0;
239
240 if (load_default_titles)
241 {
242 LoadTitleFile("titles.txt",false);
243
244 if (lang_info)
245 {
246 char lang[100];
247 snprintf(lang,sizeof(lang),"titles-%s.txt",lang_info);
248 LoadTitleFile(lang,false);
249 }
250
251 LoadTitleFile("titles.local.txt",false);
252 }
253
254 while (first_title_fname)
255 {
256 StringList_t * sl = first_title_fname;
257 LoadTitleFile(sl->str,true);
258 first_title_fname = sl->next;
259 FREE((char*)sl->str);
260 FREE(sl);
261 }
262
263 #ifdef xxDEBUG
264 TRACE("Title DB with %d titles:\n",title_db.used);
265 DumpIDDB(&title_db,TRACE_FILE);
266 #endif
267 }
268 }
269
270 ///////////////////////////////////////////////////////////////////////////////
271
GetTitle(ccp id6,ccp default_if_failed)272 ccp GetTitle ( ccp id6, ccp default_if_failed )
273 {
274 if ( !title_mode || !id6 || !*id6 )
275 return default_if_failed;
276
277 InitializeTDB();
278 TDBfind_t stat;
279 int idx = FindID(&title_db,id6,&stat,0);
280 TRACE("#T# GetTitle(%s) tm=%d idx=%d/%d/%d stat=%d -> %s %s\n",
281 id6, title_mode, idx, title_db.used, title_db.size, stat,
282 idx < title_db.used ? title_db.list[idx]->id : "",
283 idx < title_db.used ? title_db.list[idx]->title : "" );
284 ASSERT( stat == IDB_NOT_FOUND || idx < title_db.used );
285 return stat == IDB_NOT_FOUND
286 ? default_if_failed
287 : title_db.list[idx]->title;
288 }
289
290 //
291 ///////////////////////////////////////////////////////////////////////////////
292 /////////////// scan id helpers ///////////////
293 ///////////////////////////////////////////////////////////////////////////////
294
ScanArgID(char buf[7],ccp arg,bool trim_end)295 static ccp ScanArgID
296 (
297 char buf[7], // result buffer for ID6: 6 chars + NULL
298 // On error 'buf7' is filled with NULL
299 ccp arg, // argument to scan. Comma is a separator
300 bool trim_end // true: remove trailing '.'
301 )
302 {
303 if (!arg)
304 {
305 memset(buf,0,6);
306 return 0;
307 }
308
309 while ( *arg > 0 && *arg <= ' ' )
310 arg++;
311
312 ccp start = arg;
313 int err = 0, wildcards = 0;
314 while ( *arg > ' ' && *arg != ',' && *arg != '=' )
315 {
316 int ch = *arg++;
317 if ( ch == '+' || ch == '*' )
318 wildcards++;
319 else if (!isalnum(ch) && !strchr("_.",ch))
320 err++;
321 }
322 const int arglen = arg - start;
323 if ( err || wildcards > 1 || !arglen || arglen > 6 )
324 {
325 memset(buf,0,6);
326 return start;
327 }
328
329 char * dest = buf;
330 for ( ; start < arg; start++ )
331 {
332 if ( *start == '+' || *start == '*' )
333 {
334 int count = 7 - arglen;
335 while ( count-- > 0 )
336 *dest++ = '.';
337 }
338 else
339 *dest++ = toupper((int)*start);
340 DASSERT( dest <= buf + 6 );
341 }
342
343 if (trim_end)
344 while ( dest[-1] == '.' )
345 dest--;
346 else
347 while ( dest < buf+6 )
348 *dest++ = '.';
349 *dest = 0;
350
351 while ( *arg > 0 && *arg <= ' ' || *arg == ',' )
352 arg++;
353 return arg;
354 }
355
356 ///////////////////////////////////////////////////////////////////////////////
357
ScanPatID(StringField_t * sf_id6,StringField_t * sf_pat,ccp arg,bool trim_end,bool allow_arg)358 static ccp ScanPatID // return NULL if ok or a pointer to the invalid text
359 (
360 StringField_t * sf_id6, // valid pointer: add real ID6
361 StringField_t * sf_pat, // valid pointer: add IDs with pattern '.'
362 ccp arg, // argument to scan. Comma is a separator
363 bool trim_end, // true: remove trailing '.'
364 bool allow_arg // true: allow and store '=arg'
365 )
366 {
367 DASSERT(sf_id6);
368 DASSERT(sf_pat);
369
370 char buf[7];
371 while ( arg && *arg )
372 {
373 arg = ScanArgID(buf,arg,trim_end);
374 TRACE(" -> |%s|\n",arg);
375 if (!*buf)
376 return arg;
377
378 ccp eq_arg = 0;
379 if ( arg && *arg == '=' )
380 {
381 if (!allow_arg)
382 return arg;
383 eq_arg = ++arg;
384 arg = 0;
385 }
386
387 if ( sf_id6 != sf_pat && strchr(buf,'.') )
388 InsertStringID6(sf_pat,buf,SEL_UNUSED,eq_arg);
389 else
390 InsertStringID6(sf_id6,buf,SEL_UNUSED,eq_arg);
391 }
392 return 0;
393 }
394
395 ///////////////////////////////////////////////////////////////////////////////
396
AddId(StringField_t * sf_id6,StringField_t * sf_pat,ccp arg,int select_mode)397 static enumError AddId
398 (
399 StringField_t * sf_id6,
400 StringField_t * sf_pat,
401 ccp arg,
402 int select_mode
403 )
404 {
405 DASSERT(sf_id6);
406 DASSERT(sf_pat);
407
408
409 if ( select_mode & SEL_F_FILE )
410 {
411 char id[7];
412 ccp end = ScanArgID(id,arg,false);
413 TRACE("->|%s|\n",end);
414 if ( *id && ( !end || !*end ) )
415 {
416 if (strchr(id,'.'))
417 {
418 TRACE("ADD PAT/FILE: %s\n",id);
419 InsertStringID6(sf_pat,id,SEL_UNUSED,0);
420 }
421 else
422 {
423 TRACE("ADD ID6/FILE: %s\n",id);
424 InsertStringID6(sf_id6,id,SEL_UNUSED,0);
425 }
426 }
427 else
428 {
429 int idlen;
430 ScanID(id,&idlen,arg);
431 if ( idlen == 4 || idlen == 6 )
432 {
433 TRACE("ADD ID/FILE: %s\n",id);
434 InsertStringID6(sf_id6,id,SEL_UNUSED,0);
435 }
436 }
437 }
438 else
439 {
440 TRACE("ADD PAT/PARAM: %s\n",arg);
441 ccp res = ScanPatID(sf_id6,sf_pat,arg,false,
442 (select_mode & SEL_F_PARAM) != 0 );
443 if (res)
444 return ERROR0(ERR_SYNTAX,"Not a ID: %s\n",res);
445 }
446 return ERR_OK;
447 }
448
449 ///////////////////////////////////////////////////////////////////////////////
450
FindPatID(StringField_t * sf_id6,StringField_t * sf_pat,ccp id6,bool mark_matching)451 static IdItem_t * FindPatID
452 (
453 StringField_t * sf_id6, // valid pointer: search real ID6
454 StringField_t * sf_pat, // valid pointer: search IDs with pattern '.'
455 ccp id6, // valid id6
456 bool mark_matching // true: mark *all* matching records
457 )
458 {
459 if (!id6)
460 return 0;
461
462 IdItem_t * found = 0;
463 if (sf_id6)
464 {
465 found = (IdItem_t*)FindStringField(sf_id6,id6);
466 if (found)
467 {
468 if ( found->flag == 2 )
469 return found;
470
471 found->flag = SEL_USED;
472 if (!mark_matching)
473 return found;
474 }
475 }
476
477 if (sf_pat)
478 {
479 IdItem_t **ptr = (IdItem_t**)sf_pat->field, **end;
480 for ( end = ptr + sf_pat->used; ptr < end; ptr++ )
481 {
482 ccp p1 = ptr[0]->id6;
483 ccp p2 = id6;
484 while ( *p1 && *p2 && ( *p1 == '.' || *p2 == '.' || *p1 == *p2 ))
485 p1++, p2++;
486 if ( !*p1 && !*p2 )
487 {
488 (*ptr)->flag = SEL_USED;
489 if (!mark_matching)
490 return *ptr;
491 if (!found)
492 found = *ptr;
493 }
494 }
495 }
496
497 return found;
498 }
499
500 //
501 ///////////////////////////////////////////////////////////////////////////////
502 /////////////// select id interface ///////////////
503 ///////////////////////////////////////////////////////////////////////////////
504
505 static bool include_db_enabled = false;
506
507 static StringField_t include_id6 = {0,0,0}; // include id6 (without wildcard '.')
508 static StringField_t include_pat = {0,0,0}; // include pattern (with wildcard '.')
509 static StringField_t exclude_id6 = {0,0,0}; // exclude id6 (without wildcard '.')
510 static StringField_t exclude_pat = {0,0,0}; // exclude pattern (with wildcard '.')
511
512 static StringField_t include_fname = {0,0,0}; // include filenames
513 static StringField_t exclude_fname = {0,0,0}; // exclude filenames
514
515 int disable_exclude_db = 0; // disable exclude db at all if > 0
516 bool include_first = false; // use include rules before exclude
517
518 ///////////////////////////////////////////////////////////////////////////////
519 ///////////////////////////////////////////////////////////////////////////////
520
AddIncludeID(ccp arg,int select_mode)521 int AddIncludeID ( ccp arg, int select_mode )
522 {
523 include_db_enabled = true;
524 return AddId(&include_id6,&include_pat,arg,select_mode);
525 }
526
527 ///////////////////////////////////////////////////////////////////////////////
528
AddIncludePath(ccp arg,int unused)529 int AddIncludePath ( ccp arg, int unused )
530 {
531 char buf[PATH_MAX];
532 if (realpath(arg,buf))
533 arg = buf;
534
535 InsertStringField(&include_fname,arg,false);
536
537 include_db_enabled = true;
538 return 0;
539 }
540
541 ///////////////////////////////////////////////////////////////////////////////
542 ///////////////////////////////////////////////////////////////////////////////
543
AddExcludeID(ccp arg,int select_mode)544 int AddExcludeID ( ccp arg, int select_mode )
545 {
546 return AddId(&exclude_id6,&exclude_pat,arg,select_mode);
547 }
548
549 ///////////////////////////////////////////////////////////////////////////////
550
AddExcludePath(ccp arg,int unused)551 int AddExcludePath ( ccp arg, int unused )
552 {
553 noPRINT("AddExcludePath(%s,%d)\n",arg,unused);
554 char buf[PATH_MAX];
555 if (realpath(arg,buf))
556 arg = buf;
557
558 InsertStringField(&exclude_fname,arg,false);
559 return 0;
560 }
561
562 ///////////////////////////////////////////////////////////////////////////////
563 ///////////////////////////////////////////////////////////////////////////////
564
CheckExcludePath(ccp path,StringField_t * sf,StringField_t * sf_id6,int max_dir_depth)565 static void CheckExcludePath
566 ( ccp path, StringField_t * sf, StringField_t * sf_id6, int max_dir_depth )
567 {
568 TRACE("CheckExcludePath(%s,%p,%d)\n",path,sf,max_dir_depth);
569 DASSERT(sf);
570 DASSERT(sf_id6);
571
572 File_t f;
573 InitializeFile(&f);
574 if (OpenFile(&f,path,IOM_NO_STREAM))
575 return;
576
577 AnalyzeFT(&f);
578 ClearFile(&f,false);
579
580 if ( *f.id6_src )
581 {
582 TRACE(" - exclude id %s\n",f.id6_src);
583 InsertStringID6(sf_id6,f.id6_src,SEL_UNUSED,0);
584 }
585 else if ( max_dir_depth > 0 && f.ftype == FT_ID_DIR )
586 {
587 char real_path[PATH_MAX];
588 if (realpath(path,real_path))
589 path = real_path;
590 if (InsertStringField(sf,path,false))
591 {
592 TRACE(" - exclude dir %s\n",path);
593 DIR * dir = opendir(path);
594 if (dir)
595 {
596 char buf[PATH_MAX], *bufend = buf+sizeof(buf);
597 char * dest = StringCopyE(buf,bufend-1,path);
598 if ( dest > buf && dest[-1] != '/' )
599 *dest++ = '/';
600
601 max_dir_depth--;
602
603 for(;;)
604 {
605 struct dirent * dent = readdir(dir);
606 if (!dent)
607 break;
608 ccp n = dent->d_name;
609 if ( n[0] != '.' )
610 {
611 StringCopyE(dest,bufend,dent->d_name);
612 CheckExcludePath(buf,sf,sf_id6,max_dir_depth);
613 }
614 }
615 closedir(dir);
616 }
617 }
618 }
619 }
620
621 ///////////////////////////////////////////////////////////////////////////////
622
SetupExcludeDB()623 void SetupExcludeDB()
624 {
625 TRACE("SetupExcludeDB()");
626
627 if (include_fname.used)
628 {
629 TRACELINE;
630 StringField_t sf;
631 InitializeStringField(&sf);
632 ccp * ptr = include_fname.field + include_fname.used;
633 while ( ptr-- > include_fname.field )
634 CheckExcludePath(*ptr,&sf,&include_id6,opt_recurse_depth);
635 ResetStringField(&sf);
636 ResetStringField(&include_fname);
637 }
638
639 if (exclude_fname.used)
640 {
641 TRACELINE;
642 StringField_t sf;
643 InitializeStringField(&sf);
644 ccp * ptr = exclude_fname.field + exclude_fname.used;
645 while ( ptr-- > exclude_fname.field )
646 CheckExcludePath(*ptr,&sf,&exclude_id6,opt_recurse_depth);
647 ResetStringField(&sf);
648 ResetStringField(&exclude_fname);
649 }
650 }
651
652 ///////////////////////////////////////////////////////////////////////////////
653
DefineExcludePath(ccp path,int max_dir_depth)654 void DefineExcludePath ( ccp path, int max_dir_depth )
655 {
656 TRACE("DefineExcludePath(%s,%d)\n",path,max_dir_depth);
657
658 if (exclude_fname.used)
659 SetupExcludeDB();
660
661 StringField_t sf;
662 InitializeStringField(&sf);
663 CheckExcludePath(path,&sf,&exclude_id6,max_dir_depth);
664 ResetStringField(&sf);
665 }
666
667 ///////////////////////////////////////////////////////////////////////////////
668 ///////////////////////////////////////////////////////////////////////////////
669
IsExcludeActive()670 bool IsExcludeActive()
671 {
672 if ( exclude_fname.used || include_fname.used )
673 SetupExcludeDB();
674
675 return include_id6.used
676 || include_pat.used
677 || exclude_id6.used
678 || exclude_pat.used;
679 }
680
681 ///////////////////////////////////////////////////////////////////////////////
682 ///////////////////////////////////////////////////////////////////////////////
683
IsExcluded(ccp id6)684 bool IsExcluded ( ccp id6 )
685 {
686 noTRACE("IsExcluded(%s) dis=%d ena=%d, n=%d+%d\n",
687 id6, disable_exclude_db, include_db_enabled,
688 exclude_fname.used, include_fname.used );
689
690 if ( disable_exclude_db > 0 )
691 return false;
692
693 if ( exclude_fname.used || include_fname.used )
694 SetupExcludeDB();
695
696 if ( include_first
697 && include_db_enabled
698 && FindPatID(&include_id6,&include_pat,id6,false) )
699 return false;
700
701 if (FindPatID(&exclude_id6,&exclude_pat,id6,false))
702 return true;
703
704 return !include_first
705 && include_db_enabled
706 && !FindPatID(&include_id6,&include_pat,id6,false);
707 }
708
709 ///////////////////////////////////////////////////////////////////////////////
710
DumpExcludeDB()711 void DumpExcludeDB()
712 {
713 SetupExcludeDB();
714 noPRINT("DumpExcludeDB() n=%d+%d\n",exclude_pat.used,exclude_pat.used);
715
716 ccp *ptr = exclude_id6.field, *end;
717 for ( end = ptr + exclude_id6.used; ptr < end; ptr++ )
718 printf("%s\n",*ptr);
719
720 ptr = exclude_pat.field;
721 for ( end = ptr + exclude_pat.used; ptr < end; ptr++ )
722 printf("%s\n",*ptr);
723 }
724
725 //
726 ///////////////////////////////////////////////////////////////////////////////
727 /////////////// param id6 interface ///////////////
728 ///////////////////////////////////////////////////////////////////////////////
729
730 StringField_t param_id6 = {0,0,0}; // param id6 (without wildcard '.')
731 StringField_t param_pat = {0,0,0}; // param pattern (with wildcard '.')
732
733 ///////////////////////////////////////////////////////////////////////////////
734
ClearParamDB()735 void ClearParamDB()
736 {
737 ResetStringField(¶m_id6);
738 ResetStringField(¶m_pat);
739 }
740
741 ///////////////////////////////////////////////////////////////////////////////
742
AddParamID(ccp arg,int select_mode)743 int AddParamID ( ccp arg, int select_mode )
744 {
745 return AddId(¶m_id6,¶m_pat,arg,select_mode);
746 }
747
748 ///////////////////////////////////////////////////////////////////////////////
749
CountParamID()750 int CountParamID()
751 {
752 return param_id6.used + param_pat.used;
753 }
754
755 ///////////////////////////////////////////////////////////////////////////////
756
FindParamID(ccp id6)757 IdItem_t * FindParamID ( ccp id6 )
758 {
759 return FindPatID(¶m_id6,¶m_pat,id6,true);
760 }
761
762 ///////////////////////////////////////////////////////////////////////////////
763
DumpParamDB(enumSelectUsed mask,bool warn)764 int DumpParamDB ( enumSelectUsed mask, bool warn )
765 {
766 int count_id6 = 0, count_pat = 0;
767
768 IdItem_t **ptr = (IdItem_t**)param_id6.field, **end;
769 for ( end = ptr + param_id6.used; ptr < end; ptr++ )
770 {
771 IdItem_t * item = *ptr;
772 DASSERT(item);
773 if ( item->flag & mask )
774 {
775 count_id6++;
776 if (warn)
777 ERROR0(ERR_WARNING,"Disc with ID6 [%s] not found.\n",item->id6);
778 else
779 printf("%s%s%s\n", item->id6, *item->arg ? "=" : "", item->arg );
780 }
781 }
782
783 ptr = (IdItem_t**)param_pat.field;
784 for ( end = ptr + param_pat.used; ptr < end; ptr++ )
785 {
786 IdItem_t * item = *ptr;
787 DASSERT(item);
788 if ( item->flag & mask )
789 {
790 count_pat++;
791 if (warn)
792 ERROR0(ERR_WARNING,"Disc with pattern [%s] not found.\n",item->id6);
793 else
794 printf("%s%s%s\n", item->id6, *item->arg ? "=" : "", item->arg );
795 }
796 }
797
798 if ( warn && count_id6 + count_pat > 1 )
799 {
800 if ( count_id6 && count_pat )
801 ERROR0(ERR_WARNING,"==> %u disc ID%s and %u pattern not found!\n",
802 count_id6, count_id6 == 1 ? "" : "s", count_pat );
803 else if ( count_id6 )
804 ERROR0(ERR_WARNING,"==> %u disc ID%s not found!\n",
805 count_id6, count_id6 == 1 ? "" : "s" );
806 else if ( count_pat )
807 ERROR0(ERR_WARNING,"==> %u disc pattern not found!\n",
808 count_pat );
809 if ( count_id6 || count_pat )
810 putchar('\n');
811 }
812
813 return count_id6 + count_pat;
814 }
815
816 ///////////////////////////////////////////////////////////////////////////////
817
SetupParamDB(ccp default_param,bool warn,bool allow_arg)818 int SetupParamDB
819 (
820 // return the number of valid parameters or NULL on error
821
822 ccp default_param, // not NULL: use this if no param is defined
823 bool warn, // print a warning if no param is defined
824 bool allow_arg // allow arguments '=arg'
825 )
826 {
827 if ( default_param && !n_param )
828 AddParam(default_param,false);
829
830 enumSelectID id1 = SEL_ID;
831 enumSelectID id2 = SEL_FILE;
832 if ( allow_arg )
833 {
834 id1 |= SEL_F_PARAM;
835 id2 |= SEL_F_PARAM;
836 }
837
838 ClearParamDB();
839 ParamList_t * param;
840 for ( param = first_param; param; param = param->next )
841 AtFileHelper(param->arg,id1,id2,AddParamID);
842
843 const int count = CountParamID();
844 if ( !count && warn )
845 ERROR0(ERR_MISSING_PARAM,"Missing parameters (list of IDs)!\n");
846
847 return count;
848 }
849
850 ///////////////////////////////////////////////////////////////////////////////
851
CheckParamSlot(struct WBFS_t * wbfs,int slot,bool open_disc,ccp * ret_id6,ccp * ret_title)852 IdItem_t * CheckParamSlot
853 (
854 // return NULL if no disc at slot found or disc not match or disabled
855 // or a pointer to the ID6
856
857 struct WBFS_t * wbfs, // valid and opened WBFS
858 int slot, // valid slot index
859 bool open_disc, // true: open the disc
860 ccp * ret_id6, // not NULL: store pointer to ID6 of disc
861 // The ID6 is valid until the WBFS is closed
862 ccp * ret_title // not NULL: store pointer to title of disc
863 // The title is searched in the title db first
864 // The title may be stored in a static buffer
865 )
866 {
867 DASSERT(wbfs);
868 DASSERT(wbfs->wbfs);
869 DASSERT( slot >= 0 && slot < wbfs->wbfs->max_disc );
870
871 CloseWDisc(wbfs);
872
873 if (ret_title)
874 *ret_title = 0;
875 if (ret_id6)
876 *ret_id6 = 0;
877
878 ccp id6 = wbfs_load_id_list(wbfs->wbfs,false)[slot];
879 if (!*id6)
880 return 0;
881
882 IdItem_t * item = FindParamID(id6);
883 if ( !item || IsExcluded(id6) )
884 return 0;
885
886 if (open_disc)
887 OpenWDiscID6(wbfs,id6);
888
889 if (ret_title)
890 {
891 static char disc_title[WII_TITLE_SIZE+1];
892 ccp title = GetTitle(id6,0);
893 if (!title)
894 {
895 if ( wbfs->disc || !OpenWDiscID6(wbfs,id6) )
896 {
897 wd_header_t *dh = GetWDiscHeader(wbfs);
898 if (dh)
899 {
900 StringCopyS(disc_title,sizeof(disc_title),(ccp)dh->disc_title);
901 title = disc_title;
902 }
903 if (!open_disc)
904 CloseWDisc(wbfs);
905 }
906 }
907 *ret_title = title ? title : "?";
908 }
909
910 TRACE("CheckParamSlot(,%u,%d,%u) => disc=%p, %s, %s\n",
911 slot, open_disc, ret_title!=0,
912 wbfs->disc, id6, ret_title ? *ret_title : "-" );
913
914 if (ret_id6)
915 *ret_id6 = id6;
916 return item;
917 }
918
919 //
920 ///////////////////////////////////////////////////////////////////////////////
921 /////////////// low level title id interface ///////////////
922 ///////////////////////////////////////////////////////////////////////////////
923
924 // disable extended tracing
925
926 #undef xTRACE
927
928 #if 0
929 #define xTRACE TRACE
930 #else
931 #define xTRACE noTRACE
932 #endif
933
934 ///////////////////////////////////////////////////////////////////////////////
935
FindID(ID_DB_t * db,ccp id,TDBfind_t * p_stat,int * p_num)936 int FindID ( ID_DB_t * db, ccp id, TDBfind_t * p_stat, int * p_num )
937 {
938 ASSERT(db);
939 if ( !db || !db->used )
940 {
941 if (p_stat)
942 *p_stat = IDB_NOT_FOUND;
943 if (p_num)
944 *p_num = 0;
945 return 0;
946 }
947
948 ID_t * elem = 0;
949 size_t id_len = strlen(id);
950 if ( id_len > sizeof(elem->id)-1 )
951 id_len = sizeof(elem->id)-1;
952
953 int beg = 0, end = db->used-1;
954 while ( beg <= end )
955 {
956 int idx = (beg+end)/2;
957 elem = db->list[idx];
958 const int cmp_stat = memcmp(id,elem->id,id_len);
959 noTRACE(" - check: %d..%d..%d: %d = %s\n",beg,idx,end,cmp_stat,elem->id);
960 if ( cmp_stat < 0 )
961 end = idx-1;
962 else if ( cmp_stat > 0 )
963 beg = idx + 1;
964 else
965 {
966 beg = idx;
967 break;
968 }
969 }
970 ASSERT( beg >= 0 && beg <= db->used );
971 elem = db->list[beg];
972
973 TDBfind_t stat = IDB_NOT_FOUND;
974 if ( beg < db->used )
975 {
976 if (!memcmp(id,elem->id,id_len))
977 stat = elem->id[id_len] ? IDB_EXTENSION_FOUND : IDB_ID_FOUND;
978 else if (!memcmp(id,elem->id,strlen(elem->id)))
979 stat = IDB_ABBREV_FOUND;
980 else if ( beg > 0 )
981 {
982 elem = db->list[beg-1];
983 xTRACE("cmp-1[%s,%s] -> %d\n",id,elem->id,memcmp(id,elem->id,strlen(elem->id)));
984 if (!memcmp(id,elem->id,strlen(elem->id)))
985 {
986 stat = IDB_ABBREV_FOUND;
987 beg--;
988 }
989 }
990 xTRACE("cmp[%s,%s] %d %d -> %d\n", id, elem->id,
991 memcmp(id,elem->id,id_len), memcmp(id,elem->id,strlen(elem->id)), stat );
992 }
993 xTRACE("#T# FindID(%p,%s,%p,%p) idx=%d/%d/%d, stat=%d, found=%s\n",
994 db, id, p_stat, p_num, beg, db->used, db->size,
995 stat, beg < db->used && elem ? elem->id : "-" );
996
997 if (p_stat)
998 *p_stat = stat;
999
1000 if (p_num)
1001 {
1002 int idx = beg;
1003 while ( idx < db->used && !memcmp(id,db->list[idx],id_len) )
1004 idx++;
1005 xTRACE(" - num = %d\n",idx-beg);
1006 *p_num = idx - beg; }
1007
1008 return beg;
1009 }
1010
1011 ///////////////////////////////////////////////////////////////////////////////
1012
InsertID(ID_DB_t * db,ccp id,ccp title)1013 int InsertID ( ID_DB_t * db, ccp id, ccp title )
1014 {
1015 ASSERT(db);
1016 xTRACE("-----\n");
1017
1018 // remove all previous definitions first
1019 int idx = RemoveID(db,id,true);
1020
1021 if ( db->used == db->size )
1022 {
1023 db->size += tdb_grow_size;
1024 db->list = (ID_t**)REALLOC(db->list,db->size*sizeof(*db->list));
1025 }
1026 ASSERT( db->list );
1027 ASSERT( idx >= 0 && idx <= db->used );
1028
1029 ID_t ** elem = db->list + idx;
1030 xTRACE(" - insert: %s|%s|%s\n",
1031 idx>0 ? elem[-1]->id : "-", id, idx<db->used ? elem[0]->id : "-");
1032 memmove( elem+1, elem, (db->used-idx)*sizeof(*elem) );
1033 db->used++;
1034 ASSERT( db->used <= db->size );
1035
1036 int tlen = title ? strlen(title) : 0;
1037 ID_t * t = *elem = (ID_t*)MALLOC(sizeof(ID_t)+tlen);
1038
1039 StringCopyS(t->id,sizeof(t->id),id);
1040 StringCopyS(t->title,tlen+1,title);
1041
1042 xTRACE("#T# InsertID(%p,%s,) [%d], id=%s title=%s\n",
1043 db, id, idx, t->id, t->title );
1044
1045 return 0;
1046 }
1047
1048 ///////////////////////////////////////////////////////////////////////////////
1049
RemoveID(ID_DB_t * db,ccp id,bool remove_extended)1050 int RemoveID ( ID_DB_t * db, ccp id, bool remove_extended )
1051 {
1052 ASSERT(db);
1053
1054 TDBfind_t stat;
1055 int count;
1056 int idx = FindID(db,id,&stat,&count);
1057
1058 switch(stat)
1059 {
1060 case IDB_NOT_FOUND:
1061 break;
1062
1063 case IDB_ABBREV_FOUND:
1064 idx++;
1065 break;
1066
1067 case IDB_ID_FOUND:
1068 case IDB_EXTENSION_FOUND:
1069 xTRACE("#T# RemoveID(%p,%s,%d) idx=%d, count=%d\n",
1070 db, id, remove_extended, idx, count );
1071 ASSERT(count>0);
1072 ASSERT(db->used>=count);
1073 ID_t ** elem = db->list + idx;
1074 int c;
1075 for ( c = count; c > 0; c--, elem++ )
1076 {
1077 xTRACE(" - remove %s = %s\n",(*elem)->id,(*elem)->title);
1078 FREE(*elem);
1079 }
1080
1081 db->used -= count;
1082 elem = db->list + idx;
1083 memmove( elem, elem+count, (db->used-idx)*sizeof(*elem) );
1084 break;
1085
1086 default:
1087 ASSERT(0);
1088 }
1089
1090 return idx;
1091 }
1092
1093 ///////////////////////////////////////////////////////////////////////////////
1094
DumpIDDB(ID_DB_t * db,FILE * f)1095 void DumpIDDB ( ID_DB_t * db, FILE * f )
1096 {
1097 if ( !db || !db->list || !f )
1098 return;
1099
1100 ID_t ** list = db->list, **end;
1101 for ( end = list + db->used; list < end; list++ )
1102 {
1103 ID_t * elem = *list;
1104 if (*elem->title)
1105 fprintf(f,"%-6s = %s\n",elem->id,elem->title);
1106 else
1107 fprintf(f,"%-6s\n",elem->id);
1108 }
1109 }
1110
1111 //
1112 ///////////////////////////////////////////////////////////////////////////////
1113 /////////////// system menu interface ///////////////
1114 ///////////////////////////////////////////////////////////////////////////////
1115
1116 static bool system_menu_loaded = false;
1117 static StringField_t system_menu = {0};
1118
1119 ///////////////////////////////////////////////////////////////////////////////
1120
LoadSystemMenuTab()1121 static void LoadSystemMenuTab()
1122 {
1123 if (!system_menu_loaded)
1124 {
1125 system_menu_loaded = true;
1126 ResetStringField(&system_menu);
1127
1128 char buf[PATH_MAX+100];
1129 FILE *f = 0;
1130
1131 ccp * sp;
1132 for ( sp = search_path; *sp && !f; sp++ )
1133 {
1134 snprintf(buf,sizeof(buf),"%s%s",*sp,"system-menu.txt");
1135 f = fopen(buf,"r");
1136 }
1137
1138 if (f)
1139 {
1140 TRACE("SYS-MENU: f=%p: %s\n",f,buf);
1141
1142 while (fgets(buf,sizeof(buf),f))
1143 {
1144 char * ptr;
1145 u32 vers = strtoul(buf,&ptr,10);
1146 if ( !vers || ptr == buf )
1147 continue;
1148
1149 while ( *ptr > 0 && *ptr <= ' ' )
1150 ptr++;
1151 if ( *ptr != '=' )
1152 continue;
1153 ptr++;
1154 while ( *ptr > 0 && *ptr <= ' ' )
1155 ptr++;
1156 ccp text = ptr;
1157 while (*ptr)
1158 ptr++;
1159 ptr--;
1160 while ( *ptr > 0 && *ptr <= ' ' )
1161 ptr--;
1162 *++ptr = 0;
1163
1164 const int len = snprintf(buf,sizeof(buf),"%u%c%s",vers,0,text) + 1;
1165 noPRINT("SYS-MENU: %6u = '%s' [%u]\n",vers,buf+strlen(buf)+1,len);
1166
1167 char * dest = MALLOC(len);
1168 memcpy(dest,buf,len);
1169 InsertStringField(&system_menu,dest,true);
1170 }
1171
1172 fclose(f);
1173 }
1174 }
1175 }
1176
1177 ///////////////////////////////////////////////////////////////////////////////
1178
GetSystemMenu(u32 version,ccp return_if_not_found)1179 ccp GetSystemMenu
1180 (
1181 u32 version, // system version number
1182 ccp return_if_not_found // return value if not found
1183 )
1184 {
1185 LoadSystemMenuTab();
1186
1187 char key[20];
1188 snprintf(key,sizeof(key),"%u",version);
1189 ccp res = FindStringField(&system_menu,key);
1190 return res ? res + strlen(res) + 1 : return_if_not_found;
1191 }
1192
1193 //
1194 ///////////////////////////////////////////////////////////////////////////////
1195 /////////////// END ///////////////
1196 ///////////////////////////////////////////////////////////////////////////////
1197
1198