diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2018-07-02 01:06:39 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2018-07-02 03:08:59 +0200 |
commit | c2a2445897af17adb56a32dcf111312763a575d4 (patch) | |
tree | ad459cdd682aff3a011d11b6f2a3c518c60dec6a /aoe2hd |
initial commit
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
Diffstat (limited to 'aoe2hd')
-rwxr-xr-x | aoe2hd/.gitignore | 4 | ||||
-rw-r--r-- | aoe2hd/Makefile | 32 | ||||
-rwxr-xr-x | aoe2hd/aoe2hd_pwnd.cbp | 78 | ||||
-rwxr-xr-x | aoe2hd/include/CodeGenerator.h | 40 | ||||
-rwxr-xr-x | aoe2hd/include/CodeInjector.h | 61 | ||||
-rwxr-xr-x | aoe2hd/include/CodePatcher.h | 45 | ||||
-rwxr-xr-x | aoe2hd/include/ModuleMemory.h | 57 | ||||
-rwxr-xr-x | aoe2hd/include/aoe2hd.h | 58 | ||||
-rwxr-xr-x | aoe2hd/include/native.h | 56 | ||||
-rwxr-xr-x | aoe2hd/include/utils.h | 13 | ||||
-rwxr-xr-x | aoe2hd/src/CodeGenerator.cpp | 100 | ||||
-rwxr-xr-x | aoe2hd/src/CodeInjector.cpp | 165 | ||||
-rwxr-xr-x | aoe2hd/src/CodePatcher.cpp | 111 | ||||
-rwxr-xr-x | aoe2hd/src/ModuleMemory.cpp | 118 | ||||
-rwxr-xr-x | aoe2hd/src/main.cpp | 184 | ||||
-rwxr-xr-x | aoe2hd/src/native.c | 173 | ||||
-rwxr-xr-x | aoe2hd/src/utils.cpp | 19 |
17 files changed, 1314 insertions, 0 deletions
diff --git a/aoe2hd/.gitignore b/aoe2hd/.gitignore new file mode 100755 index 0000000..45cf9a5 --- /dev/null +++ b/aoe2hd/.gitignore @@ -0,0 +1,4 @@ +/bin +/obj +/*.depend +/*.layout diff --git a/aoe2hd/Makefile b/aoe2hd/Makefile new file mode 100644 index 0000000..a27fe02 --- /dev/null +++ b/aoe2hd/Makefile @@ -0,0 +1,32 @@ +CC = i686-w64-mingw32-gcc +CXX = i686-w64-mingw32-g++ +CFLAGS := -Iinclude -Os -s -Wall -fvisibility=hidden -ffunction-sections -fdata-sections -ffast-math -fomit-frame-pointer -fexpensive-optimizations -Wl,--gc-sections -m32 -static -static-libgcc -static-libstdc++ +LDFLAGS := + +TARGETS := aoe2hd + +AOE2HD_SRC := CodeGenerator.cpp CodeInjector.cpp CodePatcher.cpp main.cpp ModuleMemory.cpp utils.cpp + + +all: $(TARGETS) + +%.o: %.c + @echo 'Building file: $<' + @echo 'Invoking: GCC C Compiler' + $(CC) $(CFLAGS) -D_GNU_SOURCE=1 -DPSAPI_VERSION=1 -std=gnu99 $(CFLAGS) -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o $@ $< + @echo 'Finished building: $<' + @echo ' ' +%.o: %.cpp + @echo 'Building file: $<' + @echo 'Invoking: GCC C++ Compiler' + $(CXX) $(CFLAGS) -D_GNU_SOURCE=1 -DPSAPI_VERSION=1 -std=c++11 $(CFLAGS) -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o $@ $< + @echo 'Finished building: $<' + @echo ' ' + + +aoe2hd: $(patsubst %.cpp,src/%.o,$(AOE2HD_SRC)) src/native.o + @echo 'Building target: $@' + @echo 'Invoking: GCC C Linker' + $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ -lpsapi + @echo 'Finished building target: $@' + @echo ' ' diff --git a/aoe2hd/aoe2hd_pwnd.cbp b/aoe2hd/aoe2hd_pwnd.cbp new file mode 100755 index 0000000..ebb5884 --- /dev/null +++ b/aoe2hd/aoe2hd_pwnd.cbp @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<CodeBlocks_project_file> + <FileVersion major="1" minor="6" /> + <Project> + <Option title="aoe2hd_pwnd" /> + <Option pch_mode="2" /> + <Option compiler="gcc" /> + <Build> + <Target title="Debug"> + <Option output="bin/Debug/aoe2hd_pwnd" prefix_auto="1" extension_auto="1" /> + <Option object_output="obj/Debug/" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Compiler> + <Add option="-g" /> + <Add directory="include" /> + </Compiler> + </Target> + <Target title="Release"> + <Option output="bin/Release/aoe2hd_pwnd" prefix_auto="1" extension_auto="1" /> + <Option object_output="obj/Release/" /> + <Option type="1" /> + <Option compiler="gcc" /> + <Compiler> + <Add option="-fomit-frame-pointer" /> + <Add option="-fexpensive-optimizations" /> + <Add option="-flto" /> + <Add option="-Os" /> + <Add option="-fvisibility=hidden" /> + <Add option="-ffunction-sections" /> + <Add option="-fdata-sections" /> + <Add directory="include" /> + </Compiler> + <Linker> + <Add option="-flto" /> + <Add option="-s" /> + <Add option="-Wl,-gc-sections" /> + </Linker> + </Target> + </Build> + <Compiler> + <Add option="-Wall" /> + <Add option="-std=c++11" /> + <Add option="-m32" /> + <Add option="-fexceptions" /> + <Add option="-DPSAPI_VERSION=1" /> + </Compiler> + <Linker> + <Add option="-O2" /> + <Add option="-static-libstdc++" /> + <Add option="-static-libgcc" /> + <Add option="-static" /> + <Add option="-m32" /> + <Add library="psapi" /> + </Linker> + <Unit filename="include/CodeGenerator.h" /> + <Unit filename="include/CodeInjector.h" /> + <Unit filename="include/CodePatcher.h" /> + <Unit filename="include/ModuleMemory.h" /> + <Unit filename="include/aoe2hd.h" /> + <Unit filename="include/native.h" /> + <Unit filename="include/utils.h" /> + <Unit filename="src/CodeGenerator.cpp" /> + <Unit filename="src/CodeInjector.cpp" /> + <Unit filename="src/CodePatcher.cpp" /> + <Unit filename="src/ModuleMemory.cpp" /> + <Unit filename="src/main.cpp" /> + <Unit filename="src/native.c"> + <Option compilerVar="CC" /> + </Unit> + <Unit filename="src/utils.cpp" /> + <Extensions> + <code_completion /> + <envvars /> + <debugger /> + </Extensions> + </Project> +</CodeBlocks_project_file> diff --git a/aoe2hd/include/CodeGenerator.h b/aoe2hd/include/CodeGenerator.h new file mode 100755 index 0000000..e0649dd --- /dev/null +++ b/aoe2hd/include/CodeGenerator.h @@ -0,0 +1,40 @@ +#ifndef CODEGENERATOR_H +#define CODEGENERATOR_H + +#include <string> +#include <vector> + +#include "CodeInjector.h" + + +std::vector<unsigned char> x86_relJump(unsigned long dst, + unsigned long src); + +class CodeGenerator +{ +public: + CodeGenerator(const native_data& nd); + virtual ~CodeGenerator(); + void clear() + { + codes.clear(); + } + bool hasCode(int index); + CodeGenerator& addCode(const std::vector<unsigned char>& code); + CodeGenerator& setCode(int index, const std::vector<unsigned char>& code); + CodeGenerator& setCodeSized(int index, const std::vector<unsigned char>& code); + CodeGenerator& setRel32JMP(int index, unsigned long dst, unsigned long src, bool reversed = false); + std::vector<unsigned char>::size_type buildSize(int maxCodes = -1); + std::vector<unsigned char> build(); + std::vector<unsigned char> buildAndClear(); + std::string toString(); +private: + const native_data& nd; + std::vector<std::vector<unsigned char>> codes; + unsigned long diffRel32JMP(bool reversed, int index = -1) + { + return (!reversed ? buildSize(index) - 0x5 : buildSize(index)); + } +}; + +#endif // CODEGENERATOR_H diff --git a/aoe2hd/include/CodeInjector.h b/aoe2hd/include/CodeInjector.h new file mode 100755 index 0000000..189b580 --- /dev/null +++ b/aoe2hd/include/CodeInjector.h @@ -0,0 +1,61 @@ +#ifndef CODEINJECTOR_H +#define CODEINJECTOR_H + +#include <vector> +#include <map> +#include <string> + +extern "C" { +#include "native.h" +} + + +typedef struct code_bin +{ + unsigned long addr; + unsigned long siz; + bool operator<(const code_bin& a) const + { + return addr < a.addr; + } +} code_bin; + +typedef struct code_seg +{ + unsigned long addr; + unsigned long siz; + std::map<const std::string, code_bin> children; +} code_seg; + +class CodeInjector +{ +public: + CodeInjector(const native_data& nd); + virtual ~CodeInjector(); + bool allocCodeSegment(const std::string& name, + unsigned long siz = 4096); + bool addCode(const std::string& name, const std::string& code_name, + const std::vector<unsigned char>& code); + bool addCode(const std::string& name, const std::string& code_name, + unsigned long siz); + bool setCode(const std::string& name, const std::string& code_name, + const std::vector<unsigned char>& code, + unsigned long offset = 0); + bool delCode(const std::string& name, const std::string& code_name); + unsigned long getCodeAddr(const std::string& name, const std::string& code_name); + bool getCodeSeg(const std::string& name, code_seg *seg); + bool getCodeBin(const std::string& name, const std::string& code_name, code_bin *bin); + std::string toString(); +private: + const native_data& nd; + std::map<std::string, code_seg> code_map; + bool codeSegExists(const std::string& name) + { + return code_map.find(name) != code_map.end(); + } + bool codeBinExists(const std::string& name, const std::string& code_name); + std::vector<code_bin> convertCodeSegChildren(const std::string& name); + unsigned long findCodeCave(const std::string& name, unsigned long siz); +}; + +#endif // CODEINJECTOR_H diff --git a/aoe2hd/include/CodePatcher.h b/aoe2hd/include/CodePatcher.h new file mode 100755 index 0000000..1713b21 --- /dev/null +++ b/aoe2hd/include/CodePatcher.h @@ -0,0 +1,45 @@ +#ifndef CODEPATCHER_H +#define CODEPATCHER_H + +#include <vector> +#include <map> + +extern "C" { +#include "native.h" +} + + +typedef struct code_patch +{ + unsigned long addr; + std::vector<unsigned char> old_code; + std::vector<unsigned char> new_code; + long new_offset; + long suspend; +} code_patch; + +class CodePatcher +{ +public: + CodePatcher(const native_data& nd); + virtual ~CodePatcher(); + bool addPatch(const std::string& name, + unsigned long addr, + const std::vector<unsigned char>& old_code, + const std::vector<unsigned char>& new_code, + long new_offset = 0); + void setPatchSuspend(const std::string& name, long doSuspend); + bool doPatch(const std::string& name, int doUnPatch); + bool autoPatch(const std::string& name); + std::string toString(); +private: + const native_data& nd; + std::map<std::string, code_patch> patch_map; + bool codePatchExists(const std::string& name) + { + return patch_map.find(name) != patch_map.end(); + } + bool codeCmp(unsigned long addr, std::vector<unsigned char> code); +}; + +#endif // CODEPATCHER_H diff --git a/aoe2hd/include/ModuleMemory.h b/aoe2hd/include/ModuleMemory.h new file mode 100755 index 0000000..2fa2584 --- /dev/null +++ b/aoe2hd/include/ModuleMemory.h @@ -0,0 +1,57 @@ +#ifndef PROCESSMEMORY_H +#define PROCESSMEMORY_H + +#include <map> +#include <set> +#include <string> + +extern "C" { +#include "native.h" +} + + +typedef struct target_ptr +{ + unsigned long base; + unsigned long offset; + unsigned long ptr; + bool valid; + std::string dependency; + std::set<std::string> children; +} target_ptr; + +class ModuleMemory +{ +public: + ModuleMemory(const native_data& nd); + virtual ~ModuleMemory(); + unsigned long getPtr(const std::string& name); + unsigned long getPtr(const std::string& name, unsigned long *dest_ptr); + unsigned long getPtr(const std::string& name, unsigned long base, unsigned long offset); + unsigned long recheckPtr(const std::string& name); + void revalidateAllPtr(); + bool ptrSetDependency(const std::string& name, const std::string& dependency); + bool getData(const std::string& name, void *buffer, unsigned long siz); + std::string toString(); + std::string toStringStats(); +private: + const native_data& nd; + std::map<std::string, target_ptr> ptr_map; + unsigned long ptr_read_count; + unsigned long ptr_invalid_count; + bool ptrExists(const std::string& name) + { + return ptr_map.find(name) != ptr_map.end(); + } + bool ptrValid(const std::string& name) + { + if (ptrExists(name) && ptr_map[name].valid) + { + return true; + } + else ++ptr_invalid_count; + return false; + } +}; + +#endif // PROCESSMEMORY_H diff --git a/aoe2hd/include/aoe2hd.h b/aoe2hd/include/aoe2hd.h new file mode 100755 index 0000000..424f71f --- /dev/null +++ b/aoe2hd/include/aoe2hd.h @@ -0,0 +1,58 @@ +#ifndef AOE2HD_H_INCLUDED +#define AOE2HD_H_INCLUDED + +#define DUMMY5 0x90,0x90,0x90,0x90,0x90 /* nop; nop; nop; nop; nop */ + +/* SAFE! */ +#define MAP_NOFOG 0x45BE43 +#define MAP_NOFOG0 0x8B,0x0C,0x81 /* mov ecx,[ecx+eax*4] */ +#define MAP_NOFOG1 0x8B,0x45,0x10 /* mov eax,[ebp+10] */ +#define MAP_NOFOGI 0x81,0xC9,0x00,0x04,0x00,0x00 /* or ecx,0x00000400 */ + +/* SAFE! */ +#define MAP_MINI 0x46CA33 +#define MAP_MINI0 0x8B,0x0C,0x88 /* mov ecx,[eax+ecx*4] */ +#define MAP_MINI1 0x8B,0x87,0x34,0x01,0x00,0x00 /* mov eax,[edi+00000134] */ +#define MAP_MINII 0x81,0xC9,0x00,0x00,0x00,0x04 /* or ecx,0x04000000 */ + +/* NOT SAFE -> DESYNC POSSIBLE! */ +#define MAP_SMTH 0x46CEE8 +#define MAP_SMTH0 0x8B,0x04,0x88 /* mov eax,[eax+ecx*4] */ +#define MAP_SMTH1 0x8B,0x8F,0x34,0x01,0x00,0x00 /* mov ecx,[edi+00000134] */ +#define MAP_SMTHI 0x0D,0x00,0x04,0x00,0x00 /* or eax,0x00000400 */ + +/* NOT SAFE! .> DESYNC POSSIBLE! */ +#define MAP_UNIT 0x47F851 +#define MAP_UNIT0 0x8B,0x01 /* mov eax,[ecx] */ +#define MAP_UNIT1 0x8B,0xD0,0x8B,0x8D,0x34,0xFF,0xFF,0xFF /* mov edx,eax; mov ecx,[ebp-000000CC] */ +#define MAP_UNITI 0x0D,0x00,0x04,0x00,0x00 /* or eax,0x00000400 */ + +/* MAP/MINIMAP FLAGS: + * NOFOG_BY_UNIT.....: 0x00000002 + * NOFOG_ALL.........: 0x00000400 + * DISCOVERED_BY_UNIT: 0x00020000 + * DISCOVERED_ALL....: 0x04000000 + * MAP_FULL_VISIABLE.: DISCOVERED_ALL | NOFOG_ALL + * MAP_SPY_LIKE......: DISCOVERED_BY_UNIT | NOFOG_BY_UNIT + */ + +struct resources +{ + float food; + float wood; + float stone; + float gold; + float remainingPop; + unsigned char garbage_1[4]; + float currentAge; + unsigned char garbage_2[16]; + float currentPop; +}; + +struct mapsize +{ + uint32_t cells_x; + uint32_t cells_y; +}; + +#endif // AOE2HD_H_INCLUDED diff --git a/aoe2hd/include/native.h b/aoe2hd/include/native.h new file mode 100755 index 0000000..b599e0d --- /dev/null +++ b/aoe2hd/include/native.h @@ -0,0 +1,56 @@ +#ifndef NATIVE_H_INCLUDED +#define NATIVE_H_INCLUDED + +#include <windows.h> +#include <stdbool.h> + +#define EXPORT __declspec(dllexport) + +typedef struct native_data native_data; + +typedef unsigned long(*alloc_mem_fn)(const native_data *nd, + unsigned long siz); +typedef bool(*read_mem_fn)(const native_data *nd, + unsigned long addr, void *buffer, + unsigned long siz); +typedef bool(*write_mem_fn)(const native_data *nd, + unsigned long addr, const void *buffer, + unsigned long siz); +typedef bool(*suspend_proc_fn)(const native_data *nd, + int doResume); + +typedef struct win_proc +{ + DWORD pid; + HANDLE hndl; + unsigned long modbase; +} win_proc; + +typedef struct native_data +{ + win_proc proc; + alloc_mem_fn alloc_fn; + read_mem_fn read_fn; + write_mem_fn write_fn; + suspend_proc_fn suspend_fn; +} native_data; + +EXPORT void initNativeData(native_data *nd); +EXPORT void cls(HANDLE hConsole); +EXPORT bool get_module_proc(native_data *nd, + LPCTSTR window_name); +EXPORT bool get_module_base(native_data *nd, + LPCTSTR module_name); + +EXPORT unsigned long mem_alloc(const native_data *nd, + unsigned long siz); +EXPORT bool read_procmem(const native_data *nd, + unsigned long addr, void *buffer, + unsigned long siz); +EXPORT bool write_procmem(const native_data *nd, + unsigned long addr, const void *buffer, + unsigned long siz); +EXPORT bool suspendProcess(const native_data *nd, + int doResume); + +#endif // NATIVE_H_INCLUDED diff --git a/aoe2hd/include/utils.h b/aoe2hd/include/utils.h new file mode 100755 index 0000000..1d71b90 --- /dev/null +++ b/aoe2hd/include/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include <vector> +#include <string> + + +namespace utils +{ +std::string convertBinToHexstr(const std::vector<unsigned char>& bin); +}; + +#endif // UTILS_H diff --git a/aoe2hd/src/CodeGenerator.cpp b/aoe2hd/src/CodeGenerator.cpp new file mode 100755 index 0000000..74149dd --- /dev/null +++ b/aoe2hd/src/CodeGenerator.cpp @@ -0,0 +1,100 @@ +#include "CodeGenerator.h" + +#include <assert.h> +#include <sstream> +#include <iomanip> + +#include "native.h" +#include "utils.h" + + +std::vector<unsigned char> x86_relJump(unsigned long dst, + unsigned long src) +{ + std::vector<unsigned char> code(5); + code[0] = 0xE9; + unsigned long addr = dst - src; + code[1] = (*((unsigned char *)(&addr)+0)); + code[2] = (*((unsigned char *)(&addr)+1)); + code[3] = (*((unsigned char *)(&addr)+2)); + code[4] = (*((unsigned char *)(&addr)+3)); + return code; +} + +CodeGenerator::CodeGenerator(const native_data& nd) + : nd(nd), codes() +{ +} + +CodeGenerator::~CodeGenerator() +{ +} + +CodeGenerator& CodeGenerator::addCode(const std::vector<unsigned char>& code) +{ + codes.push_back(code); + return *this; +} + +CodeGenerator& CodeGenerator::setCode(int index, const std::vector<unsigned char>& code) +{ + codes.at(index) = code; + return *this; +} + +CodeGenerator& CodeGenerator::setCodeSized(int index, const std::vector<unsigned char>& code) +{ + assert(codes.at(index).size() == code.size()); + return setCode(index, code); +} + +CodeGenerator& CodeGenerator::setRel32JMP(int index, unsigned long dst, unsigned long src, bool reversed) +{ + if (!reversed) + { + dst += nd.proc.modbase - diffRel32JMP(reversed, index); + } + else + { + src += nd.proc.modbase + diffRel32JMP(reversed, index); + } + auto jmp = x86_relJump(dst, src); + setCodeSized(index, jmp); + return *this; +} + +std::vector<unsigned char>::size_type CodeGenerator::buildSize(int maxCodes) +{ + std::vector<unsigned char>::size_type total = 0; + for (auto& code : codes) + { + total += code.size(); + if (maxCodes-- == 0) + break; + } + return total; +} + +std::vector<unsigned char> CodeGenerator::build() +{ + std::vector<unsigned char> result; + for (auto& code : codes) + { + result.insert(result.end(), code.begin(), code.end()); + } + return result; +} + +std::vector<unsigned char> CodeGenerator::buildAndClear() +{ + auto result = build(); + clear(); + return result; +} + +std::string CodeGenerator::toString() +{ + std::stringstream out; + out << "CodeBin: " << utils::convertBinToHexstr(build()) << std::endl; + return out.str(); +} diff --git a/aoe2hd/src/CodeInjector.cpp b/aoe2hd/src/CodeInjector.cpp new file mode 100755 index 0000000..b62f969 --- /dev/null +++ b/aoe2hd/src/CodeInjector.cpp @@ -0,0 +1,165 @@ +#include <assert.h> + +#include <sstream> +#include <iomanip> +#include <algorithm> + +#include "CodeInjector.h" + +CodeInjector::CodeInjector(const native_data& nd) + : nd(nd) +{ + assert(nd.alloc_fn && nd.write_fn); +} + +CodeInjector::~CodeInjector() +{ +} + +bool CodeInjector::allocCodeSegment(const std::string& name, unsigned long siz) +{ + if (codeSegExists(name)) + return false; + code_seg seg = {0}; + seg.siz = siz; + seg.addr = nd.alloc_fn(&nd, siz); + if (!seg.addr) + return false; + code_map[name] = seg; + return true; +} + +bool CodeInjector::addCode(const std::string& name, const std::string& code_name, + const std::vector<unsigned char>& code) +{ + assert(code.size()); + if (!codeSegExists(name) + || codeBinExists(name, code_name)) + return false; + auto cave = findCodeCave(name, code.size()); + if (!cave) + return false; + code_bin bin = {0}; + bin.addr = cave; + bin.siz = code.size(); + if (!nd.write_fn(&nd, bin.addr, &code[0], bin.siz)) + return false; + code_map[name].children[code_name] = bin; + return true; +} + +bool CodeInjector::addCode(const std::string& name, const std::string& code_name, + unsigned long siz) +{ + assert(siz); + std::vector<unsigned char> code(siz, 0x90); + return addCode(name, code_name, code); +} + +bool CodeInjector::setCode(const std::string& name, const std::string& code_name, + const std::vector<unsigned char>& code, + unsigned long offset) +{ + assert(code.size()); + if (!codeSegExists(name) + || !codeBinExists(name, code_name)) + return false; + code_bin bin = {0}; + if (!getCodeBin(name, code_name, &bin)) + return false; + assert(bin.addr && bin.siz); + if (bin.addr + offset + code.size() > bin.addr + bin.siz) + return false; + return nd.write_fn(&nd, bin.addr + offset, &code[0], code.size()); +} + +bool CodeInjector::delCode(const std::string& name, const std::string& code_name) +{ + if (!codeBinExists(name, code_name)) + return false; + code_map[name].children[code_name]; + return code_map[name].children.erase(code_name) > 0; +} +unsigned long CodeInjector::getCodeAddr(const std::string& name, const std::string& code_name) +{ + assert(codeBinExists(name, code_name)); + assert(code_map[name].children[code_name].addr); + return code_map[name].children[code_name].addr; +} + +bool CodeInjector::getCodeSeg(const std::string& name, code_seg *seg) +{ + assert(seg); + if (!codeSegExists(name)) + return false; + *seg = code_map[name]; + return true; +} + +bool CodeInjector::getCodeBin(const std::string& name, const std::string& code_name, code_bin *bin) +{ + assert(bin); + if (!codeBinExists(name, code_name)) + return false; + *bin = code_map[name].children[code_name]; + return true; +} + +std::string CodeInjector::toString() +{ + std::stringstream out; + for (auto& code : code_map) + { + out << std::setw(16) << code.first << "[ " + << std::setw(8) << std::hex << code.second.addr << " , " + << std::setw(8) << std::hex << code.second.siz + << " ]" << std::endl; + for (auto& child : code.second.children) + { + out << std::setw(18) << child.first << "[ " + << std::setw(8) << std::hex << child.second.addr << " , " + << std::setw(6) << std::hex << child.second.siz + << " ]" << std::endl; + } + } + return out.str(); +} + +bool CodeInjector::codeBinExists(const std::string& name, const std::string& code_name) +{ + if (!codeSegExists(name)) + return false; + return code_map[name].children.find(code_name) + != code_map[name].children.end(); +} + +std::vector<code_bin> CodeInjector::convertCodeSegChildren(const std::string& name) +{ + auto cs = code_map[name].children; + std::vector<code_bin> ret; + ret.reserve(cs.size()); + std::for_each(cs.begin(), cs.end(), [&ret](std::pair<const std::string, code_bin> element) + { + ret.push_back(element.second); + }); + return ret; +} + +unsigned long CodeInjector::findCodeCave(const std::string& name, unsigned long siz) +{ + auto& cs = code_map[name]; + auto cl = convertCodeSegChildren(name); + std::sort(cl.begin(), cl.end()); + unsigned long end_addr = cs.addr; + for (auto& el : cl) + { + if (el.addr >= end_addr + siz) + { + return end_addr; + } + end_addr = el.addr + el.siz; + } + if (end_addr <= cs.addr + cs.siz) + return end_addr; + return 0; +} diff --git a/aoe2hd/src/CodePatcher.cpp b/aoe2hd/src/CodePatcher.cpp new file mode 100755 index 0000000..b96a2ab --- /dev/null +++ b/aoe2hd/src/CodePatcher.cpp @@ -0,0 +1,111 @@ +#include <assert.h> + +#include <sstream> +#include <iomanip> + +#include "CodePatcher.h" +#include "utils.h" +#include "native.h" + + +CodePatcher::CodePatcher(const native_data& nd) + : nd(nd) +{ + assert(nd.read_fn && nd.write_fn && nd.suspend_fn); +} + +CodePatcher::~CodePatcher() +{ +} + +bool CodePatcher::addPatch(const std::string& name, + unsigned long addr, + const std::vector<unsigned char>& old_code, + const std::vector<unsigned char>& new_code, + long new_offset) +{ + assert(addr); + assert(old_code.size() == new_code.size()); + if (codePatchExists(name)) + return false; + code_patch patch = {0}; + patch.addr = nd.proc.modbase + addr; + patch.old_code = old_code; + patch.new_code = new_code; + patch.new_offset = new_offset; + patch_map[name] = patch; + return true; +} + +void CodePatcher::setPatchSuspend(const std::string& name, long doSuspend) +{ + if (codePatchExists(name)) + patch_map[name].suspend = doSuspend; +} + +bool CodePatcher::doPatch(const std::string& name, int doUnPatch) +{ + if (!codePatchExists(name)) + return false; + auto& patch = patch_map[name]; + if (codeCmp(patch.addr + patch.new_offset, patch.new_code)) + return false; + if (patch.suspend) + nd.suspend_fn(&nd, 0); + bool ret = false; + if (doUnPatch) + { + ret = nd.write_fn(&nd, patch.addr, &patch.old_code[0], patch.old_code.size()); + } + else + { + ret = nd.write_fn(&nd, patch.addr, &patch.new_code[0], patch.new_code.size()); + } + if (patch.suspend) + nd.suspend_fn(&nd, 1); + return ret; +} + +bool CodePatcher::autoPatch(const std::string& name) +{ + if (!doPatch(name, 0)) + { + if (!doPatch(name, 1)) + return false; + return doPatch(name, 0); + } + return true; +} + +std::string CodePatcher::toString() +{ + std::stringstream out; + for (auto& patch : patch_map) + { + out << std::setw(16) << patch.first << "[ " + << std::setw(8) << std::hex << patch.second.addr << " , " + << std::setw(8) << std::hex << patch.second.new_offset + << " ]" << std::endl + << std::setw(23) << "Old: " + << utils::convertBinToHexstr(patch.second.old_code) << std::endl + << std::setw(23) << "New: " + << utils::convertBinToHexstr(patch.second.new_code) << std::endl; + } + return out.str(); +} + +bool CodePatcher::codeCmp(unsigned long addr, std::vector<unsigned char> code) +{ + if (code.size() == 0) + return true; + unsigned char buf[code.size()] = {0}; + if (!nd.read_fn(&nd, addr, &buf[0], code.size())) + return false; + /* TODO: replace with memcmp? */ + for (unsigned i = 0; i < code.size(); ++i) + { + if (buf[i] != code[i]) + return false; + } + return true; +} diff --git a/aoe2hd/src/ModuleMemory.cpp b/aoe2hd/src/ModuleMemory.cpp new file mode 100755 index 0000000..19e501b --- /dev/null +++ b/aoe2hd/src/ModuleMemory.cpp @@ -0,0 +1,118 @@ +#include <assert.h> + +#include <sstream> +#include <iomanip> + +#include "ModuleMemory.h" + +ModuleMemory::ModuleMemory(const native_data& nd) + : nd(nd), ptr_map(), ptr_read_count(0), ptr_invalid_count(0) +{ + assert(nd.read_fn); +} + +ModuleMemory::~ModuleMemory() +{ +} + +unsigned long ModuleMemory::getPtr(const std::string& name) +{ + bool valid = ptrExists(name) && ptrValid(name); + if (!valid) + return 0; + return ptr_map[name].ptr; +} + +unsigned long ModuleMemory::getPtr(const std::string& name, unsigned long *dest_ptr) +{ + assert(dest_ptr); + unsigned long ret = getPtr(name); + *dest_ptr = ret; + return ret; +} + +unsigned long ModuleMemory::getPtr(const std::string& name, unsigned long base, + unsigned long offset) +{ + target_ptr out = {0}; + out.base = base; + out.offset = offset; + out.valid = nd.read_fn(&nd, base + offset, &out.ptr, sizeof(out.ptr)); + ptr_map[name] = out; + if (out.valid) + { + ++ptr_read_count; + return out.ptr; + } + else + { + return 0; + } +} + +unsigned long ModuleMemory::recheckPtr(const std::string& name) +{ + if (!ptrExists(name)) + return 0; + target_ptr old = ptr_map[name]; + unsigned long new_ptr = getPtr(name, old.base, old.offset); + if (!new_ptr) + { + ptr_map[name].valid = false; + } + return new_ptr; +} + +void ModuleMemory::revalidateAllPtr() +{ +} + +bool ModuleMemory::getData(const std::string& name, void *buffer, unsigned long siz) +{ + assert(buffer); + if (!getPtr(name)) + return false; + if (!nd.read_fn(&nd, ptr_map[name].ptr, buffer, siz)) + { + ptr_map[name].valid = false; + } + return ptr_map[name].valid; +} + +bool ModuleMemory::ptrSetDependency(const std::string& name, const std::string& dependency) +{ + if (!getPtr(name) || !getPtr(dependency)) + return false; + ptr_map[name].dependency = dependency; + ptr_map[dependency].children.insert(name); + return true; +} + +std::string ModuleMemory::toString() +{ + std::stringstream out; + for (auto& ptr : ptr_map) + { + out << std::setw(16) << ptr.first << "[ " + << std::setw(8) << std::hex << ptr.second.base << " + " + << std::setw(8) << std::hex << ptr.second.offset << " = " + << std::setw(8) << std::hex << ptr.second.ptr << " , " + << std::setw(5) << (ptr.second.valid ? "TRUE" : "FALSE") << " , " + << std::setw(16) << (ptr.second.dependency.c_str() ? ptr.second.dependency.c_str() : "") + << " ]"; + for (auto& child : ptr.second.children) + { + out << ", " << child; + } + out << std::endl; + } + return out.str(); +} + +std::string ModuleMemory::toStringStats() +{ + std::stringstream out; + out << "PtrReadCount: " << ptr_read_count << " , " + << "PtrInvalidCount: " << ptr_invalid_count; + return out.str(); +} diff --git a/aoe2hd/src/main.cpp b/aoe2hd/src/main.cpp new file mode 100755 index 0000000..2e064f0 --- /dev/null +++ b/aoe2hd/src/main.cpp @@ -0,0 +1,184 @@ +/************************************ + * AoE2 HD Steam + * Minimap/NoFog-Hack (with a restriction) + * + * coded by lnslbrty/dev0, \x90 + * + * This hack may cause desyncs! + ************************************/ + +#include <windows.h> +#include <psapi.h> +#include <stdio.h> +#include <assert.h> + +#include <vector> +#include <iostream> +#include <iomanip> +#include <string> +#include <sstream> + +#include "CodeGenerator.h" +#include "CodePatcher.h" +#include "CodeInjector.h" +#include "ModuleMemory.h" +extern "C" { +#include "native.h" +#include "aoe2hd.h" +} + + +int main(int argc, char **argv) +{ + using namespace std; + + (void) argc; + (void) argv; + + native_data nd = {0}; + initNativeData(&nd); + + assert(get_module_proc(&nd, "Age of Empires II: HD Edition")); + assert(get_module_base(&nd, "AoK HD.exe")); + + ModuleMemory mm(nd); + mm.getPtr("MainClass", nd.proc.modbase, 0x009C7774); + mm.getPtr("GameClass", mm.getPtr("MainClass"), 0x4); + mm.getPtr("PlayerArray", mm.getPtr("GameClass"), 0x184); + mm.getPtr("PlayerNameBase", nd.proc.modbase, 0x006DB62C); + mm.getPtr("PlayerNamePtr0", mm.getPtr("PlayerNameBase"), 0x794); + mm.getPtr("PlayerNamePtr1", mm.getPtr("PlayerNamePtr0"), 0x5C); + + mm.ptrSetDependency("GameClass", "MainClass"); + mm.ptrSetDependency("PlayerArray", "GameClass"); + mm.ptrSetDependency("PlayerNamePtr0", "PlayerNameBase"); + mm.ptrSetDependency("PlayerNamePtr1", "PlayerNamePtr0"); + + for (unsigned long i = 1; i < 9; ++i) + { + stringstream player; + player << "Player" << i; + mm.getPtr(player.str(), (unsigned long)mm.getPtr("PlayerArray"), 0x8 * i); + mm.ptrSetDependency(player.str(), "PlayerArray"); + + stringstream player_res; + player_res << "Player" << i << "Resources"; + mm.getPtr(player_res.str(), (unsigned long)mm.getPtr(player.str()), 0x3C); + mm.ptrSetDependency(player_res.str(), player.str()); + + stringstream player_name; + player_name << "Player" << i << "Name"; + mm.getPtr(player_name.str(), (unsigned long)mm.getPtr("PlayerNamePtr1"), 0xBC + (0x60 * (i-1))); + mm.ptrSetDependency(player_name.str(), "PlayerNamePtr1"); + } + + CodeInjector ci(nd); + assert(ci.allocCodeSegment("MapCode")); + CodePatcher cp(nd); + CodeGenerator original(nd), injected(nd); + + { + original.addCode({DUMMY5}); + injected.addCode({MAP_NOFOG0}).addCode({MAP_NOFOGI}).addCode({MAP_NOFOG1}).addCode({DUMMY5}); + + assert(ci.addCode("MapCode", "NoFog", injected.buildSize())); + injected.setRel32JMP(3, MAP_NOFOG, ci.getCodeAddr("MapCode", "NoFog")); + ci.setCode("MapCode", "NoFog", injected.buildAndClear()); + + original.setRel32JMP(0, ci.getCodeAddr("MapCode", "NoFog"), MAP_NOFOG, true).addCode({0x90}); + assert(cp.addPatch("NoFog", MAP_NOFOG, {MAP_NOFOG0,MAP_NOFOG1}, original.buildAndClear())); + cp.setPatchSuspend("NoFog", 1); + assert(cp.autoPatch("NoFog")); + } + { + original.addCode({DUMMY5}); + injected.addCode({MAP_MINI0}).addCode({MAP_MINII}).addCode({MAP_MINI1}).addCode({DUMMY5}); + + assert(ci.addCode("MapCode", "MiniMap", injected.buildSize())); + injected.setRel32JMP(3, MAP_MINI, ci.getCodeAddr("MapCode", "MiniMap")); + ci.setCode("MapCode", "MiniMap", injected.buildAndClear()); + + original.setRel32JMP(0, ci.getCodeAddr("MapCode", "MiniMap"), MAP_MINI, true).addCode({0x90,0x90,0x90,0x90}); + assert(cp.addPatch("MiniMap", MAP_MINI, {MAP_MINI0,MAP_MINI1}, original.buildAndClear())); + cp.setPatchSuspend("MiniMap", 1); + assert(cp.autoPatch("MiniMap")); + } + { + original.addCode({DUMMY5}); + injected.addCode({MAP_SMTH0}).addCode({MAP_SMTHI}).addCode({MAP_SMTH1}).addCode({DUMMY5}) + .addCode({DUMMY5,DUMMY5,DUMMY5,DUMMY5,DUMMY5}); + + assert(ci.addCode("MapCode", "Smth", injected.buildSize())); + injected.setRel32JMP(3, MAP_SMTH, ci.getCodeAddr("MapCode", "Smth")); + ci.setCode("MapCode", "Smth", injected.buildAndClear()); + + original.setRel32JMP(0, ci.getCodeAddr("MapCode", "Smth"), MAP_SMTH, true).addCode({0x90,0x90,0x90,0x90}); + assert(cp.addPatch("Smth", MAP_SMTH, {MAP_SMTH0,MAP_SMTH1}, original.buildAndClear())); + cp.setPatchSuspend("Smth", 1); + assert(cp.autoPatch("Smth")); + } + { + original.addCode({DUMMY5}); + injected.addCode({MAP_UNIT0}).addCode({MAP_UNITI}).addCode({MAP_UNIT1}).addCode({DUMMY5}); + + assert(ci.addCode("MapCode", "Units", injected.buildSize())); + injected.setRel32JMP(3, MAP_UNIT, ci.getCodeAddr("MapCode", "Units")); + ci.setCode("MapCode", "Units", injected.buildAndClear()); + + original.setRel32JMP(0, ci.getCodeAddr("MapCode", "Units"), MAP_UNIT, true).addCode({0x90,0x90,0x90,0x90,0x90}); + assert(cp.addPatch("Units", MAP_UNIT, {MAP_UNIT0,MAP_UNIT1}, original.buildAndClear())); + cp.setPatchSuspend("Units", 1); + assert(cp.autoPatch("Units")); + } + + cout << ci.toString() << endl; + cout << cp.toString() << endl; + cout << "[PRESS A KEY TO CONTINUE]" << endl; + system("pause"); + + while (1) + { + cls( GetStdHandle( STD_OUTPUT_HANDLE )); + + while (!mm.recheckPtr("MainClass")) + { + Sleep(1000); + } + mm.revalidateAllPtr(); + + for (unsigned long i = 1; i < 9; ++i) + { + + stringstream player_res; + player_res << "Player" << i << "Resources"; + struct resources res = {0}; + if (!mm.getData(player_res.str(), &res, sizeof(res))) + { + continue; + } + + cout << "player[" << i << "]: " + << "wood.: " << setw(8) << dec << (unsigned long)res.wood << ", " + << "food.: " << setw(8) << dec << (unsigned long)res.food << ", " + << "gold.: " << setw(8) << dec << (unsigned long)res.gold << ", " + << "stone: " << setw(8) << dec << (unsigned long)res.stone << endl; + cout << " " << setw(8) + << "rpop.: " << setw(8) << dec << res.remainingPop << ", " + << "tpop.: " << setw(8) << dec << res.currentPop << endl; + + stringstream player_name; + player_name << "Player" << i << "Name"; + char name[32] = {0}; + mm.getData(player_name.str(), &name, 31); + cout << " " << name << endl; + } + + //cout << mm.toString() << endl; + //cout << mm.toStringStats() << endl; + //system("pause"); + Sleep(1000); + } + CloseHandle(nd.proc.hndl); + + return 0; +} diff --git a/aoe2hd/src/native.c b/aoe2hd/src/native.c new file mode 100755 index 0000000..d086555 --- /dev/null +++ b/aoe2hd/src/native.c @@ -0,0 +1,173 @@ +#include <windows.h> +#include <psapi.h> +#include <stdio.h> +#include <stdbool.h> +#include <assert.h> + +#include "native.h" + + +typedef LONG (NTAPI *NtSuspendProcess)(IN HANDLE ProcessHandle); +typedef LONG (NTAPI *NtResumeProcess)(IN HANDLE ProcessHandle); + +/* Standard error macro for reporting API errors */ +#define PERR(bSuccess, api){if(!(bSuccess)) printf("%s:Error %ld from %s \ + on line %ld\n", __FILE__, GetLastError(), api, (long)__LINE__);} + +void initNativeData(native_data *nd) +{ + assert(nd); + nd->alloc_fn = mem_alloc; + nd->read_fn = read_procmem; + nd->write_fn = write_procmem; + nd->suspend_fn = suspendProcess; +} + +/* see: https://support.microsoft.com/en-us/help/99261/how-to-performing-clear-screen-cls-in-a-console-application */ +void cls(HANDLE hConsole) +{ + COORD coordScreen = { 0, 0 }; /* here's where we'll home the + cursor */ + BOOL bSuccess; + DWORD cCharsWritten; + CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */ + DWORD dwConSize; /* number of character cells in + the current buffer */ + + /* get the number of character cells in the current buffer */ + + bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); + PERR( bSuccess, "GetConsoleScreenBufferInfo" ); + dwConSize = csbi.dwSize.X * csbi.dwSize.Y; + + /* fill the entire screen with blanks */ + + bSuccess = FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', + dwConSize, coordScreen, &cCharsWritten ); + PERR( bSuccess, "FillConsoleOutputCharacter" ); + + /* get the current text attribute */ + + bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); + PERR( bSuccess, "ConsoleScreenBufferInfo" ); + + /* now set the buffer's attributes accordingly */ + + bSuccess = FillConsoleOutputAttribute( hConsole, csbi.wAttributes, + dwConSize, coordScreen, &cCharsWritten ); + PERR( bSuccess, "FillConsoleOutputAttribute" ); + + /* put the cursor at (0, 0) */ + + bSuccess = SetConsoleCursorPosition( hConsole, coordScreen ); + PERR( bSuccess, "SetConsoleCursorPosition" ); + return; +} + +bool get_module_proc(native_data *nd, LPCTSTR window_name) +{ + HWND hwnd; + + assert(window_name); + hwnd = FindWindow(NULL, window_name); + if (!hwnd) + goto error; + GetWindowThreadProcessId(hwnd, &nd->proc.pid); + if (!nd->proc.pid) + goto error; + nd->proc.hndl = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION + | PROCESS_VM_READ | PROCESS_VM_WRITE, 0, nd->proc.pid); +error: + return nd->proc.hndl != NULL; +} + +bool get_module_base(native_data *nd, LPCTSTR module_name) +{ + HMODULE hMods[1024]; + DWORD cbNeeded; + unsigned int i; + + assert(module_name); + if (EnumProcessModules(nd->proc.hndl, hMods, sizeof(hMods), &cbNeeded)) + { + for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) + { + TCHAR szModName[MAX_PATH]; + TCHAR szModPath[MAX_PATH]; + + if (GetModuleBaseName(nd->proc.hndl, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR)) + && GetModuleFileNameEx(nd->proc.hndl, hMods[i], szModPath, + sizeof(szModPath) / sizeof(TCHAR))) + { + if (strncmp(szModName, module_name, MAX_PATH) == 0) + { + nd->proc.modbase =(unsigned long)(hMods[i]); + return true; + } + } + } + } + return false; +} + +bool read_procmem(const native_data *nd, unsigned long addr, + void *buffer, unsigned long siz) +{ + SIZE_T bytes_read = 0; + unsigned long *vmptr = (unsigned long *)addr; + + assert(addr && buffer && siz); + if (!ReadProcessMemory(nd->proc.hndl, vmptr, buffer, siz, &bytes_read)) + return false; + if (bytes_read != siz) + return false; + + return true; +} + +bool write_procmem(const native_data *nd, unsigned long addr, const void *buffer, unsigned long siz) +{ + SIZE_T bytes_written = 0; + unsigned long *vmptr = (unsigned long *)addr; + + assert(addr && buffer && siz); + if (!WriteProcessMemory(nd->proc.hndl, vmptr, buffer, siz, &bytes_written)) + return false; + if (bytes_written != siz) + return false; + + return true; +} + +unsigned long mem_alloc(const native_data *nd, unsigned long siz) +{ + return (unsigned long)VirtualAllocEx(nd->proc.hndl, NULL, siz, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); +} + +/* see: https://github.com/mridgers/clink/issues/420 */ +bool suspendProcess(const native_data *nd, int doResume) +{ + bool ret = false; + NtSuspendProcess pfnNtSuspendProcess = + (NtSuspendProcess)GetProcAddress(GetModuleHandle("ntdll"), "NtSuspendProcess"); + NtResumeProcess pfnNtResumeProcess = + (NtResumeProcess)GetProcAddress(GetModuleHandle("ntdll"), "NtResumeProcess"); + + HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, nd->proc.pid); + if (!processHandle) + return false; + if (doResume) + { + if (pfnNtResumeProcess(processHandle) == 0) + ret = true; + } + else + { + if (pfnNtSuspendProcess(processHandle) == 0) + ret = true; + } + CloseHandle(processHandle); + return ret; +} diff --git a/aoe2hd/src/utils.cpp b/aoe2hd/src/utils.cpp new file mode 100755 index 0000000..7639ab7 --- /dev/null +++ b/aoe2hd/src/utils.cpp @@ -0,0 +1,19 @@ +#include "utils.h" + +#include <sstream> +#include <iomanip> + + +namespace utils +{ +std::string convertBinToHexstr(const std::vector<unsigned char>& bin) +{ + std::stringstream buffer; + for (auto byte : bin) + { + buffer << std::hex << std::setfill('0'); + buffer << std::setw(2) << static_cast<unsigned>(byte); + } + return buffer.str(); +} +} |