diff --git a/AzureFlare/AzureFlare.vcxproj b/AzureFlare/AzureFlare.vcxproj index 9ae598f..ff56880 100644 --- a/AzureFlare/AzureFlare.vcxproj +++ b/AzureFlare/AzureFlare.vcxproj @@ -95,17 +95,24 @@ - - + + + + + - - + + + + + + diff --git a/AzureFlare/AzureFlare.vcxproj.filters b/AzureFlare/AzureFlare.vcxproj.filters index 41d044a..391bd34 100644 --- a/AzureFlare/AzureFlare.vcxproj.filters +++ b/AzureFlare/AzureFlare.vcxproj.filters @@ -21,10 +21,19 @@ Source Files - + Source Files - + + Source Files + + + Source Files + + + Source Files + + Source Files @@ -34,13 +43,25 @@ - + Header Files - + + Header Files + + + Header Files + + + Header Files + + + Header Files + + Header Files - + Header Files diff --git a/AzureFlare/dllmain.cpp b/AzureFlare/dllmain.cpp index 37f787c..afbd68e 100644 --- a/AzureFlare/dllmain.cpp +++ b/AzureFlare/dllmain.cpp @@ -10,8 +10,8 @@ #include #include -#include "gameguard.h" -#include "gameregion.h" +#include "logging.h" +#include "patches/patches.h" using namespace std::string_view_literals; @@ -71,11 +71,7 @@ const char* fallbackUrl = "localhost"; #pragma endregion -#if _DEBUG -#define DLOG(...) printf_s(__VA_ARGS__) -#else -#define DLOG(...) -#endif + FARPROC p[ALLFUNC_COUNT] = { 0 }; @@ -86,7 +82,6 @@ OriginalGetHostByName pOriginalGetHostByName = nullptr; bool loaded = FALSE; bool canRedirectServers = TRUE; -bool canBypassGameGuard = false; toml::table config; #define AF_SERVER_REDIRECT(outBuffer, passedValue, origAddr, newAddr, comment) \ @@ -173,21 +168,14 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) freopen("CON", "w", stdout); #endif - // As the configuration is loaded now, we can start setting the values we need to. + // Set the server redirection patch status here now, the rest are handled elsewhere canRedirectServers = config.at_path("patches.redirect").value_or(false); - canBypassGameGuard = config.at_path("patches.gameguard").value_or(false); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); - // Bypass GameGuard if we enabled the patch - if (canBypassGameGuard) - { - PatchGameGuard(config); - } - - // Set Episode 4 mode if it was set on config - PatchEpisode4Mode(config.at_path("patches.episode4_mode").value_or(false)); + // Do our patches now + DoMemoryPatches(config); DetourTransactionCommit(); @@ -202,15 +190,6 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); - // Clean up GameGuard if we enabled the bypass - if (canBypassGameGuard) - { - UnpatchGameGuard(); - } - - // Remove the Episode 4 patch - UnpatchEpisode4Mode(); - // Finalize the transaction DetourTransactionCommit(); FreeLibrary(hDLL); diff --git a/AzureFlare/gameguard.cpp b/AzureFlare/gameguard.cpp index fc95032..4a816d4 100644 --- a/AzureFlare/gameguard.cpp +++ b/AzureFlare/gameguard.cpp @@ -2,12 +2,14 @@ #include #include #include - +#include #include #include #include +#include "logging.h" +#include "signatures.h" #include "memory.h" #include "gameguard.h" @@ -15,6 +17,9 @@ #define NPGAMEMON_SUCCESS 0x755 +#define PSOBB_REGION_START 0x401000 +#define PSOBB_REGION_END PSOBB_REGION_START + 0x638000 + // Signature for InitNPGameMon // Notes: return value seems to be an unsigned int? some places say it's a DWORD, others unsigned int // Signature from https://github.com/fuzziqersoftware/newserv/issues/336#issuecomment-2028486289 and @@ -26,12 +31,6 @@ BYTE InitNPGameMonSignature[] = { 0x8B, 0x0D, 0xCC, 0xCC, 0xCC, 0xCC, 0x85, 0xC9 // https://github.com/fatrolls/nProtect-GameGuard/blob/164940ba81d318c300234c75f04da1412b554621/old/NPGameLib.c#L25 BYTE PreInitNPGameMonASignature[] = { 0xA1, 0xCC, 0xCC, 0xCC, 0xCC, 0x53, 0x33, 0xDB, 0x3B, 0xC3, 0x74, 0x04 }; -// Signature for the function that decides if the game is Episode 4 or not -BYTE IsEp4Signature[] = { 0xB8, 0x30, 0x11, 0x00, 0x00 }; - -// Typedef for the GameRegion function -typedef bool (*OriginalIsEp4)(); - // Pointer to the original CreateProcessA OriginalCreateProcessA originalCreateProcessA = CreateProcessA; @@ -44,6 +43,8 @@ OriginalPreInitNPGameMonA originalPreInitGameGuard = nullptr; // Pointer to the original game region function OriginalIsEp4 originalIsEp4Address = nullptr; +OriginalGetGameLanguage originalGameLanguage = nullptr; + // Pointer to the original GetStartupInfoA auto startupInfoA = static_cast(GetStartupInfoA); @@ -98,6 +99,18 @@ int __cdecl BypassGameGuardPreInit(char* a1) return 1; } +int GetGameLanguage() { + // 0: Japanese (j) + // 1: English (e) + // 2: German (g) + // 3: French (f) + // 4: Spanish (s) + // 5: Chinese Simplified (cs) + // 6: Chinese Traditional (ct) + // 7: Korean (h) + return 1; +} + int IsEp4() { return az_config.at_path("patches.episode4_mode").value_or(false); @@ -129,11 +142,10 @@ void ApplyGameGuardPatches(HMODULE hModule) void WINAPI AZ_GetStartUpInfoA(LPSTARTUPINFOA lpStartupInfo) { - uintptr_t baseAddress = reinterpret_cast(_ReturnAddress()); - const auto caller = GetOwningModule(baseAddress); + const auto caller = GetOwningModule(reinterpret_cast(_ReturnAddress())); if (caller == GetModuleHandleA("PsoBB.exe")) { - uintptr_t address = ScanProcessMemory(InitNPGameMonSignature, "xx????xxxxxxxx????x", 0, 0x01000000); + uintptr_t address = ScanProcessMemory(InitNPGameMonSignature, "xx????xxxxxxxx????x", PSOBB_REGION_START, PSOBB_REGION_END); // Restarting the entire detours process makes it work. No idea why. if (address != 0) { @@ -145,8 +157,7 @@ void WINAPI AZ_GetStartUpInfoA(LPSTARTUPINFOA lpStartupInfo) DetourTransactionCommit(); } - address = ScanProcessMemory(PreInitNPGameMonASignature, "x????xxxxxxx", 0, 0x01000000); - + address = ScanProcessMemory(PreInitNPGameMonASignature, "x????xxxxxxx", PSOBB_REGION_START, PSOBB_REGION_END); if (address != 0) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); @@ -157,24 +168,68 @@ void WINAPI AZ_GetStartUpInfoA(LPSTARTUPINFOA lpStartupInfo) } // Patch episode 4 data now - address = ScanProcessMemory(IsEp4Signature, "xxxxx", 0, 0x01000000); + address = ScanProcessMemory(IsEp4Signature, "xxxxx", PSOBB_REGION_START, PSOBB_REGION_END); if (address != 0) { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); + originalIsEp4Address = reinterpret_cast(address); DetourAttach(&reinterpret_cast(originalIsEp4Address), IsEp4); DetourTransactionCommit(); } + + // Game Language + address = ScanProcessMemory(GameLanguageSignature, "xxxxx", PSOBB_REGION_START, PSOBB_REGION_END); + if (address != 0) { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + + originalGameLanguage = reinterpret_cast(address); + DetourAttach(&reinterpret_cast(originalGameLanguage), GetGameLanguage); + + DetourTransactionCommit(); + } + + + address = ScanProcessMemory(IMESignature, "xxxxxxxxxx", PSOBB_REGION_START, PSOBB_REGION_END); + if (address != 0) { + int FinalAddress = address + 0x56; + + *(int*)FinalAddress = 0x008EC39C; + } } return startupInfoA(lpStartupInfo); } + +#ifdef _DEBUG +auto handleFIleA = static_cast(CreateFileA); +HANDLE WINAPI MyCreateFileA( + LPCSTR lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + HANDLE hTemplateFile) +{ + DLOG("CreateFileA: %s\n", lpFileName); + auto demhandle = handleFIleA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + + return demhandle; +} +#endif + void PatchGameGuard(toml::table config) { az_config = config; +#ifdef _DEBUG + DetourAttach(&reinterpret_cast(handleFIleA), MyCreateFileA); +#endif + // Patch the GameGuard functions for unpacked versions of the client ApplyGameGuardPatches(GetModuleHandle(NULL)); diff --git a/AzureFlare/gameguard.h b/AzureFlare/gameguard.h deleted file mode 100644 index a3185e6..0000000 --- a/AzureFlare/gameguard.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include - -// Typedef for the original CreateProcessA function -typedef BOOL(WINAPI* OriginalCreateProcessA)( - LPCSTR lpApplicationName, - LPSTR lpCommandLine, - LPSECURITY_ATTRIBUTES lpProcessAttributes, - LPSECURITY_ATTRIBUTES lpThreadAttributes, - BOOL bInheritHandles, - DWORD dwCreationFlags, - LPVOID lpEnvironment, - LPCSTR lpCurrentDirectory, - LPSTARTUPINFOA lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation - ); - -// Typedef for the original InitNPGameMon -typedef unsigned int (*OriginalGameGuardInit)(); - -// Typedef for the original PreInitNPGameMonA -typedef int (*OriginalPreInitNPGameMonA)(char* a1); - -void PatchGameGuard(toml::table config); -void UnpatchGameGuard(); \ No newline at end of file diff --git a/AzureFlare/gameregion.cpp b/AzureFlare/gameregion.cpp deleted file mode 100644 index ca54553..0000000 --- a/AzureFlare/gameregion.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include - -#include "memory.h" -#include "gameregion.h" - -// Signature for the function that decides if the game is Episode 4 or not -BYTE IsEpisode4Signature[] = { 0xB8, 0x30, 0x11, 0x00, 0x00 }; - -// Pointer to the original game region function -OriginalIsEpisode4 originalIsEpisode4Address = nullptr; - -int IsEpisode4Value = 0; - -int IsEpisode4() -{ - return IsEpisode4Value; -} - -void PatchEpisode4Mode(int value) -{ - // Set the value from the config - IsEpisode4Value = value; - - // Get the base address of psobb.exe - void* IsEpisode4Address = FindPatternInModule(GetModuleHandle(NULL), IsEpisode4Signature, sizeof(IsEpisode4Signature)); - if (IsEpisode4Address != nullptr) - { - originalIsEpisode4Address = reinterpret_cast(IsEpisode4Address); - DetourAttach(&reinterpret_cast(originalIsEpisode4Address), IsEpisode4); - } -} - -void UnpatchEpisode4Mode() -{ - DetourDetach(&reinterpret_cast(originalIsEpisode4Address), IsEpisode4); -} \ No newline at end of file diff --git a/AzureFlare/gameregion.h b/AzureFlare/gameregion.h deleted file mode 100644 index 1f62f16..0000000 --- a/AzureFlare/gameregion.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -// Typedef for the GameRegion function -typedef bool (*OriginalIsEpisode4)(); - -void PatchEpisode4Mode(int); -void UnpatchEpisode4Mode(); \ No newline at end of file diff --git a/AzureFlare/logging.h b/AzureFlare/logging.h new file mode 100644 index 0000000..9f5163a --- /dev/null +++ b/AzureFlare/logging.h @@ -0,0 +1,12 @@ +#ifndef __AZ_LOGGING +#define __AZ_LOGGING + +#include + +#if _DEBUG +#define DLOG(...) printf_s(__VA_ARGS__) +#else +#define DLOG(...) +#endif + +#endif // !__AZ_LOGGING diff --git a/AzureFlare/memory.cpp b/AzureFlare/memory.cpp index bdefda3..ec815da 100644 --- a/AzureFlare/memory.cpp +++ b/AzureFlare/memory.cpp @@ -8,6 +8,10 @@ #include "memory.h" +// TODO: Get this based on the EXE, but these correspond to JPBB 1.25.11 +#define PSOBB_CODE_REGION_START 0x401000 +#define PSOBB_CODE_REGION_END PSOBB_CODE_REGION_START + 0x638000 + #pragma comment(lib, "psapi.lib") void* FindPatternInModule(HMODULE hModule, BYTE* pattern, SIZE_T patternSize) { @@ -40,7 +44,7 @@ void* FindPatternInModule(HMODULE hModule, BYTE* pattern, SIZE_T patternSize) { return nullptr; } -uintptr_t ScanProcessMemory(const uint8_t* pattern, const char* mask, uintptr_t startAddress, uintptr_t endAddress) { +uintptr_t ScanSliceProcessMemory(const uint8_t* pattern, const char* mask, uintptr_t startAddress, uintptr_t endAddress) { size_t patternSize = strlen(mask); for (uintptr_t address = startAddress; address < endAddress; ) { @@ -75,4 +79,10 @@ uintptr_t ScanProcessMemory(const uint8_t* pattern, const char* mask, uintptr_t } return 0; +} + + +uintptr_t ScanProcessMemory(const uint8_t* pattern, const char* mask) +{ + return ScanSliceProcessMemory(pattern, mask, PSOBB_CODE_REGION_START, PSOBB_CODE_REGION_END); } \ No newline at end of file diff --git a/AzureFlare/memory.h b/AzureFlare/memory.h index 2189b00..497060a 100644 --- a/AzureFlare/memory.h +++ b/AzureFlare/memory.h @@ -4,7 +4,8 @@ #include void* FindPatternInModule(HMODULE hModule, BYTE* pattern, SIZE_T patternSize); -uintptr_t ScanProcessMemory(const uint8_t* pattern, const char* mask, uintptr_t startAddress, uintptr_t endAddress); +uintptr_t ScanSliceProcessMemory(const uint8_t* pattern, const char* mask, uintptr_t startAddress, uintptr_t endAddress); +uintptr_t ScanProcessMemory(const uint8_t* pattern, const char* mask); static inline HMODULE __stdcall GetOwningModule(const uintptr_t address) diff --git a/AzureFlare/patches/gameguard.cpp b/AzureFlare/patches/gameguard.cpp new file mode 100644 index 0000000..af3f8e5 --- /dev/null +++ b/AzureFlare/patches/gameguard.cpp @@ -0,0 +1,146 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "../logging.h" +#include "../memory.h" +#include "gameguard.h" + +#define NPGAMEMON_SUCCESS 0x755 + +// TODO: Get this based on the EXE, but these correspond to JPBB 1.25.11 +#define PSOBB_CODE_REGION_START 0x401000 +#define PSOBB_CODE_REGION_END PSOBB_CODE_REGION_START + 0x638000 + +// Signature for InitNPGameMon +// Notes: return value seems to be an unsigned int? some places say it's a DWORD, others unsigned int +// Signature from https://github.com/fuzziqersoftware/newserv/issues/336#issuecomment-2028486289 and +// https://github.com/fatrolls/nProtect-GameGuard/blob/164940ba81d318c300234c75f04da1412b554621/old/NPGameLib.c#L22 +BYTE InitNPGameMonSignature[] = { 0x8B, 0x0D, 0xCC, 0xCC, 0xCC, 0xCC, 0x85, 0xC9, 0x75, 0x03, 0x33, 0xC0, 0xC3, 0xE9, 0xCC, 0xCC, 0xCC, 0xCC, 0x90 }; + +// Signature for PreInitNPGameMonA +// Notes: From: https://github.com/fuzziqersoftware/newserv/issues/336#issuecomment-2028486289 and +// https://github.com/fatrolls/nProtect-GameGuard/blob/164940ba81d318c300234c75f04da1412b554621/old/NPGameLib.c#L25 +BYTE PreInitNPGameMonASignature[] = { 0xA1, 0xCC, 0xCC, 0xCC, 0xCC, 0x53, 0x33, 0xDB, 0x3B, 0xC3, 0x74, 0x04 }; + +// Pointer to the original CreateProcessA +auto originalCreateProcessA = static_cast(CreateProcessA); + +// GameGuard Process Name +const char* GameGuardProcessName = "GameGuard.des"; + +// Function to not load GameGuard's main process +BOOL WINAPI CreateProcessGameGuard( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) +{ + // Let the game "know" GameGuard was loaded + if (strstr(lpApplicationName, GameGuardProcessName) != nullptr) + { + return TRUE; + } + + // Call back the original CreateProcessA function + return originalCreateProcessA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation + ); +} + +unsigned int BypassGameGuardInit() +{ + return NPGAMEMON_SUCCESS; +} + +int __cdecl BypassGameGuardPreInit(char* a1) +{ + // Seems to work if I send any non-zero value, or 0 + // I'm sending a non-zero value for now. + return 1; +} + +void PatchGameGuardProcess() +{ + DLOG("Patching GameGuard Process\n"); + + // Detour CreateProcessA to bypass GameGuard's process + DetourAttach(&reinterpret_cast(originalCreateProcessA), CreateProcessGameGuard); +} + +// This is actually a bit more complex than the unpacked EXE, but the heavylifting is done in patches.cpp +void PatchPackedGameGuard() +{ + DLOG("Patching packed GameGuard functions\n"); + + // Holder for both of the addresses for GameGuard + uintptr_t address = ScanProcessMemory(InitNPGameMonSignature, "xx????xxxxxxxx????x"); + + DLOG("Scanned for InitNPGameMon's signature\n"); + + // Restarting the entire detours process makes it work. No idea why. + if (address != 0) { + DLOG("Found InitNPGameMon signature at %X\n", address); + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&reinterpret_cast(address), BypassGameGuardInit); + DetourTransactionCommit(); + } + + DLOG("InitNPGameMon scanned and probably patched\n"); + + address = ScanProcessMemory(PreInitNPGameMonASignature, "x????xxxxxxx"); + + DLOG("Scanned for PreInitNPGameMonA's signature\n"); + + if (address != 0) { + DLOG("Found PreInitNPGameMon signature at %X\n", address); + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&reinterpret_cast(address), BypassGameGuardPreInit); + DetourTransactionCommit(); + } +} + +void PatchUnpackedGameGuard() +{ + DLOG("Patching unpacked GameGuard functions\n"); + + auto hModule = GetModuleHandle(NULL); + + // Find the address for InitNPGameMon + void* InitNPGameMonAddress = FindPatternInModule(hModule, InitNPGameMonSignature, sizeof(InitNPGameMonSignature)); + if (InitNPGameMonAddress != nullptr) + { + DLOG("Found unpacked InitNPGameMon signature at %p\n", InitNPGameMonAddress); + + // Attach our GameGuard Bypass + DetourAttach(&reinterpret_cast(InitNPGameMonAddress), BypassGameGuardInit); + } + + // Find the address for PreInitNPGameMonA + // Notes: Bypassing this function will avoid generating the GameGuard folder and its sole npgl.erl file + void* PreInitNPGameMonAddress = FindPatternInModule(hModule, PreInitNPGameMonASignature, sizeof(PreInitNPGameMonASignature)); + if (PreInitNPGameMonAddress != nullptr) + { + DLOG("Found unpacked PreInitNPGameMon signature at %p\n", PreInitNPGameMonAddress); + + // Attach our second GameGuard Bypass + DetourAttach(&reinterpret_cast(PreInitNPGameMonAddress), BypassGameGuardPreInit); + } +} \ No newline at end of file diff --git a/AzureFlare/patches/gameguard.h b/AzureFlare/patches/gameguard.h new file mode 100644 index 0000000..aa7a676 --- /dev/null +++ b/AzureFlare/patches/gameguard.h @@ -0,0 +1,6 @@ +#pragma once + +void PatchGameGuardProcess(); + +void PatchUnpackedGameGuard(); +void PatchPackedGameGuard(); \ No newline at end of file diff --git a/AzureFlare/patches/input.cpp b/AzureFlare/patches/input.cpp new file mode 100644 index 0000000..16a4531 --- /dev/null +++ b/AzureFlare/patches/input.cpp @@ -0,0 +1,41 @@ +#define WIN32_LEAN_AND_MEAN +#include + +#include "../logging.h" +#include "../memory.h" + +#include "input.h" + +constexpr auto INPUT_ADDRESS_OFFSET = 0x56; +constexpr auto PATCHED_CALL = 0x008EC39C; + +// Signature for the chat IME function +BYTE IMESignature[] = { 0x55, 0x8B, 0xEC, 0x6A, 0xFF, 0x68, 0x88, 0x6E, 0x99, 0x00 }; + +static void PatchDirectInput(uintptr_t address) +{ + int FinalAddress = address + INPUT_ADDRESS_OFFSET; + + // Actually patch input by using another instruction + // Personally I have no idea why this works, but it's in Solybum's repo, so I'm using it. + // Thanks man! + *(int*)FinalAddress = PATCHED_CALL; +} + +void PatchPackedDirectInput() +{ + uintptr_t address = ScanProcessMemory(IMESignature, "xxxxxxxxxx"); + if (address != 0) + { + PatchDirectInput(address); + } +} + +void PatchUnpackedDirectInput() +{ + void* DirectInputAddress = FindPatternInModule(GetModuleHandle(NULL), IMESignature, sizeof(IMESignature)); + if (DirectInputAddress != nullptr) + { + PatchDirectInput(reinterpret_cast(DirectInputAddress)); + } +} \ No newline at end of file diff --git a/AzureFlare/patches/input.h b/AzureFlare/patches/input.h new file mode 100644 index 0000000..21bc63d --- /dev/null +++ b/AzureFlare/patches/input.h @@ -0,0 +1,4 @@ +#pragma once + +void PatchPackedDirectInput(); +void PatchUnpackedDirectInput(); \ No newline at end of file diff --git a/AzureFlare/patches/language.cpp b/AzureFlare/patches/language.cpp new file mode 100644 index 0000000..ed782ca --- /dev/null +++ b/AzureFlare/patches/language.cpp @@ -0,0 +1,61 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "../logging.h" +#include "../memory.h" + +#include "language.h" + +// Signature for the game language function +BYTE GameLanguageSignature[] = { 0xE8, 0x03, 0x19, 0xD9, 0xFF }; + +int LanguageId; + +int GetGameLanguage() { + // 0: Japanese (j) + // 1: English (e) + // 2: German (g) + // 3: French (f) + // 4: Spanish (s) + // 5: Chinese Simplified (cs) + // 6: Chinese Traditional (ct) + // 7: Korean (h) + return LanguageId; +} + +void PatchPackedGameLanguage(int GameLanguageId) +{ + LanguageId = GameLanguageId; + + DLOG("Patching packed game language function\n"); + + uintptr_t address = ScanProcessMemory(GameLanguageSignature, "xxxxx"); + + DLOG("Scanned for GetGameLanguage's signature\n"); + + if (address != 0) + { + DLOG("Found packed GetGameLanguage at %X\n", address); + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&reinterpret_cast(address), GetGameLanguage); + DetourTransactionCommit(); + } + + DLOG("GetGameLanguage scanned and probably patched\n"); +} + +void PatchUnpackedGameLanguage(int GameLanguageId) +{ + LanguageId = GameLanguageId; + + DLOG("Patching unpacked game language function\n"); + + void* GameLanguageAddress = FindPatternInModule(GetModuleHandle(NULL), GameLanguageSignature, sizeof(GameLanguageSignature)); + if (GameLanguageAddress != nullptr) + { + DLOG("Found unpacked GetGameLanguage at %p\n", GameLanguageAddress); + DetourAttach(&reinterpret_cast(GameLanguageAddress), GetGameLanguage); + } +} \ No newline at end of file diff --git a/AzureFlare/patches/language.h b/AzureFlare/patches/language.h new file mode 100644 index 0000000..67ce559 --- /dev/null +++ b/AzureFlare/patches/language.h @@ -0,0 +1,6 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include + +void PatchPackedGameLanguage(int GameLanguageId); +void PatchUnpackedGameLanguage(int GameLanguageId); \ No newline at end of file diff --git a/AzureFlare/patches/misc.cpp b/AzureFlare/patches/misc.cpp new file mode 100644 index 0000000..7833736 --- /dev/null +++ b/AzureFlare/patches/misc.cpp @@ -0,0 +1,43 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include "../logging.h" +#include "../memory.h" + +#include "misc.h" + +// Signature for the function that decides if the game is Episode 4 or not +BYTE IsEp4Signature[] = { 0xB8, 0x30, 0x11, 0x00, 0x00 }; + +bool IsEpisode4Enabled; + +int IsEp4Mode() +{ + return IsEpisode4Enabled; +} + +void PatchPackedEpisode4State(bool enabled) +{ + IsEpisode4Enabled = enabled; + + uintptr_t address = ScanProcessMemory(IsEp4Signature, "xxxxx"); + if (address != 0) + { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&reinterpret_cast(address), IsEp4Mode); + DetourTransactionCommit(); + } +} + +void PatchUnpackedEpisode4State(bool enabled) +{ + IsEpisode4Enabled = enabled; + + void* IsEp4Address = FindPatternInModule(GetModuleHandle(NULL), IsEp4Signature, sizeof(IsEp4Signature)); + if (IsEp4Address != nullptr) + { + DetourAttach(&reinterpret_cast(IsEp4Address), IsEp4Mode); + } +} \ No newline at end of file diff --git a/AzureFlare/patches/misc.h b/AzureFlare/patches/misc.h new file mode 100644 index 0000000..3b2fcae --- /dev/null +++ b/AzureFlare/patches/misc.h @@ -0,0 +1,4 @@ +#pragma once + +void PatchPackedEpisode4State(bool enabled); +void PatchUnpackedEpisode4State(bool enabled); \ No newline at end of file diff --git a/AzureFlare/patches/patches.cpp b/AzureFlare/patches/patches.cpp new file mode 100644 index 0000000..c99a4f6 --- /dev/null +++ b/AzureFlare/patches/patches.cpp @@ -0,0 +1,95 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include +#include + +#include "../memory.h" +#include "../logging.h" +#include "patches.h" +#include "gameguard.h" +#include "language.h" +#include "input.h" +#include "misc.h" + +#pragma intrinsic(_ReturnAddress) + +// Store configuration specifics +bool canBypassGameGuard; +bool enableDirectInput; +bool isEpisode4Enabled; +int clientLanguage; + +// Pointer to the original GetStartupInfoA +auto startupInfoA = static_cast(GetStartupInfoA); + +void WINAPI AZ_GetStartUpInfoA(LPSTARTUPINFOA lpStartupInfo) +{ + const auto caller = GetOwningModule(reinterpret_cast(_ReturnAddress())); + if (caller == GetModuleHandleA("PsoBB.exe")) + { + if (canBypassGameGuard) + { + PatchPackedGameGuard(); + } + + // Patch the game language + // If the config entry is commented, default to the original Japanese mode + PatchPackedGameLanguage(clientLanguage); + + // Patch Direct Input + // Normally Teth executbles have this built in, but this may be needed sometimes + if (enableDirectInput) + { + PatchPackedDirectInput(); + } + + // Patch Eepisode 4 Status + if (isEpisode4Enabled) + { + //PatchPackedEpisode4State(isEpisode4Enabled); + } + } + + return startupInfoA(lpStartupInfo); +} + +void DoMemoryPatches(toml::table config) +{ + canBypassGameGuard = config.at_path("patches.gameguard").value_or(false); + enableDirectInput = config.at_path("patches.enable_direct_input").value_or(false); + clientLanguage = config.at_path("patches.client_language").value_or(0); + isEpisode4Enabled = config.at_path("patches.episode4_mode").value_or(false); + + // Hook into GetStartupInfoA to allow hooking into the packed EXE's memory + DetourAttach(&reinterpret_cast(startupInfoA), AZ_GetStartUpInfoA); + + // We're repeating our patches here, so they work in the unpacked EXE + + // Patch the unpacked EXE if possible, and the process + if (canBypassGameGuard) + { + PatchUnpackedGameGuard(); + PatchGameGuardProcess(); + } + + // Patch the game language + // If the config entry is commented, default to the original Japanese mode + PatchUnpackedGameLanguage(clientLanguage); + + // Patch Direct Input + // Normally Teth executbles have this built in, but this may be needed sometimes + if (enableDirectInput) + { + PatchUnpackedDirectInput(); + } + + // Patch Eepisode 4 Status + if (isEpisode4Enabled) + { + //PatchUnpackedEpisode4State(isEpisode4Enabled); + } +} \ No newline at end of file diff --git a/AzureFlare/patches/patches.h b/AzureFlare/patches/patches.h new file mode 100644 index 0000000..b311541 --- /dev/null +++ b/AzureFlare/patches/patches.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void DoMemoryPatches(toml::table config); \ No newline at end of file diff --git a/psobb.cfg b/psobb.cfg index a35dd39..940441b 100644 --- a/psobb.cfg +++ b/psobb.cfg @@ -1,13 +1,30 @@ -# AzureFlare configuration +# AzureFlare configuration file [patches] # Bypass GameGuard gameguard = true + # Enable server redirections redirect = true + # Set Episode 4 mode episode4_mode = true +# Client Language +# Note: PSOBB was released only in Japan, US and China, but support for other languages +# is built into the client as well. Each language expects its respective files to be +# in the data folder, otherwise expect the game to crash as soon it reaches the title +# screen. In order from 0: Japanese (0), English (1), German (2), French (3), Spanish (4), +# Chinese Simplified (5), Chinese Traditional (6), Korean (7) +# Any language that isn't Japanese, English, and Chinese Simplified was not officially supported. +# To use the english files, please download them from newserv's repo and extract to your data folder: +# https://github.com/fuzziqersoftware/newserv/tree/master/notes/psobb/usbb-resources +client_language = 1 # Set client to English + +# Enable Direct Input +# Will enable direct input to the client if you don't have the required IME to write. +enable_direct_input = true + # Server redirection settings # You can set where the server it'll point instead of the defunct official addresses. # Note: Redirections are ignored if the exe has IP addresses as server address.