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(&param_id6);
738     ResetStringField(&param_pat);
739 }
740 
741 ///////////////////////////////////////////////////////////////////////////////
742 
AddParamID(ccp arg,int select_mode)743 int AddParamID ( ccp arg, int select_mode )
744 {
745     return AddId(&param_id6,&param_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(&param_id6,&param_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