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.