In the software world there are a huge number of programs forgotten by their developers. Well, when there is already a good alternative. And if it is not? The program may catastrophically lack some trifles, some annoying errors may cause a lot of inconvenience to users over the years, and the program may refuse to work on new versions of the OS. Not always there are source codes to put the program in order. If the program is simple, it is not difficult to create an alternative in a short time. But if the program is large and complex, what to do in this case? It is not always rational to spend time and money on the development of a complete analogue, because it is possible to expand the functionality within reasonable limits and correct most of the errors already in the finished executable file.
In this article, techniques for modifying executable files will be demonstrated using the example of expanding the functionality of the legendary game
Age of Empires II (real-time strategy).
So, what do we have:
- The game was released in 1999 and has the status of abandonware - that is, it is no longer offered for sale by the manufacturer and does not make a profit.
- Last update released in 2000, no revision
- the game is a good example of complex software, the full analogue of which is almost impossible to recreate
- there are annoying bugs that were not corrected by the developers
Perhaps you can try to change something without looking at the ban in the license agreement. Don't you think so? :)
Denial of responsibility
The work was carried out solely for educational purposes, and in no case is it a call for violation of the current legislation. The author does not bear any responsibility for the illegal use of the submitted materials.
')
Information about the "patient"
The explored game is written on the Genie engine, the development of which from 1997 to 2000 was carried out by Ensemble Studios. During this time, 4 games from the Age of Empires series were released on this engine. Next Genie was licensed to LucasArts, which in 2001-2002 released 2 more games on this engine, but from the Star Wars series. By this time, the engine is already very outdated, because it worked in 256 color mode, which is very modest compared to games even in 2000. That is why the engine development was stopped, no new games based on it were released.
The game engine Genie downloads all game graphics, music, and most importantly - lists of units and their characteristics - from external files. That is, all games based on this engine for the most part have very similar executable files, and almost all modifications for one game are ported to others without any problems.
What do we want to achieve?
1. Disable CD availability check
In modern laptops (netbooks, etc.) is no longer a rarity when there is no built-in CD drive. Moreover, since the game is very old, the original CD may stop being read, and someone could lose it altogether. As a result, the owners of a completely legal copy of the game lose their ability to launch the game. So, perhaps, let the game run without a disk.
2. Add support for window mode
Age of Empires supports 3 fixed resolutions: 800 × 600, 1024 × 768 and 1280 × 1024. With these resolutions, the picture on widescreen monitors looks stretched, which causes discomfort. You can add support for new permissions to the game, but since the game uses its own interface graphics for each resolution, we’ll leave it for later, and for the time being we will limit ourselves to adding a windowed mode of operation to the game.
3. Add support for configuration files
To enable some system game options, they have to be transmitted via the command line every time the program is started. You can put all the parameters into a shortcut, but after moving the game to another directory, the shortcut will stop working. The way out is to add support for configuration files.
4. Make the game portable
The game is not portable. To run on another computer is not enough to copy the game files, you must go through the entire process of installing the game. In connection with the widespread use of external drives, I would like to endow the game with this property.
5. Correct known errors
In the game interface there are unpleasant errors. For example, in the list of recent games in the IP game connection dialog, previously entered addresses are truncated, which reduces the convenience of the entire fast connection function to nil; you have to manually enter the server address each time.
What do we need for this?
IDA Pro is an interactive disassembler and debugger widely used for reverse engineering. Able to build clear even to beginners flowcharts. All his power is manifested in interactive interaction with the user. After an automatic analysis, the user can give meaningful names to functions and variables, comment on the code, etc. Enough small knowledge in the field of the assembler in order to start doing research programs right now!
Flat Assembler is a freeware multipass assembler. FASM has a small size and a very high compilation speed, has a rich and capacious macro syntax that allows you to automate many routine tasks. One useful feature is the generation of clean machine code without headers, etc. All this is useful to us for the generation of assembly inserts.
Hexplorer is a free HEX editor. It has a simple x86 disassembler, which makes it easier to navigate inside the executable file. It is used directly for manual modification of a binary file without resizing it.
So, let's begin.
Disable CD availability check
IDA will help us find the code that is responsible for checking for the presence of a CD. It turned out to be very easy to do. It is enough to trace the calls of the GetDriveTypeA and GetVolumeInformationA functions that are used to obtain information about the drive, as we find the CD presence check function at 4485A0h (the base address is 400000h, that is, physically in the file the machine code is located at 485A0h), which returns to eax unit.
CheckCD proc near
sub esp, 214h
push ebx
push esi
; ...
; many checks
; ...
test eax, eax
jz cd_check_fail
cd_check_ok:
pop esi
mov al, 1
pop ebx
add esp, 214h
retn 4
cd_check_fail:
pop esi
pop ebx
add esp, 214h
retn 4
CheckCD endp
It is worth noting that this function requests the CDPath value from the registry where the drive letter from which the game was installed is stored. That is, the game is tied specifically to the letter of the disk from which it was installed, and if there is an original disk in another drive, the game will not start anyway. The simplest solution in this case is to replace the jz cd_check_fail command, which takes 2 bytes, with 2 nop operations, so that after checking the disk in eax, one is always returned. However, the function is designed in such a way that if there is no CDPath value in the registry, the function immediately returns 0, which prevents the program from being portable. Therefore, we replace the entire function with 3 simple commands:
use32
xor eax, eax
inc eax
retn 4
For obtaining machine codes of these commands FASM is useful to us.
fasm src.asm dst.bin
The main advantage of this assembler for us now is that it allows us to generate pure machine code without headers. We get (line by command):
31 C0
40
C2 04 00
That is, we need to place these 6 bytes at the beginning of the function of detecting the presence of a CD, and replace the remaining commands with nop (code 90h).
Source machine code | Received machine code |
---|
8B 44 24 04 81 EC 14 02
00 00 85 C0 53 56 8B D9
... | 33 C0 40 C2 04 00 90 90
90 90 90 90 90 90 90 90
... |
In the usual HEX editor, we replace the machine codes mentioned above starting at offset 485A0h, after which the game will never require the CD to run, even if the registry does not have the necessary key. At the same time, we freed 288 bytes for additional machine code, which will be useful to us in the future.
Bug fix
When connected to an IP network game, the game displays a list of IP and game names to which the player has connected before. However, for some reason, the IP address was often not completely saved, several of his last characters were truncated. It is necessary to understand what is happening and correct the error.
Let's look for the code responsible for storing the IP addresses of the latest games in the registry. We know that the game saves a list of the latest IP games in keys with the names like RecentGameName% d and RecentGameAddr% d. Using IDA, we find 3 references to these lines. It is very easy to determine where the necessary code is - in the code with offset 0512116h these keys are written, otherwise - reading. We will not give the found section of machine code - there are about 50 instructions, the general meaning of which can be represented as a small fragment of C code.
for (int i = 0; i <count; i ++)
{
char * keyname;
sprintf (keyname, "RecentGameName% d", i);
RegSetVal (1, keyname, gamename [i], strlen (gamename [i]));
sprintf (keyname, "RecentGameAddr% d", i);
RegSetVal (1, keyname, gameaddr [i], strlen (gamename [i]));
}
To set the value in the registry, the game uses its own wrapper function RegSetVal (int, char * ValueName, char * Data, int DataSize) - the name of the function and parameters are of course invented, they are not in the machine code. As you can see, the game saves in the registry exactly as many characters of the IP address as there are in the name of the network game. Apparently, the programmer copied 2 lines of saving the name of the game to save the address, but forgot to correct the name of the array in the strlen call.
To correct the error, we have 2 options: substitute the correct string in strlen or place a constant 16 instead of strlen (maximum length of the address string, for example, "255.255.255.255").
What does the strlen (gamename [i]) call look like in machine code?
call _sprintf; + 0x1022CF
mov edi, ebp
or ecx, 0FFFFFFFFh
xor eax, eax
add esp, 0Ch
repne scasb
not ecx
push ecx
The compiler optimized the code and replaced the call to the function strlen () by calculating the length in place. In the previous call to strlen (), the same line was used, in the optimization again - the pointer is placed in ebp once during the first call. That is, we do not have a precious pair of bytes in order to place the correct pointer in ebp. You can, of course, make jmp outside the function, then back - but this is not the most beautiful method. Therefore, let us dwell on the simplest version of correcting the error - we will always save 16 bytes of the address. To do this, replace the above machine code with the following:
Assembly code | Machine code |
---|
call _sprintf; + 0x1022CF
add esp, 0Ch
mov ecx, 0Fh | E8 CF 22 10 00
83 4 0
B9 0F 00 00 00 |
Configuration file support
The game supports many useful command line parameters that it would be convenient to save somewhere in the file, so as not to transfer them every time you start it. The best way to implement this is to force the game to load the external library at each start and perform an initialization function. With such an organization, you can add many useful things to configuration files, such as: loading additional libraries (modules) without having to modify the executable file, intercepting registry functions to organize the storage of all game data in the game configuration file, and not in the registry. It would not be amiss to support multiple profiles with different sets of settings with the ability to quickly switch them. So let's get started.
The most convenient connection point for the external library was the ebueulax.dll library download function, which is responsible for displaying the license agreement. It is called almost immediately after the start of WinMain, however, in addition to loading the ebueulax.dll library and executing the EBUEula function, it does a lot of unnecessary actions, so we completely rewrite it so that it loads the config.dll library and performs the LoadConfig library function with a location pointer command line in memory (to change it) as a parameter.
Assembly code | Machine code |
---|
LoadConfigLib proc near
sub esp, 208h
push esi
push edi
push ebx
push ecx
push edx
mov esi, ds: LoadLibraryA
push 67B8C4h; lpLibFileName
call esi; LoadLibraryA
mov ebx, eax
test ebx, ebx
jz short loc_587443
push offset aLoadconfig
push ebx; hModule
call ds: GetProcAddress
mov edi, eax
test edi edi
jz short loc_58743C
mov eax, [esp + 220h]; ** args
push eax
call edi
jmp short loc_587443
loc_58743C:
push ebx; hLibModule
call ds: FreeLibrary
loc_587443:
pop edx
pop ecx
pop ebx
pop edi
pop esi
add esp, 208h
retn
LoadConfigLib endp |
81 EC 08 02 00 00
56
57
53
51
52
8B 35 E0 51 63 00
68 C4 B8 67 00
FF D6
8B D8
85 DB
74 25
68 B8 B8 67 00
53
FF 15 C8 50 63 00
8B F8
85 FF
74 0C
8B 84 24 20 02 00 00
50
FF D7
EB 07
53
FF 15 20 51 63 00
5A
59
5B
5F
5E
81 C4 08 02 00 00
C3 |
The code above makes several calls to library functions. Their addresses were manually inserted into the machine code. In the next article (if you don’t mind :)) I’ll try to tell you how to use FASM to generate code that is completely ready to be inserted into an executable file.
Replace the original function (starting at offset 187400h) with a new HEX editor. Since the new function is much shorter than the original, do not forget to replace the remaining bytes with the operation code nop - 90h. We received another 192 bytes of free bytes for the additional machine code. This place can be used in the future.
Source machine code | Received machine code |
---|
81 EC 08 02 00 00 53 56
8B 35 E0 51 63 00 57 68
CC B8 67 00 FF D6 8B D8
85 DB 75 0A 5F 5E 5B 81
C4 08 02 00 00 C3 68 C4
B8 67 00 53 FF 15 C8 50
63 00 8B F8 85 FF 75 13
53 FF 15 20 51 63 00 5F
5E 33 C0 5B 81 C4 08 02
00 00 C3 8B 84 24 18 02
00 00 50 FF D6 8B F0 85
... | 81 EC 08 02 00 00 56 57
53 51 52 8B 35 E0 51 63
00 68 C4 B8 67 00 FF D6
8B D8 85 DB 74 25 68 B8
B8 67 00 53 FF 15 C8 50
63 00 8B F8 85 FF 74 0C
8B 84 24 20 02 00 00 50
FF D7 EB 07 53 FF 15 20
51 63 00 5A 59 5B 5F 5E
81 C4 08 02 00 00 C3 90
90 90 90 90 90 90 90 90
... |
The rest of the work is done by the config.dll module. It can be written in C / C ++ / Delphi. Its task is simple - to accept from the executable file a pointer to the application command line and, depending on the settings in the configuration file, modify it. As a bonus, we can use the ability to load additional modules by configuring this in the configuration file. To do this, we simply need to call the WinAPI function LoadLibrary by the name of the module file that needs to be loaded into the address space of the game.
Support windowed mode
Support for windowing is organized as a separate module wndmode.dll, which is loaded by config.dll. This module is engaged in intercepting all calls to DirectDraw, modifies the parameters so that the program works in the window, and only after that transfers control to the original DirectDraw functions.
This is implemented by changing the function being intercepted. The first few bytes of the intercepted function are replaced with the unconditional command to intercept function. This trick is quite simply implemented in WinNT (since WinNT has its own copy of system library images for each process), but it is practically unrealizable in Win9X (because in Win9X, if you can make changes to the system library image, then only in the address spaces of all processes ).
By itself, the wndmode.dll module is a strongly modified version of the d3dhook.dll library, where it is implemented:
- Full independence from the program D3D Windower
- Settings are loaded from the [WINDOWMODE] section of the wndmode.ini file
- Default settings are overwritten for Genie compatibility.
- Added Border parameter that turns on / off the frame around the window
- If the game resolution is equal to the system resolution, the frame is automatically removed.
Now this library is ready to infiltrate almost any DirectDraw game in order to make it window. So if someone modifies other games, it will come in handy.
What happened?
The modification work has been done a lot; here we have reviewed only a few changes, since the description of even small changes requires tremendous effort. If you wish, you can explore the rest of the modifications in IDA by downloading the finished modified file.
In general, the following results were achieved:
- Instead of ebueulax.dll (license display), config.dll is loaded (support for configuration files)
- Window operation mode (wndmode.dll module - modified d3dhook.dll)
- The code of the functions of working with the cursor is adapted to work in windowed mode (it solves many unpleasant problems caused by the fact that the game is not designed to work in the window)
- The game works correctly without prior installation (just copy the game files), the game does not need a CD
- Fixed the bug of the original game, when IP addresses were truncated in the list of recent games when playing by IP
- Changed the order and location of the buttons in the “Single Player” and “Editor” menus, fixed the error with the selection of the buttons in the “One Player” menu (an impressive section of the code was rewritten)
- The executable file can be run from any subdirectory, and not only from age2_x1 or the root (the beginning of the WinMain procedure is almost completely rewritten)
- The empires2.exe file is not checked (it can be deleted)
- When the MIDIMUSIC parameter is on, after folding, the melody starts anew (instruments are not reset)
All changes are stable, no failures are detected. In the windowed mode, the problem of displaying the system cursor simultaneously with the game after it leaves the area of ​​the game window and returns back remains unresolved. However, this is caused only by the fact that the game engine is not designed to run in the window and does not provide an option when the cursor leaves the game window. In the next version of the modification this will be fixed.
The logical conclusion of the development of this modification could be the introduction of game room support into the game with the creation of a corresponding game server, since the official game rooms of Age of Empires at Zone were closed a few years ago. All this is quite possible to implement, without having at the same time on hand no source codes.
Conclusion
If you are interested in trying what I did in action, you can download it from my website (
Age of Empires II: The Conquerors (Lite Edition) ).
Forgive me for what is written here about everything at once, and not in great detail. I tried to describe the general scheme. In the following articles, you can consider each stage of modification separately, give some advice, etc.