diff --git a/appveyor.yml b/appveyor.yml index b67edd7..9e36d2d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,8 @@ after_build: - cmd: >- md %TMP%\full\ + md %TMP%\full\patches\ + md %TMP%\full\plugins\ md %TMP%\full\plugins\pv_equip\ @@ -19,9 +21,10 @@ after_build: md %TMP%\full\ram\databank\ - md %TMP%\update\ + md %TMP%\update\patches\ + md %TMP%\update\plugins\ md %TMP%\update\plugins\pv_equip\ @@ -34,6 +37,8 @@ after_build: copy "%APPVEYOR_BUILD_FOLDER%\source-code\data\README - PD Loader.url" %TMP%\update\ + copy %APPVEYOR_BUILD_FOLDER%\source-code\data\patches\custom_freeplay_text_example.p %TMP%\update\patches\ + copy %APPVEYOR_BUILD_FOLDER%\source-code\data\plugins\ShaderPatch.ini %TMP%\update\plugins\ copy %APPVEYOR_BUILD_FOLDER%\source-code\data\plugins\keyconfig.ini %TMP%\update\plugins\ @@ -46,6 +51,8 @@ after_build: copy %APPVEYOR_BUILD_FOLDER%\source-code\x64\%CONFIGURATION%\dnsapi.dll %TMP%\full\ + copy %APPVEYOR_BUILD_FOLDER%\source-code\data\patches\custom_freeplay_text_example.p %TMP%\full\patches\ + copy %APPVEYOR_BUILD_FOLDER%\source-code\x64\%CONFIGURATION%\*.dva %TMP%\full\plugins\ copy "%APPVEYOR_BUILD_FOLDER%\source-code\data\README - PD Loader.url" %TMP%\full\ diff --git a/source-code/data/patches/custom_freeplay_text_example.p b/source-code/data/patches/custom_freeplay_text_example.p new file mode 100644 index 0000000..a41445e --- /dev/null +++ b/source-code/data/patches/custom_freeplay_text_example.p @@ -0,0 +1,6 @@ +//# Enable FREE PLAY +// Just in case it's disabled... +//1403BABEA:75 // ...enable it. + +//# Custom FREE PLAY text +//1409F61F0:!My text \ No newline at end of file diff --git a/source-code/data/plugins/config.ini b/source-code/data/plugins/config.ini index 8ce9e90..5c0bcee 100644 --- a/source-code/data/plugins/config.ini +++ b/source-code/data/plugins/config.ini @@ -58,6 +58,8 @@ Enhanced_Stage_Manager=0 Enhanced_Stage_Manager_Encore=1 #Unlock PSEUDO modules (incomplete, will default to Miku) Unlock_Pseudo=0 +#Enable custom patches +Custom_Patches=1 [Graphics] # Set to -1 to unlock the frame rate. diff --git a/source-code/source/plugins/Launcher/FeelsThinkMan.gif b/source-code/source/plugins/Launcher/FeelsThinkMan.gif new file mode 100644 index 0000000..bd88e26 Binary files /dev/null and b/source-code/source/plugins/Launcher/FeelsThinkMan.gif differ diff --git a/source-code/source/plugins/Launcher/Launcher.vcxproj b/source-code/source/plugins/Launcher/Launcher.vcxproj index b75347e..57b35d0 100644 --- a/source-code/source/plugins/Launcher/Launcher.vcxproj +++ b/source-code/source/plugins/Launcher/Launcher.vcxproj @@ -124,6 +124,9 @@ ui.h + + + diff --git a/source-code/source/plugins/Launcher/SkinnedMessageBox.h b/source-code/source/plugins/Launcher/SkinnedMessageBox.h index 18b41b6..f78d298 100644 --- a/source-code/source/plugins/Launcher/SkinnedMessageBox.h +++ b/source-code/source/plugins/Launcher/SkinnedMessageBox.h @@ -253,8 +253,7 @@ private: ref class SkinnedMessageBoxForm : Form // https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messageboxicon?view=netframework-4.7.2 if (icon == MessageBoxIcon::Error) // also Hand and Stop { - Drawing::Icon^ erricon = gcnew Drawing::Icon(SystemIcons::Error, iconsize, iconsize); - iconPB->Image = erricon->ToBitmap(); + iconPB->Image = (Image^)(ResMgr->GetObject("FeelsThinkMan")); iconPB->AccessibleDescription = "Error"; //this.Icon = SystemIcons.Error; System::Media::SystemSounds::Hand->Play(); diff --git a/source-code/source/plugins/Launcher/SkinnedMessageBox.resx b/source-code/source/plugins/Launcher/SkinnedMessageBox.resx index 2bf6d6b..be0a343 100644 --- a/source-code/source/plugins/Launcher/SkinnedMessageBox.resx +++ b/source-code/source/plugins/Launcher/SkinnedMessageBox.resx @@ -118,6 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + FeelsThinkMan.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + PokeSnarf4xd.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/source-code/source/plugins/Launcher/framework.h b/source-code/source/plugins/Launcher/framework.h index d18ba3e..ce32c63 100644 --- a/source-code/source/plugins/Launcher/framework.h +++ b/source-code/source/plugins/Launcher/framework.h @@ -10,6 +10,7 @@ #include "ConfigOption.h" #include "PluginConfig.h" +#include int (__cdecl* divaMain)(int argc, const char** argv, const char** envp) = (int(__cdecl*)(int argc, const char** argv, const char** envp))0x140194D90; @@ -203,7 +204,9 @@ ConfigOptionBase* optionsArray[] = { new BooleanOption(L"opengl_patch_b", LAUNCHER_SECTION, CONFIG_FILE, L"OpenGL Patch B", L"Ignores some OpenGL-related errors. Don't use both patches at the same time unless you're know what you're doing.", L"", false), new OptionMetaSpacer(8), + new BooleanOption(L"custom_patches", PATCHES_SECTION, CONFIG_FILE, L"Enable custom patches", L"Enable all custom patches.", true, false), new BooleanOption(L"no_gpu_dialog", LAUNCHER_SECTION, CONFIG_FILE, L"Disable GPU Warning", L"Disables the warning dialog for unsupported GPUs.", L"", false), + //new BooleanOption(L"ignore_exe_checksum", PATCHES_SECTION, CONFIG_FILE, L"Ignore exe checksum", L"Use at your own risk.", false, false), new StringOption(L"command_line", LAUNCHER_SECTION, CONFIG_FILE, L"Command Line:", L"Allows setting command line parameters for the game when using the launcher.\nDisabling the launcher will bypass this.", L"", false), }; diff --git a/source-code/source/plugins/Launcher/ui.h b/source-code/source/plugins/Launcher/ui.h index 2731af2..1613679 100644 --- a/source-code/source/plugins/Launcher/ui.h +++ b/source-code/source/plugins/Launcher/ui.h @@ -666,6 +666,8 @@ private: System::Void Button_Help_Click(System::Object^ sender, System::EventArg System::Diagnostics::Process::Start("https://notabug.org/nastys/PD-Loader/wiki"); } private: System::Void Button_Launch_Click(System::Object^ sender, System::EventArgs^ e) { + + //SkinnedMessageBox::Show(this, "It looks like the executable has been tampered with, or the version of the game is not 7.10.\n\nPlease use the \"patches\" folder instead of modifying the executable directly (or disable verification).", "PD Launcher", MessageBoxButtons::OK, MessageBoxIcon::Error); SaveSettings(); diff --git a/source-code/source/plugins/Patches/Patches.vcxproj b/source-code/source/plugins/Patches/Patches.vcxproj index 2d811ac..da64b37 100644 --- a/source-code/source/plugins/Patches/Patches.vcxproj +++ b/source-code/source/plugins/Patches/Patches.vcxproj @@ -102,6 +102,9 @@ + + + diff --git a/source-code/source/plugins/Patches/Patches.vcxproj.filters b/source-code/source/plugins/Patches/Patches.vcxproj.filters index 1f5bfee..03a4db1 100644 --- a/source-code/source/plugins/Patches/Patches.vcxproj.filters +++ b/source-code/source/plugins/Patches/Patches.vcxproj.filters @@ -19,4 +19,9 @@ Source Files + + + Header Files + + \ No newline at end of file diff --git a/source-code/source/plugins/Patches/dllmain.cpp b/source-code/source/plugins/Patches/dllmain.cpp index c94c06c..5b0dfbb 100644 --- a/source-code/source/plugins/Patches/dllmain.cpp +++ b/source-code/source/plugins/Patches/dllmain.cpp @@ -3,16 +3,19 @@ #include #include #include +#include #include #include #include #include "PluginConfigApi.h" #include +#include "framework.h" #pragma comment(lib, "detours.lib") void InjectCode(void* address, const std::vector data); void ApplyPatches(); +void ApplyCustomPatches(std::wstring CPATCH_FILE_STRING); void(__cdecl* originalTAA)(DWORD *a1, int a2) = (void(__cdecl*)(DWORD *a1, int a2))0x1404B23A0; void(__cdecl* originalMLAA)(__int64 a1, int a2) = (void(__cdecl*)(__int64 a1, int a2))0x1404B2210; @@ -57,24 +60,29 @@ BOOL APIENTRY DllMain(HMODULE hModule, void patchMovieExt(std::string moviefile, void* address) { bool isinmdata = 0; - for (int i = 0; i < 1000; i++) - { - std::stringstream ss; - ss << std::setw(3) << std::setfill('0') << i; - if (std::filesystem::exists("../mdata/M" + ss.str() + "/rom/movie/" + moviefile + ".mp4")) + try { + for (std::filesystem::path p : std::filesystem::directory_iterator("../mdata")) { - isinmdata = 1; - //std::cout << "Movie " << moviefile << ".mp4 found in M" << ss.str() << std::endl; - break; + if (std::filesystem::path(p).filename().string().at(0) != 'M' || std::filesystem::path(p).filename().string().length() > 4) continue; + if (std::filesystem::exists(p.string() + "/rom/movie/" + moviefile + ".mp4")) + { + isinmdata = 1; + //std::cout << "[Patches] Movie " << moviefile << ".mp4 found in M" << ss.str() << std::endl; + break; + } } } + catch (const std::filesystem::filesystem_error & e) { + std::cout << "[Patches] File system error " << e.what() << " " << e.path1() << " " << e.path2() << " " << e.code() << std::endl; + } + if (isinmdata || std::filesystem::exists("../rom/movie/" + moviefile + ".mp4")) { InjectCode(address, { 0x6D, 0x70, 0x34 }); - std::cout << "Movie " << moviefile << " patched to mp4\n"; + std::cout << "[Patches] Movie " << moviefile << " patched to mp4\n"; return; } - //std::cout << "Movie " << moviefile << " NOT patched to mp4\n"; + //std::cout << "[Patches] Movie " << moviefile << " NOT patched to mp4\n"; return; } @@ -131,17 +139,17 @@ void ApplyPatches() { { (void*)0x00000001406A1FEF,{ 0xC7, 0x44, 0x24, 0x74, 0x00, 0x00, 0x00, 0x00 } }, // MOV dword ptr [RSP + 0x74],0x0 { (void*)0x00000001406A1FF7,{ 0xEB, 0x0E } }, // JMP 0x1406a2007 (to rest of function as usual) // add new code - { (void*)0x00000001406A1FF9,{ 0x66, 0x48, 0x0F, 0x6E, 0xC2 } }, // MOVQ XMM0,RDX (load touch pos) - { (void*)0x00000001406A1FFE,{ 0xEB, 0x5D } }, // JMP 0x1406a205d - { (void*)0x00000001406A205D,{ 0x0F, 0x2A, 0x0D, 0xB8, 0x6A, 0x31, 0x00 } }, // CVTPI2PS XMM1,qword ptr [0x1409b8b1c] (load 1280x720) - { (void*)0x00000001406A2064,{ 0x0F, 0x12, 0x51, 0x1C } }, // MOVLPS XMM2,qword ptr [RCX + 0x1c] (load actual res) - { (void*)0x00000001406A2068,{ 0xE9, 0x14, 0xFF, 0xFF, 0xFF } }, // JMP 0x1406a1f81 - { (void*)0x00000001406A1F81,{ 0x0F, 0x59, 0xC1 } }, // MULPS XMM0,XMM1 - { (void*)0x00000001406A1F84,{ 0x0F, 0x5E, 0xC2 } }, // DIVPS XMM0,XMM2 - { (void*)0x00000001406A1F87,{ 0x66, 0x0F, 0xD6, 0x44, 0x24, 0x10 } }, // MOVQ qword ptr [RSP+0x10],XMM0 - { (void*)0x00000001406A1F8D,{ 0xEB, 0x06 } }, // JMP 0x1406a1f95 (back to original function) - // jmp to new code - { (void*)0x00000001406A1F90,{ 0xEB, 0x67 } }, // JMP 0x1406a1ff9 + { (void*)0x00000001406A1FF9,{ 0x66, 0x48, 0x0F, 0x6E, 0xC2 } }, // MOVQ XMM0,RDX (load touch pos) + { (void*)0x00000001406A1FFE,{ 0xEB, 0x5D } }, // JMP 0x1406a205d + { (void*)0x00000001406A205D,{ 0x0F, 0x2A, 0x0D, 0xB8, 0x6A, 0x31, 0x00 } }, // CVTPI2PS XMM1,qword ptr [0x1409b8b1c] (load 1280x720) + { (void*)0x00000001406A2064,{ 0x0F, 0x12, 0x51, 0x1C } }, // MOVLPS XMM2,qword ptr [RCX + 0x1c] (load actual res) + { (void*)0x00000001406A2068,{ 0xE9, 0x14, 0xFF, 0xFF, 0xFF } }, // JMP 0x1406a1f81 + { (void*)0x00000001406A1F81,{ 0x0F, 0x59, 0xC1 } }, // MULPS XMM0,XMM1 + { (void*)0x00000001406A1F84,{ 0x0F, 0x5E, 0xC2 } }, // DIVPS XMM0,XMM2 + { (void*)0x00000001406A1F87,{ 0x66, 0x0F, 0xD6, 0x44, 0x24, 0x10 } }, // MOVQ qword ptr [RSP+0x10],XMM0 + { (void*)0x00000001406A1F8D,{ 0xEB, 0x06 } }, // JMP 0x1406a1f95 (back to original function) + // jmp to new code + { (void*)0x00000001406A1F90,{ 0xEB, 0x67 } }, // JMP 0x1406a1ff9 }; printf("[Patches] Patches loaded\n"); @@ -171,6 +179,7 @@ void ApplyPatches() { auto nTAA = GetPrivateProfileIntW(L"graphics", L"taa", TRUE, CONFIG_FILE); auto nMLAA = GetPrivateProfileIntW(L"graphics", L"mlaa", TRUE, CONFIG_FILE); auto nStereo = GetPrivateProfileIntW(L"patches", L"stereo", TRUE, CONFIG_FILE); + auto nCustomPatches = GetPrivateProfileIntW(L"patches", L"custom_patches", TRUE, CONFIG_FILE); // The old stereo patch... // Use 2 channels instead of 4 @@ -420,6 +429,27 @@ void ApplyPatches() { // Don't update the touch slider state so we can write our own InjectCode((void*)0x000000014061579B, { 0x90, 0x90, 0x90, 0x8B, 0x42, 0xE0, 0x90, 0x90, 0x90 }); } + + if (nCustomPatches) + { + std::cout << "[Patches] Reading custom patches...\n"; + try { + for (std::filesystem::path p : std::filesystem::directory_iterator("../patches")) + { + std::string extension = std::filesystem::path(p).extension().string(); + if (extension == ".p" || extension == ".P") + { + std::cout << "[Patches] Reading custom patch file: " << std::filesystem::path(p).filename().string() << std::endl; + ApplyCustomPatches(std::filesystem::path(p).wstring()); + } + } + } + catch (const std::filesystem::filesystem_error &e) { + std::cout << "[Patches] File system error " << e.what() << " " << e.path1() << " " << e.path2() << " " << e.code() << std::endl; + } + + std::cout << "[Patches] All custom patches applied\n"; + } } void InjectCode(void* address, const std::vector data) @@ -432,6 +462,97 @@ void InjectCode(void* address, const std::vector data) VirtualProtect(address, byteCount, oldProtect, nullptr); } +void ApplyCustomPatches(std::wstring CPATCH_FILE_STRING) +{ + LPCWSTR CPATCH_FILE = CPATCH_FILE_STRING.c_str(); + std::ifstream fileStream(CPATCH_FILE_STRING); + + if (!fileStream.is_open()) + return; + + std::string line; + + // check for BOM + std::getline(fileStream, line); + if (line.size() >= 3 && line.rfind("\xEF\xBB\xBF", 0) == 0) + fileStream.seekg(3); + else + fileStream.seekg(0); + + while (std::getline(fileStream, line)) + { + if (line[0] == '#') + { + std::cout << "[Patches]" << line.substr(1, line.size()-1) << std::endl; + continue; + } + if (line.find(':') == std::string::npos || (line[0] == '/' && line[1] == '/')) continue; + + std::vector commentHSplit = SplitString(line, "#"); + std::vector commentDSSplit = SplitString(commentHSplit[0], "//"); + std::vector colonSplit = SplitString(commentDSSplit[0], ":"); + if (colonSplit.size() != 2) continue; + long long int address; + std::istringstream iss(colonSplit[0]); + iss >> std::setbase(16) >> address; + if (address == 0) std::cout << "[Patches] Custom patch address wrong: " << std::hex << address << std::endl; + + if (colonSplit[1].at(0) == '!') + { + std::vector fullColonSplit = SplitString(line, ":"); + for (int i = 1; i < fullColonSplit[1].size(); i++) + { + std::cout << "[Patches] Patching: " << std::hex << address << ":" << fullColonSplit[1].at(i) << std::endl; + unsigned char byte_u = fullColonSplit[1].at(i); + std::vector patch = { byte_u }; + InjectCode((void*)address, patch); + address++; + } + } + else + { + std::vector bytes = SplitString(colonSplit[1], " "); + if (bytes.size() < 1) continue; + + std::string comment_string = ""; + int comment_counter = 0; + if (commentHSplit.size() > 1) + { + bool ignore = 1; + for (std::string comment : commentHSplit) + { + if (ignore) + { + ignore = 0; + continue; + } + comment_string = comment_string + comment; + } + } + + for (std::string bytestring : bytes) + { + int byte; + std::istringstream issb(bytestring); + issb >> std::setbase(16) >> byte; + unsigned char byte_u = byte; + std::cout << "[Patches] Patching: " << std::hex << address << ":" << std::hex << byte; + if (comment_string != "") + { + std::cout << " #" << comment_string.at(comment_counter); + comment_counter++; + } + std::cout << std::endl; + std::vector patch = { byte_u }; + InjectCode((void*)address, patch); + address++; + } + } + } + + fileStream.close(); +} + using namespace PluginConfig; diff --git a/source-code/source/plugins/Patches/framework.h b/source-code/source/plugins/Patches/framework.h new file mode 100644 index 0000000..1b75502 --- /dev/null +++ b/source-code/source/plugins/Patches/framework.h @@ -0,0 +1,36 @@ +#pragma once +#include + +std::string TrimString(const std::string& str, const std::string& whitespace) +{ + const size_t strBegin = str.find_first_not_of(whitespace); + + if (strBegin == std::string::npos) + return ""; + + const size_t strEnd = str.find_last_not_of(whitespace); + const size_t strRange = strEnd - strBegin + 1; + + return str.substr(strBegin, strRange); +} + +std::vector SplitString(const std::string& str, const std::string& delim) +{ + std::vector tokens; + size_t prev = 0, pos = 0; + do + { + pos = str.find(delim, prev); + if (pos == std::string::npos) + pos = str.length(); + + std::string token = str.substr(prev, pos - prev); + + if (!token.empty()) + tokens.push_back(token); + + prev = pos + delim.length(); + } while (pos < str.length() && prev < str.length()); + + return tokens; +}