diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0374fe1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +empires/ +/obj +/.vs +src/.vs +*.zip \ No newline at end of file diff --git a/README.md b/README.md index 8f586bb..7029366 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ # empires-assert-patcher +## Usage +* extract the latest release to your `Empires Dedicated Server` folder +* edit `run_patched_server.bat` with your favourite text editor +* make necessary changes to `SET "ARGS=-console -toconsole -insecure +map emp_arid +sv_lan 1 +maxplayers 32 -nobreakpad"` +* start `run_patched_server.bat` to patch and run the server +### original run_patched_server +``` +:: DO NOT MODIFY +@ECHO OFF +SETLOCAL + +:: MODIFY AS NECESSARY +SET "ARGS=-console -toconsole -insecure +map emp_arid +sv_lan 1 +maxplayers 32 -nobreakpad" + +:: DO NOT MODIFY +empires\bin\empires-assert-patcher.exe +IF %ERRORLEVEL% 1 ( + PAUSE + EXIT /B +) +start "" srcds.exe -game "empires" %ARGS% +``` \ No newline at end of file diff --git a/run_patched_server.bat b/run_patched_server.bat new file mode 100644 index 0000000..0569ee9 --- /dev/null +++ b/run_patched_server.bat @@ -0,0 +1,14 @@ +:: DO NOT MODIFY +@ECHO OFF +SETLOCAL + +:: MODIFY AS NECESSARY +SET "ARGS=-console -toconsole -insecure +map emp_arid +sv_lan 1 +maxplayers 32 -nobreakpad" + +:: DO NOT MODIFY +empires\bin\empires-assert-patcher.exe +IF %ERRORLEVEL% 1 ( + PAUSE + EXIT /B +) +start "" srcds.exe -game "empires" %ARGS% \ No newline at end of file diff --git a/src/empires-assert-patcher.sln b/src/empires-assert-patcher.sln new file mode 100644 index 0000000..1f4be77 --- /dev/null +++ b/src/empires-assert-patcher.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "empires-assert-patcher", "empires-assert-patcher.vcxproj", "{9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}" +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 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Debug|x64.ActiveCfg = Debug|x64 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Debug|x64.Build.0 = Debug|x64 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Debug|x86.ActiveCfg = Debug|Win32 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Debug|x86.Build.0 = Debug|Win32 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Release|x64.ActiveCfg = Release|x64 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Release|x64.Build.0 = Release|x64 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Release|x86.ActiveCfg = Release|Win32 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {34AC155B-66F3-4F18-9C85-36D2D0B870CF} + EndGlobalSection +EndGlobal diff --git a/src/empires-assert-patcher.vcxproj b/src/empires-assert-patcher.vcxproj new file mode 100644 index 0000000..7c6c693 --- /dev/null +++ b/src/empires-assert-patcher.vcxproj @@ -0,0 +1,156 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {9E17EDBC-F74F-452C-9D53-1047CB0FEBC5} + empiresassertpatcher + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + $(SolutionDir)..\empires\bin\ + $(SolutionDir)..\obj\$(Configuration)-$(Platform)\ + $(ProjectName) + + + false + + + false + $(SolutionDir)..\empires\bin\ + $(SolutionDir)..\obj\$(Configuration)-$(Platform)\ + $(ProjectName) + false + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + Speed + + + Console + true + true + false + + + + + + + + + \ No newline at end of file diff --git a/src/empires-assert-patcher.vcxproj.filters b/src/empires-assert-patcher.vcxproj.filters new file mode 100644 index 0000000..a91ad0e --- /dev/null +++ b/src/empires-assert-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/src/empires-assert-patcher.vcxproj.user b/src/empires-assert-patcher.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/src/empires-assert-patcher.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..860dba3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,204 @@ +/* empires-assert-patcher created by ryuku@ennui.software - license can be found in included LICENSE file */ + +#define VC_EXTRALEAN +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include + +/* + server.dll+30C90 - 68 80915917 - push server.dll+3C9180 { ("i:\git-runner\builds\e2c1ae76\0\empires_code\empires_main\mp\sr") } + server.dll+30C95 - 8B F0 - mov esi,eax + server.dll+30C97 - FF 15 ECF15817 - call dword ptr [server.dll+3BF1EC] { ->tier0.CallAssertFailedNotifyFunc } + server.dll+30C9D - 83 C4 20 - add esp,20 { 32 } + server.dll+30CA0 - 85 F6 - test esi,esi + server.dll+30CA2 - 75 20 - jne server.dll+30CC4 { patch to EB 20 } + server.dll+30CA4 - FF 15 F4F15817 - call dword ptr [server.dll+3BF1F4] { ->tier0.ShouldUseNewAssertDialog } + server.dll+30CAA - 84 C0 - test al,al + server.dll+30CAC - 74 15 - je server.dll+30CC3 + server.dll+30CAE - 57 - push edi + server.dll+30CAF - 6A 1C - push 1C { 28 } + server.dll+30CB1 - 68 80915917 - push server.dll+3C9180 { ("i:\git-runner\builds\e2c1ae76\0\empires_code\empires_main\mp\sr") } + server.dll+30CB6 - FF 15 F0F15817 - call dword ptr [server.dll+3BF1F0] { ->tier0.DoNewAssertDialog } + server.dll+30CBC - 83 C4 0C - add esp,0C { 12 } + server.dll+30CBF - 84 C0 - test al,al + server.dll+30CC1 - 74 01 - je server.dll+30CC4 + server.dll+30CC3 - CC - int 3 + server.dll+30CC4 - 5F - pop edi + + 83 C4 20 85 F6 75 20 FF 15 F4 F1 58 17 84 C0 74 15 + 83 C4 20 85 F6 75 20 FF 15 ?? ?? ?? ?? 84 C0 74 15 + */ + +// comment for better performance +#define WORKING_INDICATOR + +const std::wstring fileName = L"server.dll"; +const char* mask = "xxxxx?xxx????xxxx"; +const char* aob = "\x83\xC4\x20\x85\xF6\x75\x20\xFF\x15\xF4\xF1\x58\x17\x84\xC0\x74\x15"; +const DWORD aobSize = 17; + +int DoPatch(const BYTE* pMapped, const DWORD& offset) +{ + *(BYTE*)(pMapped + offset + 5) = '\xEB'; + wprintf(L"patched: "); + for (DWORD i = 0; i < aobSize; i++) wprintf(L"%02x ", (BYTE)*(pMapped + offset + i)); + return 0; +} + +int lastMatch = 0; + +bool VerifyPatch(const BYTE* pMapped, const DWORD& offset, const std::vector& matches) +{ + if (matches.size() > lastMatch) + { + if (*(BYTE*)(pMapped + matches[lastMatch] + 5) == (BYTE)'\xEB') + { + wprintf(L"existing patch found, nothing to do\n"); + return true; + } + + lastMatch++; + } + + return false; +} + +int SearchFailed(const std::vector& matches) +{ + if (matches.size() > 0) wprintf(L"only expected a single match, patch failed\n"); + else wprintf(L"patch not found, patch failed\n"); + return 1; +} + +namespace fs = std::filesystem; +void GetExePath(std::wstring& exePath); +int AOBSearch(const std::wstring filePath); + +int main() +{ + std::wstring exePath; + GetExePath(exePath); + + wprintf(L"%s...", fileName.c_str()); + if (!fs::exists(exePath + fileName)) + { + wprintf( + L"not found\n" + "this executable needs to be in the same folder as %s", fileName.c_str() + ); + + return 1; + } + + wprintf(L"found\n"); + return AOBSearch(exePath + fileName); +} + +void GetExePath(std::wstring& exePath) +{ + DWORD bufSize = MAX_PATH; + wchar_t* buf = new wchar_t[bufSize]; + + bufSize = GetModuleFileNameW(NULL, buf, bufSize); + if (bufSize > MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + delete[] buf; + buf = new wchar_t[bufSize]; + GetModuleFileNameW(NULL, buf, bufSize); + } + + exePath = std::wstring(buf); + exePath = exePath.substr(0, exePath.find_last_of('\\') + 1); + delete[] buf; +} + +int AOBSearch(const std::wstring filePath) +{ + int exitcode = 0; + HANDLE hFile, hMapped; + DWORD dwSize = 0; + DWORD offset = 0; + BYTE* pMapped; + std::vector matches; + + // open file to prepare for mapping + if ((hFile = CreateFileW(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + { + wprintf(L"error: failed to open %s\n", filePath.c_str()); + return 1; + } + + // get file size, needed to determine when EOF + dwSize = GetFileSize(hFile, NULL); + + // map to memory + if ((hMapped = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL)) == NULL) + { + wprintf(L"error: failed to create file mapping\n"); + goto CLOSE_FILE; + } + + if ((pMapped = (BYTE*)MapViewOfFile(hMapped, FILE_MAP_ALL_ACCESS, 0, 0, 0)) == NULL) + { + wprintf(L"error: failed to map view of file\n"); + goto CLOSE_MAPPED; + } + +#ifndef WORKING_INDICATOR + wprintf(L"searching for aob...\n"); +#endif + + // iterate through BYTEs of file + for (;offset < dwSize - aobSize; offset++) + { + +#ifdef WORKING_INDICATOR + + static int dotCount = 0; + dotCount = dotCount == 5 ? 0 : dotCount + 1; + wprintf(L"\rsearching for aob%.*s%*s", dotCount, L".....", 5 - dotCount, L""); + fflush(stdout); + +#endif + + // perform BYTE compare to find aob + for (DWORD i = 0; i < aobSize; i++) + { + // if not wildcard and not matching BYTE, stop checking from this offset + if (mask[i] != '?' && (BYTE)*(pMapped + offset + i) != (BYTE)*(aob + i)) break; + + // if reached expected size, found a matching aob + if (i == aobSize - 1) + { + matches.push_back(offset); + break; + } + } + + // if patch has been verified, no need to continue + if (VerifyPatch(pMapped, offset, matches)) goto EXIT; + } + + wprintf(L"%llu matches found\n", matches.size()); + + for (const auto& m : matches) + { + wprintf(L"offset: %x - ", m); + for (DWORD i = 0; i < aobSize; i++) wprintf(L"%02x ", (BYTE)*(pMapped + m + i)); + wprintf(L"\n"); + } + + exitcode = matches.size() == 1 ? DoPatch(pMapped, matches[0]) : SearchFailed(matches); + +EXIT: + UnmapViewOfFile(pMapped); +CLOSE_MAPPED: + CloseHandle(hMapped); +CLOSE_FILE: + CloseHandle(hFile); + + return exitcode; +} \ No newline at end of file diff --git a/zip_release.bat b/zip_release.bat new file mode 100644 index 0000000..25469f5 --- /dev/null +++ b/zip_release.bat @@ -0,0 +1,5 @@ +@ECHO OFF +SETLOCAL +IF EXIST empires-assert-patcher.zip DEL empires-assert-patcher.zip +IF EXIST empires-assert-patcher.zip EXIT /B +7z a empires-assert-patcher.zip empires/bin/empires-assert-patcher.exe run_patched_server.bat LICENSE README.md \ No newline at end of file