Introduction
Age of Mythology is a real-time strategy game in which the player seeks to build his civilization and defeat all enemies. In standard mode, the player starts the game on a map completely blacked out, which means unknown and unexplored territory.


')
During the game, the user explores the map, and terrain designations, resources, enemy structures appear on the studied areas, which are superimposed with a "fog of war", denoting the investigated territory that the player does not see.


The goal of this post will be to create the hack that opens the entire map, giving the player a significant advantage. He will allow the player to see what opponents are doing on the map and choose the best time and place to attack. This hack will be developed for the original version of the game, but later I will show how to apply it to the new, expanded version, which is now being distributed to
Steam .
Here are the hashes for the main executable file, which will be parsed by reverse engineering in this article:
CRC32 : 7F1AF498
MD5 : 09876F130D02AE760A6B06CE6A9C92DB
SHA-1 : AAAC9CD38B51BEB3D29930D13A87C191ABF9CAD4
Part one: the hard way
Getting started
Our goal is to develop what will open the player all the card data, giving the player complete information about what is happening in the game. The good news: the disclosure and concealment of the card is the built-in functionality of the game. The game supports playback of recorded games, and the option of disclosing-hiding the card is part of the UI of this function.

The “Fog of War” button controls the disclosure of the map and the return to the normal state in which the player sees only what he has investigated. The plan is to find the handler for this button and trace the location of the disclosure logic. Having found it, we simply execute the injection DLL in the game process to call the map disclosure function. For this job, a tool like
Cheat Engine is useful, which is useful for examining and modifying memory, debugging, disassembling, and other operations in the context of hacking games. In this article I will not describe the work with this tool, for this there are many other resources.
After starting and connecting the Cheat Engine, the question is where the code interacts with the button. The easiest way to find out is to use standard programming practices. In particular, the active button will have a value of 1 somewhere in memory, and an inactive one will have a value of 0. Therefore, it becomes a matter of testing and patience. A search in the process memory of the value “1” (when the button is active) returned 337,597 results. If you try to do the same, do not expect the values ​​to be the same.

This is too much to check. Press the button again to make it inactive, and search for the value "0". The program will return 376 - still too much.

Repeating this process several times reduced the search area to the already quite convenient 21 addresses.

20 of these 21 were very close to each other.
0x08FC71A4 seemed to be an exception in this series. After studying it more carefully and changing the value "0", it was possible to switch the button to an inactive state. So, we found the right address, and the rest 20 can be safely discarded. The next step is to find out what is recording it.

At this stage, the Cheat Engine
turns on the debugger and tracks all write operations at
0x08FC71A4 . After several button presses, the following commands were revealed. These commands recorded at
0x08FC71A4 .

Next, you need to examine them and begin to set control points in order to better understand what is happening next to these write operations. Checkpoint setting on write commands

and scuffling with the game allowed to find out that this function is called for each button. Here
ECX is a pointer to the button, and +
0x1A4 probably contains the
IsToggled property, which assigns the corresponding button state. This assignment is performed in the second write command, where
EDX can be “0” (inactive) or “1” (active). The code may seem a bit complicated, but it just checks that the enabled state is correct, and then sets the
IsToggled property before calling the function and returning it.
The recipient's address
+ 0x14B670 is also a code that applies to all buttons. Here we need to slowly go around everything and find areas of code that can relate to the “Fog of War” button. You can apply different approaches, but I usually use the following:
- Call addresses calculated by register. This may mean an event handling mechanism, executed after a button's state changes, something like OnChanged / OnEnabled / OnDisabled or a similar function.
- Function parameters that are function pointers.
- Calls to functions that receive arguments 1 or 0.
A step at
+ 0x14B670 gives us the following (partial) assembler code presented below. In the assembly code, absolute addresses are indicated, and not the address of the
beginning of the module in the memory + offset , because they are much easier to copy from IDA than from the Cheat Engine.
.text:0054B670 mov eax, large fs:0
.text:0054B676 push 0FFFFFFFFh
.text:0054B678 push offset SEH_54B670
.text:0054B67D push eax
.text:0054B67E mov large fs:0, esp
.text:0054B685 sub esp, 8
.text:0054B688 push esi
.text:0054B689 mov esi, ecx
.text:0054B68B mov eax, [esi+148h]
.text:0054B691 push edi
.text:0054B692 mov edi, [esi]
.text:0054B694 push eax
.text:0054B695 push esi
.text:0054B696 lea ecx, [esp+24h+var_10]
.text:0054B69A call sub_4D7470
.text:0054B69F mov ecx, [eax]
.text:0054B6A1 push ecx
.text:0054B6A2 push 1
.text:0054B6A4 mov ecx, esi
.text:0054B6A6 call dword ptr [edi+54h]
.text:0054B6A9 cmp [esp+1Ch+arg_0], 0Dh
.text:0054B6AE jnz loc_54B769
.text:0054B6B4 lea edi, [esi+154h]
...
It
turned out that the call with a call at
0x004D7470 (
red ) returns fairly quickly, so it will not be shown here. The next call (
blue ) in
+ 0x14B6A6 makes the call through the register. This is a good candidate for careful study. This function can call two possible addresses:
...
.text:0054BF98 push 0Ch
.text:0054BF9A call dword ptr [eax+0CCh]
.text:0054BFA0
.text:0054BFA0 loc_54BFA0: ; CODE XREF: sub_54BF80+Fj
.text:0054BFA0 ; sub_54BF80+14j
.text:0054BFA0 mov ecx, [esp+0Ch+arg_8]
.text:0054BFA4 push ecx
.text:0054BFA5 push edi
.text:0054BFA6 push ebx
.text:0054BFA7 mov ecx, esi
.text:0054BFA9 call sub_4D4EF0
.text:0054BFAE pop edi
...
The command at
+ 0x14BF9A (
red ) is never called when debugging and passing, so there is no point in studying it. For research, only the next call to
+ 0x14BFA9 (
blue ) remains. This feature has proven to be very large in size and has a wide branch with many possible call points. With debugging, you can skip most of this logic. By tracing the code that is executed only when the “Fog of War” button is active, we allocate only three call sites.
...
.text:004D504C cmp esi, dword_A9D068
.text:004D5052 jz short loc_4D5087
.text:004D5054 push esi
.text:004D5055 call sub_424750
.text:004D505A mov edi, eax
.text:004D505C add esp, 4
.text:004D505F test edi, edi
.text:004D5061 jz short loc_4D5070
.text:004D5063 push esi
.text:004D5064 call sub_4D58B0
.text:004D5069 add esp, 4
.text:004D506C test edi, edi
.text:004D506E jnz short loc_4D5079
.text:004D5070
.text:004D5070 loc_4D5070: ; CODE XREF: sub_4D4EF0+171j
.text:004D5070 pop edi
.text:004D5071 pop esi
.text:004D5072 pop ebp
.text:004D5073 xor al, al
.text:004D5075 pop ebx
.text:004D5076 retn 0Ch
.text:004D5079 ; ---------------------------------------------------------------------------
.text:004D5079
.text:004D5079 loc_4D5079: ; CODE XREF: sub_4D4EF0+17Ej
.text:004D5079 mov eax, [esp+10h+arg_4]
.text:004D507D mov edx, [edi]
.text:004D507F push ebp
.text:004D5080 push eax
.text:004D5081 push ebx
.text:004D5082 mov ecx, edi
.text:004D5084 call dword ptr [edx+54h]
.text:004D5087
.text:004D5087 loc_4D5087: ; CODE XREF: sub_4D4EF0+157j
.text:004D5087 ; sub_4D4EF0+162j
.text:004D5087 pop edi
...
A call to
+ 0xD5055 (
red ) after tracing leads to a dead end. The same applies to
+ 0xD5064 (
orange ). If you enter them using the debugger and start tracing the code execution path, it turns out that these two functions have very similar behavior. However, nothing says that they have something in common with the operation of the Fog of War button in relation to the interaction with the map. Setting a checkpoint on these two commands indicates that they are constantly being called from somewhere and only executes the logic of the caller. At this stage, we are still in the general code relating to the UI and pressing the buttons, so it’s safe enough to calculate that these two functions have nothing to do with the disclosure of the map.
The last place to call is
+ 0xD5084 (
blue ). Entry into it leads to
+ 0xD4EF0 , which is another big feature.
.text:004D4EF0 push ebx
.text:004D4EF1 mov ebx, [esp+4+arg_0]
.text:004D4EF5 push ebp
.text:004D4EF6 mov ebp, [esp+8+arg_8]
.text:004D4EFA push esi
.text:004D4EFB mov esi, ecx
.text:004D4EFD mov ecx, [esi+0B8h]
...
If you put a control point in it, then it works all the time, that is, it is also a standard processing code. If you go further, you can see that it returns to the code presented in the previous listing. The same two calls to
0x00424750 and
0x004D58B0 will be made. Then the call is made to
[EDX + 0x54] , but this time the
EDX will have a different meaning. In this second call, this leads to the following function at
+ 0xD0C70 :
.text:004D0C70 mov ecx, [ecx+14Ch]
.text:004D0C76 test ecx, ecx
.text:004D0C78 jz short loc_4D0C91
.text:004D0C7A mov edx, [esp+arg_8]
.text:004D0C7E mov eax, [ecx]
.text:004D0C80 push edx
.text:004D0C81 mov edx, [esp+4+arg_4]
.text:004D0C85 push edx
.text:004D0C86 mov edx, [esp+8+arg_0]
.text:004D0C8A push edx
.text:004D0C8B call dword ptr [eax+30h]
.text:004D0C8E retn 0Ch
.text:004D0C91 ; ---------------------------------------------------------------------------
.text:004D0C91
.text:004D0C91 loc_4D0C91: ; CODE XREF: sub_4D0C70+8j
.text:004D0C91 xor al, al
.text:004D0C93 retn 0Ch
.text:004D0C93 sub_4D0C70 endp
There is only one real place to call, so this function is quite simple to analyze. Setting a checkpoint indicates that it is being called from everywhere, that is, it is a common code. Calling
[EAX + 0x30] leads to
+ 0x680D0 . Repeating the process with checkpoints shows that it is still called from everywhere, so there is nothing useful here.
.text:004680D0 push 0FFFFFFFFh
.text:004680D2 push offset SEH_4680D0
.text:004680D7 mov eax, large fs:0
.text:004680DD push eax
.text:004680DE mov large fs:0, esp
.text:004680E5 sub esp, 0F8h
.text:004680EB mov eax, [esp+104h+arg_8]
.text:004680F2 push ebx
.text:004680F3 push ebp
.text:004680F4 push esi
.text:004680F5 mov esi, [esp+110h+arg_0]
.text:004680FC push edi
.text:004680FD mov ebp, ecx
.text:004680FF mov ecx, [esp+114h+arg_4]
.text:00468106 push eax
.text:00468107 push ecx
.text:00468108 push esi
.text:00468109 mov ecx, ebp
.text:0046810B mov [esp+120h+var_F0], ebp
.text:0046810F call sub_4718B0
.text:00468114 test al, al
...
Search for a specific code
A step into the first place of a call at
+ 0x6810F takes us to a function that contains a huge transition table (in the screenshot below). This may be a promising indication that we have found an area managing events or executing event handling mechanisms.

Step-by-step code execution leads us to the following case:
.text:00471DB4 loc_471DB4: ; CODE XREF: sub_4718B0+4FDj
.text:00471DB4 ; DATA XREF: .text:off_471FA0o
.text:00471DB4 push edi ; jumptable 00471DAD case 4
.text:00471DB5 call sub_54E7D0
.text:00471DBA mov esi, eax
.text:00471DBC add esp, 4
.text:00471DBF test esi, esi
.text:00471DC1 jz loc_471F5F ; jumptable 00471DAD case 3
.text:00471DC7 push edi
.text:00471DC8 call sub_4D58B0
.text:00471DCD add esp, 4
.text:00471DD0 test esi, esi
.text:00471DD2 jz loc_471F5F ; jumptable 00471DAD case 3
.text:00471DD8 mov edx, [esi+1A4h]
.text:00471DDE mov ecx, [esp+50h+var_40]
.text:00471DE2 cmp edx, ebx
.text:00471DE4 setz al
.text:00471DE7 push eax
.text:00471DE8 call sub_58EA10
.text:00471DED mov al, 1
.text:00471DEF jmp loc_471F65
...
By setting the control point to
+ 0x71DB4 (
pink ) and continuing, we found out that nothing more constantly gets here. When you click on the button "Fog of War" we see that it is running
+ 0x71DB4 . And finally, after a long trace, we get evidence that we are inside the code related to the “Fog of War” button. The first call command is at
+ 0x71DB5 (
red ). This function receives one parameter via
EDI , and it is always a constant value. By following the code step by step and carefully observing the values ​​of all parameters or address / load addresses, we do not find anything that indicates the value of the switch button. In particular, by pressing the map-disclosing-hiding button and tracing the function, we did not find anything changing, so we excluded it. The command at
+ 0x71DC8 (
orange ) calls the address
0x004D58B0 , which we have already investigated above. The same thing happens with the function. It always gets the same value as the previous function and says nothing about whether it writes the switch value or controls the code based on this value.
The next call is at
+ 0x71DE8 . This function also receives one parameter and this is also the last function called before exiting the transition table processing function. In the
turquoise block there is a very interesting code. The value is loaded from
[ESI + 0x1A4] , then compared to
EBX . The result of this comparison assigns the
AL byte a value of 0 or 1.
EAX , which will be 0 or 1, then passed as a function argument to the address
0x0058EA10 . Pressing a button in the game and step through shows that
EBX always contains the value 1, and
EDX contains 0 or 1, depending on whether the map is hidden or uncovered. It can be assumed that this is the function that is used to reveal and hide the map. The assembly listing for
0x0058EA10 is shown below:
.text:0058EA10 sub_58EA10 proc near ; CODE XREF: sub_4718B0+538p
.text:0058EA10 ; sub_58DF30+919p ...
.text:0058EA10
.text:0058EA10 arg_0 = dword ptr 4
.text:0058EA10
.text:0058EA10 push ebx
.text:0058EA11 mov ebx, [esp+4+arg_0]
.text:0058EA15 mov [ecx+53h], bl
.text:0058EA18 mov eax, dword_A9D244
.text:0058EA1D mov ecx, [eax+140h]
.text:0058EA23 test ecx, ecx
.text:0058EA25 jz short loc_58EA43
.text:0058EA27 push 1
.text:0058EA29 push ebx
.text:0058EA2A call sub_5316B0
.text:0058EA2F mov ecx, dword_A9D244
.text:0058EA35 mov ecx, [ecx+140h]
.text:0058EA3B push 1
.text:0058EA3D push ebx
.text:0058EA3E call sub_5316D0
.text:0058EA43
.text:0058EA43 loc_58EA43: ; CODE XREF: sub_58EA10+15j
.text:0058EA43 pop ebx
.text:0058EA44 retn 4
.text:0058EA44 sub_58EA10 endp
It passes the value 0 or 1 to two more functions, each of which takes two parameters. The first parameter is the switching value 0 or 1, and the second is always a hard-coded value of 1. Looking at these two functions, you can see that they write a value of 0 or 1 to the object, and then call the function
.text:005316B0 ; =============== SUBROUTINE =======================================
.text:005316B0
.text:005316B0
.text:005316B0 public sub_5316B0
.text:005316B0 sub_5316B0 proc near ; CODE XREF: sub_442070+1684p
.text:005316B0 ; sub_4C91E0+14Cp ...
.text:005316B0
.text:005316B0 arg_0 = byte ptr 4
.text:005316B0 arg_4 = dword ptr 8
.text:005316B0
.text:005316B0 mov edx, [esp+arg_4]
.text:005316B4 mov al, [esp+arg_0]
.text:005316B8 push edx
.text:005316B9 push 1
.text:005316BB mov [ecx+40Eh], al
.text:005316C1 call sub_5316F0
.text:005316C6 retn 8
.text:005316C6 sub_5316B0 endp
.text:005316C6
.text:005316C6 ; ---------------------------------------------------------------------------
.text:005316C9 align 10h
.text:005316D0
.text:005316D0 ; =============== SUBROUTINE =======================================
.text:005316D0
.text:005316D0
.text:005316D0 sub_5316D0 proc near ; CODE XREF: sub_442070+1698p
.text:005316D0 ; sub_4C91E0+137p ...
.text:005316D0
.text:005316D0 arg_0 = byte ptr 4
.text:005316D0 arg_4 = dword ptr 8
.text:005316D0
.text:005316D0 mov edx, [esp+arg_4]
.text:005316D4 mov al, [esp+arg_0]
.text:005316D8 push edx
.text:005316D9 push 1
.text:005316DB mov [ecx+40Fh], al
.text:005316E1 call sub_5316F0
.text:005316E6 retn 8
.text:005316E6 sub_5316D0 endp
Replacement patch
mov al, [esp + arg_0]on
mov al, 0
nop
nopleads to the fact that the mini-map is constantly open, regardless of the state of the “Fog of War” button. We found the code responsible for disclosing and hiding the card.
Hack development
At this stage, you can create a hack, it will be just a matter of calling
0x0058EA10 with a value of true / false, depending on the state of the map we need. However, there is a small problem: there is a write command in
[ECX + 0x53] at
0x0058EA15 . This means that we will need to transfer to the address
+ 0x53 an object with a recordable field that will serve as the "
this " parameter, usually transmitted via
ECX using
__thiscall .
ECX is then overwritten after loading from a fixed address, so this seems like a safe approach. The dirty code for this task is below:
#include <Windows.h> struct DummyObj { char Junk[0x53]; }; DummyObj dummy = { 0 }; using pToggleMapFnc = void (__thiscall *)(void *pDummyObj, bool bHideAll); int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: { (void)DisableThreadLibraryCalls(hModule); pToggleMapFnc ToggleMap = (pToggleMapFnc)0x0058EA10; while (!GetAsyncKeyState('0')) { if (GetAsyncKeyState('7')) { ToggleMap(&dummy, true); } else if (GetAsyncKeyState('8')) { ToggleMap(&dummy, false); } Sleep(10); } break; } case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
After injection of the DLL into the game process, you can switch the card to a fully open or closed view using the “7” and “8” keys.


Conclusion
This concludes the development of a hack card for the game. This approach was very complicated and complicated, and in the next part of the article I will show you how to simplify everything a lot by using the useful information left by the developers in the executable file. Reading the article, you can decide that the work from the beginning to the end was quite linear, but in fact, for the sake of brevity, many paths in the code were missed, which led me to a dead end. If they had remained, then they themselves and their explanation could pull on the volume of the dissertation. During the initial development of the hack, I went through various ways of executing code many times, making notes on what might be necessary. As a result, this article contains only useful information, arranged in a coherent and almost linear guide.
Part two: easy way
In the previous part, I talked about how to create a hack card using the built-in functions of the game. This technique used the ability to switch the hidden / open state. I consistently applied these built-in functions to search by assembly code. As a result, this led me to the logic of hiding and revealing a card in which it was possible to write a hack, causing these possibilities. In this part, a much simpler technique will be considered, and it was only possible due to the useful strings found in the binary file.
In this part, I will use
x64dbg , an excellent debugger and disassembler, which I consider to be the successor of the outdated
OllyDbg . Unfortunately, in this part I did not often use it, because I almost did not need analysis in the process of executing the code (after all, this part is called the “simple method”). Assembler fragments are inserted from
IDA Pro , because I find its copy-paste format to be the most readable.
Starting from connecting to the process and dumping the strings (right mouse button -> Search for -> Current Module -> String references) of the main executable file, we received 25817 strings - a rather large area for searching.

The filter on the line "map" gives us a more convenient set. After reviewing it, I found several lines that may lead to something interesting:
"TrSetFogAndBlackmap (<true / false> <true / false>): fog and black map on / off."«TrRevealEntireMap - shows a whole map of the revealed mode works»
"TrPlayerResetBlackMap (: Resets the black map for a given HUMAN player."
"Map visibility"
"Blackmap ([integerState]): toggles or sets unexplored black map rendering."
The two most promising lines are highlighted in
orange . The strings give a clear idea of ​​what the function is doing, and even provide arguments to the parameters. It seems that the "trX" functions are associated with the
trigger system in the game, which allows map creators to add effects and conditions. Examining the links in the first line leads to the following:
...
.text:008B2B76 loc_8B2B76: ; CODE XREF: sub_8AE4A0+46CDj
.text:008B2B76 mov ecx, esi
.text:008B2B78 call sub_59C270
.text:008B2B7D push 1
.text:008B2B7F push offset loc_8AAEE0
.text:008B2B84 push offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B89 mov ecx, esi
.text:008B2B8B call sub_59BE80
.text:008B2B90 test al, al
.text:008B2B92 jnz short loc_8B2BAE
.text:008B2B94 push offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B99 push offset aSyscallConfigE ; "Syscall config error - Unable to add th"...
.text:008B2B9E push esi ; int
.text:008B2B9F call sub_59DBC0
...
The code here begins with passing a string, a pointer to a function, and constants (1) as arguments to another function (
cyan ). The return value of this call is checked for equality 0, which is an error state (
blue ). Looking at what happens in the disassembler, you will notice that this template is used everywhere. This code and the code surrounding it tries to register the triggers and reports the name of the trigger, the event handling mechanism to the place where the trigger code is located, and the unknown constant 1 so far. Considering this, you should continue the search in the event handling mechanism.
The transition to the event handling mechanism leads us to the following code fragment:
.text:008AAEE0 loc_8AAEE0: ; DATA XREF: sub_8AE4A0+46DFo
.text:008AAEE0 mov eax, dword_A9D244
.text:008AAEE5 mov ecx, [eax+140h]
.text:008AAEEB test ecx, ecx
.text:008AAEED jz short locret_8AAF13
.text:008AAEEF mov edx, [esp+4]
.text:008AAEF3 push 0
.text:008AAEF5 push edx
.text:008AAEF6 call sub_5316B0
.text:008AAEFB mov eax, [esp+8]
.text:008AAEFF mov ecx, dword_A9D244
.text:008AAF05 mov ecx, [ecx+140h]
.text:008AAF0B push 0
.text:008AAF0D push eax
.text:008AAF0E call sub_5316D0
.text:008AAF13
.text:008AAF13 locret_8AAF13: ; CODE XREF: .text:008AAEEDj
.text:008AAF13 retn
Two calls here (
green ) should be familiar to you if you carefully read the first part of the article. These are two functions that, as we discovered, control the disclosure and concealment of the map. Each function receives a "
this " pointer, which, as we see here, is loaded from a permanent address and most likely is a class for the main player, with a value of true / false describing what happens to the map. There is a third immutable parameter 0, which is different from the immutable parameter 1 in another place of the call from the previous part of the article. Perhaps he indicates that the state of the card is changed by the player or the trigger.
Knowing this, the hack from the previous part can be done a little better. In the old hack there was a problem with providing a fake pointer "
this ", which should have had a writeable field, and there was only one switching option: true / false. Based on the documentation obtained by the string dump, this function takes two boolean values; presumably, they control the superimposed black color and fog of war, shading areas already explored by the player, but which the player does not currently see.
New (but still a little dirty) is presented below:
#include <Windows.h> using pToggleMapFnc = void (__cdecl *)(bool bEnableBlackOverlay, bool bEnableFogOfWar); int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: { (void)DisableThreadLibraryCalls(hModule); pToggleMapFnc ToggleMap = (pToggleMapFnc)0x008AAEE0; while (!GetAsyncKeyState('0')) { if (GetAsyncKeyState('6')) { ToggleMap(true, true); } else if (GetAsyncKeyState('7')) { ToggleMap(true, false); } else if (GetAsyncKeyState('8')) { ToggleMap(false, true); } else if (GetAsyncKeyState('9')) { ToggleMap(false, false); } Sleep(10); } break; } case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
Calling a function with different combinations of parameters, we managed to get the following behavior:
True / True - superimposed black with fog of war
True / False - there is no black imposed, there is a fog of war. There are no marks on the map.
False / True - superimposed black without fog of war. Investigated areas are always visible.
False / False - no overlaid black, no fog of war. The whole map is visible.
Below are screenshots for all four states:




Hack has become more clean, because now he performs a direct function call and does not require the transfer of anything unknown. I hope, obviously, why I consider it “an easy way”, unlike the previous solution, which required long debugging and tracing.
In the next, last part of the article, we will think about how to make this hack a little cleaner and more professional. In addition, we will consider what is needed to port the hack for the new version of the game
Extended Edition .
Part Three: Putting It All Together
In the previous two parts, we talked about how to develop the hack maps of Age of Mythology. We did this by finding and performing the reverse development of the parts of the game responsible for switching the states of the map (superimposed black layer, fog of war, fully revealed map) and calling these functions through a DLL injected into the game process. In this short part we will complete the topic by adding an injector to the source code, which will inject the hack dll developed by us into the Age of Mythology process. The hack will work in multiplayer mode, in the original game and in the enhanced version.
The code is laid out on
github and as a whole does not demand an explanation. The card's hack dll exports the
KeyboardProc callback, which controls the logic for switching the states of the card depending on the user keystrokes (7, 8, 9, 0). The injector installs a keyboard hook in the game, which injects a hack dll into the game and activates the KeyboardProc callback. After that, all keystrokes transmitted to the game are intercepted and checked for compliance with the four keys for switching card states. If the switch key is pressed, the corresponding function is called, changing the state of the card.