diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ee5385 --- /dev/null +++ b/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ee227ca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "funchook"] + path = funchook + url = https://github.com/kubo/funchook diff --git a/EDF5ModLoader/.clang-format b/EDF5ModLoader/.clang-format new file mode 100644 index 0000000..6eda296 --- /dev/null +++ b/EDF5ModLoader/.clang-format @@ -0,0 +1,9 @@ +--- +BasedOnStyle: Microsoft +AlignEscapedNewlines: Left +BreakBeforeBraces: Attach +ColumnLimit: '9999' +TabWidth: '4' +UseTab: ForIndentation + +... diff --git a/EDF5ModLoader/EDF5ModLoader.sln b/EDF5ModLoader/EDF5ModLoader.sln new file mode 100644 index 0000000..90d1718 --- /dev/null +++ b/EDF5ModLoader/EDF5ModLoader.sln @@ -0,0 +1,41 @@ + +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.vcxproj", "{684F548A-EDFD-47D3-AA40-4307FCD281AA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Patcher", "..\Patcher\Patcher.vcxproj", "{45AF38B1-69F6-4B91-A8FF-3F762E0432F2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Debug|x64.ActiveCfg = Debug|x64 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Debug|x64.Build.0 = Debug|x64 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Debug|x86.ActiveCfg = Debug|Win32 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Debug|x86.Build.0 = Debug|Win32 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Release|x64.ActiveCfg = Release|x64 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Release|x64.Build.0 = Release|x64 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Release|x86.ActiveCfg = Release|Win32 + {684F548A-EDFD-47D3-AA40-4307FCD281AA}.Release|x86.Build.0 = Release|Win32 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Debug|x64.ActiveCfg = Debug|x64 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Debug|x64.Build.0 = Debug|x64 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Debug|x86.ActiveCfg = Debug|Win32 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Debug|x86.Build.0 = Debug|Win32 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Release|x64.ActiveCfg = Release|x64 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Release|x64.Build.0 = Release|x64 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Release|x86.ActiveCfg = Release|Win32 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7DCC4A5E-FAD9-4273-80CB-549DE58673F0} + EndGlobalSection +EndGlobal diff --git a/EDF5ModLoader/EDF5ModLoader.vcxproj b/EDF5ModLoader/EDF5ModLoader.vcxproj new file mode 100644 index 0000000..a0e7e27 --- /dev/null +++ b/EDF5ModLoader/EDF5ModLoader.vcxproj @@ -0,0 +1,215 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {684F548A-EDFD-47D3-AA40-4307FCD281AA} + Win32Proj + EDF5ModLoader + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + winmm + true + + + winmm + true + + + winmm + false + + + winmm + false + + + $(VcpkgUserTriplet)-static-md + + + $(VcpkgUserTriplet)-static-md + + + $(VcpkgUserTriplet)-static-md + + + $(VcpkgUserTriplet)-static-md + + + + NotUsing + Level3 + true + WIN32;_DEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + .\;..\funchook\src;..\funchook\include;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Windows + true + false + winmm.def + kernel32.lib;psapi.lib;%(AdditionalDependencies) + + + + + NotUsing + Level3 + true + _DEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + .\;..\funchook\src;..\funchook\include;%(AdditionalIncludeDirectories) + MultiThreadedDebug + + + Windows + true + false + winmm.def + kernel32.lib;psapi.lib;%(AdditionalDependencies) + + + + + NotUsing + Level3 + true + true + WIN32;NDEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + .\;..\funchook\src;..\funchook\include;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + false + false + winmm.def + kernel32.lib;psapi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + NotUsing + Level3 + true + true + NDEBUG;EDF5MODLOADER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + .\;..\funchook\src;..\funchook\include;%(AdditionalIncludeDirectories) + true + + + Windows + true + true + false + false + winmm.def + kernel32.lib;psapi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EDF5ModLoader/EDF5ModLoader.vcxproj.filters b/EDF5ModLoader/EDF5ModLoader.vcxproj.filters new file mode 100644 index 0000000..22b26c7 --- /dev/null +++ b/EDF5ModLoader/EDF5ModLoader.vcxproj.filters @@ -0,0 +1,74 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/EDF5ModLoader/LoggerTweaks.h b/EDF5ModLoader/LoggerTweaks.h new file mode 100644 index 0000000..08765a6 --- /dev/null +++ b/EDF5ModLoader/LoggerTweaks.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace eml { + inline const char *severityToStringLower(plog::Severity severity) { + switch (severity) { + case plog::fatal: + return "fatal"; + case plog::error: + return "error"; + case plog::warning: + return "warn"; + case plog::info: + return "info"; + case plog::debug: + return "debug"; + case plog::verbose: + return "verb"; + default: + return "none"; + } + } + + template + class TxtFormatter { + public: + static plog::util::nstring header() { + return plog::util::nstring(); + } + + static plog::util::nstring format(const plog::Record &record) { + tm t; + plog::util::localtime_s(&t, &record.getTime().time); + + 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(record.getTime().millitm) << PLOG_NSTR("] "); + ss << PLOG_NSTR("[") << shizo << PLOG_NSTR("] [") << severityToStringLower(record.getSeverity()) << PLOG_NSTR("] "); + ss << record.getMessage() << PLOG_NSTR("\n"); + + return ss.str(); + } + }; +} // namespace eml \ No newline at end of file diff --git a/EDF5ModLoader/PluginAPI.h b/EDF5ModLoader/PluginAPI.h new file mode 100644 index 0000000..cbeecf8 --- /dev/null +++ b/EDF5ModLoader/PluginAPI.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +union PluginVersion { + struct { + unsigned short build; + unsigned short patch; + unsigned short minor; + unsigned short major; + }; + unsigned long long raw; +}; +static_assert(sizeof(PluginVersion) == 8, "PluginVersion union has unexpected size"); + +// Helper macro to fill out the PluginVersion field +#define PLUG_VER(a, b, c, d) {{d, c, b, a}} + +typedef struct { + enum { MaxInfoVer = 1 }; + unsigned long infoVersion; + const char *name; + PluginVersion version; +} PluginInfo; +static_assert(sizeof(PluginInfo) == 24, "PluginInfo struct has unexpected size"); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/EDF5ModLoader/config.h b/EDF5ModLoader/config.h new file mode 100644 index 0000000..81f4c53 --- /dev/null +++ b/EDF5ModLoader/config.h @@ -0,0 +1,6 @@ +#pragma once + +// Funchook configuration +#define WIN32 +#define DISASM_ZYDIS 1 +//#define FUNCHOOK_EXPORTS \ No newline at end of file diff --git a/EDF5ModLoader/cpp.hint b/EDF5ModLoader/cpp.hint new file mode 100644 index 0000000..919a374 --- /dev/null +++ b/EDF5ModLoader/cpp.hint @@ -0,0 +1,2 @@ +#define EMLAPI __declspec(dllexport) +#define EMLAPI __declspec(dllimport) diff --git a/EDF5ModLoader/dllmain.cpp b/EDF5ModLoader/dllmain.cpp new file mode 100644 index 0000000..3bf6037 --- /dev/null +++ b/EDF5ModLoader/dllmain.cpp @@ -0,0 +1,368 @@ +#define _CRT_SECURE_NO_WARNINGS +#define WIN32_LEAN_AND_MEAN + +#define PLOG_OMIT_LOG_DEFINES +//#define PLOG_EXPORT + +#include + +#include +#include +#include +#include + +#include +#include + +#include "proxy.h" +#include "PluginAPI.h" +#include "LoggerTweaks.h" + + +typedef struct { + PluginInfo *info; + void *module; +} PluginData; + +static std::vector plugins; // Holds all plugins loaded +typedef bool (*LoadDef)(PluginInfo*); + +static funchook_t *funchook; +// 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 for every file required used (and more?), hooked to redirect file access to Mods folder +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; + +// Verify PluginData->module can store a HMODULE +static_assert(sizeof(HMODULE) == sizeof(PluginData::module), "module field cannot store an HMODULE"); + +static inline BOOL FileExistsW(LPCWSTR szPath) { + DWORD dwAttrib = GetFileAttributesW(szPath); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +// Search and load all *.dll files in Mods\Plugins\ folder +static void LoadPlugins(void) { + WIN32_FIND_DATAW ffd; + PLOG_INFO << "Loading plugins"; + HANDLE hFind = FindFirstFileW(L"Mods\\Plugins\\*.dll", &ffd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + PLOG_INFO << "Loading Plugin: " << ffd.cFileName; + wchar_t *plugpath = new wchar_t[MAX_PATH]; + wcscpy(plugpath, L"Mods\\Plugins\\"); + wcscat(plugpath, ffd.cFileName); + HMODULE plugin = LoadLibraryW(plugpath); + delete[] plugpath; + if (plugin != NULL) { + LoadDef loadfunc = (LoadDef)GetProcAddress(plugin, "EML5_Load"); + bool unload = false; + if (loadfunc != NULL) { + PluginInfo *pluginInfo = new PluginInfo(); + pluginInfo->infoVersion = 0; + if (loadfunc(pluginInfo)) { + // Validate PluginInfo + if (pluginInfo->infoVersion == 0) { + PLOG_ERROR << "PluginInfo infoVersion 0, expected " << PluginInfo::MaxInfoVer; + unload = true; + } else if (pluginInfo->name == NULL) { + PLOG_ERROR << "Plugin missing name"; + unload = true; + } else if (pluginInfo->infoVersion > PluginInfo::MaxInfoVer) { + PLOG_ERROR << "Plugin has unsupported infoVersion " << pluginInfo->infoVersion << " expected " << PluginInfo::MaxInfoVer; + unload = true; + } else { + switch (pluginInfo->infoVersion) { + case 1: + default: + // Latest info version + PluginData *pluginData = new PluginData; + pluginData->info = pluginInfo; + pluginData->module = plugin; + plugins.push_back(pluginData); + break; + } + static_assert(PluginInfo::MaxInfoVer == 1, "Supported version changed, update version handling and this number"); + } + } else { + PLOG_INFO << "Unloading plugin"; + unload = true; + } + if (unload) { + delete pluginInfo; + } + } else { + PLOG_WARNING << "Plugin does not contain EML5_Load function"; + unload = true; + } + if (unload) { + FreeLibrary(plugin); + } + } else { + DWORD dwError = GetLastError(); + PLOG_ERROR << "Failed to load plugin: error " << dwError; + } + } + } while (FindNextFileW(hFind, &ffd) != 0); + // Check if finished with error + DWORD dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_FILES) { + PLOG_ERROR << "Failed to search for plugins: error " << dwError; + } + FindClose(hFind); + } else { + DWORD dwError = GetLastError(); + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { + PLOG_ERROR << "Failed to search for plugins: error " << dwError; + } + } +} + +// 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) { + PLOG_INFO << "Additional initialization"; + + // Load plugins + LoadPlugins(); + + PLOG_INFO << "Initialization finished"; + } + return fnk3d8f00_orig(unk); +} + +// app:/ to Mods folder redirector +static void *__fastcall fnk27380_hook(void *unk, const wchar_t *path, unsigned long long length) { + // path is not always null terminated + // length does not include null terminator + if (path != NULL && length >= wcslen(L"app:/") && _wcsnicmp(L"app:/", path, wcslen(L"app:/")) == 0) { + IF_PLOG(plog::verbose) { + wchar_t* npath = new wchar_t[length + 1]; + wmemcpy(npath, path, length); + npath[length] = L'\0'; + PLOG_VERBOSE << "Hook: " << npath; + delete[] npath; + } + + wchar_t *modpath = new wchar_t[length + 3]; + wcscpy(modpath, L"./Mods/"); + wmemcpy(modpath + wcslen(modpath), path + wcslen(L"app:/"), length - wcslen(L"app:/")); + modpath[length + 2] = L'\0'; + PLOG_DEBUG << "Checking for " << modpath; + if (FileExistsW(modpath)) { + PLOG_DEBUG << "Redirecting access to " << modpath; + void *ret = fnk27380_orig(unk, modpath, length + 2); + delete[] modpath; + return ret; + } else { + delete[] modpath; + } + } + + void *ret = fnk27380_orig(unk, path, length); + return ret; +} + +// 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 fnk27680_hook_main(const char *fmt, ...) { + if (fmt != NULL) { + va_list args; + va_start(args, fmt); + if (fmt[0] == 'L' && fmt[1] == '\0' && !wcscmp((wchar_t*)fmt, L"LoadComplete:%s %s %d\n")) { + // This wide string is formatted with normal strings + fmt = "LoadComplete:%s %s %d"; + } + // This is sometimes called with wide strings and normal strings + // Try to automatically detect + if (fmt[0] != '\0' && fmt[1] == '\0') { + int required = _vsnwprintf(NULL, 0, (wchar_t*)fmt, args); + wchar_t *buffer = new wchar_t[(size_t)required + 1]; + _vsnwprintf(buffer, (size_t)required + 1, (wchar_t*)fmt, args); + va_end(args); + // Remove new line from end of message if present + if (required >= 1 && buffer[required - 1] == L'\n') { + buffer[required - 1] = L'\0'; + } + PLOG_INFO_(1) << buffer; + delete[] buffer; + } else { + int required = _vsnprintf(NULL, 0, fmt, args); + char *buffer = new char[(size_t)required + 1]; + _vsnprintf(buffer, (size_t)required + 1, fmt, args); + va_end(args); + // See above comment + if (required >= 1 && buffer[required - 1] == '\n') { + buffer[required - 1] = '\0'; + } + PLOG_INFO_(1) << buffer; + delete[] buffer; + } + } else { + PLOG_INFO_(1) << "(null)"; + } +} + +// Names for the log formatter +static const char ModLoaderStr[] = "ModLoader"; +static const char GameStr[] = "Game"; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + int rv; + static plog::RollingFileAppender> mlLogOutput("ModLoader.log"); + static plog::RollingFileAppender> gameLogOutput("game.log"); + + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: { + // For optimization + DisableThreadLibraryCalls(hModule); + + // Open Log file + DeleteFileW(L"ModLoader.log"); + DeleteFileW(L"game.log"); + plog::init(plog::debug, &mlLogOutput); + plog::init<1>(plog::debug, &gameLogOutput); + + // 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, 2, 0); + PluginData *selfData = new PluginData; + selfData->info = selfInfo; + selfData->module = hModule; + plugins.push_back(selfData); + + PluginVersion v = selfInfo->version; + PLOG_INFO.printf("EDF5ModLoader v%u.%u.%u Initializing\n", 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); + } + + wcscat_s(path, L"\\System32\\winmm.dll"); + + PLOG_INFO << "Loading real winmm.dll"; + PLOG_INFO << "Setting up dll proxy functions"; + setupFunctions(LoadLibraryW(path)); + + // Create ModLoader folders + CreateDirectoryW(L"Mods", NULL); + CreateDirectoryW(L"Mods\\Plugins", NULL); + + // Setup funchook + HMODULE hmodEXE = GetModuleHandleW(NULL); + // funchook_set_debug_file("funchook.log"); + funchook = funchook_create(); + + // Hook function for additional ModLoader initialization + PLOG_INFO << "Hooking EDF5.exe+3d8f00 (Additional initialization)"; + fnk3d8f00_orig = (fnk3d8f00_func)((PBYTE)hmodEXE + 0x3d8f00); + rv = funchook_prepare(funchook, (void**)&fnk3d8f00_orig, fnk3d8f00_hook); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to setup EDF5.exe+3d8f00 hook: " << funchook_error_message(funchook) << " (" << rv << ")"; + } + + // Add Mods folder redirector + PLOG_INFO << "Hooking EDF5.exe+27380 (Mods folder redirector)"; + fnk27380_orig = (fnk27380_func)((PBYTE)hmodEXE + 0x27380); + rv = funchook_prepare(funchook, (void**)&fnk27380_orig, fnk27380_hook); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to setup EDF5.exe+27380 hook: " << funchook_error_message(funchook) << " (" << rv << ")"; + } + + // Add logging stub hook + PLOG_INFO << "Hooking EDF5.exe+27680 (Logging stub hook)"; + fnk27680_orig = (fnk27680_func)((PBYTE)hmodEXE + 0x27680); + rv = funchook_prepare(funchook, (void**)&fnk27680_orig, fnk27680_hook); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to setup EDF5.exe+27680 hook: " << funchook_error_message(funchook) << " (" << rv << ")"; + } + + // Install hooks + rv = funchook_install(funchook, 0); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to install hooks: " << funchook_error_message(funchook) << " (" << rv << ")"; + } else { + PLOG_INFO << "Installed hooks"; + } + + // Finished + PLOG_INFO << "Basic initialization complete"; + + break; + } + case DLL_PROCESS_DETACH: { + PLOG_INFO << "EDF5ModLoader Unloading"; + + // Disable hooks + rv = funchook_uninstall(funchook, 0); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to uninstall hooks: " << funchook_error_message(funchook) << " (" << rv << ")"; + } else { + PLOG_INFO << "Uninstalled hooks"; + rv = funchook_destroy(funchook); + if (rv != 0) { + // Error + PLOG_ERROR << "Failed to destroy Funchook instance: " << funchook_error_message(funchook) << " (" << rv << ")"; + } + } + + // Unload all plugins + PLOG_INFO << "Unloading plugins"; + for (PluginData *pluginData : plugins) { + delete pluginData->info; + if (pluginData->module != hModule) { + FreeLibrary((HMODULE)(pluginData->module)); + } + delete pluginData; + } + plugins.clear(); + + // Unload real winmm.dll + PLOG_INFO << "Unloading real winmm.dll"; + cleanupProxy(); + break; + + // TODO: Close log file? + } + } + return TRUE; +} diff --git a/EDF5ModLoader/proxy.c b/EDF5ModLoader/proxy.c new file mode 100644 index 0000000..b1ae52a --- /dev/null +++ b/EDF5ModLoader/proxy.c @@ -0,0 +1,563 @@ +#include +#include + +#pragma region Proxy +struct winmm_dll { + HMODULE dll; + FARPROC oCloseDriver; + FARPROC oDefDriverProc; + FARPROC oDriverCallback; + FARPROC oDrvGetModuleHandle; + FARPROC oGetDriverModuleHandle; + FARPROC oOpenDriver; + FARPROC oPlaySound; + FARPROC oPlaySoundA; + FARPROC oPlaySoundW; + FARPROC oSendDriverMessage; + FARPROC oWOWAppExit; + FARPROC oauxGetDevCapsA; + FARPROC oauxGetDevCapsW; + FARPROC oauxGetNumDevs; + FARPROC oauxGetVolume; + FARPROC oauxOutMessage; + FARPROC oauxSetVolume; + FARPROC ojoyConfigChanged; + FARPROC ojoyGetDevCapsA; + FARPROC ojoyGetDevCapsW; + FARPROC ojoyGetNumDevs; + FARPROC ojoyGetPos; + FARPROC ojoyGetPosEx; + FARPROC ojoyGetThreshold; + FARPROC ojoyReleaseCapture; + FARPROC ojoySetCapture; + FARPROC ojoySetThreshold; + FARPROC omciDriverNotify; + FARPROC omciDriverYield; + FARPROC omciExecute; + FARPROC omciFreeCommandResource; + FARPROC omciGetCreatorTask; + FARPROC omciGetDeviceIDA; + FARPROC omciGetDeviceIDFromElementIDA; + FARPROC omciGetDeviceIDFromElementIDW; + FARPROC omciGetDeviceIDW; + FARPROC omciGetDriverData; + FARPROC omciGetErrorStringA; + FARPROC omciGetErrorStringW; + FARPROC omciGetYieldProc; + FARPROC omciLoadCommandResource; + FARPROC omciSendCommandA; + FARPROC omciSendCommandW; + FARPROC omciSendStringA; + FARPROC omciSendStringW; + FARPROC omciSetDriverData; + FARPROC omciSetYieldProc; + FARPROC omidiConnect; + FARPROC omidiDisconnect; + FARPROC omidiInAddBuffer; + FARPROC omidiInClose; + FARPROC omidiInGetDevCapsA; + FARPROC omidiInGetDevCapsW; + FARPROC omidiInGetErrorTextA; + FARPROC omidiInGetErrorTextW; + FARPROC omidiInGetID; + FARPROC omidiInGetNumDevs; + FARPROC omidiInMessage; + FARPROC omidiInOpen; + FARPROC omidiInPrepareHeader; + FARPROC omidiInReset; + FARPROC omidiInStart; + FARPROC omidiInStop; + FARPROC omidiInUnprepareHeader; + FARPROC omidiOutCacheDrumPatches; + FARPROC omidiOutCachePatches; + FARPROC omidiOutClose; + FARPROC omidiOutGetDevCapsA; + FARPROC omidiOutGetDevCapsW; + FARPROC omidiOutGetErrorTextA; + FARPROC omidiOutGetErrorTextW; + FARPROC omidiOutGetID; + FARPROC omidiOutGetNumDevs; + FARPROC omidiOutGetVolume; + FARPROC omidiOutLongMsg; + FARPROC omidiOutMessage; + FARPROC omidiOutOpen; + FARPROC omidiOutPrepareHeader; + FARPROC omidiOutReset; + FARPROC omidiOutSetVolume; + FARPROC omidiOutShortMsg; + FARPROC omidiOutUnprepareHeader; + FARPROC omidiStreamClose; + FARPROC omidiStreamOpen; + FARPROC omidiStreamOut; + FARPROC omidiStreamPause; + FARPROC omidiStreamPosition; + FARPROC omidiStreamProperty; + FARPROC omidiStreamRestart; + FARPROC omidiStreamStop; + FARPROC omixerClose; + FARPROC omixerGetControlDetailsA; + FARPROC omixerGetControlDetailsW; + FARPROC omixerGetDevCapsA; + FARPROC omixerGetDevCapsW; + FARPROC omixerGetID; + FARPROC omixerGetLineControlsA; + FARPROC omixerGetLineControlsW; + FARPROC omixerGetLineInfoA; + FARPROC omixerGetLineInfoW; + FARPROC omixerGetNumDevs; + FARPROC omixerMessage; + FARPROC omixerOpen; + FARPROC omixerSetControlDetails; + FARPROC ommDrvInstall; + FARPROC ommGetCurrentTask; + FARPROC ommTaskBlock; + FARPROC ommTaskCreate; + FARPROC ommTaskSignal; + FARPROC ommTaskYield; + FARPROC ommioAdvance; + FARPROC ommioAscend; + FARPROC ommioClose; + FARPROC ommioCreateChunk; + FARPROC ommioDescend; + FARPROC ommioFlush; + FARPROC ommioGetInfo; + FARPROC ommioInstallIOProcA; + FARPROC ommioInstallIOProcW; + FARPROC ommioOpenA; + FARPROC ommioOpenW; + FARPROC ommioRead; + FARPROC ommioRenameA; + FARPROC ommioRenameW; + FARPROC ommioSeek; + FARPROC ommioSendMessage; + FARPROC ommioSetBuffer; + FARPROC ommioSetInfo; + FARPROC ommioStringToFOURCCA; + FARPROC ommioStringToFOURCCW; + FARPROC ommioWrite; + FARPROC ommsystemGetVersion; + FARPROC osndPlaySoundA; + FARPROC osndPlaySoundW; + FARPROC otimeBeginPeriod; + FARPROC otimeEndPeriod; + FARPROC otimeGetDevCaps; + FARPROC otimeGetSystemTime; + FARPROC otimeGetTime; + FARPROC otimeKillEvent; + FARPROC otimeSetEvent; + FARPROC owaveInAddBuffer; + FARPROC owaveInClose; + FARPROC owaveInGetDevCapsA; + FARPROC owaveInGetDevCapsW; + FARPROC owaveInGetErrorTextA; + FARPROC owaveInGetErrorTextW; + FARPROC owaveInGetID; + FARPROC owaveInGetNumDevs; + FARPROC owaveInGetPosition; + FARPROC owaveInMessage; + FARPROC owaveInOpen; + FARPROC owaveInPrepareHeader; + FARPROC owaveInReset; + FARPROC owaveInStart; + FARPROC owaveInStop; + FARPROC owaveInUnprepareHeader; + FARPROC owaveOutBreakLoop; + FARPROC owaveOutClose; + FARPROC owaveOutGetDevCapsA; + FARPROC owaveOutGetDevCapsW; + FARPROC owaveOutGetErrorTextA; + FARPROC owaveOutGetErrorTextW; + FARPROC owaveOutGetID; + FARPROC owaveOutGetNumDevs; + FARPROC owaveOutGetPitch; + FARPROC owaveOutGetPlaybackRate; + FARPROC owaveOutGetPosition; + FARPROC owaveOutGetVolume; + FARPROC owaveOutMessage; + FARPROC owaveOutOpen; + FARPROC owaveOutPause; + FARPROC owaveOutPrepareHeader; + FARPROC owaveOutReset; + FARPROC owaveOutRestart; + FARPROC owaveOutSetPitch; + FARPROC owaveOutSetPlaybackRate; + FARPROC owaveOutSetVolume; + FARPROC owaveOutUnprepareHeader; + FARPROC owaveOutWrite; +} winmm; + +FARPROC PA = 0; +int runASM(); + +void fCloseDriver() { PA = winmm.oCloseDriver; runASM(); } +void fDefDriverProc() { PA = winmm.oDefDriverProc; runASM(); } +void fDriverCallback() { PA = winmm.oDriverCallback; runASM(); } +void fDrvGetModuleHandle() { PA = winmm.oDrvGetModuleHandle; runASM(); } +void fGetDriverModuleHandle() { PA = winmm.oGetDriverModuleHandle; runASM(); } +void fOpenDriver() { PA = winmm.oOpenDriver; runASM(); } +void fPlaySound() { PA = winmm.oPlaySound; runASM(); } +void fPlaySoundA() { PA = winmm.oPlaySoundA; runASM(); } +void fPlaySoundW() { PA = winmm.oPlaySoundW; runASM(); } +void fSendDriverMessage() { PA = winmm.oSendDriverMessage; runASM(); } +void fWOWAppExit() { PA = winmm.oWOWAppExit; runASM(); } +void fauxGetDevCapsA() { PA = winmm.oauxGetDevCapsA; runASM(); } +void fauxGetDevCapsW() { PA = winmm.oauxGetDevCapsW; runASM(); } +void fauxGetNumDevs() { PA = winmm.oauxGetNumDevs; runASM(); } +void fauxGetVolume() { PA = winmm.oauxGetVolume; runASM(); } +void fauxOutMessage() { PA = winmm.oauxOutMessage; runASM(); } +void fauxSetVolume() { PA = winmm.oauxSetVolume; runASM(); } +void fjoyConfigChanged() { PA = winmm.ojoyConfigChanged; runASM(); } +void fjoyGetDevCapsA() { PA = winmm.ojoyGetDevCapsA; runASM(); } +void fjoyGetDevCapsW() { PA = winmm.ojoyGetDevCapsW; runASM(); } +void fjoyGetNumDevs() { PA = winmm.ojoyGetNumDevs; runASM(); } +void fjoyGetPos() { PA = winmm.ojoyGetPos; runASM(); } +void fjoyGetPosEx() { PA = winmm.ojoyGetPosEx; runASM(); } +void fjoyGetThreshold() { PA = winmm.ojoyGetThreshold; runASM(); } +void fjoyReleaseCapture() { PA = winmm.ojoyReleaseCapture; runASM(); } +void fjoySetCapture() { PA = winmm.ojoySetCapture; runASM(); } +void fjoySetThreshold() { PA = winmm.ojoySetThreshold; runASM(); } +void fmciDriverNotify() { PA = winmm.omciDriverNotify; runASM(); } +void fmciDriverYield() { PA = winmm.omciDriverYield; runASM(); } +void fmciExecute() { PA = winmm.omciExecute; runASM(); } +void fmciFreeCommandResource() { PA = winmm.omciFreeCommandResource; runASM(); } +void fmciGetCreatorTask() { PA = winmm.omciGetCreatorTask; runASM(); } +void fmciGetDeviceIDA() { PA = winmm.omciGetDeviceIDA; runASM(); } +void fmciGetDeviceIDFromElementIDA() { PA = winmm.omciGetDeviceIDFromElementIDA; runASM(); } +void fmciGetDeviceIDFromElementIDW() { PA = winmm.omciGetDeviceIDFromElementIDW; runASM(); } +void fmciGetDeviceIDW() { PA = winmm.omciGetDeviceIDW; runASM(); } +void fmciGetDriverData() { PA = winmm.omciGetDriverData; runASM(); } +void fmciGetErrorStringA() { PA = winmm.omciGetErrorStringA; runASM(); } +void fmciGetErrorStringW() { PA = winmm.omciGetErrorStringW; runASM(); } +void fmciGetYieldProc() { PA = winmm.omciGetYieldProc; runASM(); } +void fmciLoadCommandResource() { PA = winmm.omciLoadCommandResource; runASM(); } +void fmciSendCommandA() { PA = winmm.omciSendCommandA; runASM(); } +void fmciSendCommandW() { PA = winmm.omciSendCommandW; runASM(); } +void fmciSendStringA() { PA = winmm.omciSendStringA; runASM(); } +void fmciSendStringW() { PA = winmm.omciSendStringW; runASM(); } +void fmciSetDriverData() { PA = winmm.omciSetDriverData; runASM(); } +void fmciSetYieldProc() { PA = winmm.omciSetYieldProc; runASM(); } +void fmidiConnect() { PA = winmm.omidiConnect; runASM(); } +void fmidiDisconnect() { PA = winmm.omidiDisconnect; runASM(); } +void fmidiInAddBuffer() { PA = winmm.omidiInAddBuffer; runASM(); } +void fmidiInClose() { PA = winmm.omidiInClose; runASM(); } +void fmidiInGetDevCapsA() { PA = winmm.omidiInGetDevCapsA; runASM(); } +void fmidiInGetDevCapsW() { PA = winmm.omidiInGetDevCapsW; runASM(); } +void fmidiInGetErrorTextA() { PA = winmm.omidiInGetErrorTextA; runASM(); } +void fmidiInGetErrorTextW() { PA = winmm.omidiInGetErrorTextW; runASM(); } +void fmidiInGetID() { PA = winmm.omidiInGetID; runASM(); } +void fmidiInGetNumDevs() { PA = winmm.omidiInGetNumDevs; runASM(); } +void fmidiInMessage() { PA = winmm.omidiInMessage; runASM(); } +void fmidiInOpen() { PA = winmm.omidiInOpen; runASM(); } +void fmidiInPrepareHeader() { PA = winmm.omidiInPrepareHeader; runASM(); } +void fmidiInReset() { PA = winmm.omidiInReset; runASM(); } +void fmidiInStart() { PA = winmm.omidiInStart; runASM(); } +void fmidiInStop() { PA = winmm.omidiInStop; runASM(); } +void fmidiInUnprepareHeader() { PA = winmm.omidiInUnprepareHeader; runASM(); } +void fmidiOutCacheDrumPatches() { PA = winmm.omidiOutCacheDrumPatches; runASM(); } +void fmidiOutCachePatches() { PA = winmm.omidiOutCachePatches; runASM(); } +void fmidiOutClose() { PA = winmm.omidiOutClose; runASM(); } +void fmidiOutGetDevCapsA() { PA = winmm.omidiOutGetDevCapsA; runASM(); } +void fmidiOutGetDevCapsW() { PA = winmm.omidiOutGetDevCapsW; runASM(); } +void fmidiOutGetErrorTextA() { PA = winmm.omidiOutGetErrorTextA; runASM(); } +void fmidiOutGetErrorTextW() { PA = winmm.omidiOutGetErrorTextW; runASM(); } +void fmidiOutGetID() { PA = winmm.omidiOutGetID; runASM(); } +void fmidiOutGetNumDevs() { PA = winmm.omidiOutGetNumDevs; runASM(); } +void fmidiOutGetVolume() { PA = winmm.omidiOutGetVolume; runASM(); } +void fmidiOutLongMsg() { PA = winmm.omidiOutLongMsg; runASM(); } +void fmidiOutMessage() { PA = winmm.omidiOutMessage; runASM(); } +void fmidiOutOpen() { PA = winmm.omidiOutOpen; runASM(); } +void fmidiOutPrepareHeader() { PA = winmm.omidiOutPrepareHeader; runASM(); } +void fmidiOutReset() { PA = winmm.omidiOutReset; runASM(); } +void fmidiOutSetVolume() { PA = winmm.omidiOutSetVolume; runASM(); } +void fmidiOutShortMsg() { PA = winmm.omidiOutShortMsg; runASM(); } +void fmidiOutUnprepareHeader() { PA = winmm.omidiOutUnprepareHeader; runASM(); } +void fmidiStreamClose() { PA = winmm.omidiStreamClose; runASM(); } +void fmidiStreamOpen() { PA = winmm.omidiStreamOpen; runASM(); } +void fmidiStreamOut() { PA = winmm.omidiStreamOut; runASM(); } +void fmidiStreamPause() { PA = winmm.omidiStreamPause; runASM(); } +void fmidiStreamPosition() { PA = winmm.omidiStreamPosition; runASM(); } +void fmidiStreamProperty() { PA = winmm.omidiStreamProperty; runASM(); } +void fmidiStreamRestart() { PA = winmm.omidiStreamRestart; runASM(); } +void fmidiStreamStop() { PA = winmm.omidiStreamStop; runASM(); } +void fmixerClose() { PA = winmm.omixerClose; runASM(); } +void fmixerGetControlDetailsA() { PA = winmm.omixerGetControlDetailsA; runASM(); } +void fmixerGetControlDetailsW() { PA = winmm.omixerGetControlDetailsW; runASM(); } +void fmixerGetDevCapsA() { PA = winmm.omixerGetDevCapsA; runASM(); } +void fmixerGetDevCapsW() { PA = winmm.omixerGetDevCapsW; runASM(); } +void fmixerGetID() { PA = winmm.omixerGetID; runASM(); } +void fmixerGetLineControlsA() { PA = winmm.omixerGetLineControlsA; runASM(); } +void fmixerGetLineControlsW() { PA = winmm.omixerGetLineControlsW; runASM(); } +void fmixerGetLineInfoA() { PA = winmm.omixerGetLineInfoA; runASM(); } +void fmixerGetLineInfoW() { PA = winmm.omixerGetLineInfoW; runASM(); } +void fmixerGetNumDevs() { PA = winmm.omixerGetNumDevs; runASM(); } +void fmixerMessage() { PA = winmm.omixerMessage; runASM(); } +void fmixerOpen() { PA = winmm.omixerOpen; runASM(); } +void fmixerSetControlDetails() { PA = winmm.omixerSetControlDetails; runASM(); } +void fmmDrvInstall() { PA = winmm.ommDrvInstall; runASM(); } +void fmmGetCurrentTask() { PA = winmm.ommGetCurrentTask; runASM(); } +void fmmTaskBlock() { PA = winmm.ommTaskBlock; runASM(); } +void fmmTaskCreate() { PA = winmm.ommTaskCreate; runASM(); } +void fmmTaskSignal() { PA = winmm.ommTaskSignal; runASM(); } +void fmmTaskYield() { PA = winmm.ommTaskYield; runASM(); } +void fmmioAdvance() { PA = winmm.ommioAdvance; runASM(); } +void fmmioAscend() { PA = winmm.ommioAscend; runASM(); } +void fmmioClose() { PA = winmm.ommioClose; runASM(); } +void fmmioCreateChunk() { PA = winmm.ommioCreateChunk; runASM(); } +void fmmioDescend() { PA = winmm.ommioDescend; runASM(); } +void fmmioFlush() { PA = winmm.ommioFlush; runASM(); } +void fmmioGetInfo() { PA = winmm.ommioGetInfo; runASM(); } +void fmmioInstallIOProcA() { PA = winmm.ommioInstallIOProcA; runASM(); } +void fmmioInstallIOProcW() { PA = winmm.ommioInstallIOProcW; runASM(); } +void fmmioOpenA() { PA = winmm.ommioOpenA; runASM(); } +void fmmioOpenW() { PA = winmm.ommioOpenW; runASM(); } +void fmmioRead() { PA = winmm.ommioRead; runASM(); } +void fmmioRenameA() { PA = winmm.ommioRenameA; runASM(); } +void fmmioRenameW() { PA = winmm.ommioRenameW; runASM(); } +void fmmioSeek() { PA = winmm.ommioSeek; runASM(); } +void fmmioSendMessage() { PA = winmm.ommioSendMessage; runASM(); } +void fmmioSetBuffer() { PA = winmm.ommioSetBuffer; runASM(); } +void fmmioSetInfo() { PA = winmm.ommioSetInfo; runASM(); } +void fmmioStringToFOURCCA() { PA = winmm.ommioStringToFOURCCA; runASM(); } +void fmmioStringToFOURCCW() { PA = winmm.ommioStringToFOURCCW; runASM(); } +void fmmioWrite() { PA = winmm.ommioWrite; runASM(); } +void fmmsystemGetVersion() { PA = winmm.ommsystemGetVersion; runASM(); } +void fsndPlaySoundA() { PA = winmm.osndPlaySoundA; runASM(); } +void fsndPlaySoundW() { PA = winmm.osndPlaySoundW; runASM(); } +void ftimeBeginPeriod() { PA = winmm.otimeBeginPeriod; runASM(); } +void ftimeEndPeriod() { PA = winmm.otimeEndPeriod; runASM(); } +void ftimeGetDevCaps() { PA = winmm.otimeGetDevCaps; runASM(); } +void ftimeGetSystemTime() { PA = winmm.otimeGetSystemTime; runASM(); } +void ftimeGetTime() { PA = winmm.otimeGetTime; runASM(); } +void ftimeKillEvent() { PA = winmm.otimeKillEvent; runASM(); } +void ftimeSetEvent() { PA = winmm.otimeSetEvent; runASM(); } +void fwaveInAddBuffer() { PA = winmm.owaveInAddBuffer; runASM(); } +void fwaveInClose() { PA = winmm.owaveInClose; runASM(); } +void fwaveInGetDevCapsA() { PA = winmm.owaveInGetDevCapsA; runASM(); } +void fwaveInGetDevCapsW() { PA = winmm.owaveInGetDevCapsW; runASM(); } +void fwaveInGetErrorTextA() { PA = winmm.owaveInGetErrorTextA; runASM(); } +void fwaveInGetErrorTextW() { PA = winmm.owaveInGetErrorTextW; runASM(); } +void fwaveInGetID() { PA = winmm.owaveInGetID; runASM(); } +void fwaveInGetNumDevs() { PA = winmm.owaveInGetNumDevs; runASM(); } +void fwaveInGetPosition() { PA = winmm.owaveInGetPosition; runASM(); } +void fwaveInMessage() { PA = winmm.owaveInMessage; runASM(); } +void fwaveInOpen() { PA = winmm.owaveInOpen; runASM(); } +void fwaveInPrepareHeader() { PA = winmm.owaveInPrepareHeader; runASM(); } +void fwaveInReset() { PA = winmm.owaveInReset; runASM(); } +void fwaveInStart() { PA = winmm.owaveInStart; runASM(); } +void fwaveInStop() { PA = winmm.owaveInStop; runASM(); } +void fwaveInUnprepareHeader() { PA = winmm.owaveInUnprepareHeader; runASM(); } +void fwaveOutBreakLoop() { PA = winmm.owaveOutBreakLoop; runASM(); } +void fwaveOutClose() { PA = winmm.owaveOutClose; runASM(); } +void fwaveOutGetDevCapsA() { PA = winmm.owaveOutGetDevCapsA; runASM(); } +void fwaveOutGetDevCapsW() { PA = winmm.owaveOutGetDevCapsW; runASM(); } +void fwaveOutGetErrorTextA() { PA = winmm.owaveOutGetErrorTextA; runASM(); } +void fwaveOutGetErrorTextW() { PA = winmm.owaveOutGetErrorTextW; runASM(); } +void fwaveOutGetID() { PA = winmm.owaveOutGetID; runASM(); } +void fwaveOutGetNumDevs() { PA = winmm.owaveOutGetNumDevs; runASM(); } +void fwaveOutGetPitch() { PA = winmm.owaveOutGetPitch; runASM(); } +void fwaveOutGetPlaybackRate() { PA = winmm.owaveOutGetPlaybackRate; runASM(); } +void fwaveOutGetPosition() { PA = winmm.owaveOutGetPosition; runASM(); } +void fwaveOutGetVolume() { PA = winmm.owaveOutGetVolume; runASM(); } +void fwaveOutMessage() { PA = winmm.owaveOutMessage; runASM(); } +void fwaveOutOpen() { PA = winmm.owaveOutOpen; runASM(); } +void fwaveOutPause() { PA = winmm.owaveOutPause; runASM(); } +void fwaveOutPrepareHeader() { PA = winmm.owaveOutPrepareHeader; runASM(); } +void fwaveOutReset() { PA = winmm.owaveOutReset; runASM(); } +void fwaveOutRestart() { PA = winmm.owaveOutRestart; runASM(); } +void fwaveOutSetPitch() { PA = winmm.owaveOutSetPitch; runASM(); } +void fwaveOutSetPlaybackRate() { PA = winmm.owaveOutSetPlaybackRate; runASM(); } +void fwaveOutSetVolume() { PA = winmm.owaveOutSetVolume; runASM(); } +void fwaveOutUnprepareHeader() { PA = winmm.owaveOutUnprepareHeader; runASM(); } +void fwaveOutWrite() { PA = winmm.owaveOutWrite; runASM(); } + +void setupFunctions(HMODULE dll) { + winmm.dll = dll; + winmm.oCloseDriver = GetProcAddress(winmm.dll, "CloseDriver"); + winmm.oDefDriverProc = GetProcAddress(winmm.dll, "DefDriverProc"); + winmm.oDriverCallback = GetProcAddress(winmm.dll, "DriverCallback"); + winmm.oDrvGetModuleHandle = GetProcAddress(winmm.dll, "DrvGetModuleHandle"); + winmm.oGetDriverModuleHandle = GetProcAddress(winmm.dll, "GetDriverModuleHandle"); + winmm.oOpenDriver = GetProcAddress(winmm.dll, "OpenDriver"); + winmm.oPlaySound = GetProcAddress(winmm.dll, "PlaySound"); + winmm.oPlaySoundA = GetProcAddress(winmm.dll, "PlaySoundA"); + winmm.oPlaySoundW = GetProcAddress(winmm.dll, "PlaySoundW"); + winmm.oSendDriverMessage = GetProcAddress(winmm.dll, "SendDriverMessage"); + winmm.oWOWAppExit = GetProcAddress(winmm.dll, "WOWAppExit"); + winmm.oauxGetDevCapsA = GetProcAddress(winmm.dll, "auxGetDevCapsA"); + winmm.oauxGetDevCapsW = GetProcAddress(winmm.dll, "auxGetDevCapsW"); + winmm.oauxGetNumDevs = GetProcAddress(winmm.dll, "auxGetNumDevs"); + winmm.oauxGetVolume = GetProcAddress(winmm.dll, "auxGetVolume"); + winmm.oauxOutMessage = GetProcAddress(winmm.dll, "auxOutMessage"); + winmm.oauxSetVolume = GetProcAddress(winmm.dll, "auxSetVolume"); + winmm.ojoyConfigChanged = GetProcAddress(winmm.dll, "joyConfigChanged"); + winmm.ojoyGetDevCapsA = GetProcAddress(winmm.dll, "joyGetDevCapsA"); + winmm.ojoyGetDevCapsW = GetProcAddress(winmm.dll, "joyGetDevCapsW"); + winmm.ojoyGetNumDevs = GetProcAddress(winmm.dll, "joyGetNumDevs"); + winmm.ojoyGetPos = GetProcAddress(winmm.dll, "joyGetPos"); + winmm.ojoyGetPosEx = GetProcAddress(winmm.dll, "joyGetPosEx"); + winmm.ojoyGetThreshold = GetProcAddress(winmm.dll, "joyGetThreshold"); + winmm.ojoyReleaseCapture = GetProcAddress(winmm.dll, "joyReleaseCapture"); + winmm.ojoySetCapture = GetProcAddress(winmm.dll, "joySetCapture"); + winmm.ojoySetThreshold = GetProcAddress(winmm.dll, "joySetThreshold"); + winmm.omciDriverNotify = GetProcAddress(winmm.dll, "mciDriverNotify"); + winmm.omciDriverYield = GetProcAddress(winmm.dll, "mciDriverYield"); + winmm.omciExecute = GetProcAddress(winmm.dll, "mciExecute"); + winmm.omciFreeCommandResource = GetProcAddress(winmm.dll, "mciFreeCommandResource"); + winmm.omciGetCreatorTask = GetProcAddress(winmm.dll, "mciGetCreatorTask"); + winmm.omciGetDeviceIDA = GetProcAddress(winmm.dll, "mciGetDeviceIDA"); + winmm.omciGetDeviceIDFromElementIDA = GetProcAddress(winmm.dll, "mciGetDeviceIDFromElementIDA"); + winmm.omciGetDeviceIDFromElementIDW = GetProcAddress(winmm.dll, "mciGetDeviceIDFromElementIDW"); + winmm.omciGetDeviceIDW = GetProcAddress(winmm.dll, "mciGetDeviceIDW"); + winmm.omciGetDriverData = GetProcAddress(winmm.dll, "mciGetDriverData"); + winmm.omciGetErrorStringA = GetProcAddress(winmm.dll, "mciGetErrorStringA"); + winmm.omciGetErrorStringW = GetProcAddress(winmm.dll, "mciGetErrorStringW"); + winmm.omciGetYieldProc = GetProcAddress(winmm.dll, "mciGetYieldProc"); + winmm.omciLoadCommandResource = GetProcAddress(winmm.dll, "mciLoadCommandResource"); + winmm.omciSendCommandA = GetProcAddress(winmm.dll, "mciSendCommandA"); + winmm.omciSendCommandW = GetProcAddress(winmm.dll, "mciSendCommandW"); + winmm.omciSendStringA = GetProcAddress(winmm.dll, "mciSendStringA"); + winmm.omciSendStringW = GetProcAddress(winmm.dll, "mciSendStringW"); + winmm.omciSetDriverData = GetProcAddress(winmm.dll, "mciSetDriverData"); + winmm.omciSetYieldProc = GetProcAddress(winmm.dll, "mciSetYieldProc"); + winmm.omidiConnect = GetProcAddress(winmm.dll, "midiConnect"); + winmm.omidiDisconnect = GetProcAddress(winmm.dll, "midiDisconnect"); + winmm.omidiInAddBuffer = GetProcAddress(winmm.dll, "midiInAddBuffer"); + winmm.omidiInClose = GetProcAddress(winmm.dll, "midiInClose"); + winmm.omidiInGetDevCapsA = GetProcAddress(winmm.dll, "midiInGetDevCapsA"); + winmm.omidiInGetDevCapsW = GetProcAddress(winmm.dll, "midiInGetDevCapsW"); + winmm.omidiInGetErrorTextA = GetProcAddress(winmm.dll, "midiInGetErrorTextA"); + winmm.omidiInGetErrorTextW = GetProcAddress(winmm.dll, "midiInGetErrorTextW"); + winmm.omidiInGetID = GetProcAddress(winmm.dll, "midiInGetID"); + winmm.omidiInGetNumDevs = GetProcAddress(winmm.dll, "midiInGetNumDevs"); + winmm.omidiInMessage = GetProcAddress(winmm.dll, "midiInMessage"); + winmm.omidiInOpen = GetProcAddress(winmm.dll, "midiInOpen"); + winmm.omidiInPrepareHeader = GetProcAddress(winmm.dll, "midiInPrepareHeader"); + winmm.omidiInReset = GetProcAddress(winmm.dll, "midiInReset"); + winmm.omidiInStart = GetProcAddress(winmm.dll, "midiInStart"); + winmm.omidiInStop = GetProcAddress(winmm.dll, "midiInStop"); + winmm.omidiInUnprepareHeader = GetProcAddress(winmm.dll, "midiInUnprepareHeader"); + winmm.omidiOutCacheDrumPatches = GetProcAddress(winmm.dll, "midiOutCacheDrumPatches"); + winmm.omidiOutCachePatches = GetProcAddress(winmm.dll, "midiOutCachePatches"); + winmm.omidiOutClose = GetProcAddress(winmm.dll, "midiOutClose"); + winmm.omidiOutGetDevCapsA = GetProcAddress(winmm.dll, "midiOutGetDevCapsA"); + winmm.omidiOutGetDevCapsW = GetProcAddress(winmm.dll, "midiOutGetDevCapsW"); + winmm.omidiOutGetErrorTextA = GetProcAddress(winmm.dll, "midiOutGetErrorTextA"); + winmm.omidiOutGetErrorTextW = GetProcAddress(winmm.dll, "midiOutGetErrorTextW"); + winmm.omidiOutGetID = GetProcAddress(winmm.dll, "midiOutGetID"); + winmm.omidiOutGetNumDevs = GetProcAddress(winmm.dll, "midiOutGetNumDevs"); + winmm.omidiOutGetVolume = GetProcAddress(winmm.dll, "midiOutGetVolume"); + winmm.omidiOutLongMsg = GetProcAddress(winmm.dll, "midiOutLongMsg"); + winmm.omidiOutMessage = GetProcAddress(winmm.dll, "midiOutMessage"); + winmm.omidiOutOpen = GetProcAddress(winmm.dll, "midiOutOpen"); + winmm.omidiOutPrepareHeader = GetProcAddress(winmm.dll, "midiOutPrepareHeader"); + winmm.omidiOutReset = GetProcAddress(winmm.dll, "midiOutReset"); + winmm.omidiOutSetVolume = GetProcAddress(winmm.dll, "midiOutSetVolume"); + winmm.omidiOutShortMsg = GetProcAddress(winmm.dll, "midiOutShortMsg"); + winmm.omidiOutUnprepareHeader = GetProcAddress(winmm.dll, "midiOutUnprepareHeader"); + winmm.omidiStreamClose = GetProcAddress(winmm.dll, "midiStreamClose"); + winmm.omidiStreamOpen = GetProcAddress(winmm.dll, "midiStreamOpen"); + winmm.omidiStreamOut = GetProcAddress(winmm.dll, "midiStreamOut"); + winmm.omidiStreamPause = GetProcAddress(winmm.dll, "midiStreamPause"); + winmm.omidiStreamPosition = GetProcAddress(winmm.dll, "midiStreamPosition"); + winmm.omidiStreamProperty = GetProcAddress(winmm.dll, "midiStreamProperty"); + winmm.omidiStreamRestart = GetProcAddress(winmm.dll, "midiStreamRestart"); + winmm.omidiStreamStop = GetProcAddress(winmm.dll, "midiStreamStop"); + winmm.omixerClose = GetProcAddress(winmm.dll, "mixerClose"); + winmm.omixerGetControlDetailsA = GetProcAddress(winmm.dll, "mixerGetControlDetailsA"); + winmm.omixerGetControlDetailsW = GetProcAddress(winmm.dll, "mixerGetControlDetailsW"); + winmm.omixerGetDevCapsA = GetProcAddress(winmm.dll, "mixerGetDevCapsA"); + winmm.omixerGetDevCapsW = GetProcAddress(winmm.dll, "mixerGetDevCapsW"); + winmm.omixerGetID = GetProcAddress(winmm.dll, "mixerGetID"); + winmm.omixerGetLineControlsA = GetProcAddress(winmm.dll, "mixerGetLineControlsA"); + winmm.omixerGetLineControlsW = GetProcAddress(winmm.dll, "mixerGetLineControlsW"); + winmm.omixerGetLineInfoA = GetProcAddress(winmm.dll, "mixerGetLineInfoA"); + winmm.omixerGetLineInfoW = GetProcAddress(winmm.dll, "mixerGetLineInfoW"); + winmm.omixerGetNumDevs = GetProcAddress(winmm.dll, "mixerGetNumDevs"); + winmm.omixerMessage = GetProcAddress(winmm.dll, "mixerMessage"); + winmm.omixerOpen = GetProcAddress(winmm.dll, "mixerOpen"); + winmm.omixerSetControlDetails = GetProcAddress(winmm.dll, "mixerSetControlDetails"); + winmm.ommDrvInstall = GetProcAddress(winmm.dll, "mmDrvInstall"); + winmm.ommGetCurrentTask = GetProcAddress(winmm.dll, "mmGetCurrentTask"); + winmm.ommTaskBlock = GetProcAddress(winmm.dll, "mmTaskBlock"); + winmm.ommTaskCreate = GetProcAddress(winmm.dll, "mmTaskCreate"); + winmm.ommTaskSignal = GetProcAddress(winmm.dll, "mmTaskSignal"); + winmm.ommTaskYield = GetProcAddress(winmm.dll, "mmTaskYield"); + winmm.ommioAdvance = GetProcAddress(winmm.dll, "mmioAdvance"); + winmm.ommioAscend = GetProcAddress(winmm.dll, "mmioAscend"); + winmm.ommioClose = GetProcAddress(winmm.dll, "mmioClose"); + winmm.ommioCreateChunk = GetProcAddress(winmm.dll, "mmioCreateChunk"); + winmm.ommioDescend = GetProcAddress(winmm.dll, "mmioDescend"); + winmm.ommioFlush = GetProcAddress(winmm.dll, "mmioFlush"); + winmm.ommioGetInfo = GetProcAddress(winmm.dll, "mmioGetInfo"); + winmm.ommioInstallIOProcA = GetProcAddress(winmm.dll, "mmioInstallIOProcA"); + winmm.ommioInstallIOProcW = GetProcAddress(winmm.dll, "mmioInstallIOProcW"); + winmm.ommioOpenA = GetProcAddress(winmm.dll, "mmioOpenA"); + winmm.ommioOpenW = GetProcAddress(winmm.dll, "mmioOpenW"); + winmm.ommioRead = GetProcAddress(winmm.dll, "mmioRead"); + winmm.ommioRenameA = GetProcAddress(winmm.dll, "mmioRenameA"); + winmm.ommioRenameW = GetProcAddress(winmm.dll, "mmioRenameW"); + winmm.ommioSeek = GetProcAddress(winmm.dll, "mmioSeek"); + winmm.ommioSendMessage = GetProcAddress(winmm.dll, "mmioSendMessage"); + winmm.ommioSetBuffer = GetProcAddress(winmm.dll, "mmioSetBuffer"); + winmm.ommioSetInfo = GetProcAddress(winmm.dll, "mmioSetInfo"); + winmm.ommioStringToFOURCCA = GetProcAddress(winmm.dll, "mmioStringToFOURCCA"); + winmm.ommioStringToFOURCCW = GetProcAddress(winmm.dll, "mmioStringToFOURCCW"); + winmm.ommioWrite = GetProcAddress(winmm.dll, "mmioWrite"); + winmm.ommsystemGetVersion = GetProcAddress(winmm.dll, "mmsystemGetVersion"); + winmm.osndPlaySoundA = GetProcAddress(winmm.dll, "sndPlaySoundA"); + winmm.osndPlaySoundW = GetProcAddress(winmm.dll, "sndPlaySoundW"); + winmm.otimeBeginPeriod = GetProcAddress(winmm.dll, "timeBeginPeriod"); + winmm.otimeEndPeriod = GetProcAddress(winmm.dll, "timeEndPeriod"); + winmm.otimeGetDevCaps = GetProcAddress(winmm.dll, "timeGetDevCaps"); + winmm.otimeGetSystemTime = GetProcAddress(winmm.dll, "timeGetSystemTime"); + winmm.otimeGetTime = GetProcAddress(winmm.dll, "timeGetTime"); + winmm.otimeKillEvent = GetProcAddress(winmm.dll, "timeKillEvent"); + winmm.otimeSetEvent = GetProcAddress(winmm.dll, "timeSetEvent"); + winmm.owaveInAddBuffer = GetProcAddress(winmm.dll, "waveInAddBuffer"); + winmm.owaveInClose = GetProcAddress(winmm.dll, "waveInClose"); + winmm.owaveInGetDevCapsA = GetProcAddress(winmm.dll, "waveInGetDevCapsA"); + winmm.owaveInGetDevCapsW = GetProcAddress(winmm.dll, "waveInGetDevCapsW"); + winmm.owaveInGetErrorTextA = GetProcAddress(winmm.dll, "waveInGetErrorTextA"); + winmm.owaveInGetErrorTextW = GetProcAddress(winmm.dll, "waveInGetErrorTextW"); + winmm.owaveInGetID = GetProcAddress(winmm.dll, "waveInGetID"); + winmm.owaveInGetNumDevs = GetProcAddress(winmm.dll, "waveInGetNumDevs"); + winmm.owaveInGetPosition = GetProcAddress(winmm.dll, "waveInGetPosition"); + winmm.owaveInMessage = GetProcAddress(winmm.dll, "waveInMessage"); + winmm.owaveInOpen = GetProcAddress(winmm.dll, "waveInOpen"); + winmm.owaveInPrepareHeader = GetProcAddress(winmm.dll, "waveInPrepareHeader"); + winmm.owaveInReset = GetProcAddress(winmm.dll, "waveInReset"); + winmm.owaveInStart = GetProcAddress(winmm.dll, "waveInStart"); + winmm.owaveInStop = GetProcAddress(winmm.dll, "waveInStop"); + winmm.owaveInUnprepareHeader = GetProcAddress(winmm.dll, "waveInUnprepareHeader"); + winmm.owaveOutBreakLoop = GetProcAddress(winmm.dll, "waveOutBreakLoop"); + winmm.owaveOutClose = GetProcAddress(winmm.dll, "waveOutClose"); + winmm.owaveOutGetDevCapsA = GetProcAddress(winmm.dll, "waveOutGetDevCapsA"); + winmm.owaveOutGetDevCapsW = GetProcAddress(winmm.dll, "waveOutGetDevCapsW"); + winmm.owaveOutGetErrorTextA = GetProcAddress(winmm.dll, "waveOutGetErrorTextA"); + winmm.owaveOutGetErrorTextW = GetProcAddress(winmm.dll, "waveOutGetErrorTextW"); + winmm.owaveOutGetID = GetProcAddress(winmm.dll, "waveOutGetID"); + winmm.owaveOutGetNumDevs = GetProcAddress(winmm.dll, "waveOutGetNumDevs"); + winmm.owaveOutGetPitch = GetProcAddress(winmm.dll, "waveOutGetPitch"); + winmm.owaveOutGetPlaybackRate = GetProcAddress(winmm.dll, "waveOutGetPlaybackRate"); + winmm.owaveOutGetPosition = GetProcAddress(winmm.dll, "waveOutGetPosition"); + winmm.owaveOutGetVolume = GetProcAddress(winmm.dll, "waveOutGetVolume"); + winmm.owaveOutMessage = GetProcAddress(winmm.dll, "waveOutMessage"); + winmm.owaveOutOpen = GetProcAddress(winmm.dll, "waveOutOpen"); + winmm.owaveOutPause = GetProcAddress(winmm.dll, "waveOutPause"); + winmm.owaveOutPrepareHeader = GetProcAddress(winmm.dll, "waveOutPrepareHeader"); + winmm.owaveOutReset = GetProcAddress(winmm.dll, "waveOutReset"); + winmm.owaveOutRestart = GetProcAddress(winmm.dll, "waveOutRestart"); + winmm.owaveOutSetPitch = GetProcAddress(winmm.dll, "waveOutSetPitch"); + winmm.owaveOutSetPlaybackRate = GetProcAddress(winmm.dll, "waveOutSetPlaybackRate"); + winmm.owaveOutSetVolume = GetProcAddress(winmm.dll, "waveOutSetVolume"); + winmm.owaveOutUnprepareHeader = GetProcAddress(winmm.dll, "waveOutUnprepareHeader"); + winmm.owaveOutWrite = GetProcAddress(winmm.dll, "waveOutWrite"); +} + +void cleanupProxy(void) { + if (winmm.dll != NULL) { + FreeLibrary(winmm.dll); + winmm.dll = NULL; + } +} +#pragma endregion \ No newline at end of file diff --git a/EDF5ModLoader/proxy.h b/EDF5ModLoader/proxy.h new file mode 100644 index 0000000..f3fb157 --- /dev/null +++ b/EDF5ModLoader/proxy.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void setupFunctions(HMODULE); +void cleanupProxy(void); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/EDF5ModLoader/winmm.asm b/EDF5ModLoader/winmm.asm new file mode 100644 index 0000000..d475ed4 --- /dev/null +++ b/EDF5ModLoader/winmm.asm @@ -0,0 +1,55 @@ +.data +extern PA : qword + +extern fnk27680_hook_main : proto +save_ret QWORD 0 +save_rax QWORD 0 +save_rcx QWORD 0 +save_rdx QWORD 0 +save_r8 QWORD 0 +save_r9 QWORD 0 +save_r10 QWORD 0 +save_r11 QWORD 0 + +.code +runASM proc +jmp qword ptr [PA] +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 +; Save registers +mov save_rax, rax +mov save_rcx, rcx +mov save_rdx, rdx +mov save_r8, r8 +mov save_r9, r9 +mov save_r10, r10 +mov save_r11, r11 +; Save return address +mov rax, [rsp] +mov save_ret, rax +; Replace return address and do actual work +add rsp,8 +call fnk27680_hook_main +; Restore return address +mov rax, save_ret +push rax +; Restore registers +mov r11, save_r11 +mov r10, save_r10 +mov r9, save_r9 +mov r8, save_r8 +mov rdx, save_rdx +mov rcx, save_rcx +mov rax, save_rax +; Original function code +mov QWORD PTR [rsp+16], rdx +mov QWORD PTR [rsp+24], r8 +mov QWORD PTR [rsp+32], r9 +; Goodbye +ret +fnk27680_hook endp +end diff --git a/EDF5ModLoader/winmm.def b/EDF5ModLoader/winmm.def new file mode 100644 index 0000000..04c3b69 --- /dev/null +++ b/EDF5ModLoader/winmm.def @@ -0,0 +1,182 @@ +LIBRARY winmm +EXPORTS + CloseDriver=fCloseDriver @1 + DefDriverProc=fDefDriverProc @2 + DriverCallback=fDriverCallback @3 + DrvGetModuleHandle=fDrvGetModuleHandle @4 + GetDriverModuleHandle=fGetDriverModuleHandle @5 + OpenDriver=fOpenDriver @6 + PlaySound=fPlaySound @7 + PlaySoundA=fPlaySoundA @8 + PlaySoundW=fPlaySoundW @9 + SendDriverMessage=fSendDriverMessage @10 + WOWAppExit=fWOWAppExit @11 + auxGetDevCapsA=fauxGetDevCapsA @12 + auxGetDevCapsW=fauxGetDevCapsW @13 + auxGetNumDevs=fauxGetNumDevs @14 + auxGetVolume=fauxGetVolume @15 + auxOutMessage=fauxOutMessage @16 + auxSetVolume=fauxSetVolume @17 + joyConfigChanged=fjoyConfigChanged @18 + joyGetDevCapsA=fjoyGetDevCapsA @19 + joyGetDevCapsW=fjoyGetDevCapsW @20 + joyGetNumDevs=fjoyGetNumDevs @21 + joyGetPos=fjoyGetPos @22 + joyGetPosEx=fjoyGetPosEx @23 + joyGetThreshold=fjoyGetThreshold @24 + joyReleaseCapture=fjoyReleaseCapture @25 + joySetCapture=fjoySetCapture @26 + joySetThreshold=fjoySetThreshold @27 + mciDriverNotify=fmciDriverNotify @28 + mciDriverYield=fmciDriverYield @29 + mciExecute=fmciExecute @30 + mciFreeCommandResource=fmciFreeCommandResource @31 + mciGetCreatorTask=fmciGetCreatorTask @32 + mciGetDeviceIDA=fmciGetDeviceIDA @33 + mciGetDeviceIDFromElementIDA=fmciGetDeviceIDFromElementIDA @34 + mciGetDeviceIDFromElementIDW=fmciGetDeviceIDFromElementIDW @35 + mciGetDeviceIDW=fmciGetDeviceIDW @36 + mciGetDriverData=fmciGetDriverData @37 + mciGetErrorStringA=fmciGetErrorStringA @38 + mciGetErrorStringW=fmciGetErrorStringW @39 + mciGetYieldProc=fmciGetYieldProc @40 + mciLoadCommandResource=fmciLoadCommandResource @41 + mciSendCommandA=fmciSendCommandA @42 + mciSendCommandW=fmciSendCommandW @43 + mciSendStringA=fmciSendStringA @44 + mciSendStringW=fmciSendStringW @45 + mciSetDriverData=fmciSetDriverData @46 + mciSetYieldProc=fmciSetYieldProc @47 + midiConnect=fmidiConnect @48 + midiDisconnect=fmidiDisconnect @49 + midiInAddBuffer=fmidiInAddBuffer @50 + midiInClose=fmidiInClose @51 + midiInGetDevCapsA=fmidiInGetDevCapsA @52 + midiInGetDevCapsW=fmidiInGetDevCapsW @53 + midiInGetErrorTextA=fmidiInGetErrorTextA @54 + midiInGetErrorTextW=fmidiInGetErrorTextW @55 + midiInGetID=fmidiInGetID @56 + midiInGetNumDevs=fmidiInGetNumDevs @57 + midiInMessage=fmidiInMessage @58 + midiInOpen=fmidiInOpen @59 + midiInPrepareHeader=fmidiInPrepareHeader @60 + midiInReset=fmidiInReset @61 + midiInStart=fmidiInStart @62 + midiInStop=fmidiInStop @63 + midiInUnprepareHeader=fmidiInUnprepareHeader @64 + midiOutCacheDrumPatches=fmidiOutCacheDrumPatches @65 + midiOutCachePatches=fmidiOutCachePatches @66 + midiOutClose=fmidiOutClose @67 + midiOutGetDevCapsA=fmidiOutGetDevCapsA @68 + midiOutGetDevCapsW=fmidiOutGetDevCapsW @69 + midiOutGetErrorTextA=fmidiOutGetErrorTextA @70 + midiOutGetErrorTextW=fmidiOutGetErrorTextW @71 + midiOutGetID=fmidiOutGetID @72 + midiOutGetNumDevs=fmidiOutGetNumDevs @73 + midiOutGetVolume=fmidiOutGetVolume @74 + midiOutLongMsg=fmidiOutLongMsg @75 + midiOutMessage=fmidiOutMessage @76 + midiOutOpen=fmidiOutOpen @77 + midiOutPrepareHeader=fmidiOutPrepareHeader @78 + midiOutReset=fmidiOutReset @79 + midiOutSetVolume=fmidiOutSetVolume @80 + midiOutShortMsg=fmidiOutShortMsg @81 + midiOutUnprepareHeader=fmidiOutUnprepareHeader @82 + midiStreamClose=fmidiStreamClose @83 + midiStreamOpen=fmidiStreamOpen @84 + midiStreamOut=fmidiStreamOut @85 + midiStreamPause=fmidiStreamPause @86 + midiStreamPosition=fmidiStreamPosition @87 + midiStreamProperty=fmidiStreamProperty @88 + midiStreamRestart=fmidiStreamRestart @89 + midiStreamStop=fmidiStreamStop @90 + mixerClose=fmixerClose @91 + mixerGetControlDetailsA=fmixerGetControlDetailsA @92 + mixerGetControlDetailsW=fmixerGetControlDetailsW @93 + mixerGetDevCapsA=fmixerGetDevCapsA @94 + mixerGetDevCapsW=fmixerGetDevCapsW @95 + mixerGetID=fmixerGetID @96 + mixerGetLineControlsA=fmixerGetLineControlsA @97 + mixerGetLineControlsW=fmixerGetLineControlsW @98 + mixerGetLineInfoA=fmixerGetLineInfoA @99 + mixerGetLineInfoW=fmixerGetLineInfoW @100 + mixerGetNumDevs=fmixerGetNumDevs @101 + mixerMessage=fmixerMessage @102 + mixerOpen=fmixerOpen @103 + mixerSetControlDetails=fmixerSetControlDetails @104 + mmDrvInstall=fmmDrvInstall @105 + mmGetCurrentTask=fmmGetCurrentTask @106 + mmTaskBlock=fmmTaskBlock @107 + mmTaskCreate=fmmTaskCreate @108 + mmTaskSignal=fmmTaskSignal @109 + mmTaskYield=fmmTaskYield @110 + mmioAdvance=fmmioAdvance @111 + mmioAscend=fmmioAscend @112 + mmioClose=fmmioClose @113 + mmioCreateChunk=fmmioCreateChunk @114 + mmioDescend=fmmioDescend @115 + mmioFlush=fmmioFlush @116 + mmioGetInfo=fmmioGetInfo @117 + mmioInstallIOProcA=fmmioInstallIOProcA @118 + mmioInstallIOProcW=fmmioInstallIOProcW @119 + mmioOpenA=fmmioOpenA @120 + mmioOpenW=fmmioOpenW @121 + mmioRead=fmmioRead @122 + mmioRenameA=fmmioRenameA @123 + mmioRenameW=fmmioRenameW @124 + mmioSeek=fmmioSeek @125 + mmioSendMessage=fmmioSendMessage @126 + mmioSetBuffer=fmmioSetBuffer @127 + mmioSetInfo=fmmioSetInfo @128 + mmioStringToFOURCCA=fmmioStringToFOURCCA @129 + mmioStringToFOURCCW=fmmioStringToFOURCCW @130 + mmioWrite=fmmioWrite @131 + mmsystemGetVersion=fmmsystemGetVersion @132 + sndPlaySoundA=fsndPlaySoundA @133 + sndPlaySoundW=fsndPlaySoundW @134 + timeBeginPeriod=ftimeBeginPeriod @135 + timeEndPeriod=ftimeEndPeriod @136 + timeGetDevCaps=ftimeGetDevCaps @137 + timeGetSystemTime=ftimeGetSystemTime @138 + timeGetTime=ftimeGetTime @139 + timeKillEvent=ftimeKillEvent @140 + timeSetEvent=ftimeSetEvent @141 + waveInAddBuffer=fwaveInAddBuffer @142 + waveInClose=fwaveInClose @143 + waveInGetDevCapsA=fwaveInGetDevCapsA @144 + waveInGetDevCapsW=fwaveInGetDevCapsW @145 + waveInGetErrorTextA=fwaveInGetErrorTextA @146 + waveInGetErrorTextW=fwaveInGetErrorTextW @147 + waveInGetID=fwaveInGetID @148 + waveInGetNumDevs=fwaveInGetNumDevs @149 + waveInGetPosition=fwaveInGetPosition @150 + waveInMessage=fwaveInMessage @151 + waveInOpen=fwaveInOpen @152 + waveInPrepareHeader=fwaveInPrepareHeader @153 + waveInReset=fwaveInReset @154 + waveInStart=fwaveInStart @155 + waveInStop=fwaveInStop @156 + waveInUnprepareHeader=fwaveInUnprepareHeader @157 + waveOutBreakLoop=fwaveOutBreakLoop @158 + waveOutClose=fwaveOutClose @159 + waveOutGetDevCapsA=fwaveOutGetDevCapsA @160 + waveOutGetDevCapsW=fwaveOutGetDevCapsW @161 + waveOutGetErrorTextA=fwaveOutGetErrorTextA @162 + waveOutGetErrorTextW=fwaveOutGetErrorTextW @163 + waveOutGetID=fwaveOutGetID @164 + waveOutGetNumDevs=fwaveOutGetNumDevs @165 + waveOutGetPitch=fwaveOutGetPitch @166 + waveOutGetPlaybackRate=fwaveOutGetPlaybackRate @167 + waveOutGetPosition=fwaveOutGetPosition @168 + waveOutGetVolume=fwaveOutGetVolume @169 + waveOutMessage=fwaveOutMessage @170 + waveOutOpen=fwaveOutOpen @171 + waveOutPause=fwaveOutPause @172 + waveOutPrepareHeader=fwaveOutPrepareHeader @173 + waveOutReset=fwaveOutReset @174 + waveOutRestart=fwaveOutRestart @175 + waveOutSetPitch=fwaveOutSetPitch @176 + waveOutSetPlaybackRate=fwaveOutSetPlaybackRate @177 + waveOutSetVolume=fwaveOutSetVolume @178 + waveOutUnprepareHeader=fwaveOutUnprepareHeader @179 + waveOutWrite=fwaveOutWrite @180 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ae2f596 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Patcher/Patcher.vcxproj b/Patcher/Patcher.vcxproj new file mode 100644 index 0000000..95cd82d --- /dev/null +++ b/Patcher/Patcher.vcxproj @@ -0,0 +1,167 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {45AF38B1-69F6-4B91-A8FF-3F762E0432F2} + Win32Proj + Patcher + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + false + + + true + + + true + + + false + + + + NotUsing + Level3 + MaxSpeed + true + true + true + NDEBUG;PATCHER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + ..\EDF5ModLoader\ + + + Windows + true + true + true + false + + + + + NotUsing + Level3 + Disabled + true + WIN32;_DEBUG;PATCHER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + ..\EDF5ModLoader\ + + + Windows + true + false + + + + + NotUsing + Level3 + Disabled + true + _DEBUG;PATCHER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + ..\EDF5ModLoader\ + + + Windows + true + false + + + + + NotUsing + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;PATCHER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + ..\EDF5ModLoader\ + + + Windows + true + true + true + false + + + + + + + + + \ No newline at end of file diff --git a/Patcher/Patcher.vcxproj.filters b/Patcher/Patcher.vcxproj.filters new file mode 100644 index 0000000..fd6c32e --- /dev/null +++ b/Patcher/Patcher.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/Patcher/Patches/IncreaseChatLimit.txt b/Patcher/Patches/IncreaseChatLimit.txt new file mode 100644 index 0000000..1ee64b1 --- /dev/null +++ b/Patcher/Patches/IncreaseChatLimit.txt @@ -0,0 +1,3 @@ +; Author: Souzooka +491480: B0[80]0F B6 C0 90 ; Maximum amount of characters in chat message. Default 0x80 (128) +49546E: 41 B8[A8 02 00 00]90 44 89 86 40 09 00 00 ; Width of the chat dialog. Default 0x2A8 (680) \ No newline at end of file diff --git a/Patcher/Patches/MouseJitterFix.txt b/Patcher/Patches/MouseJitterFix.txt new file mode 100644 index 0000000..664e745 --- /dev/null +++ b/Patcher/Patches/MouseJitterFix.txt @@ -0,0 +1,3 @@ +; Author: Souzooka +2ED698: 66 0F 1F 44 00 +5A24D7: 66 0F 1F 44 00 \ No newline at end of file diff --git a/Patcher/Patches/RemoveChatCensor.txt b/Patcher/Patches/RemoveChatCensor.txt new file mode 100644 index 0000000..a496fa7 --- /dev/null +++ b/Patcher/Patches/RemoveChatCensor.txt @@ -0,0 +1,2 @@ +; Author: Souzooka +3EF7D2: EB \ No newline at end of file diff --git a/Patcher/dllmain.cpp b/Patcher/dllmain.cpp new file mode 100644 index 0000000..ddc49b2 --- /dev/null +++ b/Patcher/dllmain.cpp @@ -0,0 +1,205 @@ +#define WIN32_LEAN_AND_MEAN +#define _CRT_SECURE_NO_WARNINGS + +#include + +#include +#include +#include +#include + +#include + +// Quick logging routines in absence of shared logging api +static FILE *hLogFile; + +#define ltlog(func, ...) do { LogDate(); func(__VA_ARGS__); fflush(hLogFile); } while (0) + +#define ltprintf(fmt, ...) ltlog(fprintf, hLogFile, fmt "\n", __VA_ARGS__); +#define ltputs(str) ltlog(fputs, str "\n", hLogFile); +#define ltputws(str) ltlog(fuputs, str L"\n", hLogFile); +#define ltwprintf(fmt, ...) ltlog(fuprintf, hLogFile, fmt L"\n", __VA_ARGS__); + +#define lbprintf(...) fprintf(hLogFile, __VA_ARGS__) + +static void LogDate(void) { + SYSTEMTIME lt; + GetLocalTime(<); + + lbprintf("[%04d-%02d-%02d %02d:%02d:%02d.%03d] ", lt.wYear, lt.wMonth, lt.wDay, lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds); +} + +static int fuputs(const wchar_t* str, FILE* file) { + int wstr_len = (int)wcslen(str); + int num_chars = WideCharToMultiByte(CP_UTF8, 0, str, wstr_len, NULL, 0, NULL, NULL); + PCHAR strTo = (PCHAR)HeapAlloc(GetProcessHeap(), 0, ((SIZE_T)num_chars + 1) * sizeof(CHAR)); + if (strTo != NULL) { + WideCharToMultiByte(CP_UTF8, 0, str, wstr_len, strTo, num_chars, NULL, NULL); + strTo[num_chars] = '\0'; + int rc = fputs(strTo, file); + HeapFree(GetProcessHeap(), 0, strTo); + return rc; + } else { + fputs("(Memory allocation failure)", file); + } + return 0; // TODO: What to return? +} + +static int fuprintf(FILE *file, const wchar_t *fmt, ...) { + va_list args; + va_start(args, fmt); + int required = _vsnwprintf(NULL, 0, fmt, args); + wchar_t* buffer = new wchar_t[(size_t)required+1]; + int rc = _vsnwprintf(buffer, (size_t)required+1, fmt, args); + va_end(args); + fuputs(buffer, file); + delete[] buffer; + return rc; +} + +// Parsed patch record +typedef struct { + uint64_t offset; + unsigned char* bytes; + size_t length; +} PatchRecord; + +// Injects patches into game process +static void WriteBuffer(void *addr, void *data, size_t len) { + DWORD oldProtect; + VirtualProtect(addr, len, PAGE_EXECUTE_READWRITE, &oldProtect); + memcpy(addr, data, len); + VirtualProtect(addr, len, oldProtect, &oldProtect); +} + +// Remove whitespace and configurable data indicator (brackets) +static int patchfilter(int c) { + return isspace(c) || c == '[' || c == ']'; +} + +extern "C" { +BOOL __declspec(dllexport) EML5_Load(PluginInfo *pluginInfo) { + // Patcher does not need to remain loaded, so not filling PluginInfo + hLogFile = fopen("Patcher.log", "wb"); + WIN32_FIND_DATAW ffd; + std::fstream pfile; + UINT_PTR hmodEXE = (UINT_PTR)GetModuleHandleW(NULL); + + ltputs("Loading patches"); + HANDLE hFind = FindFirstFileW(L"Mods\\Patches\\*.txt", &ffd); + + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ltwprintf(L"Loading patch: %s", ffd.cFileName); + wchar_t* patchPath = new wchar_t[MAX_PATH]; + wcscpy(patchPath, L"Mods\\Patches\\"); + wcscat(patchPath, ffd.cFileName); + pfile.open(patchPath, std::ios::in); + delete[] patchPath; + if (pfile.is_open()) { + std::string patchInput; + bool patch = true; + std::vector patches; + while (getline(pfile, patchInput)) { + // Clean line of whitespace + patchInput.erase(std::remove_if(patchInput.begin(), patchInput.end(), patchfilter), patchInput.end()); + + // Remove comments + size_t scpos = patchInput.find(";"); + if (scpos != std::string::npos) { + patchInput = patchInput.substr(0, scpos); + } + + // Ignore empty lines + if (!patchInput.empty()) { + // Check for colon + size_t cpos = patchInput.find(":"); + if (cpos != std::string::npos) { + // Split input into two parts + std::string address = patchInput.substr(0, cpos); + std::string patchData = patchInput.substr(cpos + 1); + // Validate patch input + if (address.find_first_not_of("0123456789ABCDEFabcdef") != std::string::npos) { + ltputs("Malformed patch input: Non hexadecimal characters in address"); + patch = false; + } else if (patchData.find_first_not_of("0123456789ABCDEFabcdef") != std::string::npos) { + ltputs("Malformed patch input: Non hexadecimal characters in patch data"); + patch = false; + } else if (patchData.size() % 2 != 0) { + ltprintf("Incomplete patch data for address %s", address.c_str()); + patch = false; + } else if (patch) { + // Parse patch input + size_t hsize = patchData.size() / 2; + unsigned char* bytes = new unsigned char[hsize]; + char hexPart[3]; + hexPart[2] = '\0'; + for (size_t i = 0; i < patchData.size(); i += 2) { + hexPart[0] = patchData[i]; + hexPart[1] = patchData[i + 1]; + bytes[i / 2] = (unsigned char)strtoul(hexPart, NULL, 16); + } + PatchRecord* record = new PatchRecord; + record->offset = strtoull(address.c_str(), NULL, 16); + record->bytes = bytes; + record->length = hsize; + patches.push_back(record); + } + } else { + ltputs("Malformed patch input: Missing colon"); + } + } + } + if (patch) { + // Apply memory patches + for (PatchRecord* record : patches) { + ltprintf("Patching %I64d bytes at EDF5.exe+%I64x", record->length, record->offset); + WriteBuffer((void*)(hmodEXE + record->offset), record->bytes, record->length); + } + } else { + ltputs("Ignoring patch"); + } + // Clean up memory + for (PatchRecord *precord : patches) { + delete[] precord->bytes; + delete precord; + } + patches.clear(); + pfile.close(); + } else { + ltputs("Failed to open patch file"); + } + } + } while (FindNextFileW(hFind, &ffd) != 0); + // Check if finished with error + DWORD dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_FILES) { + ltprintf("Failed to search for patches: error %lu", dwError); + } + FindClose(hFind); + } else { + DWORD dwError = GetLastError(); + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { + ltprintf("Failed to search for patches: error %lu", dwError); + } + } + + // Close log file + fclose(hLogFile); + + return false; // Unload plugin +} +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hModule); + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e37519 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# EDF5 ModLoader + +A very basic rudimentary modloader for Earth Defence Force 5. +Supports automatic Root.cpk redirection and DLL plugin loading. +Writes internal game logging to game.log + +This repository contains submodules! Please use `--recurse-submodules` when cloning. + +## Installation +Get the latest package from [Releases](https://github.com/BlueAmulet/EDF5ModLoader/releases) and unpack it in the same folder as EDF5.exe + +https://github.com/BlueAmulet/EDF5ModLoader/releases + +## Plugins +### Patcher +Patcher is a plugin to perform runtime memory patches. It accepts .txt files in `Mods\Patches` of the format: +```Offset: Hex bytes ; Optional comment``` +Where 'Offset' is a hexadecimal offset in memory from the base address of EDF5.exe +And 'Hex bytes' are a series of hexadecimal bytes to patch into that address. +All data including and following a semicolon is ignored up to the end of the line. +Patches by [Souzooka](https://github.com/Souzooka) are included by default. + +### Making your own +The Plugin API is in `PluginAPI.h` and is currently unfinished and subject to change. +Plugins should export a function of type `bool __fastcall EML5_Load(PluginInfo*)` +Return true to remain loaded in memory, and false to unload in case of error or desired behavior. +If your plugin remains in memory, fill out the PluginInfo struct: +``` +pluginInfo->infoVersion = PluginInfo::MaxInfoVer; +pluginInfo->name = "Plugin Name"; +pluginInfo->version = PLUG_VER(Major, Minor, Patch, Build); +``` + +## Building +You will need [Visual Studio 2019](https://visualstudio.microsoft.com/vs/community/) and [vcpkg](https://github.com/microsoft/vcpkg) + +To setup vcpkg and required libraries: +``` +git clone https://github.com/microsoft/vcpkg +cd vcpkg +bootstrap-vcpkg.bat +vcpkg install zydis:x64-windows-static-md plog:x64-windows-static-md +``` \ No newline at end of file diff --git a/funchook b/funchook new file mode 160000 index 0000000..f3928f9 --- /dev/null +++ b/funchook @@ -0,0 +1 @@ +Subproject commit f3928f99f5ec77bf750c8c7ebaec44e44ba34257