Skip to content

Commit

Permalink
Add support for EDF4.1
Browse files Browse the repository at this point in the history
Add pointer sets for both EDF5 and EDF4.1
Determine which game the ModLoader is running on
Hook initterm for additional initialization
  • Loading branch information
BlueAmulet committed Jul 19, 2021
1 parent e0cc0a0 commit 6af60d5
Show file tree
Hide file tree
Showing 27 changed files with 168 additions and 134 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -361,5 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd

# EDF5 ModLoader packages
# EDF ModLoader packages
*.zip
2 changes: 1 addition & 1 deletion EDF5ModLoader.sln → EDFModLoader.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30503.244
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EDF5ModLoader", "EDF5ModLoader\EDF5ModLoader.vcxproj", "{684F548A-EDFD-47D3-AA40-4307FCD281AA}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EDFModLoader", "EDFModLoader\EDFModLoader.vcxproj", "{684F548A-EDFD-47D3-AA40-4307FCD281AA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Patcher", "Patcher\Patcher.vcxproj", "{45AF38B1-69F6-4B91-A8FF-3F762E0432F2}"
EndProject
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{684F548A-EDFD-47D3-AA40-4307FCD281AA}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>EDF5ModLoader</RootNamespace>
<RootNamespace>EDFModLoader</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>EDFModLoader</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
Expand Down Expand Up @@ -104,7 +105,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;_DEBUG;EDFMODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>.\;..\HookLib\HookLib\HookLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
Expand All @@ -123,7 +124,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;EDFMODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>.\;..\HookLib\HookLib\HookLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
Expand All @@ -143,7 +144,7 @@
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>WIN32;NDEBUG;EDFMODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>.\;..\HookLib\HookLib\HookLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
Expand All @@ -166,7 +167,7 @@
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;EDFMODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>.\;..\HookLib\HookLib\HookLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
Expand Down
File renamed without changes.
4 changes: 3 additions & 1 deletion EDF5ModLoader/LoggerTweaks.h → EDFModLoader/LoggerTweaks.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ namespace eml {
plog::util::nostringstream ss;
ss << PLOG_NSTR("[") << t.tm_year + 1900 << "-" << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mon + 1 << PLOG_NSTR("-") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_mday << PLOG_NSTR(" ");
ss << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_hour << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_min << PLOG_NSTR(":") << std::setfill(PLOG_NSTR('0')) << std::setw(2) << t.tm_sec << PLOG_NSTR(".") << std::setfill(PLOG_NSTR('0')) << std::setw(3) << static_cast<int>(record.getTime().millitm) << PLOG_NSTR("] ");
ss << PLOG_NSTR("[") << shizo << PLOG_NSTR("] [") << severityToStringLower(record.getSeverity()) << PLOG_NSTR("] ");
if (shizo) {
ss << PLOG_NSTR("[") << shizo << PLOG_NSTR("] [") << severityToStringLower(record.getSeverity()) << PLOG_NSTR("] ");
}
ss << record.getMessage() << PLOG_NSTR("\n");

return ss.str();
Expand Down
File renamed without changes.
145 changes: 77 additions & 68 deletions EDF5ModLoader/dllmain.cpp → EDFModLoader/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ typedef bool (__fastcall *LoadDef)(PluginInfo*);

static std::vector<void*> hooks; // Holds all original hooked functions

// Called one at beginning and end of game, hooked to perform additional initialization
typedef int (__fastcall *fnk3d8f00_func)(char);
static fnk3d8f00_func fnk3d8f00_orig;
// Called during initialization of CRT
typedef void* (__fastcall *initterm_func)(void*, void*);
static initterm_func initterm_orig;
// Handles opening files from disk or through CRI File System
typedef void* (__fastcall *fnk244d0_func)(void*, void*, void*);
static fnk244d0_func fnk244d0_orig;
// Puts wide string in weird string structure
typedef void* (__fastcall *fnk27380_func)(void*, const wchar_t*, unsigned long long);
static fnk27380_func fnk27380_orig;
// printf-like logging stub, usually given wide strings, sometimes normal strings
typedef void (__fastcall *fnk27680_func)(const wchar_t*);
static fnk27680_func fnk27680_orig;
typedef void (__fastcall *gamelog_func)(const wchar_t*);
static gamelog_func gamelog_orig;

// Verify PluginData->module can store a HMODULE
static_assert(sizeof(HMODULE) == sizeof(PluginData::module), "module field cannot store an HMODULE");
Expand All @@ -63,6 +63,8 @@ constexpr size_t cwslen(wchar_t const (&)[N]) {
return N - 1;
}

#define wcsstart(a, b) (!wcsncmp(a, b, cwslen(b)))

// Hook wrapper functions
BOOLEAN EDFMLAPI SetHookWrap(const void *Interceptor, void **Original) {
if (Original != NULL && *Original != NULL && SetHook(*Original, Interceptor, Original)) {
Expand Down Expand Up @@ -114,6 +116,21 @@ static BOOL LoadPluginsB = TRUE;
static BOOL Redirect = TRUE;
static BOOL GameLog = FALSE;

// Pointer sets
typedef struct {
uintptr_t offset;
const wchar_t *search;
const char *ident;
const char *plugfunc;
uintptr_t pointers[4];
} PointerSet;

int pointerSet = -1;
PointerSet psets[2] = { //
{0xebcbd0, L"EarthDefenceForce 5 for PC", "EDF5", "EML5_Load", {0x9c835a, 0x244d0, 0x27380, 0x27680}}, // EDF 5
{0xaa36d0, L"EarthDefenceForce 4.1 for Windows", "EDF41", "EML4_Load", {0x667102, 0x8ed80, 0x91580, 0x91790}}, // EDF 4.1
};

// Search and load all *.dll files in Mods\Plugins\ folder
static void LoadPlugins(void) {
WIN32_FIND_DATAW ffd;
Expand All @@ -129,7 +146,7 @@ static void LoadPlugins(void) {
wcscat_s(plugpath, ffd.cFileName);
HMODULE plugin = LoadLibraryW(plugpath);
if (plugin != NULL) {
LoadDef loadfunc = (LoadDef)GetProcAddress(plugin, "EML5_Load");
LoadDef loadfunc = (LoadDef)GetProcAddress(plugin, psets[pointerSet].plugfunc);
bool unload = false;
if (loadfunc != NULL) {
PluginInfo *pluginInfo = new PluginInfo();
Expand Down Expand Up @@ -166,7 +183,7 @@ static void LoadPlugins(void) {
delete pluginInfo;
}
} else {
PLOG_WARNING << "Plugin does not contain EML5_Load function";
PLOG_WARNING << "Plugin does not contain " << psets[pointerSet].plugfunc << " function";
unload = true;
}
if (unload) {
Expand All @@ -192,26 +209,11 @@ static void LoadPlugins(void) {
}
}

// Add "+ModLoader" to game window
static void ModifyTitle(void) {
HWND edfHWND = FindWindowW(L"xgs::Framework", L"EarthDefenceForce 5 for PC");
if (edfHWND != NULL) {
int length = GetWindowTextLengthW(edfHWND);
const wchar_t *suffix = L" +ModLoader";
wchar_t *buffer = new wchar_t[length + wcslen(suffix) + 1];
GetWindowTextW(edfHWND, buffer, length + wcslen(suffix) + 1);
wcscat(buffer, suffix);
SetWindowTextW(edfHWND, buffer);
delete[] buffer;
} else {
PLOG_WARNING << "Failed to get window handle to EDF5";
}
}

// Early hook into game process
static int __fastcall fnk3d8f00_hook(char unk) {
// Called with 0 at beginning, 1 later
if (unk == 0) {
static void* __fastcall initterm_hook(void *unk1, void *unk2) {
static bool initialized = false;
if (!initialized) {
initialized = true;
PLOG_INFO << "Additional initialization";

// Load plugins
Expand All @@ -223,7 +225,7 @@ static int __fastcall fnk3d8f00_hook(char unk) {

PLOG_INFO << "Initialization finished";
}
return fnk3d8f00_orig(unk);
return initterm_orig(unk1, unk2);
}

struct oddstr {
Expand All @@ -241,7 +243,7 @@ static void *__fastcall fnk244d0_hook(void *unk1, oddstr *str, void *unk2) {
if (str->length >= 8) {
path = str->str;
}
if (path != NULL && str->length >= cwslen(L"/cri_bind/") && _wcsnicmp(L"/cri_bind/", path, cwslen(L"/cri_bind/")) == 0) {
if (path != NULL && str->length >= cwslen(L"/cri_bind/") && wcsstart(path, L"/cri_bind/")) {
size_t newlen = str->length + cwslen(L"./Mods/") - cwslen(L"/cri_bind/");
wchar_t *modpath = new wchar_t[newlen + 1];
wcscpy(modpath, L"./Mods/");
Expand All @@ -259,8 +261,8 @@ static void *__fastcall fnk244d0_hook(void *unk1, oddstr *str, void *unk2) {

// Internal logging hook
extern "C" {
void __fastcall fnk27680_hook(const wchar_t *fmt, ...); // wrapper to preserve registers
void __fastcall fnk27680_hook_main(const char *fmt, ...); // actual logging implementaton
void __fastcall gamelog_hook(const wchar_t *fmt, ...); // wrapper to preserve registers
void __fastcall gamelog_hook_main(const char *fmt, ...); // actual logging implementaton

// Thread Local Storage to preserve/restore registers in wrapper
// TODO: Move all of this to winmm.asm
Expand All @@ -281,7 +283,7 @@ __declspec(thread) M128A save_xmm4;
__declspec(thread) M128A save_xmm5;
}

void __fastcall fnk27680_hook_main(const char *fmt, ...) {
void __fastcall gamelog_hook_main(const char *fmt, ...) {
if (fmt != NULL) {
va_list args;
va_start(args, fmt);
Expand Down Expand Up @@ -321,11 +323,26 @@ void __fastcall fnk27680_hook_main(const char *fmt, ...) {

// Names for the log formatter
static const char ModLoaderStr[] = "ModLoader";
static const char GameStr[] = "Game";

PBYTE hmodEXE;
char hmodName[MAX_PATH];

void SetupHook(uintptr_t offset, void **func, void* hook, const char *reason, BOOL active) {
if (active) {
PLOG_INFO << "Hooking " << hmodName << "+" << std::hex << offset << " (" << reason << ")";
*func = hmodEXE + offset;
if (!SetHookWrap(hook, func)) {
// Error
PLOG_ERROR << "Failed to setup " << hmodName << "+" << std::hex << offset << " hook";
}
} else {
PLOG_INFO << "Skipping " << hmodName << "+" << std::hex << offset << " hook (" << reason << ")";
}
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
static plog::RollingFileAppender<eml::TxtFormatter<ModLoaderStr>> mlLogOutput("ModLoader.log");
static plog::RollingFileAppender<eml::TxtFormatter<GameStr>> gameLogOutput("game.log");
static plog::RollingFileAppender<eml::TxtFormatter<nullptr>> gameLogOutput("game.log");

switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
Expand Down Expand Up @@ -358,22 +375,40 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
// Add ourself to plugin list for future reference
PluginInfo *selfInfo = new PluginInfo;
selfInfo->infoVersion = PluginInfo::MaxInfoVer;
selfInfo->name = "EDF5ModLoader";
selfInfo->version = PLUG_VER(1, 0, 7, 0);
selfInfo->name = "EDFModLoader";
selfInfo->version = PLUG_VER(1, 0, 7, 1);
PluginData *selfData = new PluginData;
selfData->info = selfInfo;
selfData->module = hModule;
plugins.push_back(selfData);

// Determine what game is hosting us
hmodEXE = (PBYTE)GetModuleHandleW(NULL);
GetModuleFileNameA((HMODULE)hmodEXE, hmodName, _countof(hmodName));
char *hmodFName = PathFindFileNameA(hmodName);
memmove(hmodName, hmodFName, strlen(hmodFName) + 1);
for (int i = 0; i < _countof(psets); i++) {
size_t search_len = wcslen(psets[i].search);
if (!IsBadReadPtr(hmodEXE + psets[i].offset, search_len+1) && !wcsncmp((wchar_t*)(hmodEXE + psets[i].offset), psets[i].search, search_len)) {
pointerSet = i;
break;
}
}
if (pointerSet == -1) {
PLOG_ERROR << "Failed to determine what exe is running";
return FALSE;
}
uintptr_t *pointers = psets[pointerSet].pointers;

PluginVersion v = selfInfo->version;
PLOG_INFO.printf("EDF5ModLoader v%u.%u.%u Initializing\n", v.major, v.minor, v.patch);
PLOG_INFO.printf("EDFModLoader (%s) v%u.%u.%u Initializing\n", psets[pointerSet].ident, v.major, v.minor, v.patch);

// Setup DLL proxy
wchar_t path[MAX_PATH];
if (!GetWindowsDirectoryW(path, _countof(path))) {
DWORD dwError = GetLastError();
PLOG_ERROR << "Failed to get windows directory path: error " << dwError;
ExitProcess(EXIT_FAILURE);
return FALSE;
}

wcscat_s(path, L"\\System32\\winmm.dll");
Expand All @@ -386,49 +421,23 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
CreateDirectoryW(L"Mods", NULL);
CreateDirectoryW(L"Mods\\Plugins", NULL);

// Setup hooks
HMODULE hmodEXE = GetModuleHandleW(NULL);

// Hook function for additional ModLoader initialization
PLOG_INFO << "Hooking EDF5.exe+3d8f00 (Additional initialization)";
fnk3d8f00_orig = (fnk3d8f00_func)((PBYTE)hmodEXE + 0x3d8f00);
if (!SetHookWrap(fnk3d8f00_hook, reinterpret_cast<PVOID*>(&fnk3d8f00_orig))) {
// Error
PLOG_ERROR << "Failed to setup EDF5.exe+3d8f00 hook";
}
SetupHook(pointers[0], (PVOID*)&initterm_orig, initterm_hook, "Additional initialization", TRUE);

// Add Mods folder redirector hook
if (Redirect) {
PLOG_INFO << "Hooking EDF5.exe+244d0 (Mods folder redirector)";
fnk27380_orig = (fnk27380_func)((PBYTE)hmodEXE + 0x27380);
fnk244d0_orig = (fnk244d0_func)((PBYTE)hmodEXE + 0x244d0);
if (!SetHookWrap(fnk244d0_hook, reinterpret_cast<PVOID*>(&fnk244d0_orig))) {
// Error
PLOG_ERROR << "Failed to setup EDF5.exe+244d0 hook";
}
} else {
PLOG_INFO << "Skipping EDF5.exe+244d0 hook (Mods folder redirector)";
}
fnk27380_orig = (fnk27380_func)((PBYTE)hmodEXE + pointers[2]);
SetupHook(pointers[1], (PVOID*)&fnk244d0_orig, fnk244d0_hook, "Mods folder redirector", Redirect);

// Add internal logging hook
if (GameLog) {
PLOG_INFO << "Hooking EDF5.exe+27680 (Interal logging hook)";
fnk27680_orig = (fnk27680_func)((PBYTE)hmodEXE + 0x27680);
if (!SetHookWrap(fnk27680_hook, reinterpret_cast<PVOID*>(&fnk27680_orig))) {
// Error
PLOG_ERROR << "Failed to setup EDF5.exe+27680 hook";
}
} else {
PLOG_INFO << "Skipping EDF5.exe+27680 hook (Interal logging hook)";
}
SetupHook(pointers[3], (PVOID*)&gamelog_orig, gamelog_hook, "Interal logging hook", GameLog);

// Finished
PLOG_INFO << "Basic initialization complete";

break;
}
case DLL_PROCESS_DETACH: {
PLOG_INFO << "EDF5ModLoader Unloading";
PLOG_INFO << "EDFModLoader Unloading";

// Remove hooks
PLOG_INFO << "Removing hooks";
Expand Down
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions EDF5ModLoader/winmm.asm → EDFModLoader/winmm.asm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.data
extern PA : qword

extern fnk27680_hook_main : proto
extern gamelog_hook_main : proto

extern _tls_index : DWORD
extern save_ret : QWORD
Expand All @@ -28,7 +28,7 @@ runASM endp
; The original function normally doesn't touch any registers, or do anything.
; The code that calls this function is optimized for that.
; So we must preserve all volatile registers ourself.
fnk27680_hook proc
gamelog_hook proc

; Preserve rbx/r12
push rbx
Expand Down Expand Up @@ -80,7 +80,7 @@ pop rbx

; Replace return address and do actual work
add rsp,8
call fnk27680_hook_main
call gamelog_hook_main

; Push dummy value to be replaced later
push rax
Expand Down Expand Up @@ -140,5 +140,5 @@ pop rbx

; Goodbye
ret
fnk27680_hook endp
gamelog_hook endp
end
File renamed without changes.
Empty file.
2 changes: 2 additions & 0 deletions Patcher/EDF41/Patches.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## Default Patches
There are no patches for EDF4.1 included at this time.
Empty file added Patcher/EDF41/Patches/.gitkeep
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 6af60d5

Please sign in to comment.