Hack Age of Mythology: turning off the fog of war

Original author: Alex Abramov
  • Transfer

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 that is completely shaded with black, which means an unknown and unexplored territory.





During the game, the user examines the map, and on the investigated areas there are signs of the relief, resources, enemy buildings, which are superimposed on the "fog of war", indicating the investigated territory that the player does not see.





The goal of this post will be to create a hack that opens the entire map, giving the player a significant advantage. It will allow the player to see what the opponents are doing on the map, and choose the best time and place for the 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, advanced version, which is now distributed on Steam .

Here are the hashes for the main executable that will be parsed by reverse engineering in this article:

CRC32 : 7F1AF498
MD5 : 09876F130D02AE760A6B06CE6A9C92DB
SHA-1 : AAAC9CD38B51BEB3D29930D13A87C191ABF9CAD

Part One: The Hard Way


Getting started


Our goal is to develop what will open all the map data to the player, giving the player full information about what is happening in the game. The good news is that revealing and hiding a card is the built-in functionality of the game. The game supports playback of recorded games, and the open-hide option of the card is part of the UI of this function.



The “Fog of War” button controls the opening of the map and return to normal, in which the player sees only what he has examined. The plan is to find the handler for this button and trace the location of the map expansion logic. Once we find it, we simply inject the DLL into the game to invoke the map expansion function. A tool like the Cheat Engine is suitable for this job., which is useful for researching and changing memory, debugging, disassembling and other operations in the context of hacking games. In this article I will not describe working with this tool, there are many other resources for this.

After starting and connecting the Cheat Engine, the question is where is the code that interacts with the button. The easiest way to find out is to apply standard programming practices. In particular, the active button will have a value of 1 somewhere in the memory, and the inactive button will have the value 0. Therefore, this becomes a matter of testing and patience. A search in the process memory for 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. Having studied it more carefully and changing the value "0", it was possible to switch the button to an inactive state. So, we found the desired address, and the remaining 20 can be safely discarded. The next step is to find out what records it.



At this point, the Cheat Engine hooks up the debugger and monitors all writes to 0x08FC71A4. After several button presses, the following commands were identified. These commands wrote to 0x08FC71A4 .



Next, you need to examine them and begin to set breakpoints in order to better understand what is happening next to these recording operations. Setting a breakpoint on the recording



and fussing games allowed us to find out that this function is called for each button. Here, ECX is the pointer to the button, and + 0x1A4 probably contains the IsToggled property , which assigns the corresponding state to the button. This assignment is performed in the second write command, where EDXcan be “0” (inactive) or “1” (active). The code may seem a bit complicated, but it just checks that the included state is correct, and then sets the IsToggled property before calling the function and returning.

The recipient address + 0x14B670 is also a code that applies to all buttons. Here we need to slowly go around everything and find areas of the code that may relate to the “Fog of War” button. Different approaches can be applied, but I usually use the following:

  • Call addresses computed through the register. This may mean an event processing mechanism that executes after the state of a button changes, something like OnChanged / OnEnabled / OnDisabled, or a similar function.
  • Function parameters that are pointers to functions.
  • Calls to functions that receive arguments 1 or 0.

The step with entering + 0x14B670 gives us the following (partial) assembler code, presented below. In the assembler code, absolute addresses are indicated, not the start address of the module in memory + offset , because they are much easier to copy from the IDA than from the Cheat Engine. It turned out that a call with a call to 0x004D7470 (red) returns quite quickly, so it will not be shown here. The next call (blue) at + 0x14B6A6 makes a call through the register. This is a good candidate for careful study. This function can call two possible addresses: Command at address + 0x14BF9A

.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:0054B69Acall sub_4D7470
.text:0054B69F mov ecx, [eax]
.text:0054B6A1 push ecx
.text:0054B6A2 push 1
.text:0054B6A4 mov ecx, esi
.text:0054B6A6call dword ptr [edi+54h]
.text:0054B6A9 cmp [esp+1Ch+arg_0], 0Dh
.text:0054B6AE jnz loc_54B769
.text:0054B6B4 lea edi, [esi+154h]
...




...
.text:0054BF98 push 0Ch
.text:0054BF9Acall 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:0054BFA9call sub_4D4EF0
.text:0054BFAE pop edi
...


(red) is never called during debugging and passing, so it makes no sense to study it. For research, only the next call remains at + 0x14BFA9 (blue). This function turned out to be very large in size and has a wide branching with many possible call points. With debugging, you can skip most of this logic. By tracing the code, which is executed only with the “Fog of War” button active, we select only three call locations. A call at + 0xD5055 (red) after tracing leads to a dead end. The same applies to + 0xD5064

...
.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
...


(Orange). If you go into them using the debugger and start tracing the code execution path, it turns out that these two functions have very similar behavior. However, nothing suggests that they have something in common with the operation of the Fog of War button in relation to interacting with the map. Setting a breakpoint on these two commands shows that they are constantly called from somewhere and execute the logic of only the calling object. At this stage, we are still in the general code related to the UI and button clicks, so it’s safe enough to consider that these two functions have nothing to do with revealing the map.

The last place to call is + 0xD5084 (blue). Entering it leads to + 0xD4EF0 , which is another great function.

.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 constantly, that is, it is also a standard processing code. If we go further, we 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 a call is made to [EDX + 0x54] , but this time EDX will have a different meaning. In this second call, this leads to the following function at + 0xD0C70 : Here there is only one real place to call, so this function is quite simple to analyze. Setting a checkpoint indicates that it is called from everywhere, that is, it is a common code. Calling [EAX + 0x30] leads to

.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


+ 0x680D0 . Repeating the process with control points 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 specific code


The step with entering the first place of the call at the address + 0x6810F takes us to a function containing a huge transition table (in the screenshot below). This may be a promising indication that we have found an area that controls events or handles event processing mechanisms.



Step-by-step execution of the code leads us to the following case: Setting the breakpoint at + 0x71DB4 (pink) and continuing, we found out that nothing constantly gets here anymore. When we click on the “Fog of War” button, we see that + 0x71DB4 is running . 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

.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
...


(red). This function receives one parameter through EDI , and it is always a constant value. By executing the code step by step and carefully observing the values ​​of all parameters or addressable / loaded addresses, we do not find anything that says about the value of the button switch. In particular, by clicking on the open-hide button of the map and tracing the function, we did not find anything changing, so we excluded it. The command at the address + 0x71DC8 (orange) calls the address 0x004D58B0 , which we already examined 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 a switch value or controls code based on that value.

The next call is at+ 0x71DE8 . This function also receives one parameter, and this is also the last function called before exiting the processing function of the transition table. There is a very interesting code in the turquoise block. The value is loaded from [ESI + 0x1A4] , then compared with EBX . The result of this comparison sets the AL byte to 0 or 1. EAX , which will be 0 or 1, is then passed as an argument to the function at 0x0058EA10 . Pressing the button in the game and step by step show that EBX always contains the value 1, and EDXcontains 0 or 1, depending on whether the card is hidden or revealed. It can be assumed that this is the function that is used to reveal and hide the card. The assembler listing for 0x0058EA10 is shown below: It passes a value of 0 or 1 to two more functions, each of which takes two parameters. The first parameter is a switch value of 0 or 1, and the second is always a fixed value of 1. Looking at these two functions, you can see that they write the value 0 or 1 to the object, and then call the Patch function by replacing mov al, [esp + arg_0] on mov al, 0 nop nop

.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



.text:005316B0 ; =============== S U B R O U T I N E =======================================
.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 ; =============== S U B R O U T I N E =======================================
.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








leads to the fact that the minimap is constantly open, regardless of the state of the Fog of War button. We found the code responsible for revealing and hiding the card.

Hack development


At this stage, you can create a hack, it will only be a matter of calling 0x0058EA10 with a value of true / false, depending on the state of the card we need. However, there is a small problem: there is a write command in [ECX + 0x53] at the address 0x0058EA15 . This means that we need to transfer an object with a recordable field to the address + 0x53 , which will serve as the " this " parameter, which is usually transmitted via ECX using __thiscall . Further, in the ECX function, it is overwritten after loading from a fixed address, so this seems like a safe approach. The dirty code for this task is given below:

#include 
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 the DLL is injected into the game, you can switch the card to a fully open or closed view using the keys "7" and "8".





Conclusion


With this, we complete the development of a hack card for the game. This approach was very confusing and complicated, and in the next part of the article I will show how you can greatly simplify everything 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 missing that led me to a dead end. If they stayed, then they themselves and their explanation could pull in volume to the dissertation. During the initial development of the hack, I went through many different ways of executing the code many times, taking notes on what might be necessary. As a result, this article has collected only useful information, compiled into a holistic and almost linear guide.

Part Two: The 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 hidden / open state. I have consistently applied these built-in functions to search in assembly code. As a result, this led me to a logic that hides and reveals a map in which you could write a hack that calls these features. In this part, a much simpler technique will be considered, and it turned out to be possible only thanks to the useful lines 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 already obsolete 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 (in the end, this part is called the “easy way”). Assembled fragments are pasted from IDA Pro , because I find its copy-paste format to be the most readable.

Starting with connecting to the process and dumping the strings (right mouse button -> Search for -> Current Module -> String references) of the main executable file, we got 25817 lines - a rather large area for searching.



The filter on the line "map" gives us a more convenient set. Looking through it, I found a few lines that might lead to something interesting:

“trSetFogAndBlackmap (): fog and black map on / off. ”
“TrRevealEntireMap - shows whole map, similar to how revealed mode works”
“trPlayerResetBlackMap (: Resets the black map for a given HUMAN player.”
“Map visibility”
“blackmap ([integerState]): toggles or sets unexplored black map rendering.”


I highlighted the two most promising lines in orange, which give a clear idea of ​​what the function does, and even communicate parameter arguments. It seems that the trX functions are related to the trigger system in the game , which allows card creators to add effects and conditions. The links in the first line lead 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 (turquoise). The return value of this call is checked for equality 0, which is an error condition (blue). Looking at what happens in the disassembler, you can see that this template is used everywhere. This code and the code surrounding it tries to register triggers and reports the name of the trigger, the mechanism for processing the event to the place where the trigger code is, and the unknown constant 1. With this in mind, you should continue the search in the mechanism for processing the event.

The transition to the event processing 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 the two functions that, as we have discovered, control the revealing and hiding of the card. Each function receives a " this " pointer , which, as we see here, is loaded from a fixed address and most likely is a class for the main player together with a true / false value that describes what happens with the map. Here is the third immutable parameter 0, which differs 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 has been changed by the player or trigger.

Knowing this, the hack from the previous part can be made a little better. The old hack had a problem providing a fake pointer " this", which was supposed to have a recordable field, and there was only one switching option: true / false. Based on the documentation received by the string dump, this function takes two Boolean values; presumably, they control the superimposed black color and the fog of war, shading areas that are already investigated by the player, but which the player does not currently see.The

new (but still a bit dirty) is presented below:

#include 
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;
}

By calling a function with different combinations of parameters, we managed to obtain the following behavior:

True / True - imposed black color with fog of war.
True / False - no imposed black color , fog of war. There are no marks on the map.
False / True - superimposed black without the fog of war. Investigated areas are always visible.
False / False - no black overlay, no fog of war. The whole map is visible.

Below are screenshots for all four states: The









hack has become cleaner because it now performs a direct function call and does not require the transfer of anything unknown. I hope, obviously, why I consider this an “easy way”, unlike the previous solution, which required a 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 necessary to port the hack to 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 a Age of Mythology map hack. 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, a fully revealed map) and calling these functions through a DLL injected into the game process. In this short part, we will end the topic by adding an injector to the source code that will inject the hack DLL we developed into the Age of Mythology process. The hack will work in multiplayer mode, in the original game and in the advanced version.

The code is posted on github and generally requires no explanation. Map Hack DLL Export KeyboardProc Callback, which controls the logic of switching the state of the card depending on the keys pressed by the user (7, 8, 9, 0). The injector sets a keyboard hook into the game process, which injects the hack DLL into the game process and activates the KeyboardProc callback. After that, all keystrokes transmitted to the game are intercepted and checked for compliance with the four keys of switching the states of the map. If the switch key is pressed, the corresponding function that changes the state of the card is called.

Also popular now: