From 09525023a268a3bb3dfd2df2ad3c75da442039ab Mon Sep 17 00:00:00 2001 From: benoa Date: Sun, 30 Dec 2018 23:00:51 +0100 Subject: [PATCH] Add Plugins databases --- source/gui/GameTDB.cpp | 89 ++++++++++ source/gui/GameTDB.hpp | 2 + source/menu/menu.cpp | 5 +- source/menu/menu.hpp | 9 + source/menu/menu_game.cpp | 2 +- source/menu/menu_gameinfo.cpp | 242 ++++++++++++++++++++++--- source/plugin/plugin.cpp | 323 ++++++++++++++++++++++++++++++++++ source/plugin/plugin.hpp | 2 + 8 files changed, 649 insertions(+), 25 deletions(-) diff --git a/source/gui/GameTDB.cpp b/source/gui/GameTDB.cpp index f4ba0f44..7ac114ed 100644 --- a/source/gui/GameTDB.cpp +++ b/source/gui/GameTDB.cpp @@ -31,6 +31,9 @@ #include "config/config.hpp" #include "gecko/gecko.hpp" #include "memory/mem2.hpp" +#include "types.h" +#include "gui/coverflow.hpp" + #define NAME_OFFSET_DB "gametdb_offsets.bin" #define MAXREADSIZE 1024*1024 //Cache size only for parsing the offsets: 1MB @@ -458,6 +461,17 @@ bool GameTDB::ParseFile() bool GameTDB::FindTitle(char *data, const char * &title, const string &langCode) { + + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + title = GetNodeText(data, "", ""); + + if(title == NULL) + return false; + return true; + + } + char *language = SeekLang(data, langCode.c_str()); if(language == NULL) { @@ -465,6 +479,7 @@ bool GameTDB::FindTitle(char *data, const char * &title, const string &langCode) if(language == NULL) return false; } + title = GetNodeText(language, "", ""); if(title == NULL) @@ -487,6 +502,24 @@ bool GameTDB::GetTitle(const char *id, const char * &title) return retval; } +bool GameTDB::GetName(const char *id, const char * &name) +{ + name = NULL; + if(!id) + return false; + + char *data = GetGameNode(id); + if(!data) + return false; + + name = GetNodeText(data, "", ""); + + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + // Default to English + if(synopsis == NULL) + { + language = SeekLang(data, "EN"); + if(language == NULL) + { + MEM2_free(data); + return false; + } + synopsis = GetNodeText(language, "", ""); + } + } + MEM2_free(data); if(synopsis == NULL) @@ -619,6 +668,46 @@ bool GameTDB::GetGenres(const char *id, const char * &gen) { gen = NULL; + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + if(!id) + return false; + + char *data = GetGameNode(id); + if(!data) + return false; + + char *language = SeekLang(data, LangCode.c_str()); + if(language == NULL) + { + language = SeekLang(data, "EN"); + if(language == NULL) + { + MEM2_free(data); + return false; + } + } + gen = GetNodeText(language, "", ""); + + // If not found try in English + if(gen == NULL) + { + language = SeekLang(data, "EN"); + if(language == NULL) + { + MEM2_free(data); + return false; + } + + gen = GetNodeText(language, "", ""); + } + MEM2_free(data); + + if(gen == NULL) + return false; + return true; + } + if(id == NULL) return false; diff --git a/source/gui/GameTDB.hpp b/source/gui/GameTDB.hpp index b9cfa157..8a6ab7b1 100644 --- a/source/gui/GameTDB.hpp +++ b/source/gui/GameTDB.hpp @@ -74,6 +74,8 @@ public: const char * GetLanguageCode() { return LangCode.c_str(); }; //! Get the title of a specific game id in the language defined in LangCode bool GetTitle(const char *id, const char * &title); + //! Get the name of a specific game id + bool GetName(const char *id, const char * &name); //! Get the synopsis of a specific game id in the language defined in LangCode bool GetSynopsis(const char *id, const char * &synopsis); //! Get the region of a game for a specific game id diff --git a/source/menu/menu.cpp b/source/menu/menu.cpp index c4d0e0b9..dd1abee4 100644 --- a/source/menu/menu.cpp +++ b/source/menu/menu.cpp @@ -220,7 +220,10 @@ bool CMenu::init() m_fanartDir = m_cfg.getString("GENERAL", "dir_fanart", fmt("%s/fanart", m_dataDir.c_str())); m_screenshotDir = m_cfg.getString("GENERAL", "dir_screenshot", fmt("%s/screenshots", m_dataDir.c_str())); m_helpDir = m_cfg.getString("GENERAL", "dir_help", fmt("%s/help", m_dataDir.c_str())); - + m_cartDir = m_cfg.getString("GENERAL", "dir_cart", fmt("%s/cart_disk", m_dataDir.c_str())); + m_snapDir = m_cfg.getString("GENERAL", "dir_snap", fmt("%s/snapshots", m_dataDir.c_str())); + m_pluginDataDir = m_cfg.getString("GENERAL", "dir_plugins_data", fmt("%s/plugins_data", m_dataDir.c_str())); + /* Create our Folder Structure */ fsop_MakeFolder(m_dataDir.c_str()); //D'OH! diff --git a/source/menu/menu.hpp b/source/menu/menu.hpp index 24fcb03a..a1e02e82 100644 --- a/source/menu/menu.hpp +++ b/source/menu/menu.hpp @@ -142,6 +142,9 @@ private: string m_screenshotDir; string m_settingsDir; string m_languagesDir; + string m_cartDir; + string m_snapDir; + string m_pluginDataDir; string m_helpDir; /* NandEmulation */ @@ -618,6 +621,12 @@ private: s16 m_gameinfoLblUser[5]; s16 m_gameinfoLblControlsReq[4]; s16 m_gameinfoLblControls[4]; + s16 m_gameinfoLblSnap; + s16 m_gameinfoLblCartDisk; + s16 m_gameinfoLblOverlay; + TexData m_snap; + TexData m_cart; + TexData m_overlay; TexData m_rating; TexData m_wifi; TexData m_controlsreq[4]; diff --git a/source/menu/menu_game.cpp b/source/menu/menu_game.cpp index cfda479e..4c917b52 100644 --- a/source/menu/menu_game.cpp +++ b/source/menu/menu_game.cpp @@ -586,7 +586,7 @@ void CMenu::_game(bool launch) } } /* display game info screen */ - else if(BTN_PLUS_PRESSED && !NoGameID(hdr->type) && !coverFlipped && !m_video_playing) + else if(BTN_PLUS_PRESSED && hdr->type != TYPE_HOMEBREW && hdr->type != TYPE_SOURCE && !coverFlipped && !m_video_playing) { _hideGame();// stops trailer movie too m_banner.SetShowBanner(false); diff --git a/source/menu/menu_gameinfo.cpp b/source/menu/menu_gameinfo.cpp index 1559d46a..92e788bd 100644 --- a/source/menu/menu_gameinfo.cpp +++ b/source/menu/menu_gameinfo.cpp @@ -1,6 +1,9 @@ +#include #include "menu.hpp" #include "gui/GameTDB.hpp" +#include "plugin/plugin.hpp" +#include "plugin/crc32.h" wstringEx gameinfo_Synopsis_w; wstringEx gameinfo_Title_w; @@ -64,6 +67,9 @@ void CMenu::_gameinfo(void) m_btnMgr.hide(m_gameinfoLblGenre, true); m_btnMgr.hide(m_gameinfoLblRating, true); m_btnMgr.hide(m_gameinfoLblWifiplayers, true); + m_btnMgr.hide(m_gameinfoLblSnap, true); + m_btnMgr.hide(m_gameinfoLblCartDisk, true); + m_btnMgr.hide(m_gameinfoLblOverlay, true); for(u8 i = 0; i < ARRAY_SIZE(m_gameinfoLblControlsReq); ++i) if(m_gameinfoLblControlsReq[i] != -1) @@ -96,6 +102,9 @@ void CMenu::_gameinfo(void) m_btnMgr.show(m_gameinfoLblGenre); m_btnMgr.show(m_gameinfoLblRating); m_btnMgr.show(m_gameinfoLblWifiplayers); + m_btnMgr.show(m_gameinfoLblSnap); + m_btnMgr.show(m_gameinfoLblCartDisk); + m_btnMgr.show(m_gameinfoLblOverlay); for(u8 i = 0; i < ARRAY_SIZE(m_gameinfoLblControlsReq); ++i) if(m_gameinfoLblControlsReq[i] != -1 && i < cnt_controlsreq) @@ -130,6 +139,9 @@ void CMenu::_hideGameInfo(bool instant) m_btnMgr.hide(m_gameinfoLblGenre, instant); m_btnMgr.hide(m_gameinfoLblRating, instant); m_btnMgr.hide(m_gameinfoLblWifiplayers, instant); + m_btnMgr.hide(m_gameinfoLblSnap, instant); + m_btnMgr.hide(m_gameinfoLblCartDisk, instant); + m_btnMgr.hide(m_gameinfoLblOverlay, instant); for(u8 i = 0; i < ARRAY_SIZE(m_gameinfoLblControlsReq); ++i) if(m_gameinfoLblControlsReq[i] != -1) @@ -161,6 +173,13 @@ void CMenu::_showGameInfo(void) m_btnMgr.show(m_gameinfoLblGenre); m_btnMgr.show(m_gameinfoLblWifiplayers); + if(m_current_view == COVERFLOW_PLUGIN) + { + m_btnMgr.show(m_gameinfoLblSnap); + m_btnMgr.show(m_gameinfoLblCartDisk); + m_btnMgr.show(m_gameinfoLblOverlay); + } + for(u8 i = 0; i < ARRAY_SIZE(m_gameinfoLblUser); ++i) if(i < ARRAY_SIZE(m_gameinfoLblUser) / 2) m_btnMgr.show(m_gameinfoLblUser[i]); @@ -190,9 +209,12 @@ void CMenu::_initGameInfoMenu() m_gameinfoLblPublisher = _addLabel("GAMEINFO/PUBLISHER", theme.txtFont, L"", 40, 200, 460, 56, theme.txtFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_TOP); m_gameinfoLblRlsdate = _addLabel("GAMEINFO/RLSDATE", theme.txtFont, L"", 40, 230, 460, 56, theme.txtFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_TOP); m_gameinfoLblRegion = _addLabel("GAMEINFO/REGION", theme.txtFont, L"", 40, 260, 460, 56, theme.txtFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_TOP); - m_gameinfoLblRating = _addLabel("GAMEINFO/RATING", theme.titleFont, L"", 550, 380, 48, 60, theme.titleFontColor, 0, m_rating); + m_gameinfoLblRating = _addLabel("GAMEINFO/RATING", theme.txtFont, L"", 550, 380, 48, 60, theme.txtFontColor, 0, m_rating); m_gameinfoLblSynopsis = _addLabel("GAMEINFO/SYNOPSIS", theme.txtFont, L"", 40, 80, 560, 280, theme.txtFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_TOP); m_gameinfoLblWifiplayers = _addLabel("GAMEINFO/WIFIPLAYERS", theme.txtFont, L"", 550, 110, 68, 60, theme.txtFontColor, FTGX_JUSTIFY_LEFT | FTGX_ALIGN_TOP,m_wifi); + m_gameinfoLblSnap = _addLabel("GAMEINFO/SNAP", theme.titleFont, L"", 485, 200, m_snap.width, m_snap.height, theme.titleFontColor, 0, m_snap); + m_gameinfoLblOverlay = _addLabel("GAMEINFO/OVERLAY", theme.txtFont, L"", 485, 200, m_snap.width, m_snap.height, theme.titleFontColor, 0, m_overlay); + m_gameinfoLblCartDisk = _addLabel("GAMEINFO/CART", theme.txtFont, L"", 435, 364, m_cart.width, m_cart.height, theme.txtFontColor, FTGX_JUSTIFY_CENTER | FTGX_ALIGN_MIDDLE,m_cart); _addUserLabels(m_gameinfoLblUser, 1, 1, "GAMEINFO"); _addUserLabels(m_gameinfoLblUser, 3, 2, "GAMEINFO"); @@ -223,6 +245,9 @@ void CMenu::_initGameInfoMenu() _setHideAnim(m_gameinfoLblRlsdate, "GAMEINFO/RLSDATE", 0, -100, 0.f, 0.f); _setHideAnim(m_gameinfoLblGenre, "GAMEINFO/GENRE", 0, -100, 0.f, 0.f); _setHideAnim(m_gameinfoLblWifiplayers, "GAMEINFO/WIFIPLAYERS", 0, -100, 0.f, 0.f); + _setHideAnim(m_gameinfoLblSnap, "GAMEINFO/SNAP", 0, -100, 0.f, 0.f); + _setHideAnim(m_gameinfoLblCartDisk, "GAMEINFO/CART", 0, -100, 0.f, 0.f); + _setHideAnim(m_gameinfoLblOverlay, "GAMEINFO/OVERLAY", 0, -100, 0.f, 0.f); // _hideGameInfo(true); synopsis_h = m_theme.getInt("GAMEINFO/SYNOPSIS", "height", 280); @@ -234,7 +259,25 @@ void CMenu::_textGameInfo(void) cnt_controls = 0; GameTDB gametdb; - gametdb.OpenFile(fmt("%s/wiitdb.xml", m_settingsDir.c_str())); + char platformName[264]; + + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + // Check the platform name corresponding to the current magic number. + // We can't use magic # directly since it'd require hardcoding values and a # can be several systems(genplus) + // We can't rely on coverfolder either. Different systems can share the same folder. Or combined plugins used for the same system. + Config m_platform; + m_platform.unload(); + m_platform.load(fmt("%s/platform.ini", m_pluginDataDir.c_str()) ); + snprintf(platformName, sizeof(platformName), "%s", m_platform.getString("PLUGINS", m_plugin.PluginMagicWord).c_str()); + m_platform.unload(); + gametdb.OpenFile(fmt("%s/%s/%s.xml", m_pluginDataDir.c_str(), platformName, platformName)); + } + else + { + gametdb.OpenFile(fmt("%s/wiitdb.xml", m_settingsDir.c_str())); + } + gametdb.SetLanguageCode(m_loc.getString(m_curLanguage, "gametdb_code", "EN").c_str()); const char *TMP_Char = NULL; tdb_found = gametdb.IsLoaded(); @@ -242,11 +285,100 @@ void CMenu::_textGameInfo(void) { char GameID[7]; GameID[6] = '\0'; - strncpy(GameID, CoverFlow.getId(), 6); + + string ShortName; + + // Clear text and textures + TexData emptyTex; + gameinfo_Synopsis_w.fromUTF8(""); + m_btnMgr.setText(m_gameinfoLblDev, wfmt(_fmt("",L"")), true); + m_btnMgr.setText(m_gameinfoLblPublisher, wfmt(_fmt("",L"")), true); + m_btnMgr.setText(m_gameinfoLblRegion, wfmt(_fmt("",L"")), true); + m_btnMgr.setText(m_gameinfoLblRating, wfmt(_fmt("",L"")), true); + m_btnMgr.setTexture(m_gameinfoLblRating, emptyTex); + + m_btnMgr.setTexture(m_gameinfoLblSnap, emptyTex); + m_btnMgr.setTexture(m_gameinfoLblCartDisk, emptyTex); + m_btnMgr.setTexture(m_gameinfoLblOverlay, emptyTex); + + // Get Game ID + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + const dir_discHdr *GameHdr = CoverFlow.getHdr(); + ShortName = m_plugin.GetRomName(GameHdr); + strncpy(GameID, m_plugin.GetRomId(GameHdr, m_pluginDataDir.c_str(), platformName, ShortName).c_str(), 6); + } + else + { + strncpy(GameID, CoverFlow.getId(), 6); + } + if(gametdb.GetTitle(GameID, TMP_Char)) { gameinfo_Title_w.fromUTF8(TMP_Char); m_btnMgr.setText(m_gameinfoLblTitle, gameinfo_Title_w); + + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + // Try to find images by game's name + if(gametdb.GetName(GameID, TMP_Char)) + { + const char *snap_path = NULL; + const char *cart_path = NULL; + const char *overlay_path = NULL; + + // Use real filename without extension for arcade games. + if(strcasestr(platformName, "ARCADE") || strcasestr(platformName, "CPS") || !strncasecmp(platformName, "NEOGEO", 6)) + { + snap_path = fmt("%s/%s/%s.png", m_snapDir.c_str(), platformName, ShortName.c_str()); + cart_path = fmt("%s/%s/%s_2D.png", m_cartDir.c_str(), platformName, ShortName.c_str()); + } + // Name from the database. + else + { + snap_path = fmt("%s/%s/%s.png", m_snapDir.c_str(), platformName, TMP_Char); + cart_path = fmt("%s/%s/%s_2D.png", m_cartDir.c_str(), platformName, TMP_Char); + } + + // Try to find images by game's ID + if(!fsop_FileExist( snap_path )) + { + snap_path = fmt("%s/%s/%s.png", m_snapDir.c_str(), platformName, GameID); + cart_path = fmt("%s/%s/%s_2D.png", m_cartDir.c_str(), platformName, GameID); + + if(!fsop_FileExist( snap_path )) + { + TexHandle.Cleanup(m_snap); + TexHandle.Cleanup(m_cart); + } + } + + TexHandle.fromImageFile(m_snap, snap_path); + m_btnMgr.setTexture(m_gameinfoLblSnap, m_snap, m_snap.width, m_snap.height); + TexHandle.fromImageFile(m_cart, cart_path); + + if( m_cart.height > 112 ) + { + m_btnMgr.setTexture(m_gameinfoLblCartDisk, m_cart, 114, 128); + } + else + { + m_btnMgr.setTexture(m_gameinfoLblCartDisk, m_cart, 160, 112); + } + + overlay_path = fmt("%s/%s_overlay.png", m_snapDir.c_str(), platformName); + + if(fsop_FileExist( overlay_path )) + { + TexHandle.fromImageFile(m_overlay, overlay_path); + m_btnMgr.setTexture(m_gameinfoLblOverlay, m_overlay, m_overlay.width, m_overlay.height); + } + else + { + m_btnMgr.setTexture(m_gameinfoLblOverlay, emptyTex); + } + } + } } else { @@ -254,29 +386,54 @@ void CMenu::_textGameInfo(void) gametdb.CloseFile(); return; } + if(gametdb.GetSynopsis(GameID, TMP_Char)) - { + { gameinfo_Synopsis_w.fromUTF8(TMP_Char); m_btnMgr.setText(m_gameinfoLblSynopsis, gameinfo_Synopsis_w); } m_btnMgr.setText(m_gameinfoLblID, wfmt(L"GameID: %s", GameID), true); - if(gametdb.GetDeveloper(GameID, TMP_Char)) - m_btnMgr.setText(m_gameinfoLblDev, wfmt(_fmt("gameinfo1",L"Developer: %s"), TMP_Char), true); - if(gametdb.GetPublisher(GameID, TMP_Char)) - m_btnMgr.setText(m_gameinfoLblPublisher, wfmt(_fmt("gameinfo2",L"Publisher: %s"), TMP_Char), true); - if(gametdb.GetRegion(GameID, TMP_Char)) - m_btnMgr.setText(m_gameinfoLblRegion, wfmt(_fmt("gameinfo3",L"Region: %s"), TMP_Char), true); - if(gametdb.GetGenres(GameID, TMP_Char)) + + // Only show retrieved data else the text is often underneath the snapshot. + // It's still too long sometimes. Maybe, we should cut the string at a max length. + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) { - vector genres = stringToVector(TMP_Char, ','); - string s; - for(u32 i = 0; i < genres.size(); ++i) + if(gametdb.GetDeveloper(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblDev, wfmt(_fmt("",L"%s"), TMP_Char), true); + if(gametdb.GetPublisher(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblPublisher, wfmt(_fmt("",L"%s"), TMP_Char), true); + if(gametdb.GetRegion(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblRegion, wfmt(_fmt("gameinfo3",L"Region: %s"), TMP_Char), true); + if(gametdb.GetGenres(GameID, TMP_Char)) { - if(i > 0) - s.append(", ");// add comma & space between genres - s.append(genres[i]); + m_btnMgr.setText(m_gameinfoLblGenre, wfmt(_fmt("",L"%s"), TMP_Char), true); + } + else + { + // Genre not found, show nothing. + m_btnMgr.setText(m_gameinfoLblGenre, wfmt(_fmt("",L"")), true); + } + } + else + { + if(gametdb.GetDeveloper(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblDev, wfmt(_fmt("gameinfo1",L"Developer: %s"), TMP_Char), true); + if(gametdb.GetPublisher(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblPublisher, wfmt(_fmt("gameinfo2",L"Publisher: %s"), TMP_Char), true); + if(gametdb.GetRegion(GameID, TMP_Char)) + m_btnMgr.setText(m_gameinfoLblRegion, wfmt(_fmt("gameinfo3",L"Region: %s"), TMP_Char), true); + if(gametdb.GetGenres(GameID, TMP_Char)) + { + vector genres = stringToVector(TMP_Char, ','); + string s; + for(u32 i = 0; i < genres.size(); ++i) + { + if(i > 0) + s.append(", ");// add comma & space between genres + s.append(genres[i]); + } + m_btnMgr.setText(m_gameinfoLblGenre, wfmt(_fmt("gameinfo5",L"Genre: %s"), s.c_str()), true); } - m_btnMgr.setText(m_gameinfoLblGenre, wfmt(_fmt("gameinfo5",L"Genre: %s"), s.c_str()), true); } int PublishDate = gametdb.GetPublishDate(GameID); @@ -288,15 +445,34 @@ void CMenu::_textGameInfo(void) case 0: case 4: case 5: - m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), year, month, day), true); + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("",L"%i-%i-%i"), year, month, day), true); + else + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), year, month, day), true); break; case 1: - m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), month, day, year), true); + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("",L"%i-%i-%i"), month, day, year), true); + else + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), month, day, year), true); break; case 2: - m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), day, month, year), true); + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("",L"%i-%i-%i"), day, month, year), true); + else + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("gameinfo4",L"Release Date: %i-%i-%i"), day, month, year), true); break; } + + // Only display year or nothing if there's no date at all + if(day == 0 && month == 0) + { + if(year != 0) + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("",L"%i"), year), true); + else + m_btnMgr.setText(m_gameinfoLblRlsdate, wfmt(_fmt("",L"")), true); + } + //Ratings TexHandle.fromImageFile(m_rating, fmt("%s/norating.png", m_imgsDir.c_str())); const char *RatingValue = NULL; @@ -356,10 +532,28 @@ void CMenu::_textGameInfo(void) break; } } - m_btnMgr.setTexture(m_gameinfoLblRating, m_rating); + // Display the user's mark /20 instead because most of the rating data is missing. + if(CoverFlow.getHdr()->type == TYPE_PLUGIN) + { + if(gametdb.GetRatingValue(GameID, RatingValue)) + { + if(RatingValue[0] != '\0') + { + m_btnMgr.setText(m_gameinfoLblRating, wfmt(_fmt("",L"%s/20"), RatingValue), true); + } + } + else + { + m_btnMgr.setText(m_gameinfoLblRating, wfmt(_fmt("",L"")), true); + } + } + else + { + m_btnMgr.setText(m_gameinfoLblRating, wfmt(_fmt("",L"")), true); + m_btnMgr.setTexture(m_gameinfoLblRating, m_rating); + } //Wifi players int WifiPlayers = gametdb.GetWifiPlayers(GameID); - TexData emptyTex; if(WifiPlayers == 1) TexHandle.fromImageFile(m_wifi, fmt("%s/wifi1.png", m_imgsDir.c_str())); else if(WifiPlayers == 2) @@ -440,6 +634,8 @@ void CMenu::_textGameInfo(void) TexHandle.fromImageFile(m_controlsreq[x], fmt("%s/wiimote3.png", m_imgsDir.c_str())); else if(players == 4) TexHandle.fromImageFile(m_controlsreq[x], fmt("%s/wiimote4.png", m_imgsDir.c_str())); + else if(players == 5) + TexHandle.fromImageFile(m_controlsreq[x], fmt("%s/wiimote5.png", m_imgsDir.c_str())); else if(players == 6) TexHandle.fromImageFile(m_controlsreq[x], fmt("%s/wiimote6.png", m_imgsDir.c_str())); else if(players == 8) diff --git a/source/plugin/plugin.cpp b/source/plugin/plugin.cpp index c388b7c0..48eb4e3e 100644 --- a/source/plugin/plugin.cpp +++ b/source/plugin/plugin.cpp @@ -29,6 +29,13 @@ #include "types.h" #include "crc32.h" +// For PS1 serial +#ifdef MSB_FIRST +#define MODETEST_VAL 0x00ffffff +#else +#define MODETEST_VAL 0xffffff00 +#endif + Plugin m_plugin; void Plugin::init(const string& m_pluginsDir) { @@ -280,6 +287,322 @@ vector Plugin::CreateArgs(const char *device, const char *path, return args; } +/* Give the current game a simplified name */ +string Plugin::GetRomName(const dir_discHdr *gameHeader) +{ + if(strrchr(gameHeader->path, '/') != NULL) + { + // Remove extension + string FullName = strrchr(gameHeader->path, '/') + 1; + FullName = FullName.substr(0, FullName.find_last_of(".")); + + // Remove common suffixes and replace unwanted characters. + string ShortName = FullName.substr(0, FullName.find(" (")).substr(0, FullName.find(" [")); + replace(ShortName.begin(), ShortName.end(), '_', ' '); + return ShortName; + } + return NULL; +} + +/* Get serial from PS1 header's iso (Borrowed from Retroarch with a few c++ changes)*/ +static int GetSerialPS1(const char *path, string &Serial, int sub_channel_mixed) +{ + char *tmp; + int skip, frame_size, is_mode1, cd_sector; + char buffer[2048 * 2]; + + ifstream fp; + fp.open(path, ios::binary); + + buffer[0] = '\0'; + is_mode1 = 0; + + if ( !fp.seekg(0, ios::end) ) + goto error; + + if (!sub_channel_mixed) + { + if ( (!fp.tellg()) & 0x7FF) + { + unsigned int mode_test = 0; + + if ( !fp.seekg(0, ios::beg) ) + goto error; + + fp.read(reinterpret_cast(mode_test), 4); + + if (mode_test != MODETEST_VAL) + is_mode1 = 1; + } + } + + skip = is_mode1? 0: 24; + frame_size = sub_channel_mixed? 2448: is_mode1? 2048: 2352; + + if ( !fp.seekg(156 + skip + 16 * frame_size, ios::beg) ) + goto error; + + fp.read(buffer, 6); + + cd_sector = buffer[2] | (buffer[3] << 8) | (buffer[4] << 16); + + if ( !fp.seekg(skip + cd_sector * frame_size, ios::beg) ) + goto error; + + fp.read(buffer, 2048 * 2); + + tmp = buffer; + while (tmp < (buffer + 2048 * 2)) + { + if (!*tmp) + goto error; + + if (!strncasecmp((const char*)(tmp + 33), "SYSTEM.CNF;1", 12)) + break; + tmp += *tmp; + } + + if(tmp >= (buffer + 2048 * 2)) + goto error; + + cd_sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); + if ( !fp.seekg(skip + cd_sector * frame_size, ios::beg) ) + goto error; + + fp.read(buffer, 256); + buffer[256] = '\0'; + + tmp = buffer; + while(*tmp && strncasecmp((const char*)tmp, "boot", 4)) + tmp++; + + if(!*tmp) + goto error; + + Serial = tmp; + Serial.erase(0, Serial.find_first_of('\\') + 1); + replace(Serial.begin(), Serial.end(), '_', '-'); + Serial.erase(remove(Serial.begin(), Serial.end(), '.'), Serial.end()); + Serial.erase(Serial.find_first_of(';')); + + fp.close(); + + return 1; + +error: + return 0; +} + +/* Get serial from MegaCD header's iso +* +* All headers should have "SEGADISCSYSTEM" in the first bytes. +* The offset differs, the serial search depends on it. +*/ + +void GetSerialMegaCD(const char *path, string &Serial) +{ + ifstream infile; + char buf[10]; + + infile.open(path, ios::binary); + infile.seekg(0, ios::beg); + infile.read ((char*)buf, 4); + buf[4] = '\0'; + + // iso or bin offset. + if (!strcasecmp(buf, "SEGA")) + infile.seekg(0x182, ios::beg); + else + { + infile.seekg(0x10, ios::beg); + infile.read ((char*)buf, 4); + + if (!strcasecmp(buf, "SEGA")) + infile.seekg(0x192, ios::beg); + } + + infile.read(buf, 9); + buf[9] = '\0'; + infile.close(); + + Serial = buf; + Serial.erase(remove(Serial.begin(), Serial.end(), ' '), Serial.end()); + + // Cut at second dash, we don't need any extra code. + // Sonic CD : MK-4407(-00) + size_t dash = std::count(Serial.begin(), Serial.end(), '-'); + + if(dash > 1) + Serial.erase(Serial.find_last_of('-')); + + infile.close(); +} + + +/* Get the Game ID based on name or CRC/Serial +* +* It returns the ID used to search in the platform database(SUPERNES.xml for instance) +* and can also be used for snapshots/cartriges/discs images. +* The Game ID is a 6 length alphanumerical value. It's screenscraper ID filled with 'A' letter. +*/ +string Plugin::GetRomId(const dir_discHdr *gameHeader, const char *datadir, char *platform, const string &name) +{ + string GameID; + string CRC_Serial(12, '*'); + + // Load a platform list that is used to identify the current game. + // It contains a default filename(preferably No-intro without region flag), the GameID and then all known CRC32/serials. + // filename=GameID|crc1|crc2|etc... + // For example in SUPERNES.ini : Super Aleste=2241AA|5CA5781B|... + Config m_crc; + m_crc.unload(); + m_crc.load(fmt("%s/%s/%s.ini", datadir, platform, platform) ); + + bool found_name = false; + found_name = m_crc.has(platform, name.c_str()); + + // Get game ID based on the filename + if(found_name) + { + vector searchID = m_crc.getStrings(platform, name.c_str(), '|'); + + if(!searchID.empty()) + { + GameID = searchID[0]; + } + + m_crc.unload(); + } + // Get game ID by CRC or serial + else + { + m_crc.unload(); + + char crc_string[9]; + crc_string[0] = '\0'; + u32 buffer; + ifstream infile; + + // For arcade games use the crc zip + if(strcasestr(platform, "ARCADE") || strcasestr(platform, "CPS") || !strncasecmp(platform, "NEOGEO", 6)) + { + strncpy(crc_string, fmt("%08x", crc32file(gameHeader->path)), 8); + crc_string[8] = '\0'; + } + else + { + // Look for for the file's crc inside the archive + if(strstr(gameHeader->path, ".zip") != NULL) + { + infile.open(gameHeader->path, ios::binary); + infile.seekg(0x0e, ios::beg); + infile.read((char*)&buffer, 8); + infile.close(); + strncpy(crc_string, fmt("%08x", (u32)__builtin_bswap32(buffer)), 8); + crc_string[8] = '\0'; + } + else + { + // Check a serial in header's file instead of crc for these CD based platforms. + // CRC calculation would take up to 30 seconds! + if(!strcasecmp(platform, "MEGACD")) + { + GetSerialMegaCD(gameHeader->path, CRC_Serial); + } + else if(!strcasecmp(platform, "PS1")) + { + bool found; + found = GetSerialPS1(gameHeader->path, CRC_Serial, 0); + + if(!found) + GetSerialPS1(gameHeader->path, CRC_Serial, 1); + } + else if(!strcasecmp(platform, "ATARIST")) + { + s8 pos = m_plugin.GetPluginPosition(gameHeader->settings[0]); + string FileTypes = m_plugin.GetFileTypes(pos); + string path; + + // Parse config to get floppy A path + if(strcasestr(FileTypes.c_str(), ".cfg")) + { + Config m_cfg; + m_cfg.unload(); + m_cfg.load(fmt("%s", gameHeader->path) ); + path = m_cfg.getString("Floppy", "szDiskAFileName", ""); + m_cfg.unload(); + + // Replace usb:/ with usb1:/ if needed + if (path.find("usb:/") != std::string::npos) + { + path.insert(3, "1"); + } + } + else + { + path = gameHeader->path; + } + + if (path.find(".zip") != std::string::npos) + { + infile.open(path, ios::binary); + infile.seekg(0x0e, ios::beg); + infile.read((char*)&buffer, 8); + infile.close(); + strncpy(crc_string, fmt("%08x", (u32)__builtin_bswap32(buffer)), 8); + crc_string[8] = '\0'; + } + else + { + strncpy(crc_string, fmt("%08x", crc32file(path.c_str())), 8); + crc_string[8] = '\0'; + } + } + // Just check CRC for a regular file on any other system. + else + { + strncpy(crc_string, fmt("%08x", crc32file(gameHeader->path)), 8); + crc_string[8] = '\0'; + } + } + } + + if(crc_string[0] != '\0') + CRC_Serial = crc_string; + + // Now search ID with the obtained CRC/Serial + // Just add 2 pipes in the pattern to be sure we don't find a crc instead of ID + size_t idx; + idx=CRC_Serial.length(); + CRC_Serial.insert(0, "|").insert(idx+1, "|"); + + ifstream inputFile; + inputFile.open( fmt("%s/%s/%s.ini", datadir, platform, platform) ); + string line; + + while(getline(inputFile, line)) + { + // FIXME ahem, ignore case... + if (line.find(lowerCase( CRC_Serial ), 0) != string::npos || line.find(upperCase( CRC_Serial ), 0) != string::npos) + { + unsigned first = (line.find('=') + 1); + unsigned last = line.find_first_of('|'); + string ID = line.substr (first,last-first); + + if(ID.empty()) + { + return ""; + } + + GameID = ID; + break; + } + } + } + + return GameID; +} + string Plugin::GenerateCoverLink(dir_discHdr gameHeader, const string& constURL, Config &Checksums) { string url(constURL); diff --git a/source/plugin/plugin.hpp b/source/plugin/plugin.hpp index 976ca392..60845a2a 100644 --- a/source/plugin/plugin.hpp +++ b/source/plugin/plugin.hpp @@ -65,6 +65,8 @@ public: const char *GetDolName(u32 magic); const char *GetCoverFolderName(u32 magic); const char *GetRomDir(u8 pos); + string GetRomName(const dir_discHdr *gameHeader); + string GetRomId(const dir_discHdr *gameHeader,const char *datadir, char *platform, const string &name); int GetRomPartition(u8 pos); const string& GetFileTypes(u8 pos); wstringEx GetPluginName(u8 pos);