diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index c23eadebae..bb1e12e9c9 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -17,94 +17,87 @@ #define _PC_ // disables MIPS opcode macros. +#include "common/FileSystem.h" #include "common/StringUtil.h" -#include "Patch.h" +#include "common/ZipHelpers.h" + #include "Config.h" +#include "Patch.h" +#include "PathDefs.h" #include +#include #include -#include -#include -#include -#include -#include -#include "PathDefs.h" // This is a declaration for PatchMemory.cpp::_ApplyPatch where we're (patch.cpp) // the only consumer, so it's not made public via Patch.h // Applies a single patch line to emulation memory regardless of its "place" value. extern void _ApplyPatch(IniPatch* p); - static std::vector Patch; - struct PatchTextTable { - int code; - const wxChar* text; - PATCHTABLEFUNC* func; + int code; + const char* text; + PATCHTABLEFUNC* func; }; static const PatchTextTable commands_patch[] = -{ - { 1, L"author", PatchFunc::author}, - { 2, L"comment", PatchFunc::comment }, - { 3, L"patch", PatchFunc::patch }, - { 0, wxEmptyString, NULL } // Array Terminator + { + {1, "author", PatchFunc::author}, + {2, "comment", PatchFunc::comment}, + {3, "patch", PatchFunc::patch}, + {0, nullptr, nullptr} // Array Terminator }; static const PatchTextTable dataType[] = -{ - { 1, L"byte", NULL }, - { 2, L"short", NULL }, - { 3, L"word", NULL }, - { 4, L"double", NULL }, - { 5, L"extended", NULL }, - { 6, L"leshort", NULL}, - { 7, L"leword", NULL}, - { 8, L"ledouble", NULL}, - { 0, wxEmptyString, NULL } + { + {1, "byte", nullptr}, + {2, "short", nullptr}, + {3, "word", nullptr}, + {4, "double", nullptr}, + {5, "extended", nullptr}, + {6, "leshort", nullptr}, + {7, "leword", nullptr}, + {8, "ledouble", nullptr}, + {0, nullptr, nullptr} // Array Terminator }; static const PatchTextTable cpuCore[] = -{ - { 1, L"EE", NULL }, - { 2, L"IOP", NULL }, - { 0, wxEmptyString, NULL } + { + {1, "EE", nullptr}, + {2, "IOP", nullptr}, + {0, nullptr, nullptr} // Array Terminator }; // IniFile Functions. -static void inifile_trim(wxString& buffer) +static void inifile_trim(std::string& buffer) { - buffer.Trim(false); // trims left side. - - if (buffer.Length() <= 1) // this I'm not sure about... - air + StringUtil::StripWhitespace(&buffer); + if (std::strncmp(buffer.c_str(), "//", 2) == 0) { - buffer.Empty(); - return; + // comment + buffer.clear(); } - if (buffer.Left(2) == L"//") - { - buffer.Empty(); - return; - } - - buffer.Trim(true); // trims right side. + // check for comments at the end of a line + const std::string::size_type pos = buffer.find("//"); + if (pos != std::string::npos) + buffer.erase(pos); } -static int PatchTableExecute(const ParsedAssignmentString& set, const PatchTextTable* Table) +static int PatchTableExecute(const std::string_view& lhs, const std::string_view& rhs, const PatchTextTable* Table) { int i = 0; - while (Table[i].text[0]) + while (Table[i].text) { - if (!set.lvalue.Cmp(Table[i].text)) + if (lhs.compare(Table[i].text) == 0) { if (Table[i].func) - Table[i].func(set.lvalue, set.rvalue); + Table[i].func(lhs, rhs); break; } i++; @@ -114,16 +107,17 @@ static int PatchTableExecute(const ParsedAssignmentString& set, const PatchTextT } // This routine is for executing the commands of the ini file. -static void inifile_command(const wxString& cmd) +static void inifile_command(const std::string& cmd) { - ParsedAssignmentString set(cmd); + std::string_view key, value; + StringUtil::ParseAssignmentString(cmd, &key, &value); // Is this really what we want to be doing here? Seems like just leaving it empty/blank // would make more sense... --air - if (set.rvalue.IsEmpty()) - set.rvalue = set.lvalue; + if (value.empty()) + value = key; - /*int code = */ PatchTableExecute(set, commands_patch); + /*int code = */ PatchTableExecute(key, value, commands_patch); } // This routine loads patches from the game database (but not the config/game fixes/hacks) @@ -134,27 +128,21 @@ int LoadPatchesFromGamesDB(const std::string& crc, const GameDatabaseSchema::Gam if (patch) { for (const std::string& line : *patch) - inifile_command(StringUtil::UTF8StringToWxString(line)); + inifile_command(line); } return Patch.size(); } -void inifile_processString(const wxString& inStr) +static void inifile_processString(const std::string& inStr) { - wxString str(inStr); - inifile_trim(str); - if (!str.IsEmpty()) - inifile_command(str); -} - -// This routine receives a file from inifile_read, trims it, -// Then sends the command to be parsed. -void inifile_process(wxTextFile& f1) -{ - for (uint i = 0; i < f1.GetLineCount(); i++) + std::istringstream ss(inStr); + std::string line; + while (std::getline(ss, line)) { - inifile_processString(f1[i]); + inifile_trim(line); + if (!line.empty()) + inifile_command(line); } } @@ -163,68 +151,28 @@ void ForgetLoadedPatches() Patch.clear(); } -static int _LoadPatchFiles(const wxDirName& folderName, wxString& fileSpec, const wxString& friendlyName, int& numberFoundPatchFiles) -{ - numberFoundPatchFiles = 0; - - if (!folderName.Exists()) - { - Console.WriteLn(Color_Red, L"The %s folder ('%s') is inaccessible. Skipping...", WX_STR(friendlyName), WX_STR(folderName.ToString())); - return 0; - } - wxDir dir(folderName.ToString()); - - int before = Patch.size(); - wxString buffer; - wxTextFile f; - bool found = dir.GetFirst(&buffer, L"*", wxDIR_FILES); - while (found) - { - if (buffer.Upper().Matches(fileSpec.Upper())) - { - PatchesCon->WriteLn(Color_Green, L"Found %s file: '%s'", WX_STR(friendlyName), WX_STR(buffer)); - int before = Patch.size(); - f.Open(Path::Combine(dir.GetName(), buffer)); - inifile_process(f); - f.Close(); - int loaded = Patch.size() - before; - PatchesCon->WriteLn((loaded ? Color_Green : Color_Gray), L"Loaded %d %s from '%s' at '%s'", - loaded, WX_STR(friendlyName), WX_STR(buffer), WX_STR(folderName.ToString())); - numberFoundPatchFiles++; - } - found = dir.GetNext(&buffer); - } - - return Patch.size() - before; -} - // This routine loads patches from a zip file // Returns number of patches loaded // Note: does not reset previously loaded patches (use ForgetLoadedPatches() for that) // Note: only load patches from the root folder of the zip -int LoadPatchesFromZip(const wxString& gameCRC, const wxString& patchesArchiveFilename, wxInputStream* stream) +int LoadPatchesFromZip(const std::string& crc, const u8* zip_data, size_t zip_data_size) { - wxString upperGameCRC(gameCRC.Upper()); + zip_error ze = {}; + auto zf = zip_open_buffer_managed(zip_data, zip_data_size, ZIP_RDONLY, 0, &ze); + if (!zf) + return 0; - int before = Patch.size(); + const int before = Patch.size(); + + const std::string pnach_filename(crc + ".pnach"); + std::optional pnach_data(ReadFileInZipToString(zf.get(), pnach_filename.c_str())); + if (!pnach_data.has_value()) + return 0; + + PatchesCon->WriteLn(Color_Green, "Loading patch '%s' from archive.", pnach_filename.c_str()); + + inifile_processString(pnach_data.value()); - std::unique_ptr entry; - wxZipInputStream zip(stream); - while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) - { - wxString name = entry->GetName(); - name.MakeUpper(); - if (name.Find(upperGameCRC) == 0 && name.Find(L".PNACH") + 6u == name.Length()) - { - PatchesCon->WriteLn(Color_Green, L"Loading patch '%s' from archive '%s'", - WX_STR(entry->GetName()), WX_STR(patchesArchiveFilename)); - wxTextInputStream pnach(zip); - while (!zip.Eof()) - { - inifile_processString(pnach.ReadLine()); - } - } - } return Patch.size() - before; } @@ -232,70 +180,62 @@ int LoadPatchesFromZip(const wxString& gameCRC, const wxString& patchesArchiveFi // This routine loads patches from *.pnach files // Returns number of patches loaded // Note: does not reset previously loaded patches (use ForgetLoadedPatches() for that) -int LoadPatchesFromDir(const wxString& name, const wxDirName& folderName, const wxString& friendlyName) +int LoadPatchesFromDir(const std::string& crc, const wxDirName& folder, const char* friendly_name, bool show_error_when_missing) { - int loaded = 0; - int numberFoundPatchFiles; - - wxString filespec = name + L"*.pnach"; - loaded += _LoadPatchFiles(folderName, filespec, friendlyName, numberFoundPatchFiles); - - if (folderName.ToString().IsSameAs(EmuFolders::Cheats.ToString()) && numberFoundPatchFiles == 0) + if (!folder.Exists()) { - wxString pathName = Path::Combine(folderName, name.Upper() + L".pnach"); - PatchesCon->WriteLn(Color_Gray, L"Not found %s file: %s", WX_STR(friendlyName), WX_STR(pathName)); + Console.WriteLn(Color_Red, "The %s folder ('%s') is inaccessible. Skipping...", friendly_name, folder.ToUTF8().data()); + return 0; } - PatchesCon->WriteLn((loaded ? Color_Green : Color_Gray), L"Overall %d %s loaded", loaded, WX_STR(friendlyName)); + FileSystem::FindResultsArray files; + FileSystem::FindFiles(folder.ToUTF8(), StringUtil::StdStringFromFormat("%s*.pnach", crc.c_str()).c_str(), + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &files); + + if (show_error_when_missing && files.empty()) + { + PatchesCon->WriteLn(Color_Gray, "Not found %s file: %s" FS_OSPATH_SEPARATOR_STR "%s.pnach", + friendly_name, folder.ToUTF8().data(), crc.c_str()); + } + + const size_t before_all = Patch.size(); + + for (const FILESYSTEM_FIND_DATA& fd : files) + { + const std::string_view name(FileSystem::GetFileNameFromPath(fd.FileName)); + PatchesCon->WriteLn(Color_Green, "Found %s file: '%.*s'", friendly_name, static_cast(name.size()), name.data()); + + const std::optional pnach_data(FileSystem::ReadFileToString(fd.FileName.c_str())); + if (!pnach_data.has_value()) + continue; + + const size_t before = Patch.size(); + inifile_processString(pnach_data.value()); + const size_t loaded = Patch.size() - before; + + PatchesCon->WriteLn((loaded ? Color_Green : Color_Gray), "Loaded %zu %s from '%.*s'.", + loaded, friendly_name, static_cast(name.size()), name.data()); + } + + const size_t loaded = Patch.size() - before_all; + PatchesCon->WriteLn((loaded ? Color_Green : Color_Gray), "Overall %zu %s loaded", loaded, friendly_name); return loaded; } -static u32 StrToU32(const wxString& str, int base = 10) -{ - unsigned long l; - str.ToULong(&l, base); - return l; -} - -static u64 StrToU64(const wxString& str, int base = 10) -{ - wxULongLong_t l; - str.ToULongLong(&l, base); - return l; -} - // PatchFunc Functions. namespace PatchFunc { - void comment(const wxString& text1, const wxString& text2) + void comment(const std::string_view& text1, const std::string_view& text2) { - PatchesCon->WriteLn(L"comment: " + text2); + PatchesCon->WriteLn("comment: %.*s", static_cast(text2.length()), text2.data()); } - void author(const wxString& text1, const wxString& text2) + void author(const std::string_view& text1, const std::string_view& text2) { - PatchesCon->WriteLn(L"Author: " + text2); + PatchesCon->WriteLn("Author: %.*s", static_cast(text2.length()), text2.data()); } - struct PatchPieces - { - wxArrayString m_pieces; - - PatchPieces(const wxString& param) - { - SplitString(m_pieces, param, L","); - if (m_pieces.Count() < 5) - throw wxsFormat(L"Expected 5 data parameters; only found %d", m_pieces.Count()); - } - - const wxString& PlaceToPatch() const { return m_pieces[0]; } - const wxString& CpuType() const { return m_pieces[1]; } - const wxString& MemAddr() const { return m_pieces[2]; } - const wxString& OperandSize() const { return m_pieces[3]; } - const wxString& WriteValue() const { return m_pieces[4]; } - }; - - void patchHelper(const wxString& cmd, const wxString& param) + void patchHelper(const std::string_view& cmd, const std::string_view& param) { // Error Handling Note: I just throw simple wxStrings here, and then catch them below and // format them into more detailed cmd+data+error printouts. If we want to add user-friendly @@ -304,40 +244,58 @@ namespace PatchFunc // print the actual patch lines only in verbose mode (even in devel) if (DevConWriterEnabled) - DevCon.WriteLn(cmd + L" " + param); - - try { - PatchPieces pieces(param); - - IniPatch iPatch = {0}; - iPatch.enabled = 0; - iPatch.placetopatch = StrToU32(pieces.PlaceToPatch(), 10); - - if (iPatch.placetopatch >= _PPT_END_MARKER) - throw wxsFormat(L"Invalid 'place' value '%s' (0 - once on startup, 1: continuously)", WX_STR(pieces.PlaceToPatch())); - - iPatch.cpu = (patch_cpu_type)PatchTableExecute(pieces.CpuType(), cpuCore); - iPatch.addr = StrToU32(pieces.MemAddr(), 16); - iPatch.type = (patch_data_type)PatchTableExecute(pieces.OperandSize(), dataType); - iPatch.data = StrToU64(pieces.WriteValue(), 16); - - if (iPatch.cpu == 0) - throw wxsFormat(L"Unrecognized CPU Target: '%s'", WX_STR(pieces.CpuType())); - - if (iPatch.type == 0) - throw wxsFormat(L"Unrecognized Operand Size: '%s'", WX_STR(pieces.OperandSize())); - - iPatch.enabled = 1; // omg success!! - Patch.push_back(iPatch); + DevCon.WriteLn("%.*s %.*s", static_cast(cmd.size()), cmd.data(), + static_cast(param.size()), param.data()); } - catch (wxString& exmsg) + +#define PATCH_ERROR(fmt, ...) Console.Error("(Patch) Error Parsing: %.*s=%.*s: " fmt, \ + static_cast(cmd.size()), cmd.data(), static_cast(param.size()), param.data(), \ + __VA_ARGS__) + + // [0]=PlaceToPatch,[1]=CpuType,[2]=MemAddr,[3]=OperandSize,[4]=WriteValue + const std::vector pieces(StringUtil::SplitString(param, ',', false)); + if (pieces.size() != 5) { - Console.Error(L"(Patch) Error Parsing: %s=%s", WX_STR(cmd), WX_STR(param)); - Console.Indent().Error(exmsg); + PATCH_ERROR("Expected 5 data parameters; only found %zu", pieces.size()); + return; } + + IniPatch iPatch = {0}; + iPatch.enabled = 0; + iPatch.placetopatch = StringUtil::FromChars(pieces[0]).value_or(_PPT_END_MARKER); + + if (iPatch.placetopatch >= _PPT_END_MARKER) + { + PATCH_ERROR("Invalid 'place' value '%.*s' (0 - once on startup, 1: continuously)", + static_cast(pieces[0].size()), pieces[0].data()); + return; + } + + iPatch.cpu = (patch_cpu_type)PatchTableExecute(pieces[1], std::string_view(), cpuCore); + iPatch.addr = StringUtil::FromChars(pieces[2], 16).value_or(0); + iPatch.type = (patch_data_type)PatchTableExecute(pieces[3], std::string_view(), dataType); + iPatch.data = StringUtil::FromChars(pieces[4], 16).value_or(0); + + if (iPatch.cpu == 0) + { + PATCH_ERROR("Unrecognized CPU Target: '%s'", static_cast(pieces[1].size()), pieces[1].data()); + return; + } + + if (iPatch.type == 0) + { + PATCH_ERROR("Unrecognized Operand Size: '%s'", static_cast(pieces[3].size()), pieces[3].data()); + return; + } + + iPatch.enabled = 1; + Patch.push_back(iPatch); + +#undef PATCH_ERROR } - void patch(const wxString& cmd, const wxString& param) { patchHelper(cmd, param); } + + void patch(const std::string_view& cmd, const std::string_view& param) { patchHelper(cmd, param); } } // namespace PatchFunc // This is for applying patches directly to memory diff --git a/pcsx2/Patch.h b/pcsx2/Patch.h index d3f3db205e..31ee7c4a7c 100644 --- a/pcsx2/Patch.h +++ b/pcsx2/Patch.h @@ -38,8 +38,7 @@ #include "common/Pcsx2Defs.h" #include "SysForwardDefs.h" #include "GameDatabase.h" - -class wxInputStream; +#include enum patch_cpu_type { NO_CPU, @@ -85,7 +84,7 @@ enum patch_place_type { _PPT_END_MARKER }; -typedef void PATCHTABLEFUNC( const wxString& text1, const wxString& text2 ); +typedef void PATCHTABLEFUNC(const std::string_view& text1, const std::string_view& text2); struct IniPatch { @@ -109,8 +108,8 @@ namespace PatchFunc // - do not reset/unload previously loaded patches (use ForgetLoadedPatches() for that) // - do not actually patch the emulation memory (that happens at ApplyLoadedPatches(...) ) extern int LoadPatchesFromGamesDB(const std::string& crc, const GameDatabaseSchema::GameEntry& game); -extern int LoadPatchesFromDir(const wxString& name, const wxDirName& folderName, const wxString& friendlyName); -extern int LoadPatchesFromZip(const wxString& gameCRC, const wxString& patchesArchiveFilename, wxInputStream* stream); +extern int LoadPatchesFromDir(const std::string& crc, const wxDirName& folder, const char* friendly_name, bool show_error_when_missing); +extern int LoadPatchesFromZip(const std::string& crc, const u8* zip_data, size_t zip_data_size); // Patches the emulation memory by applying all the loaded patches with a specific place value. // Note: unless you know better, there's no need to check whether or not different patch sources diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index b5071e11fe..094fcbad0e 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -348,7 +348,7 @@ static void LoadPatches(const std::string& crc_string, bool show_messages, bool int cheat_count = 0; if (EmuConfig.EnableCheats) { - cheat_count = LoadPatchesFromDir(StringUtil::UTF8StringToWxString(crc_string), EmuFolders::Cheats, L"Cheats"); + cheat_count = LoadPatchesFromDir(crc_string, EmuFolders::Cheats, "Cheats", true); if (cheat_count > 0) { PatchesCon->WriteLn(Color_Green, "Cheats Loaded: %d", cheat_count); @@ -360,7 +360,7 @@ static void LoadPatches(const std::string& crc_string, bool show_messages, bool int ws_patch_count = 0; if (EmuConfig.EnableWideScreenPatches && s_game_crc != 0) { - if (ws_patch_count = LoadPatchesFromDir(StringUtil::UTF8StringToWxString(crc_string), EmuFolders::CheatsWS, L"Widescreen hacks")) + if (ws_patch_count = LoadPatchesFromDir(crc_string, EmuFolders::CheatsWS, "Widescreen hacks", false)) { Console.WriteLn(Color_Gray, "Found widescreen patches in the cheats_ws folder --> skipping cheats_ws.zip"); } @@ -376,7 +376,7 @@ static void LoadPatches(const std::string& crc_string, bool show_messages, bool if (!s_widescreen_cheats_data.empty()) { - ws_patch_count = LoadPatchesFromZip(StringUtil::UTF8StringToWxString(crc_string), wxT("cheats_ws.zip"), new wxMemoryInputStream(s_widescreen_cheats_data.data(), s_widescreen_cheats_data.size())); + ws_patch_count = LoadPatchesFromZip(crc_string, s_widescreen_cheats_data.data(), s_widescreen_cheats_data.size()); PatchesCon->WriteLn(Color_Green, "(Wide Screen Cheats DB) Patches Loaded: %d", ws_patch_count); } } diff --git a/pcsx2/gui/AppCoreThread.cpp b/pcsx2/gui/AppCoreThread.cpp index 35d3d09883..93260cf191 100644 --- a/pcsx2/gui/AppCoreThread.cpp +++ b/pcsx2/gui/AppCoreThread.cpp @@ -26,6 +26,7 @@ #include "common/StringUtil.h" #include "common/Threading.h" +#include "Host.h" #include "ps2/BiosTools.h" #include "GS.h" @@ -43,6 +44,9 @@ alignas(16) SysMtgsThread mtgsThread; alignas(16) AppCoreThread CoreThread; +static std::vector s_widescreen_cheats_data; +static bool s_widescreen_cheats_loaded = false; + typedef void (AppCoreThread::*FnPtr_CoreThreadMethod)(); SysCoreThread& GetCoreThread() @@ -426,12 +430,12 @@ static void _ApplySettings(const Pcsx2Config& src, Pcsx2Config& fixup) // regular cheat patches if (fixup.EnableCheats) - gameCheats.Printf(L" [%d Cheats]", LoadPatchesFromDir(GameInfo::gameCRC, EmuFolders::Cheats, L"Cheats")); + gameCheats.Printf(L" [%d Cheats]", LoadPatchesFromDir(StringUtil::wxStringToUTF8String(GameInfo::gameCRC), EmuFolders::Cheats, "Cheats", true)); // wide screen patches if (fixup.EnableWideScreenPatches) { - if (int numberLoadedWideScreenPatches = LoadPatchesFromDir(GameInfo::gameCRC, EmuFolders::CheatsWS, L"Widescreen hacks")) + if (int numberLoadedWideScreenPatches = LoadPatchesFromDir(StringUtil::wxStringToUTF8String(GameInfo::gameCRC), EmuFolders::CheatsWS, "Widescreen hacks", false)) { gameWsHacks.Printf(L" [%d widescreen hacks]", numberLoadedWideScreenPatches); Console.WriteLn(Color_Gray, "Found widescreen patches in the cheats_ws folder --> skipping cheats_ws.zip"); @@ -439,11 +443,16 @@ static void _ApplySettings(const Pcsx2Config& src, Pcsx2Config& fixup) else { // No ws cheat files found at the cheats_ws folder, try the ws cheats zip file. - const wxString cheats_ws_archive(Path::Combine(EmuFolders::Resources, wxFileName(L"cheats_ws.zip"))); - if (wxFile::Exists(cheats_ws_archive)) + if (!s_widescreen_cheats_loaded) { - wxFFileInputStream* strm = new wxFFileInputStream(cheats_ws_archive); - int numberDbfCheatsLoaded = LoadPatchesFromZip(GameInfo::gameCRC, cheats_ws_archive, strm); + std::optional> data = Host::ReadResourceFile("cheats_ws.zip"); + if (data.has_value()) + s_widescreen_cheats_data = std::move(data.value()); + } + + if (!s_widescreen_cheats_data.empty()) + { + int numberDbfCheatsLoaded = LoadPatchesFromZip(StringUtil::wxStringToUTF8String(GameInfo::gameCRC), s_widescreen_cheats_data.data(), s_widescreen_cheats_data.size()); PatchesCon->WriteLn(Color_Green, "(Wide Screen Cheats DB) Patches Loaded: %d", numberDbfCheatsLoaded); gameWsHacks.Printf(L" [%d widescreen hacks]", numberDbfCheatsLoaded); }