mirror of
https://github.com/nitraiolo/CfgUSBLoader.git
synced 2024-11-27 13:44:17 +01:00
1217 lines
29 KiB
C
1217 lines
29 KiB
C
/*
|
||
Load game information from XML - Lustar
|
||
*/
|
||
#include <zlib.h>
|
||
#include <malloc.h>
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <unistd.h>
|
||
#include "util.h"
|
||
#include "cfg.h"
|
||
#include "xml.h"
|
||
#include "wbfs.h"
|
||
#include "menu.h"
|
||
#include "mem.h"
|
||
#include "wpad.h"
|
||
#include "gettext.h"
|
||
#include "debug.h"
|
||
#include "sort.h"
|
||
|
||
#include "unzip/unzip.h"
|
||
#include "unzip/miniunz.h"
|
||
|
||
typedef struct xmlIndex
|
||
{
|
||
char *start;
|
||
char *attr;
|
||
char *val; // [-1]: '>' => '\0'
|
||
char *vend; // '<' => '\0'
|
||
char *end; // '>' => '\0'
|
||
} xmlIndex;
|
||
|
||
struct xmlTag
|
||
{
|
||
char name[16];
|
||
int len;
|
||
int opt;
|
||
};
|
||
|
||
enum TAG_I
|
||
{
|
||
TAG_ID,
|
||
//TAG_TYPE,
|
||
TAG_REGION,
|
||
//TAG_LANGUAGES,
|
||
TAG_LOCALE,
|
||
TAG_DEVELOPER,
|
||
TAG_PUBLISHER,
|
||
TAG_DATE,
|
||
TAG_GENRE,
|
||
TAG_RATING,
|
||
TAG_WIFI,
|
||
TAG_INPUT,
|
||
//TAG_ROM,
|
||
TAG_CASE,
|
||
TAG_MAX
|
||
};
|
||
|
||
struct xmlTag tags[] =
|
||
{
|
||
{ "id" },
|
||
//{ "type" },
|
||
{ "region" },
|
||
//{ "languages" },
|
||
{ "locale" },
|
||
{ "developer" },
|
||
{ "publisher" },
|
||
{ "date" },
|
||
{ "genre" },
|
||
{ "rating" },
|
||
{ "wi-fi" },
|
||
{ "input" },
|
||
//{ "rom" },
|
||
{ "case" }
|
||
};
|
||
|
||
struct xmlIndex idx[TAG_MAX];
|
||
|
||
/* config */
|
||
char xmlCfgLang[3];
|
||
char xmlcfg_filename[100];
|
||
char *xmlData;
|
||
static struct gameXMLinfo gameinfo;
|
||
|
||
struct gameXMLinfo **game_info;
|
||
int xmlgameCnt;
|
||
int array_size = 0;
|
||
|
||
obj_stack obs_game_info;
|
||
HashTable hash_game_info;
|
||
|
||
struct gameXMLinfo *get_game_info(int i)
|
||
{
|
||
return game_info[i];
|
||
}
|
||
|
||
struct gameXMLinfo *get_game_info_id(u8 *gameid)
|
||
{
|
||
int i = getIndexFromId(gameid);
|
||
if (i >= 0 && i < xmlgameCnt) return game_info[i];
|
||
return NULL;
|
||
}
|
||
|
||
bool xml_loaded = false;
|
||
|
||
extern int all_gameCnt;
|
||
bool db_debug = 0;
|
||
|
||
static char langlist[11][22] =
|
||
{{"Console Default"},
|
||
{"Japanese"},
|
||
{"English"},
|
||
{"German"},
|
||
{"French"},
|
||
{"Spanish"},
|
||
{"Italian"},
|
||
{"Dutch"},
|
||
{"S. Chinese"},
|
||
{"T. Chinese"},
|
||
{"Korean"}};
|
||
|
||
static char langcodes[11][3] =
|
||
{{""},
|
||
{"JA"},
|
||
{"EN"},
|
||
{"DE"},
|
||
{"FR"},
|
||
{"ES"},
|
||
{"IT"},
|
||
{"NL"},
|
||
{"ZH"},
|
||
{"ZH"},
|
||
{"KO"}};
|
||
|
||
|
||
void xml_stat()
|
||
{
|
||
printf(" Games in XML: %d (%d) / %d\n", xmlgameCnt, array_size, all_gameCnt);
|
||
}
|
||
|
||
char * getLang(int lang) {
|
||
if (lang < 1 || lang > 10) return "EN";
|
||
return langcodes[lang+1];
|
||
}
|
||
|
||
char * unescape(char *input, int size) {
|
||
str_replace_all(input, ">", ">", size);
|
||
str_replace_all(input, "<", "<", size);
|
||
str_replace_all(input, """, "\"", size);
|
||
str_replace_all(input, "'", "'", size);
|
||
str_replace_all(input, "&", "&", size);
|
||
return input;
|
||
}
|
||
|
||
void wordWrap(char *tmp, char *input, int width, int maxLines, int length)
|
||
{
|
||
int maxLength = width * maxLines;
|
||
char *whitespace = NULL;
|
||
unescape(input, length);
|
||
char dots[4] = {0xE2, 0x80, 0xA6, 0};
|
||
str_replace_all(input, dots, "...", length);
|
||
strncpy(tmp, input, maxLength);
|
||
int lineLength = 0;
|
||
int wordLength = 0;
|
||
int lines = 1;
|
||
|
||
char * ptr=tmp;
|
||
while (*ptr!='\0') {
|
||
if (*ptr == '\n' || *ptr == '\r')
|
||
*ptr = ' ';
|
||
ptr++;
|
||
}
|
||
str_replace_all(tmp, " ", " ", sizeof(tmp));
|
||
|
||
for (ptr=tmp;*ptr!=0;ptr++) {
|
||
if (lines >= maxLines) *ptr = 0;
|
||
if (*ptr == ' ') {
|
||
if (lineLength + wordLength > width) {
|
||
if (whitespace) {
|
||
*whitespace = '\n';
|
||
lineLength = ptr - whitespace;
|
||
whitespace = NULL;
|
||
} else {
|
||
*ptr = '\n';
|
||
lineLength = 0;
|
||
}
|
||
lines++;
|
||
} else {
|
||
whitespace = ptr;
|
||
lineLength += wordLength+1;
|
||
}
|
||
wordLength = 0;
|
||
} else if (*ptr == '\n') {
|
||
lineLength = 0;
|
||
wordLength = 0;
|
||
} else {
|
||
wordLength++;
|
||
}
|
||
}
|
||
if (length > maxLength && lineLength <= (width - 3)) {
|
||
strncat(tmp, "...", 3);
|
||
}
|
||
}
|
||
|
||
//1 = Required, 0 = Usable, -1 = Not Used
|
||
//Controller = wiimote, nunchuk, classic, guitar, etc
|
||
int getControllerTypes(char *controller, u8 * gameid)
|
||
{
|
||
gameXMLinfo *g = get_game_info_id(gameid);
|
||
if (!g) return -1;
|
||
int acc_id = get_accesory_id(controller);
|
||
if (acc_id < 0) return -1;
|
||
int i;
|
||
for (i=0; i<XML_NUM_ACCESSORY; i++) {
|
||
int x = g->accessoryID[i];
|
||
if (x == 0) break;
|
||
if (x == 1 + acc_id) return 0;
|
||
if (x == 100 + acc_id) return 1;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
const char *getControllerName(gameXMLinfo *g, int i, int *req)
|
||
{
|
||
if (i >= XML_NUM_ACCESSORY) return NULL;
|
||
int x = g->accessoryID[i];
|
||
if (x == 0) return NULL;
|
||
if (x >= 100) {
|
||
x -= 100;
|
||
*req = 1;
|
||
} else {
|
||
x -= 1;
|
||
*req = 0;
|
||
}
|
||
return get_accesory_name(x);
|
||
}
|
||
|
||
bool hasFeature(char *feature, u8 *gameid)
|
||
{
|
||
gameXMLinfo *g = get_game_info_id(gameid);
|
||
if (!g) return 0;
|
||
int f_id = get_feature_id(feature);
|
||
int i;
|
||
for (i=0; i<XML_NUM_FEATURES; i++) {
|
||
int x = g->wififeatures[i];
|
||
if (x == 0) break;
|
||
if (x == 1 + f_id) return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool hasGenre(char *genre, u8 * gameid)
|
||
{
|
||
gameXMLinfo *g = get_game_info_id(gameid);
|
||
if (g) {
|
||
if (strstr(g->genre, genre)) {
|
||
if (!strcmp(genre,"tennis")) { // if looking for tennis
|
||
if ((strstr(g->genre, "table tennis,tennis")) || (!strstr(g->genre, "table tennis"))) //exclude ones that only have table tennis
|
||
return 1;
|
||
} else
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool xml_getCaseColor(u32 *color, u8 *gameid)
|
||
{
|
||
gameXMLinfo *g = get_game_info_id(gameid);
|
||
if (g) {
|
||
if (g->caseColor != 0) {
|
||
*color = g->caseColor;
|
||
return 1;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool DatabaseLoaded() {
|
||
return xml_loaded;
|
||
}
|
||
|
||
void CloseXMLDatabase()
|
||
{
|
||
SAFE_FREE(game_info);
|
||
xmlgameCnt = 0;
|
||
array_size = 0;
|
||
obs_freeall(&obs_game_info);
|
||
hash_close(&hash_game_info);
|
||
xml_loaded = false;
|
||
}
|
||
|
||
bool OpenXMLFile(char *filename)
|
||
{
|
||
get_time(&TIME.db_load1);
|
||
memset(&gameinfo, 0, sizeof(gameinfo));
|
||
xml_loaded = false;
|
||
char* strresult = strstr(filename,".zip");
|
||
if (strresult == NULL) {
|
||
FILE *filexml;
|
||
filexml = fopen(filename, "rb");
|
||
if (!filexml)
|
||
return false;
|
||
|
||
fseek(filexml, 0 , SEEK_END);
|
||
u32 size = ftell(filexml);
|
||
rewind(filexml);
|
||
xmlData = mem_calloc(size);
|
||
if (xmlData == NULL) {
|
||
fclose(filexml);
|
||
return false;
|
||
}
|
||
u32 ret = fread(xmlData, 1, size, filexml);
|
||
fclose(filexml);
|
||
if (ret != size)
|
||
return false;
|
||
} else {
|
||
unzFile unzfile = unzOpen(filename);
|
||
if (unzfile == NULL)
|
||
return false;
|
||
|
||
unzOpenCurrentFile(unzfile);
|
||
unz_file_info zipfileinfo;
|
||
unzGetCurrentFileInfo(unzfile, &zipfileinfo, NULL, 0, NULL, 0, NULL, 0);
|
||
int zipfilebuffersize = zipfileinfo.uncompressed_size;
|
||
if (db_debug) {
|
||
printf("uncompressed xml: %dkb\n", zipfilebuffersize/1024);
|
||
}
|
||
xmlData = mem_calloc(zipfilebuffersize);
|
||
if (xmlData == NULL) {
|
||
unzCloseCurrentFile(unzfile);
|
||
unzClose(unzfile);
|
||
return false;
|
||
}
|
||
unzReadCurrentFile(unzfile, xmlData, zipfilebuffersize);
|
||
unzCloseCurrentFile(unzfile);
|
||
unzClose(unzfile);
|
||
}
|
||
get_time(&TIME.db_load2);
|
||
|
||
xml_loaded = (xmlData == NULL) ? false : true;
|
||
return xml_loaded;
|
||
}
|
||
|
||
/* convert language text into ISO 639 two-letter language code */
|
||
char *ConvertLangTextToCode(char *languagetxt)
|
||
{
|
||
if (!strcmp(languagetxt, ""))
|
||
return "EN";
|
||
int i;
|
||
for (i=1;i<=10;i++)
|
||
{
|
||
if (!strcasecmp(languagetxt,langlist[i])) // case insensitive comparison
|
||
return langcodes[i];
|
||
}
|
||
return "EN";
|
||
}
|
||
|
||
char *VerifyLangCode(char *languagetxt)
|
||
{
|
||
if (!strcmp(languagetxt, ""))
|
||
return "EN";
|
||
int i;
|
||
for (i=1;i<=10;i++)
|
||
{
|
||
if (!strcasecmp(languagetxt,langcodes[i])) // case insensitive comparison
|
||
return langcodes[i];
|
||
}
|
||
return "EN";
|
||
}
|
||
|
||
char ConvertRatingToIndex(char *ratingtext)
|
||
{
|
||
int type = -1;
|
||
if (!strcmp(ratingtext,"CERO")) { type = 0; }
|
||
if (!strcmp(ratingtext,"ESRB")) { type = 1; }
|
||
if (!strcmp(ratingtext,"PEGI")) { type = 2; }
|
||
return type;
|
||
}
|
||
|
||
int ConvertRatingToAge(char *ratingvalue, char *ratingSystem)
|
||
{
|
||
int age;
|
||
|
||
age = atoi(ratingvalue);
|
||
if ((age > 0 ) && (age <= 25)) return age;
|
||
if (ratingvalue[0] == 'A') {
|
||
if (!strncmp(ratingvalue,"AO",2)) return 18;
|
||
return 0;
|
||
}
|
||
if (ratingvalue[0] == 'B') return 12;
|
||
if (ratingvalue[0] == 'C') return 15;
|
||
if (ratingvalue[0] == 'D') return 17;
|
||
if (ratingvalue[0] == 'E') {
|
||
if (!strncmp(ratingvalue,"E1",2)) return 10; //E10+
|
||
if (!strncmp(ratingvalue,"EC",2)) return 3;
|
||
return 6;
|
||
}
|
||
if (ratingvalue[0] == 'G') return 0;
|
||
if (ratingvalue[0] == 'L') return 0;
|
||
if (ratingvalue[0] == 'T') return 13;
|
||
if (ratingvalue[0] == 'Z') return 18;
|
||
if (ratingvalue[0] == '0') return 0;
|
||
if (ratingvalue[0] == 'M') {
|
||
if (!strncmp(ratingvalue,"M18",3)) return 18;
|
||
if (!strncmp(ratingvalue,"MA15",4)) return 15;
|
||
if (!strncmp(ratingSystem,"ACB",3)
|
||
|| (!ratingSystem && (CONF_GetArea() == CONF_AREA_AUS))) return 12;
|
||
if (!strncmp(ratingSystem,"OFLC",4)
|
||
/* || (!ratingSystem && (CONF_GetShopCode() == 95)) */) return 13; //New Zeland needs updated lib for GetShopCode to work
|
||
return 17; //assume us ESRB system
|
||
}
|
||
if (ratingvalue[0] == 'P') {
|
||
if (!strncmp(ratingvalue,"PG12",4)) return 12;
|
||
if (!strncmp(ratingvalue,"PG 12",5)) return 12;
|
||
if (!strncmp(ratingvalue,"PG15",4)) return 15;
|
||
if (!strncmp(ratingvalue,"PG 15",5)) return 15;
|
||
if (!strncmp(ratingvalue,"PG",2)) return 8;
|
||
return 6;
|
||
}
|
||
if (ratingvalue[0] == 'R') {
|
||
if (!strncmp(ratingvalue,"R13",3)) return 13;
|
||
if (!strncmp(ratingvalue,"R16",3)) return 16;
|
||
if (!strncmp(ratingvalue,"R18",3)) return 18;
|
||
return 18;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
void ConvertRating(char *ratingvalue, char *fromrating, char *torating, char *destvalue, int destsize)
|
||
{
|
||
if(!strcmp(fromrating,torating)) {
|
||
strlcpy(destvalue,ratingvalue,destsize);
|
||
return;
|
||
}
|
||
|
||
strcpy(destvalue,"");
|
||
int type = -1;
|
||
int desttype = -1;
|
||
|
||
type = ConvertRatingToIndex(fromrating);
|
||
desttype = ConvertRatingToIndex(torating);
|
||
if (type == -1 || desttype == -1)
|
||
return;
|
||
|
||
/* rating conversion table */
|
||
/* the list is ordered to pick the most likely value first: */
|
||
/* EC and AO are less likely to be used so they are moved down to only be picked up when converting ESRB to PEGI or CERO */
|
||
/* the conversion can never be perfect because ratings can differ between regions for the same game */
|
||
char ratingtable[12][3][4] =
|
||
{
|
||
{{"A"},{"E"},{"3"}},
|
||
{{"A"},{"E"},{"4"}},
|
||
{{"A"},{"E"},{"6"}},
|
||
{{"A"},{"E"},{"7"}},
|
||
{{"A"},{"EC"},{"3"}},
|
||
{{"A"},{"E10+"},{"7"}},
|
||
{{"B"},{"T"},{"12"}},
|
||
{{"D"},{"M"},{"18"}},
|
||
{{"D"},{"M"},{"16"}},
|
||
{{"C"},{"T"},{"16"}},
|
||
{{"C"},{"T"},{"15"}},
|
||
{{"Z"},{"AO"},{"18"}},
|
||
};
|
||
|
||
int i;
|
||
for (i=0;i<=11;i++)
|
||
{
|
||
if (!strcmp(ratingtable[i][type],ratingvalue)) {
|
||
strlcpy(destvalue,ratingtable[i][desttype],destsize);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
char * GetLangSettingFromGame(u8 * gameid) {
|
||
int langcode;
|
||
struct Game_CFG game_cfg = CFG_read_active_game_setting((u8*)gameid);
|
||
if (game_cfg.language) {
|
||
langcode = game_cfg.language;
|
||
} else {
|
||
langcode = CFG.game.language;
|
||
}
|
||
char *langtxt = langlist[langcode];
|
||
return langtxt;
|
||
}
|
||
|
||
void strncpySafe(char *out, char *in, int max, int length) {
|
||
strlcpy(out, in, ((max < length+1) ? max : length+1));
|
||
}
|
||
|
||
void prep_tags()
|
||
{
|
||
int i;
|
||
for (i=0; i<TAG_MAX; i++) {
|
||
tags[i].len = strlen(tags[i].name);
|
||
}
|
||
tags[TAG_LOCALE].opt = 1;
|
||
}
|
||
|
||
char* scan_index(char *s, char *etag)
|
||
{
|
||
int i;
|
||
int first = 0;
|
||
int elen = strlen(etag);
|
||
memset(idx, 0, sizeof(idx));
|
||
while (1) {
|
||
s = strchr(s, '<');
|
||
if (!s) break;
|
||
s++;
|
||
if (*s == '/') {
|
||
s++;
|
||
// did we reach endtag?
|
||
if (memcmp(s, etag, elen)==0) {
|
||
// zero terminate
|
||
s[-2] = 0;
|
||
s += elen;
|
||
break;
|
||
}
|
||
continue;
|
||
}
|
||
for (i=first; i<TAG_MAX; i++) {
|
||
if (idx[i].start) {
|
||
if (i == first) first++;
|
||
continue;
|
||
}
|
||
if (memcmp(s, tags[i].name, tags[i].len)==0) {
|
||
idx[i].start = s-1;
|
||
s += tags[i].len;
|
||
idx[i].attr = s;
|
||
s = strchr(s, '>');
|
||
if (!s) goto out;
|
||
if (tags[i].opt) break;
|
||
// zero terminate
|
||
*s = 0;
|
||
s++;
|
||
// is closing /> ?
|
||
if (s[-2] != '/') {
|
||
idx[i].val = s;
|
||
// find closing tag
|
||
while (1) {
|
||
s = strstr(s, "</");
|
||
if (!s) goto out;
|
||
s += 2;
|
||
if (memcmp(s, tags[i].name, tags[i].len)==0) {
|
||
// zero terminate
|
||
s[-2] = 0;
|
||
idx[i].vend = s - 2;
|
||
s = strchr(s, '>');
|
||
if (!s) goto out;
|
||
idx[i].end = s;
|
||
s++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
} // for
|
||
} // while
|
||
out:
|
||
/*for (i=0; i<TAG_MAX; i++) {
|
||
if (idx[i].start) gecko_printf("%s:%p\n", tags[i].name, idx[i].attr);
|
||
}*/
|
||
return s;
|
||
}
|
||
|
||
static inline
|
||
int readVal(xmlIndex *x, char *val, int size)
|
||
{
|
||
int len;
|
||
if (!x->val || !x->vend) return 0;
|
||
len = x->vend - x->val;
|
||
|
||
if (len >= size) len = size - 1;
|
||
//if (len < 0) return 0;
|
||
memcpy(val, x->val, len);
|
||
val[len] = 0;
|
||
return 1;
|
||
}
|
||
|
||
static inline
|
||
char* readAttr(char *s, char *attr, char *val, int size)
|
||
{
|
||
char *p1, *p2;
|
||
*val = 0;
|
||
if (!s) return NULL;
|
||
p1 = strstr(s, attr);
|
||
if (!p1) return NULL;
|
||
p1 += strlen(attr);
|
||
if (*p1 == '"') p1++;
|
||
p2 = strchr(p1, '"');
|
||
if (!p2) return NULL;
|
||
strncpySafe(val, p1, size, p2-p1);
|
||
return p2;
|
||
}
|
||
|
||
static inline
|
||
char* readAttrInt(char *s, char *attr, int *val)
|
||
{
|
||
char *p1, *p2;
|
||
*val = 0;
|
||
p1 = strstr(s, attr);
|
||
if (!p1) return NULL;
|
||
p1 += strlen(attr);
|
||
if (*p1 == '"') p1++;
|
||
p2 = strchr(p1, '"');
|
||
if (!p2) return NULL;
|
||
*val = atoi(p1);
|
||
return p2;
|
||
}
|
||
|
||
int intcpy(char *in, int length) {
|
||
char tmp[10];
|
||
strncpy(tmp, in, length);
|
||
return atoi(tmp);
|
||
}
|
||
|
||
void readNode(char * p, char to[], char * open, char * close) {
|
||
char * tmp = strstr(p, open);
|
||
if (tmp == NULL) {
|
||
strcpy(to, "");
|
||
} else {
|
||
char * s = tmp+strlen(open);
|
||
strlcpy(to, s, strstr(tmp, close)-s+1);
|
||
}
|
||
}
|
||
|
||
void readDate(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
char *p;
|
||
p = x->attr;
|
||
if (!p) return;
|
||
p = readAttrInt(p, " year=", &g->year);
|
||
if (!p) return;
|
||
p = readAttrInt(p, " month=", &g->month);
|
||
if (!p) return;
|
||
p = readAttrInt(p, " day=", &g->day);
|
||
}
|
||
|
||
void readRatings(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
char *s, *p;
|
||
s = p = x->attr;
|
||
if (!s) return;
|
||
readAttr(s, " type=", g->ratingtype, sizeof(g->ratingtype));
|
||
readAttr(s, " value=", g->ratingvalue, sizeof(g->ratingvalue));
|
||
// descriptions not needed
|
||
/*
|
||
if (strncmp(locEnd-1, "/>", 2)==0) return;
|
||
locEnd = strstr(locStart, "</rating>");
|
||
if (!locEnd) return;
|
||
else {
|
||
char * tmp = strndup(locStart, locEnd-locStart);
|
||
char * reset = tmp;
|
||
locEnd = strstr(locStart, "\" value=\"");
|
||
if (locEnd == NULL) return;
|
||
strncpySafe(g_info->ratingtype, locStart+14, sizeof(gameinfo.ratingtype), locEnd-(locStart+14));
|
||
locStart = locEnd;
|
||
locEnd = strstr(locStart, "\">");
|
||
strncpySafe(g_info->ratingvalue, locStart+9, sizeof(gameinfo.ratingvalue), locEnd-(locStart+9));
|
||
int z = 0;
|
||
while (tmp != NULL) {
|
||
if (z >= 15) break;
|
||
tmp = strstr(tmp, "<descriptor>");
|
||
if (tmp == NULL) {
|
||
break;
|
||
} else {
|
||
char * s = tmp+strlen("<descriptor>");
|
||
tmp = strstr(tmp+1, "</descriptor>");
|
||
strncpySafe(g_info->ratingdescriptors[z], s, sizeof(g_info->ratingdescriptors[z]), tmp-s);
|
||
z++;
|
||
}
|
||
}
|
||
tmp = reset;
|
||
SAFE_FREE(tmp);
|
||
}
|
||
*/
|
||
}
|
||
|
||
void readPlayers(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
readAttrInt(x->attr, "players=", &g->players);
|
||
}
|
||
|
||
void readCaseColor(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
char cc[16];
|
||
int len, num;
|
||
u32 col;
|
||
g->caseColor = 0x0;
|
||
if (!x->attr) return;
|
||
char *p = readAttr(x->attr, "color=", cc, sizeof(cc));
|
||
if (!p) return;
|
||
num = sscanf(cc, "%x%n", &col, &len);
|
||
if (num == 1 && len == 6) {
|
||
// force alpha to opaque and mark color found
|
||
// (so black can be specified too)
|
||
g->caseColor = (col << 8) | 0xFF;
|
||
//gecko_printf("case: %s -> %08x\n", cc, g->caseColor);
|
||
}
|
||
}
|
||
|
||
void readWifi(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
char *p;
|
||
if (!x->attr) return;
|
||
p = readAttrInt(x->attr, " players=", &g->wifiplayers);
|
||
if (!p) return;
|
||
char *tmp = x->val;
|
||
char *s;
|
||
int z = 0;
|
||
while (tmp != NULL) {
|
||
if (z >= XML_NUM_FEATURES) break;
|
||
tmp = strstr(tmp, "<feature>");
|
||
if (!tmp) break;
|
||
s = tmp+strlen("<feature>");
|
||
tmp = strstr(s, "</feature>");
|
||
if (!tmp) break;
|
||
*tmp = 0;
|
||
tmp++;
|
||
int f_id = get_feature_id(s);
|
||
if (f_id >= 0) {
|
||
g->wififeatures[z] = 1 + f_id;
|
||
z++;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
void readControls(char * start, struct gameXMLinfo *g)
|
||
{
|
||
int z = 0;
|
||
char *p = start;
|
||
char *str = "<control type=\"";
|
||
int len = strlen(str);
|
||
while (p != NULL) {
|
||
if (z >= XML_NUM_ACCESSORY) break;
|
||
p = strstr(p, str);
|
||
if (p == NULL) break;
|
||
char *s = p + len;
|
||
p = strstr(p+1, "\" required=\"");
|
||
if (p == NULL) break;
|
||
bool required = (p[12] == 't' || p[12] == 'T'); // "True"
|
||
*p = 0;
|
||
p++;
|
||
int acc_id = get_accesory_id(s);
|
||
if (acc_id >= 0) {
|
||
g->accessoryID[z] = acc_id + (required ? 100 : 1);
|
||
z++;
|
||
}
|
||
}
|
||
// The followint games use controllers not supported by the gametdb database
|
||
if (strncmp(g->id, "RX5", 3) == 0) //Tony Hawk Ride
|
||
g->accessoryID[z] = get_accesory_id("skateboard") + 100;
|
||
if (strncmp(g->id, "STY", 3) == 0) //Tony Hawk Shred
|
||
g->accessoryID[z] = get_accesory_id("skateboard") + 100;
|
||
if (strncmp(g->id, "SQI", 3) == 0) //Disney Infinity
|
||
g->accessoryID[z] = get_accesory_id("infinitybase") + 100;
|
||
if (strncmp(g->id, "SSP", 3) == 0) //Skylanders Spyros Adventure
|
||
g->accessoryID[z] = get_accesory_id("portalofpower") + 100;
|
||
if (strncmp(g->id, "SKY", 3) == 0) //Skylanders Giants
|
||
g->accessoryID[z] = get_accesory_id("portalofpower") + 100;
|
||
if (strncmp(g->id, "SVX", 3) == 0) //Skylanders Swamp Force
|
||
g->accessoryID[z] = get_accesory_id("portalofpower") + 100;
|
||
if (strncmp(g->id, "SWA", 3) == 0) //DJ Hero
|
||
g->accessoryID[z] = get_accesory_id("turntable") + 100;
|
||
if (strncmp(g->id, "SWB", 3) == 0) //DJ Hero 2
|
||
g->accessoryID[z] = get_accesory_id("turntable") + 100;
|
||
if (strncmp(g->id, "RYR", 3) == 0) //Your Shape
|
||
g->accessoryID[z] = get_accesory_id("camera") + 100;
|
||
if (strncmp(g->id, "SRQ", 3) == 0) //Racquet Sports
|
||
g->accessoryID[z] = get_accesory_id("camera") + 1;
|
||
if (strncmp(g->id, "SF5", 3) == 0) //Fit in Six
|
||
g->accessoryID[z] = get_accesory_id("camera") + 1;
|
||
if (strncmp(g->id, "SE2", 3) == 0) //Active 2
|
||
g->accessoryID[z] = get_accesory_id("totalbodytracking") + 100;
|
||
if (strncmp(g->id, "SNF", 3) == 0) //NFL Training Camp
|
||
g->accessoryID[z] = get_accesory_id("totalbodytracking") + 100;
|
||
|
||
}
|
||
|
||
|
||
void readTitles(xmlIndex *x, struct gameXMLinfo *g)
|
||
{
|
||
char *locStart;
|
||
char *locEnd;
|
||
char *tmpLang;
|
||
int found = 0;
|
||
int f_title = 0;
|
||
int f_synopsis = 0;
|
||
char *locTmp;
|
||
char *titStart;
|
||
char *titEnd;
|
||
char temp_synopsis[XML_MAX_SYNOPSIS *2];
|
||
|
||
if (!x->start) return;
|
||
locEnd = x->start;
|
||
|
||
while ((locStart = strstr(locEnd, "<locale lang=\"")) != NULL) {
|
||
locEnd = strstr(locStart, "</locale>");
|
||
if (locEnd == NULL) {
|
||
locEnd = strstr(locStart, "\"/>");
|
||
if (locEnd == NULL) break;
|
||
continue;
|
||
}
|
||
*locEnd = 0; // zero terminate
|
||
locEnd++; // move to next entry
|
||
tmpLang = locStart+14;
|
||
if (memcmp(tmpLang, xmlCfgLang, 2) == 0) {
|
||
found = 3;
|
||
} else if (memcmp(tmpLang, "EN", 2) == 0) {
|
||
found = 2;
|
||
} else {
|
||
found = 1;
|
||
}
|
||
// 3. get the configured language text
|
||
// 2. if not found, get english
|
||
// 1. else get whatever is found
|
||
locTmp = locStart;
|
||
if (f_title < found) {
|
||
titStart = strstr(locTmp, "<title>");
|
||
if (titStart != NULL) {
|
||
titEnd = strstr(titStart, "</title>");
|
||
strncpySafe(g->title, titStart+7,
|
||
sizeof(g->title), titEnd-(titStart+7));
|
||
unescape(g->title, sizeof(g->title));
|
||
f_title = found;
|
||
}
|
||
}
|
||
if (f_synopsis < found) {
|
||
titStart = strstr(locTmp, "<synopsis>");
|
||
if (titStart != NULL) {
|
||
titEnd = strstr(titStart, "</synopsis>");
|
||
int len = titEnd-(titStart+10);
|
||
if (len >= sizeof(temp_synopsis)) len = sizeof(temp_synopsis) - 1;
|
||
strcopy(temp_synopsis, titStart+10, len+1);
|
||
unescape(temp_synopsis,sizeof(temp_synopsis));
|
||
len = strlen(temp_synopsis);
|
||
if (len >= XML_MAX_SYNOPSIS) len = XML_MAX_SYNOPSIS - 1;
|
||
g->synopsis = obs_alloc(&obs_game_info, len+1);
|
||
if (!g->synopsis) break;
|
||
strcopy(g->synopsis, temp_synopsis, len+1);
|
||
f_synopsis = found;
|
||
}
|
||
}
|
||
if (f_title == 3 && f_synopsis == 3) break;
|
||
}
|
||
}
|
||
|
||
bool xml_compare_key(void *cb, void *key, int handle)
|
||
{
|
||
char *id = (char*)get_game_info(handle)->id;
|
||
// Some entries in wiitdb have ID4 instead of ID6 (wiiware)
|
||
// but the image has ID6 (ID4+"00")
|
||
// so we cut the key to ID4 if it is ID4 in wiitdb
|
||
// int len = (id[4] == 0) ? 4 : 6;
|
||
// always need to compare whole id6.
|
||
// game id4 s are null terminated when they are loaded.
|
||
// only comparing id4 was causing incorrect matching for titles like wii fit plus and mario kart wii,
|
||
// when their is a channel id4 and the first 4 of the id6 match.
|
||
int len = 6;
|
||
return (strncmp(id, (char *)key, len) == 0);
|
||
}
|
||
|
||
int* xml_next_handle(void *cb, int handle)
|
||
{
|
||
return &get_game_info(handle)->hnext;
|
||
}
|
||
|
||
void LoadTitlesFromXML(char *langtxt, bool forcejptoen)
|
||
{
|
||
char * pos = xmlData;
|
||
bool forcelang = false;
|
||
struct gameXMLinfo *g;
|
||
|
||
xmlgameCnt = 0;
|
||
obs_init(&obs_game_info, 10240, mem1_realloc, mem_resize);
|
||
hash_init(&hash_game_info, 0, NULL, &hash_id4, &xml_compare_key, &xml_next_handle);
|
||
|
||
if (strcmp(langtxt,""))
|
||
forcelang = true;
|
||
if (forcelang) {
|
||
// convert language text into ISO 639 two-letter language code
|
||
strcpy(xmlCfgLang, (strlen(langtxt) == 2) ? VerifyLangCode(langtxt) : ConvertLangTextToCode(langtxt));
|
||
}
|
||
if (forcejptoen && (!strcmp(xmlCfgLang,"JA")))
|
||
strcpy(xmlCfgLang,"EN");
|
||
|
||
prep_tags();
|
||
|
||
while (1) {
|
||
if (xmlgameCnt >= array_size) {
|
||
const int alloc_chunk = 100;
|
||
array_size += alloc_chunk;
|
||
game_info = realloc(game_info, array_size * sizeof(*game_info));
|
||
}
|
||
g = game_info[xmlgameCnt] = obs_alloc(&obs_game_info, sizeof(struct gameXMLinfo));
|
||
if (g == NULL) break;
|
||
//memset(g, 0, sizeof(*g)); //already done by mem1_realloc
|
||
pos = strstr(pos, "<game");
|
||
if (pos == NULL) {
|
||
break;
|
||
}
|
||
pos = scan_index(pos, "game");
|
||
if (pos == NULL) {
|
||
break;
|
||
}
|
||
#define READ_VAL(T,V) readVal(idx+T, V, sizeof(V))
|
||
READ_VAL(TAG_ID, g->id);
|
||
if (g->id[0] == '\0') { //WTF? ERROR
|
||
printf(" ID NULL\n");
|
||
break;
|
||
}
|
||
hash_add(&hash_game_info, g->id, xmlgameCnt);
|
||
readTitles(idx+TAG_LOCALE, g);
|
||
readDate(idx+TAG_DATE, g);
|
||
readRatings(idx+TAG_RATING, g);
|
||
readWifi(idx+TAG_WIFI, g);
|
||
readControls(idx[TAG_INPUT].val, g);
|
||
READ_VAL(TAG_REGION, g->region);
|
||
READ_VAL(TAG_DEVELOPER, g->developer);
|
||
READ_VAL(TAG_PUBLISHER, g->publisher);
|
||
READ_VAL(TAG_GENRE, g->genre);
|
||
readCaseColor(idx+TAG_CASE, g);
|
||
readPlayers(idx+TAG_INPUT, g);
|
||
//ConvertRating(get_game_info(n)->ratingvalue, get_game_info(n)->ratingtype, "ESRB");
|
||
xmlgameCnt++;
|
||
}
|
||
SAFE_FREE(xmlData);
|
||
return;
|
||
}
|
||
|
||
/* load renamed titles from proper names and game info XML, needs to be after cfg_load_games */
|
||
bool OpenXMLDatabase(char* xmlfilepath, char* argdblang, bool argJPtoEN)
|
||
{
|
||
if (xml_loaded) {
|
||
return true;
|
||
}
|
||
long long start = 0;
|
||
if (db_debug) {
|
||
printf("Database Debug Info:\n");
|
||
mem_stat();
|
||
start = gettime();
|
||
}
|
||
bool opensuccess = true;
|
||
sprintf(xmlcfg_filename, "wiitdb.zip");
|
||
|
||
char pathname[200];
|
||
snprintf(pathname, sizeof(pathname), "%s", xmlfilepath);
|
||
if (xmlfilepath[strlen(xmlfilepath) - 1] != '/') snprintf(pathname, sizeof(pathname), "%s/",pathname);
|
||
snprintf(pathname, sizeof(pathname), "%s%s", pathname, xmlcfg_filename);
|
||
opensuccess = OpenXMLFile(pathname);
|
||
if (!opensuccess) {
|
||
CloseXMLDatabase();
|
||
sprintf(xmlcfg_filename, "wiitdb_%s.zip", CFG.partition);
|
||
|
||
snprintf(pathname, sizeof(pathname), "%s", xmlfilepath);
|
||
if (xmlfilepath[strlen(xmlfilepath) - 1] != '/') snprintf(pathname, sizeof(pathname), "%s/",pathname);
|
||
snprintf(pathname, sizeof(pathname), "%s%s", pathname, xmlcfg_filename);
|
||
opensuccess = OpenXMLFile(pathname);
|
||
if (!opensuccess) {
|
||
CloseXMLDatabase();
|
||
return false;
|
||
}
|
||
}
|
||
LoadTitlesFromXML(argdblang, argJPtoEN);
|
||
if (db_debug) {
|
||
printf_("Load Time: %u ms\n", diff_msec(start, gettime()));
|
||
mem_stat();
|
||
Wpad_WaitButtons();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool ReloadXMLDatabase(char* xmlfilepath, char* argdblang, bool argJPtoEN)
|
||
{
|
||
if (xml_loaded) {
|
||
if (db_debug) xml_stat();
|
||
CloseXMLDatabase();
|
||
}
|
||
return OpenXMLDatabase(xmlfilepath, argdblang, argJPtoEN);
|
||
}
|
||
|
||
int getIndexFromId(u8 * gameid)
|
||
{
|
||
if (gameid == NULL) return -1;
|
||
return hash_get(&hash_game_info, gameid);
|
||
}
|
||
|
||
/* gameid: full game id */
|
||
bool LoadGameInfoFromXML(u8 * gameid)
|
||
{
|
||
memset(&gameinfo, 0, sizeof(gameinfo));
|
||
gameXMLinfo *g = get_game_info_id(gameid);
|
||
if (!g) return false;
|
||
gameinfo = *g;
|
||
return true;
|
||
}
|
||
|
||
void RemoveChar(char *string, char toremove)
|
||
{
|
||
char *ptr;
|
||
ptr = string;
|
||
while (*ptr!='\0')
|
||
{
|
||
if (*ptr==toremove){
|
||
while (*ptr!='\0'){
|
||
*ptr=*(ptr+1);
|
||
if (*ptr!='\0') ptr++;
|
||
}
|
||
ptr=string;
|
||
}
|
||
ptr++;
|
||
}
|
||
}
|
||
|
||
char *utf8toconsole(char *string)
|
||
{
|
||
char *ptr;
|
||
ptr = string;
|
||
RemoveChar(string,0xC3);
|
||
ptr = string;
|
||
while (*ptr != '\0') {
|
||
switch (*ptr){
|
||
case 0x87: // <20>
|
||
*ptr = 0x80;
|
||
break;
|
||
case 0xA7: // <20>E
|
||
*ptr = 0x87;
|
||
break;
|
||
case 0xA0: // <20>E
|
||
*ptr = 0x85;
|
||
break;
|
||
case 0xA2: // <20>E
|
||
*ptr = 0x83;
|
||
break;
|
||
case 0x80: // <20>
|
||
*ptr = 0x41;
|
||
break;
|
||
case 0x82: // <20>
|
||
*ptr = 0x41;
|
||
break;
|
||
case 0xAA: // <20>E
|
||
*ptr = 0x88;
|
||
break;
|
||
case 0xA8: // <20>E
|
||
*ptr = 0x8A;
|
||
break;
|
||
case 0xA9: // <20>E
|
||
*ptr = 0x82;
|
||
break;
|
||
case 0x89: // <20>
|
||
*ptr = 0x90;
|
||
break;
|
||
case 0x88: // <20>
|
||
*ptr = 0x45;
|
||
break;
|
||
case 0xC5: // Okami
|
||
*ptr = 0x4F;
|
||
break;
|
||
case 0xB1: // <20>E
|
||
*ptr = 0xA4;
|
||
break;
|
||
case 0x9F: // <20>
|
||
*ptr = 0xE1;
|
||
break;
|
||
|
||
}
|
||
ptr++;
|
||
}
|
||
return string;
|
||
}
|
||
|
||
void FmtGameInfo(char *linebuf, int cols, int size)
|
||
{
|
||
*linebuf = 0;
|
||
if (gameinfo.year != 0)
|
||
snprintf(linebuf, size, "%d ", gameinfo.year);
|
||
if (strcmp(gameinfo.publisher,"") != 0)
|
||
snprintf(linebuf, size, "%s%s", linebuf, unescape(gameinfo.publisher, sizeof(gameinfo.publisher)));
|
||
if (strcmp(gameinfo.developer,"") != 0 && strcmp(gameinfo.developer,gameinfo.publisher) != 0)
|
||
snprintf(linebuf, size, "%s / %s", linebuf, unescape(gameinfo.developer, sizeof(gameinfo.developer)));
|
||
if (strlen(linebuf) >= cols) {
|
||
linebuf[cols - 3] = 0;
|
||
strcat(linebuf, "...");
|
||
}
|
||
strappend(linebuf, "\n", size);
|
||
int len = strlen(linebuf);
|
||
linebuf += len;
|
||
size -= len;
|
||
|
||
if (strcmp(gameinfo.ratingvalue,"") != 0) {
|
||
char rating[8];
|
||
STRCOPY(rating, gameinfo.ratingvalue);
|
||
if (!strcmp(gameinfo.ratingtype,"PEGI")) strcat(rating, "+");
|
||
snprintf(linebuf, size, gt("Rated %s"), rating);
|
||
strcat(linebuf, " ");
|
||
}
|
||
if (gameinfo.players != 0) {
|
||
char players[32];
|
||
if (gameinfo.players > 1) {
|
||
snprintf(D_S(players), gt("for %d players"), gameinfo.players);
|
||
} else {
|
||
strcpy(players, gt("for 1 player"));
|
||
}
|
||
strcat(linebuf, players);
|
||
if (gameinfo.wifiplayers > 0) {
|
||
snprintf(D_S(players), gt("(%d online)"), gameinfo.wifiplayers);
|
||
strcat(linebuf, " ");
|
||
strcat(linebuf, players);
|
||
}
|
||
}
|
||
}
|
||
|
||
void PrintGameInfo()
|
||
{
|
||
char linebuf[1000] = "";
|
||
FmtGameInfo(linebuf, 39, sizeof(linebuf));
|
||
printf("%s",linebuf);
|
||
}
|
||
|
||
void PrintGameSynopsis()
|
||
{
|
||
char linebuf[1000] = "";
|
||
/*
|
||
printf(" ID: %s\n", gameinfo.id);
|
||
printf(" REGION: %s\n", gameinfo.region);
|
||
printf(" TITLE: %s\n", gameinfo.title);
|
||
printf(" DEV: %s\n", gameinfo.developer);
|
||
printf(" PUB: %s\n", gameinfo.publisher);
|
||
printf(" DATE: %d / %d / %d\n", gameinfo.year, gameinfo.month, gameinfo.day);
|
||
printf(" GEN: %s\n", gameinfo.genre);
|
||
printf(" COLOR: %s\n", gameinfo.caseColor);
|
||
printf(" RAT: %s\n", gameinfo.ratingtype);
|
||
printf(" VAL: %s\n", gameinfo.ratingvalue);
|
||
printf(" RAT DESC: %s\n", gameinfo.ratingdescriptors[0]);
|
||
printf(" WIFI: %d\n", gameinfo.wifiplayers);
|
||
printf(" WIFI DESC: %s\n", gameinfo.wififeatures[0]);
|
||
printf(" PLAY: %d\n", gameinfo.players);
|
||
printf(" ACC: %s\n", gameinfo.accessories[0]);
|
||
printf(" REQ ACC: %s\n", gameinfo.accessoriesReq[0]);
|
||
*/
|
||
//INSERT STUFF HERE
|
||
if (db_debug) {
|
||
mem_stat();
|
||
xml_stat();
|
||
}
|
||
int cols, rows;
|
||
CON_GetMetrics(&cols, &rows);
|
||
cols -= 1;
|
||
rows -= 14;
|
||
if (rows < 2) rows = 2;
|
||
char *syn = gameinfo.synopsis;
|
||
if (!syn) syn = "";
|
||
wordWrap(linebuf, syn, cols, rows, strlen(syn));
|
||
printf("\n%s\n",linebuf);
|
||
}
|
||
|
||
// chartowidechar for UTF-8
|
||
// adapted from FreeTypeGX.cpp + UTF-8 fix by Rudolph
|
||
wchar_t* charToWideChar(char* strChar) {
|
||
wchar_t *strWChar[strlen(strChar) + 1];
|
||
int bt;
|
||
bt = mbstowcs(*strWChar, strChar, strlen(strChar));
|
||
if(bt > 0) {
|
||
strWChar[bt] = (wchar_t)'\0';
|
||
return *strWChar;
|
||
}
|
||
|
||
char *tempSrc = strChar;
|
||
wchar_t *tempDest = *strWChar;
|
||
while((*tempDest++ = *tempSrc++));
|
||
|
||
return *strWChar;
|
||
}
|
||
|
||
/*
|
||
<game name="Wii Play (USA) (EN,FR,ES)">
|
||
<id>RHAE01</id>
|
||
<type/>
|
||
<region>NTSC-U</region>
|
||
<languages>EN,FR,ES</languages>
|
||
<locale lang="EN">
|
||
<title>Wii Play</title>
|
||
<synopsis>Bundled with a Wii Remote, Wii Play offers a little something for everyone who enjoyed the pick-up-and-play gaming of Wii Sports.
|
||
|
||
Wii Play is made up of nine games that will test the physical and mental reflexes of players of all ages.
|
||
|
||
Whether you pick up the Wii Remote to play the shooting gallery that harkens back to the days of Duck Hunt or use it to find matching Miis, a world of fun is in your hands!
|
||
|
||
In addition to the shooting gallery and Mii-matching game, Wii play offers billiards, air hockey, tank battles, table tennis rally, Mii poses and a cow-riding race.</synopsis>
|
||
</locale>
|
||
<locale lang="ZHTW">
|
||
<title>Wii第一次接觸(美)</title>
|
||
<synopsis/>
|
||
</locale>
|
||
<locale lang="ZHCN">
|
||
<title>Wii第一次接触(美)</title>
|
||
<synopsis/>
|
||
</locale>
|
||
<developer>Nintendo EAD</developer>
|
||
<publisher>Nintendo</publisher>
|
||
<date year="2007" month="2" day="12"/>
|
||
<genre>miscellaneous</genre>
|
||
<rating type="ESRB" value="E">
|
||
<descriptor>mild cartoon violence</descriptor>
|
||
</rating>
|
||
<wi-fi players="0"/>
|
||
<input players="2">
|
||
<control type="wiimote" required="true"/>
|
||
<control type="nunchuk" required="true"/>
|
||
</input>
|
||
<rom version="" name="Wii Play (USA) (EN,FR,ES).iso" size="4699979776" crc="ffbe5d7f" md5="ac33937e2e14673f7607b6889473089e" sha1="cf734f2c97292bc4e451bf263b217aa35599062e"/>
|
||
</game>
|
||
*/
|
||
|