/*
Load game information from XML - Lustar
 - Mini-XML by Michael Sweet
 - MiniZip adapted by Tantric
*/

#include <malloc.h>
#include "unzip/unzip.h"
#include "settings/cfg.h"
#include "xml/xml.h"
//#include "cfg.h"
//#include "xml.h"

extern struct SSettings Settings; // for loader GX
extern void title_set(char *id, char *title);
extern char* trimcopy(char *dest, char *src, int size);
extern char game_partition[6];


/* config */
static bool xmldebug = false;
static char xmlcfg_filename[100] = "wiitdb";
static int xmlmaxsize = 1572864;


struct gameXMLinfo gameinfo;
struct gameXMLinfo gameinfo_reset;

static char langlist[11][22] = {{"Console Default"},
    {"Japanese"},
    {"English"},
    {"German"},
    {"French"},
    {"Spanish"},
    {"Italian"},
    {"Dutch"},
    {"S. Chinese"},
    {"T. Chinese"},
    {"Korean"}
};

static char langcodes[11][5] = {{""},
    {"JA"},
    {"EN"},
    {"DE"},
    {"FR"},
    {"ES"},
    {"IT"},
    {"NL"},
    {"ZHCN"}, // People's Republic of China
    {"ZHTW"}, // Taiwan
    {"KO"}
};

static char element_text[5000];
static mxml_node_t *nodetree=NULL;
static mxml_node_t *nodedata=NULL;
static mxml_node_t *nodeid=NULL;
static mxml_node_t *nodeidtmp=NULL;
static mxml_node_t *nodefound=NULL;
static mxml_index_t *nodeindex=NULL;
static mxml_index_t *nodeindextmp=NULL;
int xmlloadtime = 0;
char * get_nodetext(mxml_node_t *node, char *buffer, int buflen);
bool xml_loaded = false;


/* 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, bool openfile, bool loadtitles, bool keepopen) {
    if (!xml_loaded) {
        bool opensuccess = false;
        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_%s.zip", pathname, xmlcfg_filename, game_partition);
        if (openfile) opensuccess = OpenXMLFile(pathname);
        if (!opensuccess) {
		    snprintf(pathname, sizeof(pathname), "%s", xmlfilepath);
			if (xmlfilepath[strlen(xmlfilepath) - 1] != '/') snprintf(pathname, sizeof(pathname), "%s/",pathname);
            snprintf(pathname, sizeof(pathname), "%swiitdb.zip", pathname);
            if (openfile) opensuccess = OpenXMLFile(pathname);
        }
        if (!opensuccess && openfile) {
            CloseXMLDatabase();
            return false;
        }
        if (loadtitles) LoadTitlesFromXML(argdblang, argJPtoEN);
        if (!keepopen) CloseXMLDatabase();
    } else {
	    if (loadtitles) LoadTitlesFromXML(argdblang, argJPtoEN);
        if (!keepopen) CloseXMLDatabase();
	}
    return true;
}

void CloseXMLDatabase() {
    /* free memory */
    if (xml_loaded) {
        mxmlDelete(nodedata);
        mxmlDelete(nodetree);
        xml_loaded = false;
    }
}


void GetTextFromNode(mxml_node_t *currentnode, mxml_node_t *topnode, char *nodename,
                     char *attributename, char *value, int descend, char *dest, int destsize) {
    *element_text = 0;
    nodefound = mxmlFindElement(currentnode, topnode, nodename, attributename, value, descend);
    if (nodefound != NULL)	{
        if (attributename != NULL) {
            strlcpy(dest,mxmlElementGetAttr(nodefound, attributename),destsize);
        } else {
            get_nodetext(nodefound, element_text, sizeof(element_text));
            strlcpy(dest,element_text,destsize);
        }
    } else {
        strcpy(dest,"");
    }
}


bool OpenXMLFile(char *filename) {
    //if (xmldebug) dbg_time1();

    if (xml_loaded)
        return false;

    gameinfo = gameinfo_reset;
    nodedata=NULL;
    nodetree=NULL;
    nodeid=NULL;
    nodeidtmp=NULL;
    nodefound=NULL;

    char* strresult = strstr(filename,".zip");
    if (strresult == NULL) {
        /* Load XML file */
        FILE *filexml;
        filexml = fopen(filename, "rb");
        if (!filexml)
            return false;

        nodetree = mxmlLoadFile(NULL, filexml, MXML_OPAQUE_CALLBACK);
        fclose(filexml);

    } else {
        /* load zipped XML file */
        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 (zipfilebuffersize >= xmlmaxsize) {
            unzCloseCurrentFile(unzfile);
            unzClose(unzfile);
            return false;
        }

        char * zipfilebuffer = malloc(zipfilebuffersize);
        memset(zipfilebuffer, 0, zipfilebuffersize);
        if (zipfilebuffer == NULL) {
            unzCloseCurrentFile(unzfile);
            unzClose(unzfile);
            return false;
        }

        unzReadCurrentFile(unzfile, zipfilebuffer, zipfilebuffersize);
        unzCloseCurrentFile(unzfile);
        unzClose(unzfile);

        nodetree = mxmlLoadString(NULL, zipfilebuffer, MXML_OPAQUE_CALLBACK);
        free(zipfilebuffer);
    }

    if (nodetree == NULL)
        return false;

    nodedata = mxmlFindElement(nodetree, nodetree, "datafile", NULL, NULL, MXML_DESCEND);
    if (nodedata == NULL) {
        return false;
    } else {
        //if (xmldebug)	xmlloadtime = dbg_time2(NULL);
        xml_loaded = true;
        return true;
    }
}



char *GetLangSettingFromGame(char *gameid) {
    int langcode;
    struct Game_CFG *game_cfg = NULL;
    game_cfg = CFG_get_game_opt((u8*)gameid);
    if (game_cfg) {
        langcode = game_cfg->language;
    } else {
        //langcode = CFG.language; // for Configurable Loader
        langcode = Settings.language; // for Loader GX
    }
    char *langtxt = langlist[langcode];
    return langtxt;
}


/* convert language text into ISO 639 two-letter language code (+ZHTW/ZHCN) */
char *ConvertLangTextToCode(char *languagetxt) {
	// do not convert if languagetext seems to be a language code (can be 2 or 4 letters)
	if (strlen(languagetxt) <= 4)
		return languagetxt;
    int i;
    for (i=0;i<=10;i++) {
        if (!strcasecmp(languagetxt,langlist[i])) // case insensitive comparison
            return langcodes[i];
    }
    return "";
}


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;
}

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;
        }
    }
}


void LoadTitlesFromXML(char *langtxt, bool forcejptoen)
/* langtxt: set to "English","French","German", to force language for all titles, or "" to load title depending on each game's setting */
/* forcejptoen: set to true to load English title instead of Japanese title when game is set to Japanese */
{
    if (nodedata == NULL)
        return;

    bool forcelang = false;
    if (strcmp(langtxt,""))
        forcelang = true;

    char langcode[10] = "";
    if (forcelang)
        strcpy(langcode,ConvertLangTextToCode(langtxt)); /* convert language text into ISO 639 two-letter language code */

    /* create index of <id> elements */
    nodeindex = mxmlIndexNew(nodedata,"id", NULL);
    nodeid = mxmlIndexReset(nodeindex);
    *element_text = 0;
    char id_text[10];
    char title_text[200] = "";
    char title_text_EN[200] = "";

    /* search index of id elements, load all id/titles text */
    while (nodeid != NULL) {
        nodeid = mxmlIndexFind(nodeindex,"id", NULL);
        if (nodeid != NULL) {
            strcpy(title_text,"");
            strcpy(title_text_EN,"");

            get_nodetext(nodeid, element_text, sizeof(element_text));
            snprintf(id_text, 7, "%s",element_text);

            // if language is not forced, use game language setting from config
            if (!forcelang) {
                langtxt = GetLangSettingFromGame(id_text);
                strcpy(langcode,ConvertLangTextToCode(langtxt));
            }

            /* if enabled, force English title for all games set to Japanese */
            if (forcejptoen && (!strcmp(langcode,"JA")))
                strcpy(langcode,"EN");

            /* load title from nodes */
            nodefound = mxmlFindElement(nodeid, nodedata, "locale", "lang", "EN", MXML_NO_DESCEND);
            if (nodefound != NULL) {
                GetTextFromNode(nodefound, nodedata, "title", NULL, NULL, MXML_DESCEND, title_text_EN,sizeof(title_text_EN));
            }
            nodefound = mxmlFindElement(nodeid, nodedata, "locale", "lang", langcode, MXML_NO_DESCEND);
            if (nodefound != NULL) {
                GetTextFromNode(nodefound, nodedata, "title", NULL, NULL, MXML_DESCEND, title_text,sizeof(title_text));
            }

            /* fall back to English title if prefered language was not found */
            if (!strcmp(title_text,"")) {
                strcpy(title_text,title_text_EN);
            }

            snprintf(id_text, 7, "%s",id_text);
            title_set(id_text, title_text);
        }
    }

    // free memory
    mxmlIndexDelete(nodeindex);

    //if (xmldebug) xmlloadtime = dbg_time2(NULL);
}


void GetPublisherFromGameid(char *idtxt, char *dest, int destsize) {
    /* guess publisher from company list using last two characters from game id */
    nodeindextmp = mxmlIndexNew(nodedata,"company", NULL);
    nodeidtmp = mxmlIndexReset(nodeindextmp);

    *element_text=0;
    char publishercode[3];
    sprintf(publishercode,"%c%c", idtxt[4],idtxt[5]);

    while (strcmp(element_text,publishercode) != 0) {
        nodeidtmp = mxmlIndexFind(nodeindextmp,"company", NULL);
        if (nodeidtmp != NULL) {
            strlcpy(element_text,mxmlElementGetAttr(nodeidtmp, "code"),sizeof(element_text));
        } else {
            break;
        }
    }
    if (!strcmp(element_text,publishercode)) {
        strlcpy(dest,mxmlElementGetAttr(nodeidtmp, "name"),destsize);
    } else {
        strcpy(dest,"");
    }

    // free memory
    mxmlIndexDelete(nodeindextmp);
}



bool LoadGameInfoFromXML(char* gameid, char* langtxt)
/* gameid: full game id */
/* langtxt: "English","French","German" */
{
    bool exist=false;
    if (!xml_loaded || nodedata == NULL)
        return exist;

    // load game info using forced language, or game individual setting, or main language setting
    char langcode[100] = "";
    if (!strcmp(langtxt,""))
        langtxt = GetLangSettingFromGame(gameid);
    strlcpy(langcode,ConvertLangTextToCode(langtxt),sizeof(langcode));

    /* reset all game info */
    gameinfo = gameinfo_reset;

    /* index all IDs */
    nodeindex = mxmlIndexNew(nodedata,"id", NULL);
    nodeid = mxmlIndexReset(nodeindex);
    *element_text = 0;
    /* search for game matching gameid */
    while (1) {
        nodeid = mxmlIndexFind(nodeindex,"id", NULL);
        if (nodeid != NULL) {
            get_nodetext(nodeid, element_text, sizeof(element_text));
            if (!strcmp(element_text,gameid)) {
                break;
            }
        } else {
            break;
        }
    }

    if (!strcmp(element_text,gameid)) {
        /* text from elements */
        strlcpy(gameinfo.id,element_text,sizeof(gameinfo.id));
        GetTextFromNode(nodeid, nodedata, "region", NULL, NULL, MXML_NO_DESCEND, gameinfo.region,sizeof(gameinfo.region));
        GetTextFromNode(nodeid, nodedata, "version", NULL, NULL, MXML_NO_DESCEND, gameinfo.version,sizeof(gameinfo.version));
        GetTextFromNode(nodeid, nodedata, "genre", NULL, NULL, MXML_NO_DESCEND, gameinfo.genre,sizeof(gameinfo.genre));
        GetTextFromNode(nodeid, nodedata, "developer", NULL, NULL, MXML_NO_DESCEND, gameinfo.developer,sizeof(gameinfo.developer));
        GetTextFromNode(nodeid, nodedata, "publisher", NULL, NULL, MXML_NO_DESCEND, gameinfo.publisher,sizeof(gameinfo.publisher));
        GetPublisherFromGameid(gameid,gameinfo.publisherfromid,sizeof(gameinfo.publisherfromid));

        /* text from attributes */
        GetTextFromNode(nodeid, nodedata, "date", "year", NULL, MXML_NO_DESCEND, gameinfo.year,sizeof(gameinfo.year));
        GetTextFromNode(nodeid, nodedata, "date", "month", NULL,MXML_NO_DESCEND, gameinfo.month,sizeof(gameinfo.month));
        GetTextFromNode(nodeid, nodedata, "date", "day", NULL, MXML_NO_DESCEND, gameinfo.day,sizeof(gameinfo.day));
        GetTextFromNode(nodeid, nodedata, "rating", "type", NULL, MXML_NO_DESCEND, gameinfo.ratingtype,sizeof(gameinfo.ratingtype));
        GetTextFromNode(nodeid, nodedata, "rating", "value", NULL, MXML_NO_DESCEND, gameinfo.ratingvalue,sizeof(gameinfo.ratingvalue));
        GetTextFromNode(nodeid, nodedata, "rom", "crc", NULL, MXML_NO_DESCEND, gameinfo.iso_crc,sizeof(gameinfo.iso_crc));
        GetTextFromNode(nodeid, nodedata, "rom", "md5", NULL, MXML_NO_DESCEND, gameinfo.iso_md5,sizeof(gameinfo.iso_md5));
        GetTextFromNode(nodeid, nodedata, "rom", "sha1", NULL, MXML_NO_DESCEND, gameinfo.iso_sha1,sizeof(gameinfo.iso_sha1));

        /* text from child elements */
        nodefound = mxmlFindElement(nodeid, nodedata, "locale", "lang", "EN", MXML_NO_DESCEND);
        if (nodefound != NULL) {
            GetTextFromNode(nodefound, nodedata, "title", NULL, NULL, MXML_DESCEND, gameinfo.title_EN,sizeof(gameinfo.title_EN));
            GetTextFromNode(nodefound, nodedata, "synopsis", NULL, NULL, MXML_DESCEND, gameinfo.synopsis_EN,sizeof(gameinfo.synopsis_EN));
        }
        nodefound = mxmlFindElement(nodeid, nodedata, "locale", "lang", langcode, MXML_NO_DESCEND);
        if (nodefound != NULL) {
            GetTextFromNode(nodefound, nodedata, "title", NULL, NULL, MXML_DESCEND, gameinfo.title,sizeof(gameinfo.title));
            GetTextFromNode(nodefound, nodedata, "synopsis", NULL, NULL, MXML_DESCEND, gameinfo.synopsis,sizeof(gameinfo.synopsis));
        }
        // fall back to English title and synopsis if prefered language was not found
        if (!strcmp(gameinfo.title,"")) {
            strlcpy(gameinfo.title,gameinfo.title_EN,sizeof(gameinfo.title));
        }
        if (!strcmp(gameinfo.synopsis,"")) {
            strlcpy(gameinfo.synopsis,gameinfo.synopsis_EN,sizeof(gameinfo.synopsis));
        }

        /* list locale lang attributes */
        nodefound = mxmlFindElement(nodeid, nodedata, "locale", "lang", NULL, MXML_NO_DESCEND);
        if (nodefound != NULL) {
            int incr = 0;
            while (nodefound != NULL) {
                ++incr;
                strlcpy(gameinfo.locales[incr],mxmlElementGetAttr(nodefound, "lang"),sizeof(gameinfo.locales[incr]));
                nodefound = mxmlWalkNext(nodefound, nodedata, MXML_NO_DESCEND);
                if (nodefound != NULL) {
                    nodefound = mxmlFindElement(nodefound, nodedata, "locale", "lang", NULL, MXML_NO_DESCEND);
                }
            }
        }

        /* unbounded child elements */
        GetTextFromNode(nodeid, nodedata, "wi-fi", "players", NULL, MXML_NO_DESCEND, gameinfo.wifiplayers,sizeof(gameinfo.wifiplayers));
        nodefound = mxmlFindElement(nodeid, nodedata, "wi-fi", NULL, NULL, MXML_NO_DESCEND);
        if (nodefound != NULL) {
            gameinfo.wifiCnt = 0;
            nodeindextmp = mxmlIndexNew(nodefound,"feature", NULL);
            nodeidtmp = mxmlIndexReset(nodeindextmp);
            while (nodeidtmp != NULL) {
                nodeidtmp = mxmlIndexFind(nodeindextmp,"feature", NULL);
                if (nodeidtmp != NULL) {
                    ++gameinfo.wifiCnt;
                    GetTextFromNode(nodeidtmp, nodedata, "feature", NULL, NULL, MXML_DESCEND, gameinfo.wififeatures[gameinfo.wifiCnt],
                                    sizeof(gameinfo.wififeatures[gameinfo.wifiCnt]));
                    gameinfo.wififeatures[gameinfo.wifiCnt][0] = toupper((int)gameinfo.wififeatures[gameinfo.wifiCnt][0]);
                    if (gameinfo.wifiCnt == XML_ELEMMAX)
                        break;
                }
            }
            mxmlIndexDelete(nodeindextmp); // placed after each mxmlIndexNew to prevent memory leak
        }

        nodefound = mxmlFindElement(nodeid, nodedata, "rating", NULL, NULL, MXML_NO_DESCEND);
        if (nodefound != NULL) {
            gameinfo.descriptorCnt=0;
            nodeindextmp = mxmlIndexNew(nodefound,"descriptor", NULL);
            nodeidtmp = mxmlIndexReset(nodeindextmp);
            while (nodeidtmp != NULL) {
                nodeidtmp = mxmlIndexFind(nodeindextmp,"descriptor", NULL);
                if (nodeidtmp != NULL) {
                    ++gameinfo.descriptorCnt;
                    GetTextFromNode(nodeidtmp, nodedata, "descriptor", NULL, NULL, MXML_DESCEND,
                                    gameinfo.ratingdescriptors[gameinfo.descriptorCnt], sizeof(gameinfo.ratingdescriptors[gameinfo.descriptorCnt]));
                    if (gameinfo.descriptorCnt == XML_ELEMMAX)
                        break;
                }
            }
            mxmlIndexDelete(nodeindextmp);
        }

        GetTextFromNode(nodeid, nodedata, "input", "players", NULL, MXML_NO_DESCEND, gameinfo.players,sizeof(gameinfo.players));
        nodefound = mxmlFindElement(nodeid, nodedata, "input", NULL, NULL, MXML_NO_DESCEND);
        if (nodefound != NULL) {
            gameinfo.accessoryCnt=0;
            gameinfo.accessoryReqCnt=0;
            nodeindextmp = mxmlIndexNew(nodefound,"control", NULL);
            nodeidtmp = mxmlIndexReset(nodeindextmp);
            while (nodeidtmp != NULL) {
                nodeidtmp = mxmlIndexFind(nodeindextmp,"control", NULL);
                if (nodeidtmp != NULL) {
                    if (!strcmp(mxmlElementGetAttr(nodeidtmp, "required"),"true")  && gameinfo.accessoryReqCnt < XML_ELEMMAX)	{
                        ++gameinfo.accessoryReqCnt;
                        strlcpy(gameinfo.accessoriesReq[gameinfo.accessoryReqCnt],mxmlElementGetAttr(nodeidtmp, "type"),
                                sizeof(gameinfo.accessoriesReq[gameinfo.accessoryReqCnt]));
                    } else if (gameinfo.accessoryCnt < XML_ELEMMAX) {
                        ++gameinfo.accessoryCnt;
                        strlcpy(gameinfo.accessories[gameinfo.accessoryCnt],mxmlElementGetAttr(nodeidtmp, "type"),
                                sizeof(gameinfo.accessories[gameinfo.accessoryCnt]));
                    }
                }
            }
            mxmlIndexDelete(nodeindextmp);
        }

        /* convert rating value */
        ConvertRating(gameinfo.ratingvalue, gameinfo.ratingtype, "CERO",gameinfo.ratingvalueCERO,sizeof(gameinfo.ratingvalueCERO));
        ConvertRating(gameinfo.ratingvalue, gameinfo.ratingtype, "ESRB",gameinfo.ratingvalueESRB,sizeof(gameinfo.ratingvalueESRB));
        ConvertRating(gameinfo.ratingvalue, gameinfo.ratingtype, "PEGI",gameinfo.ratingvaluePEGI,sizeof(gameinfo.ratingvaluePEGI));

        /* provide genre as an array: gameinfo.genresplit */
        if (strcmp(gameinfo.genre,"") != 0) {
            gameinfo.genreCnt=0;
            const char *delimgenre = ",;";
            char genretxt[200];
            strlcpy(genretxt,gameinfo.genre,sizeof(genretxt));
            char *splitresult;
            splitresult = strtok(genretxt, delimgenre);
            if (splitresult != NULL) {
                ++gameinfo.genreCnt;
                trimcopy(splitresult,splitresult,strlen(splitresult)+1);
                strlcpy(gameinfo.genresplit[gameinfo.genreCnt],splitresult,sizeof(gameinfo.genresplit[gameinfo.genreCnt]));
                gameinfo.genresplit[gameinfo.genreCnt][0] = toupper((int)gameinfo.genresplit[gameinfo.genreCnt][0]);
                while (splitresult != NULL) {
                    splitresult = strtok(NULL, delimgenre);
                    if (splitresult != NULL && strcmp(splitresult,"")!=0) {
                        ++gameinfo.genreCnt;
                        trimcopy(splitresult,splitresult,strlen(splitresult)+1);
                        strlcpy(gameinfo.genresplit[gameinfo.genreCnt],splitresult,sizeof(gameinfo.genresplit[gameinfo.genreCnt]));
                        gameinfo.genresplit[gameinfo.genreCnt][0] = toupper((int)gameinfo.genresplit[gameinfo.genreCnt][0]);
                        if (gameinfo.genreCnt == XML_ELEMMAX)
                            break;
                    }
                }
            }

        }

        exist=true;
    } else {
        /*game not found */
        exist=false;
    }

    // if game was not found or info is missing
    // guess publisher from game id in case it is missing
    if (!strcmp(gameinfo.publisher,"")) {
        GetPublisherFromGameid(gameid,gameinfo.publisherfromid,sizeof(gameinfo.publisherfromid));
        strlcpy(gameinfo.publisher,gameinfo.publisherfromid,sizeof(gameinfo.publisher));
    }

    // if missing, get region from game ID
    if (!strcmp(gameinfo.region,"")) {
        if (gameid[3] == 'E') strlcpy(gameinfo.region,"NTSC-U",sizeof(gameinfo.region));
        if (gameid[3] == 'J') strlcpy(gameinfo.region,"NTSC-J",sizeof(gameinfo.region));
        if (gameid[3] == 'W') strlcpy(gameinfo.region,"NTSC-J",sizeof(gameinfo.region));
        if (gameid[3] == 'K') strlcpy(gameinfo.region,"NTSC-K",sizeof(gameinfo.region));
        if (gameid[3] == 'P') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'D') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'F') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'I') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'S') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'H') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'U') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
		if (gameid[3] == 'X') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'Y') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
        if (gameid[3] == 'Z') strlcpy(gameinfo.region,"PAL",sizeof(gameinfo.region));
    }

    // free memory
    mxmlIndexDelete(nodeindex);

    return exist;
}


void PrintGameInfo(bool showfullinfo) {
    if (showfullinfo) {

        //Con_Clear();

        //printf("id: %s version: %s region: %s",gameinfo.id, gameinfo.version, gameinfo.region);
        //printf("title: %s\n",gameinfo.title);
        int i;
        printf("languages:");
        for (i=1;strcmp(gameinfo.locales[i],"") != 0;i++) {
            printf(" %s",gameinfo.locales[i]);
        }
        printf("\n");
        //printf("developer: %s\n",gameinfo.developer);
        //printf("publisher: %s\n",gameinfo.publisher);
        //printf("publisher from ID: %s\n",gameinfo.publisherfromid);
        printf("year:%s month:%s day:%s\n",gameinfo.year,gameinfo.month,gameinfo.day);
        printf("genre: %s\n",gameinfo.genre);
        //printf("rating: %s %s (CERO: %s ESRB: %s PEGI: %s)\n",gameinfo.ratingtype, gameinfo.ratingvalue,
        //       gameinfo.ratingvalueCERO,gameinfo.ratingvalueESRB,gameinfo.ratingvaluePEGI);
        printf("content descriptors:");
        for (i=1;strcmp(gameinfo.wififeatures[i],"") != 0;i++) {
            printf(" %s",gameinfo.ratingdescriptors[i]);
        }
        printf("\n");
        printf("players: %s online: %s\n",gameinfo.players,gameinfo.wifiplayers);
        printf("online features:");
        for (i=1;strcmp(gameinfo.wififeatures[i],"") != 0;i++) {
            printf(" %s",gameinfo.wififeatures[i]);
        }
        printf("\n");
        printf("required accessories:");
        for (i=1;strcmp(gameinfo.accessoriesReq[i],"") != 0;i++) {
            printf(" %s",gameinfo.accessoriesReq[i]);
        }
        printf("\n");
        printf("accessories:");
        for (i=1;strcmp(gameinfo.accessories[i],"") != 0;i++) {
            printf(" %s",gameinfo.accessories[i]);
        }
        printf("\n");
        //printf("iso_crc: %s iso_md5: %s\n",gameinfo.iso_crc,gameinfo.iso_md5);
        //printf("iso_sha1: %s\n",gameinfo.iso_sha1);
        //printf("synopsis: %s\n",gameinfo.synopsis);

    } else {

        char linebuf[1000] = "";

        if (xmldebug) {
            //char xmltime[100];
            //sprintf(xmltime,"%d",xmlloadtime);
            //printf("xml load time: %s\n",xmltime);
            /*
            printf("xml forcelang: %s\n",CFG.db_lang);
            printf("xml url: %s\n",CFG.db_url);
            printf("xml file: %s\n",CFG.db_filename);
            char xmljptoen[100];
            sprintf(xmljptoen,"%d",CFG.db_JPtoEN);
            printf("xml JPtoEN: %s\n",xmljptoen);
            */
            printf(MemInfo()); // guidebug
        }

        //printf("%s: ",gameidfull);
        //printf("%s\n",gameinfo.title);
        if (strcmp(gameinfo.year,"") != 0)
            snprintf(linebuf, sizeof(linebuf), "%s ", gameinfo.year);
        if (strcmp(gameinfo.publisher,"") != 0)
            snprintf(linebuf, sizeof(linebuf), "%s%s", linebuf, gameinfo.publisher);
        if (strcmp(gameinfo.developer,"") != 0 && strcmp(gameinfo.developer,gameinfo.publisher) != 0)
            snprintf(linebuf, sizeof(linebuf), "%s / %s", linebuf, gameinfo.developer);
        if (strlen(linebuf) >= 100) {
            char buffer[200] = "";
            strlcpy(buffer, linebuf,  100);
            strcat(buffer, "...");
            snprintf(linebuf, sizeof(linebuf), "%s", buffer);
        }
        printf("%s\n",linebuf);
        strcpy(linebuf,"");

        if (strcmp(gameinfo.ratingvalue,"") != 0) {
            snprintf(linebuf, sizeof(linebuf), "rated %s", gameinfo.ratingvalue);
            if (!strcmp(gameinfo.ratingtype,"PEGI"))
                snprintf(linebuf, sizeof(linebuf), "%s+ ", linebuf);
            snprintf(linebuf, sizeof(linebuf), "%s ", linebuf);
        }
        if (strcmp(gameinfo.players,"") != 0) {
            snprintf(linebuf, sizeof(linebuf), "%sfor %s player", linebuf, gameinfo.players);
            if (atoi(gameinfo.players) > 1)
                snprintf(linebuf, sizeof(linebuf), "%ss", linebuf);
            if (atoi(gameinfo.wifiplayers) > 1)
                snprintf(linebuf, sizeof(linebuf), "%s (%s online)", linebuf, gameinfo.wifiplayers);
        }
        printf("%s\n",linebuf);
        strcpy(linebuf,"");
    }
}


char *MemInfo() {
    char linebuf[300] = "";
    char memtotal[20];
    char memused[20];
    char memnotinuse[20];
    char memcanbefreed[20];
    struct mallinfo mymallinfo = mallinfo();
    sprintf(memtotal,"%d",mymallinfo.arena/1024);
    sprintf(memused,"%d",mymallinfo.uordblks/1024);
    sprintf(memnotinuse,"%d",mymallinfo.fordblks/1024);
    sprintf(memcanbefreed,"%d",mymallinfo.keepcost/1024);
    snprintf(linebuf,sizeof(linebuf),"all:%sKB used:%sKB notused:%sKB canfree:%sKB", memtotal, memused, memnotinuse, memcanbefreed);
    char *minfo[300];
    *minfo = linebuf;
    return *minfo;
}



/*-------------------------------------------------------------------------------------*/
/* get_nodetext() - Get the text for a node, taken from mini-mxml example mxmldoc.c */
char * get_nodetext(mxml_node_t *node, char *buffer, int buflen) { /* O - Text in node, I - Node to get, I - Buffer, I - Size of buffer */
    char *ptr, *end;	/* Pointer into buffer, End of buffer */
    int len;	/* Length of node */
    mxml_node_t *current;	/* Current node */
    ptr = buffer;
    end = buffer + buflen - 1;
    for (current = node->child; current && ptr < end; current = current->next) {
        if (current->type == MXML_TEXT) {
            if (current->value.text.whitespace)
                *ptr++ = ' ';
            len = (int)strlen(current->value.text.string);
            if (len > (int)(end - ptr))
                len = (int)(end - ptr);
            memcpy(ptr, current->value.text.string, len);
            ptr += len;
        } else if (current->type == MXML_OPAQUE) {
            len = (int)strlen(current->value.opaque);
            if (len > (int)(end - ptr))
                len = (int)(end - ptr);
            memcpy(ptr, current->value.opaque, len);
            ptr += len;
        }
    }
    *ptr = '\0';
    return (buffer);
}

int GetRatingForGame(char *gameid)
{
    int retval=-1;
    if (!xml_loaded || nodedata == NULL)
        return -1;

    /* index all IDs */
    nodeindex = mxmlIndexNew(nodedata,"id", NULL);
    nodeid = mxmlIndexReset(nodeindex);
    *element_text = 0;
    /* search for game matching gameid */
    while (1) {
        nodeid = mxmlIndexFind(nodeindex,"id", NULL);
        if (nodeid != NULL) {
            get_nodetext(nodeid, element_text, sizeof(element_text));
            if (!strcmp(element_text,gameid)) {
                break;
            }
        } else {
            break;
        }
    }

    if (!strcmp(element_text,gameid)) {
		char type[5], value[5], dest[5];

        GetTextFromNode(nodeid, nodedata, "rating", "type", NULL, MXML_NO_DESCEND, type, sizeof(type));
        GetTextFromNode(nodeid, nodedata, "rating", "value", NULL, MXML_NO_DESCEND, value, sizeof(value));
        ConvertRating(value, type, "PEGI", dest, sizeof(dest));

		retval = atoi(dest);
	}
	return retval;
}