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