2016-04-02 18:00:47 +02:00
|
|
|
/****************************************************************************
|
|
|
|
* Copyright (C) 2012 FIX94
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
****************************************************************************/
|
|
|
|
#include <gccore.h>
|
|
|
|
#include <ogcsys.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <malloc.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "plugin.hpp"
|
|
|
|
#include "fileOps/fileOps.h"
|
|
|
|
#include "gui/text.hpp"
|
|
|
|
#include "gecko/gecko.hpp"
|
|
|
|
#include "devicemounter/PartitionHandle.h"
|
|
|
|
#include "devicemounter/DeviceHandler.hpp"
|
|
|
|
#include "types.h"
|
|
|
|
#include "crc32.h"
|
|
|
|
|
2018-12-30 23:00:51 +01:00
|
|
|
// For PS1 serial
|
|
|
|
#ifdef MSB_FIRST
|
|
|
|
#define MODETEST_VAL 0x00ffffff
|
|
|
|
#else
|
|
|
|
#define MODETEST_VAL 0xffffff00
|
|
|
|
#endif
|
|
|
|
|
2016-04-02 18:00:47 +02:00
|
|
|
Plugin m_plugin;
|
|
|
|
void Plugin::init(const string& m_pluginsDir)
|
|
|
|
{
|
|
|
|
PluginMagicWord[8] = '\0';
|
|
|
|
pluginsDir = m_pluginsDir;
|
2018-10-31 20:51:15 +01:00
|
|
|
Plugins.clear();
|
2016-04-02 18:00:47 +02:00
|
|
|
adding = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool PluginOptions_cmp(PluginOptions lhs, PluginOptions rhs)
|
|
|
|
{
|
|
|
|
const wchar_t *first = lhs.DisplayName.c_str();
|
|
|
|
const wchar_t *second = rhs.DisplayName.c_str();
|
|
|
|
return wchar_cmp(first, second, wcslen(first), wcslen(second));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plugin::EndAdd()
|
|
|
|
{
|
|
|
|
std::sort(Plugins.begin(), Plugins.end(), PluginOptions_cmp);
|
|
|
|
adding = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Plugin::Cleanup()
|
|
|
|
{
|
|
|
|
Plugins.clear();
|
|
|
|
adding = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plugin::AddPlugin(Config &plugin)
|
|
|
|
{
|
|
|
|
if(!adding)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
PluginOptions NewPlugin;
|
2016-04-03 02:51:40 +02:00
|
|
|
NewPlugin.DolName = plugin.getString(PLUGIN, "dolFile");
|
|
|
|
NewPlugin.coverFolder = plugin.getString(PLUGIN, "coverFolder");
|
|
|
|
NewPlugin.magic = strtoul(plugin.getString(PLUGIN, "magic").c_str(), NULL, 16);
|
|
|
|
NewPlugin.caseColor = strtoul(plugin.getString(PLUGIN, "coverColor").c_str(), NULL, 16);
|
-updating wiiflow lite to beta 4.3.0
-fixed using categories to hide GC disc 2's. Apparently this has never really worked right for some time or ever.
-fixed deleting the cached cover texture file for plugin games. Delete cover for plugins only deletes the cached texture file (.wfc)
-fixed favorites and adultonly (parental lock) for plugin games. Apparently I may have broke this a few revisions back.
-favorites and adultonly for plugin games now have their own domains in gamecfg1- [FAVORITES_PLUGINS] and [ADULTONLY_PLUGINS]. just makes it more organized.
-only wii, GC, channels are added to [PLAYCOUNT] and [LAST_PLAYED] in gamecfg1.
-now loading gamecfg1 at startup and leaving it loaded till exit. no more loading it and unloading all the time.
-fixed scrolling for game_info synopsis, credits, and help text.
-made source menu buttons wider for wiiflow default. old 80x80, now 100x80. looks better.
-display music info now defaults to off
-screensaver_disabled now defaults to yes
-show GC view button is now on by default no matter what. the only way it is disabled is if you edit wiiflow.ini manually. this is how all the view buttons work.
-removed hiding homebrew button but if wiiflow locked it won't work or in sourceflow it won't show.
-dump_list is now under [GENERAL] instead of each view. Also only works for Wii, GC, and channels.
-sorting only works for Wii, GC, and Channels now. disabled for sourceflow, plugins, homebrew, and combined view.
-now if no games are found a message is shown with the current path so you can see where wiiflow is looking. (except for plugins)
-removed auto create emuNAND for emuNAND view if gamelist is empty. just go to settings>NAND settings if you want to extract or disable it.
-now when no games are found all buttons at bottom are still accessible allowing you to change the view or go to settings and change current partition or path and even extract your NAND to create a EmuNAND. Or go to Home Menu and Install a Wii or GC game.
-removed auto extract NAND to emuNAND when launching a Wii game with EmuNAND save. Now a message is diplayed saying 'emuNAND for saves not found - using real NAND'.
-made the speed at which cover titles fade in/out almost instantly.
-removed update button from Home Menu. online update code is still there but not used and probably won't be used any more as there just isn't a need for it now.
-removed ftp button from Home Menu and all code for the FTP server. I just use WiiXplorer's FTP server. it seems to work better for me.
-disabled keep USB Alive thread for now. i think there's a possibilty it might be the cause of my SD/USB files getting corrupted.
-removed Btn B and - combo to switch partitions. didn't seem useful anymore.
-redid nand emulation settings menu. looks like this now:
pg1
Select EmuNAND
EmuNAND Enulation
Select SaveNAND
SaveNAND Emulation
pg2
Extract Saves All
Missing
Extract NAND
Select Saves Partition
-no longer blocking Select Plugin menu and View icons when using source menu combined view
-now Select Plugins Menu is like switching to plugin view but you get to choose the plugins first
-now [PLUGIN] partition= is the default partition for all plugins. to change individual plugins add 'romPartition=x' to the plugin ini. x is the partition number 0 thru 8 with SD being 0. this is how my usbloadergx plugin mod works.
2016-10-05 01:44:13 +02:00
|
|
|
NewPlugin.romPartition = plugin.getInt(PLUGIN, "rompartition", -1);
|
2016-04-03 02:51:40 +02:00
|
|
|
NewPlugin.romDir = plugin.getString(PLUGIN, "romDir");
|
|
|
|
NewPlugin.fileTypes = plugin.getString(PLUGIN, "fileTypes");
|
|
|
|
NewPlugin.Args = plugin.getStrings(PLUGIN, "arguments", '|');
|
2019-07-10 16:24:44 +02:00
|
|
|
NewPlugin.boxMode = plugin.getInt(PLUGIN, "boxmode", -1);
|
2016-04-03 02:51:40 +02:00
|
|
|
string PluginName = plugin.getString(PLUGIN, "displayname");
|
2016-04-02 18:00:47 +02:00
|
|
|
if(PluginName.size() < 2)
|
|
|
|
{
|
|
|
|
PluginName = NewPlugin.DolName;
|
|
|
|
PluginName.erase(PluginName.end() - 4, PluginName.end());
|
|
|
|
}
|
|
|
|
NewPlugin.DisplayName.fromUTF8(PluginName.c_str());
|
2016-04-03 02:51:40 +02:00
|
|
|
NewPlugin.consoleCoverID = plugin.getString(PLUGIN,"consoleCoverID");
|
2016-04-02 18:00:47 +02:00
|
|
|
|
2016-04-03 02:51:40 +02:00
|
|
|
const string &bannerfilepath = sfmt("%s/%s", pluginsDir.c_str(), plugin.getString(PLUGIN,"bannerSound").c_str());
|
2016-04-02 18:00:47 +02:00
|
|
|
fsop_GetFileSizeBytes(bannerfilepath.c_str(), &NewPlugin.BannerSoundSize);
|
|
|
|
if(NewPlugin.BannerSoundSize > 0)
|
|
|
|
NewPlugin.BannerSound = bannerfilepath;
|
|
|
|
Plugins.push_back(NewPlugin);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
s8 Plugin::GetPluginPosition(u32 magic)
|
|
|
|
{
|
|
|
|
for(u8 pos = 0; pos < Plugins.size(); pos++)
|
|
|
|
{
|
2016-04-03 02:51:40 +02:00
|
|
|
if(magic == Plugins[pos].magic)
|
2016-04-02 18:00:47 +02:00
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-04-03 02:51:40 +02:00
|
|
|
u32 Plugin::getPluginMagic(u8 pos)
|
|
|
|
{
|
|
|
|
return Plugins[pos].magic;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:00:47 +02:00
|
|
|
u8* Plugin::GetBannerSound(u32 magic)
|
|
|
|
{
|
|
|
|
if((Plugin_Pos = GetPluginPosition(magic)) >= 0)
|
|
|
|
{
|
|
|
|
u32 size = 0;
|
|
|
|
return fsop_ReadFile(Plugins[Plugin_Pos].BannerSound.c_str(), &size);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 Plugin::GetBannerSoundSize()
|
|
|
|
{
|
|
|
|
//We call that directly after GetBannerSound, so no need to search for the magic again
|
|
|
|
if(Plugin_Pos >= 0)
|
|
|
|
return Plugins[Plugin_Pos].BannerSoundSize;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *Plugin::GetDolName(u32 magic)
|
|
|
|
{
|
|
|
|
if((Plugin_Pos = GetPluginPosition(magic)) >= 0)
|
|
|
|
return Plugins[Plugin_Pos].DolName.c_str();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *Plugin::GetCoverFolderName(u32 magic)
|
|
|
|
{
|
|
|
|
if((Plugin_Pos = GetPluginPosition(magic)) >= 0)
|
2019-10-07 19:15:41 +02:00
|
|
|
return Plugins[Plugin_Pos].coverFolder.c_str();
|
2016-04-02 18:00:47 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
-updating wiiflow lite to beta 4.3.0
-fixed using categories to hide GC disc 2's. Apparently this has never really worked right for some time or ever.
-fixed deleting the cached cover texture file for plugin games. Delete cover for plugins only deletes the cached texture file (.wfc)
-fixed favorites and adultonly (parental lock) for plugin games. Apparently I may have broke this a few revisions back.
-favorites and adultonly for plugin games now have their own domains in gamecfg1- [FAVORITES_PLUGINS] and [ADULTONLY_PLUGINS]. just makes it more organized.
-only wii, GC, channels are added to [PLAYCOUNT] and [LAST_PLAYED] in gamecfg1.
-now loading gamecfg1 at startup and leaving it loaded till exit. no more loading it and unloading all the time.
-fixed scrolling for game_info synopsis, credits, and help text.
-made source menu buttons wider for wiiflow default. old 80x80, now 100x80. looks better.
-display music info now defaults to off
-screensaver_disabled now defaults to yes
-show GC view button is now on by default no matter what. the only way it is disabled is if you edit wiiflow.ini manually. this is how all the view buttons work.
-removed hiding homebrew button but if wiiflow locked it won't work or in sourceflow it won't show.
-dump_list is now under [GENERAL] instead of each view. Also only works for Wii, GC, and channels.
-sorting only works for Wii, GC, and Channels now. disabled for sourceflow, plugins, homebrew, and combined view.
-now if no games are found a message is shown with the current path so you can see where wiiflow is looking. (except for plugins)
-removed auto create emuNAND for emuNAND view if gamelist is empty. just go to settings>NAND settings if you want to extract or disable it.
-now when no games are found all buttons at bottom are still accessible allowing you to change the view or go to settings and change current partition or path and even extract your NAND to create a EmuNAND. Or go to Home Menu and Install a Wii or GC game.
-removed auto extract NAND to emuNAND when launching a Wii game with EmuNAND save. Now a message is diplayed saying 'emuNAND for saves not found - using real NAND'.
-made the speed at which cover titles fade in/out almost instantly.
-removed update button from Home Menu. online update code is still there but not used and probably won't be used any more as there just isn't a need for it now.
-removed ftp button from Home Menu and all code for the FTP server. I just use WiiXplorer's FTP server. it seems to work better for me.
-disabled keep USB Alive thread for now. i think there's a possibilty it might be the cause of my SD/USB files getting corrupted.
-removed Btn B and - combo to switch partitions. didn't seem useful anymore.
-redid nand emulation settings menu. looks like this now:
pg1
Select EmuNAND
EmuNAND Enulation
Select SaveNAND
SaveNAND Emulation
pg2
Extract Saves All
Missing
Extract NAND
Select Saves Partition
-no longer blocking Select Plugin menu and View icons when using source menu combined view
-now Select Plugins Menu is like switching to plugin view but you get to choose the plugins first
-now [PLUGIN] partition= is the default partition for all plugins. to change individual plugins add 'romPartition=x' to the plugin ini. x is the partition number 0 thru 8 with SD being 0. this is how my usbloadergx plugin mod works.
2016-10-05 01:44:13 +02:00
|
|
|
int Plugin::GetRomPartition(u8 pos)
|
|
|
|
{
|
|
|
|
return Plugins[pos].romPartition;
|
|
|
|
}
|
|
|
|
|
2016-04-03 02:51:40 +02:00
|
|
|
const char *Plugin::GetRomDir(u8 pos)
|
2016-04-02 18:00:47 +02:00
|
|
|
{
|
2016-04-03 02:51:40 +02:00
|
|
|
return Plugins[pos].romDir.c_str();
|
|
|
|
}
|
|
|
|
|
|
|
|
const string& Plugin::GetFileTypes(u8 pos)
|
|
|
|
{
|
|
|
|
return Plugins[pos].fileTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 Plugin::GetCaseColor(u8 pos)
|
|
|
|
{
|
|
|
|
return Plugins[pos].caseColor;
|
2016-04-02 18:00:47 +02:00
|
|
|
}
|
|
|
|
|
2019-07-10 16:24:44 +02:00
|
|
|
s8 Plugin::GetBoxMode(u8 pos)
|
2018-09-26 20:26:01 +02:00
|
|
|
{
|
|
|
|
return Plugins[pos].boxMode;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:00:47 +02:00
|
|
|
wstringEx Plugin::GetPluginName(u8 pos)
|
|
|
|
{
|
|
|
|
return Plugins[pos].DisplayName;
|
|
|
|
}
|
|
|
|
|
2016-04-03 02:51:40 +02:00
|
|
|
bool Plugin::PluginExist(u8 pos)
|
|
|
|
{
|
|
|
|
if(pos < Plugins.size())
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:00:47 +02:00
|
|
|
void Plugin::SetEnablePlugin(Config &cfg, u8 pos, u8 ForceMode)
|
|
|
|
{
|
|
|
|
if(pos < Plugins.size())
|
|
|
|
{
|
2016-04-03 02:51:40 +02:00
|
|
|
strncpy(PluginMagicWord, fmt("%08x", Plugins[pos].magic), 8);
|
2016-04-02 18:00:47 +02:00
|
|
|
if(ForceMode == 1)
|
|
|
|
cfg.setBool(PLUGIN_ENABLED, PluginMagicWord, false);
|
|
|
|
else if(ForceMode == 2)
|
|
|
|
cfg.setBool(PLUGIN_ENABLED, PluginMagicWord, true);
|
|
|
|
else
|
|
|
|
cfg.setBool(PLUGIN_ENABLED, PluginMagicWord, cfg.getBool(PLUGIN_ENABLED, PluginMagicWord) ? false : true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Plugin::GetEnableStatus(Config &cfg, u32 magic)
|
|
|
|
{
|
|
|
|
if((Plugin_Pos = GetPluginPosition(magic)) >= 0)
|
|
|
|
{
|
|
|
|
strncpy(PluginMagicWord, fmt("%08x", magic), 8);
|
|
|
|
return cfg.getBool(PLUGIN_ENABLED, PluginMagicWord, true);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const vector<bool> &Plugin::GetEnabledPlugins(Config &cfg, u8 *num)
|
|
|
|
{
|
|
|
|
enabledPlugins.clear();
|
|
|
|
u8 enabledPluginsNumber = 0;
|
|
|
|
for(u8 i = 0; i < Plugins.size(); i++)
|
|
|
|
{
|
2016-04-03 02:51:40 +02:00
|
|
|
strncpy(PluginMagicWord, fmt("%08x", Plugins[i].magic), 8);
|
2016-04-02 18:00:47 +02:00
|
|
|
if(cfg.getBool(PLUGIN_ENABLED, PluginMagicWord, true))
|
|
|
|
{
|
|
|
|
enabledPluginsNumber++;
|
|
|
|
enabledPlugins.push_back(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
enabledPlugins.push_back(false);
|
|
|
|
}
|
|
|
|
if(enabledPluginsNumber == Plugins.size())
|
|
|
|
enabledPlugins.clear();
|
|
|
|
if(num != NULL)
|
|
|
|
*num = enabledPluginsNumber;
|
|
|
|
return enabledPlugins;
|
|
|
|
}
|
|
|
|
|
|
|
|
vector<string> Plugin::CreateArgs(const char *device, const char *path,
|
|
|
|
const char *title, const char *loader, u32 title_len_no_ext, u32 magic)
|
|
|
|
{
|
|
|
|
vector<string> args;
|
|
|
|
Plugin_Pos = GetPluginPosition(magic);
|
|
|
|
if(Plugin_Pos < 0)
|
|
|
|
return args;
|
|
|
|
for(vector<string>::const_iterator arg = Plugins[Plugin_Pos].Args.begin();
|
|
|
|
arg != Plugins[Plugin_Pos].Args.end(); ++arg)
|
|
|
|
{
|
|
|
|
string Argument(*arg);
|
|
|
|
if(Argument.find(PLUGIN_DEV) != string::npos)
|
|
|
|
Argument.replace(Argument.find(PLUGIN_DEV), strlen(PLUGIN_DEV), device);
|
|
|
|
if(Argument.find(PLUGIN_PATH) != string::npos)
|
|
|
|
Argument.replace(Argument.find(PLUGIN_PATH), strlen(PLUGIN_PATH), path);
|
|
|
|
if(Argument.find(PLUGIN_NAME) != string::npos)
|
|
|
|
Argument.replace(Argument.find(PLUGIN_NAME), strlen(PLUGIN_NAME), title);
|
|
|
|
if(Argument.find(PLUGIN_LDR) != string::npos)
|
|
|
|
Argument.replace(Argument.find(PLUGIN_LDR), strlen(PLUGIN_LDR), loader);
|
|
|
|
if(Argument.find(PLUGIN_NOEXT) != string::npos)
|
|
|
|
Argument.replace(Argument.find(PLUGIN_NOEXT), strlen(PLUGIN_NOEXT), title, title_len_no_ext);
|
|
|
|
args.push_back(Argument);
|
|
|
|
}
|
|
|
|
return args;
|
|
|
|
}
|
|
|
|
|
2018-12-30 23:00:51 +01:00
|
|
|
/* Give the current game a simplified name */
|
2019-09-11 22:57:16 +02:00
|
|
|
string Plugin::GetRomName(const char *FullPath)
|
2018-12-30 23:00:51 +01:00
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
string FullName = strrchr(FullPath, '/') + 1;
|
|
|
|
FullName = FullName.substr(0, FullName.find_last_of("."));
|
2019-02-26 23:01:14 +01:00
|
|
|
|
2019-09-11 22:57:16 +02:00
|
|
|
// 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;
|
2018-12-30 23:00:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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<char *>(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.
|
|
|
|
*/
|
2019-09-11 22:57:16 +02:00
|
|
|
string Plugin::GetRomId(char *romPath, u32 Magic, Config &m_crc, const char *datadir, const char *platform, const char *name)
|
2018-12-30 23:00:51 +01:00
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
string GameID = "";
|
|
|
|
string CRC_Serial(12, '*');// 12 digits because of ps1 and megaCD serials which can be 10 or more digits
|
2018-12-30 23:00:51 +01:00
|
|
|
|
2019-09-11 22:57:16 +02:00
|
|
|
// Search a platform list that is used to identify the current game.
|
2018-12-30 23:00:51 +01:00
|
|
|
// 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|...
|
|
|
|
|
|
|
|
// Get game ID based on the filename
|
2019-09-11 22:57:16 +02:00
|
|
|
if(m_crc.has(platform, name))
|
2018-12-30 23:00:51 +01:00
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
vector<string> searchID = m_crc.getStrings(platform, name, '|');
|
|
|
|
if(!searchID[0].empty())
|
2018-12-30 23:00:51 +01:00
|
|
|
GameID = searchID[0];
|
|
|
|
}
|
|
|
|
// Get game ID by CRC or serial
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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))
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
strncpy(crc_string, fmt("%08x", crc32file(romPath)), 8);
|
2018-12-30 23:00:51 +01:00
|
|
|
crc_string[8] = '\0';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Look for for the file's crc inside the archive
|
2019-09-11 22:57:16 +02:00
|
|
|
if(strstr(romPath, ".zip") != NULL)
|
2018-12-30 23:00:51 +01:00
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
infile.open(romPath, ios::binary);
|
2018-12-30 23:00:51 +01:00
|
|
|
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"))
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
GetSerialMegaCD(romPath, CRC_Serial);
|
2018-12-30 23:00:51 +01:00
|
|
|
}
|
|
|
|
else if(!strcasecmp(platform, "PS1"))
|
|
|
|
{
|
|
|
|
bool found;
|
2019-09-11 22:57:16 +02:00
|
|
|
found = GetSerialPS1(romPath, CRC_Serial, 0);
|
2018-12-30 23:00:51 +01:00
|
|
|
|
|
|
|
if(!found)
|
2019-09-11 22:57:16 +02:00
|
|
|
GetSerialPS1(romPath, CRC_Serial, 1);
|
2018-12-30 23:00:51 +01:00
|
|
|
}
|
|
|
|
else if(!strcasecmp(platform, "ATARIST"))
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
s8 pos = m_plugin.GetPluginPosition(Magic);
|
2018-12-30 23:00:51 +01:00
|
|
|
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();
|
2019-09-11 22:57:16 +02:00
|
|
|
m_cfg.load(fmt("%s", romPath) );
|
2018-12-30 23:00:51 +01:00
|
|
|
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
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
path = romPath;
|
2018-12-30 23:00:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
}
|
2019-03-29 21:26:22 +01:00
|
|
|
else if(!strcasecmp(platform, "DOS"))
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
s8 pos = m_plugin.GetPluginPosition(Magic);
|
2019-03-29 21:26:22 +01:00
|
|
|
string FileTypes = m_plugin.GetFileTypes(pos);
|
|
|
|
|
|
|
|
if(strcasestr(FileTypes.c_str(), ".conf"))
|
|
|
|
{
|
|
|
|
ifstream inputFile;
|
2019-09-11 22:57:16 +02:00
|
|
|
inputFile.open(romPath, std::ios::binary);
|
2019-03-29 21:26:22 +01:00
|
|
|
string line;
|
|
|
|
std::string dospath;
|
|
|
|
std::string temp;
|
|
|
|
string delimiter = "mount c ";
|
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
while(getline(inputFile, line))
|
|
|
|
{
|
|
|
|
// Construct exe path in 3 steps:
|
|
|
|
// 1. base folder from 'mount c' command
|
|
|
|
// 2. game's folder from 'cd'
|
|
|
|
// 3. executable name.
|
|
|
|
if (line.find( delimiter, 0) != string::npos && found != 3)
|
|
|
|
{
|
|
|
|
found++;
|
|
|
|
// Remove special line ending(^M) introduced by notepad.
|
|
|
|
// Without this, dospath concatenation is messed up.
|
|
|
|
// line = trimEnd(line) from config.cpp may be a better alternative.
|
|
|
|
if (!line.empty() && line[line.length()-1] == '\r')
|
|
|
|
{
|
|
|
|
line.erase(line.length()-1);
|
|
|
|
}
|
|
|
|
temp = line.substr (delimiter.length(), (line.length() - delimiter.length()));
|
|
|
|
}
|
|
|
|
|
|
|
|
if(found == 1)
|
|
|
|
{
|
|
|
|
delimiter = "cd ";
|
|
|
|
dospath = temp;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if(found == 2)
|
|
|
|
{
|
|
|
|
dospath = dospath + '/' + temp + '/';
|
|
|
|
found++;
|
|
|
|
// break;
|
|
|
|
}
|
|
|
|
else if(found == 3)
|
|
|
|
{
|
|
|
|
if(strcasestr(line.c_str(), ".bat") || strcasestr(line.c_str(), ".com") || strcasestr(line.c_str(), ".exe"))
|
|
|
|
{
|
|
|
|
if (!line.empty() && line[line.length()-1] == '\r')
|
|
|
|
{
|
|
|
|
line.erase(line.length()-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
dospath += line;
|
|
|
|
|
|
|
|
// Replace usb:/ with usb1:/ if needed
|
|
|
|
if (dospath.find("usb:/") != std::string::npos)
|
|
|
|
{
|
|
|
|
dospath.insert(3, "1");
|
|
|
|
}
|
|
|
|
|
|
|
|
strncpy(crc_string, fmt("%08x", crc32file(dospath.c_str())), 8);
|
|
|
|
crc_string[8] = '\0';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}// Just check CRC for a regular file on any other system.
|
2018-12-30 23:00:51 +01:00
|
|
|
else
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
strncpy(crc_string, fmt("%08x", crc32file(romPath)), 8);
|
2018-12-30 23:00:51 +01:00
|
|
|
crc_string[8] = '\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(crc_string[0] != '\0')
|
2019-09-11 22:57:16 +02:00
|
|
|
{
|
2018-12-30 23:00:51 +01:00
|
|
|
CRC_Serial = crc_string;
|
2019-09-11 22:57:16 +02:00
|
|
|
//gprintf("romCRC=%s\n", crc_string);
|
|
|
|
}
|
2018-12-30 23:00:51 +01:00
|
|
|
|
2019-09-11 22:57:16 +02:00
|
|
|
/****************************************************************/
|
|
|
|
/* 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 */
|
|
|
|
/* note crc's are 8 digits but serials can be more, thats why we use idx for CRC_Serial length */
|
2018-12-30 23:00:51 +01:00
|
|
|
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))
|
|
|
|
{
|
2019-09-11 22:57:16 +02:00
|
|
|
// FIXME ahem, ignore case... - line could contain a mix of lower and upper case. if so this 'if' will not work
|
|
|
|
if(line.find(lowerCase( CRC_Serial ), 0) != string::npos || line.find(upperCase( CRC_Serial ), 0) != string::npos)
|
2018-12-30 23:00:51 +01:00
|
|
|
{
|
|
|
|
unsigned first = (line.find('=') + 1);
|
2019-09-11 22:57:16 +02:00
|
|
|
unsigned last = line.find_first_of('|');// we could just use first + 6 since all ID's are 6 digits
|
2018-12-30 23:00:51 +01:00
|
|
|
string ID = line.substr (first,last-first);
|
|
|
|
|
2019-09-11 22:57:16 +02:00
|
|
|
if(!ID.empty())
|
|
|
|
GameID = ID;
|
2018-12-30 23:00:51 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return GameID;
|
|
|
|
}
|
|
|
|
|
2016-04-02 18:00:47 +02:00
|
|
|
string Plugin::GenerateCoverLink(dir_discHdr gameHeader, const string& constURL, Config &Checksums)
|
|
|
|
{
|
|
|
|
string url(constURL);
|
|
|
|
Plugin_Pos = GetPluginPosition(gameHeader.settings[0]);
|
|
|
|
|
|
|
|
if(url.find(TAG_LOC) != url.npos)
|
|
|
|
url.replace(url.find(TAG_LOC), strlen(TAG_LOC), "EN");
|
|
|
|
|
|
|
|
if(url.find(TAG_CONSOLE) != url.npos)
|
|
|
|
url.replace(url.find(TAG_CONSOLE), strlen(TAG_CONSOLE), (Plugins[Plugin_Pos].consoleCoverID.size() ? Plugins[Plugin_Pos].consoleCoverID.c_str() : "nintendo"));
|
|
|
|
|
|
|
|
char gamePath[256];
|
|
|
|
if(string(gameHeader.path).find_last_of("/") != string::npos)
|
|
|
|
strncpy(gamePath, &gameHeader.path[string(gameHeader.path).find_last_of("/")+1], sizeof(gamePath));
|
|
|
|
else
|
|
|
|
strncpy(gamePath, gameHeader.path, sizeof(gamePath));
|
|
|
|
const string& cachedCRC = Checksums.getString("CHECKSUMS", gamePath);
|
|
|
|
char crc_string[9];
|
|
|
|
crc_string[8] = '\0';
|
|
|
|
if(cachedCRC.size() == 8)
|
|
|
|
{
|
|
|
|
gprintf("CRC32 of %s is cached\n", gamePath);
|
|
|
|
strncpy(crc_string, cachedCRC.c_str(), 8);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gprintf("Generating CRC32 for %s\n", gamePath);
|
|
|
|
u32 buffer;
|
|
|
|
ifstream infile;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
else if(strstr(gameHeader.path, ".7z") != NULL)
|
|
|
|
{
|
|
|
|
infile.open(gameHeader.path, ios::binary);
|
|
|
|
infile.seekg(-8, ios::end);
|
|
|
|
while(infile.tellg())
|
|
|
|
{
|
|
|
|
infile.read((char*)&buffer, 8);
|
|
|
|
if(buffer == 0x00050111)
|
|
|
|
break;
|
|
|
|
infile.seekg(-9, ios::cur);
|
|
|
|
}
|
|
|
|
infile.seekg(-13, ios::cur);
|
|
|
|
infile.read((char*)&buffer, 8);
|
|
|
|
infile.close();
|
|
|
|
strncpy(crc_string, fmt("%08x", (u32)__builtin_bswap32(buffer)), 8);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
strncpy(crc_string, fmt("%08x", crc32file(gameHeader.path)), 8);
|
|
|
|
Checksums.setString("CHECKSUMS", gamePath, crc_string);
|
|
|
|
Checksums.save();
|
|
|
|
}
|
|
|
|
url.replace(url.find(TAG_GAME_ID), strlen(TAG_GAME_ID), upperCase(crc_string).c_str());
|
|
|
|
gprintf("URL: %s\n", url.c_str());
|
|
|
|
return url;
|
|
|
|
}
|