From b4185fc84349a647bc6edf8edc2b6b1302907428 Mon Sep 17 00:00:00 2001 From: Michael Ensslin Date: Fri, 29 Nov 2019 01:19:19 +0100 Subject: [PATCH] Add README --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..04a6965 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Age of Empires II scrollbug fix + +## What is the scrollbug? + +Sometimes when you tab out of the game, after tabbing back in the game will be stuck in a state where it is constantly scrolling in some direction. + +This makes the game unplayable and requires a restart of the game. This is especially annoying in multiplayer games. + +The scrollbug happens mostly when running the game in Wine, but has supposedly also been observed on Windows. + +## How do I reliably reproduce the scrollbug? + +- Use Wine. I don't know if these steps work on Windows. +- Start a game, e.g. a standard single-player random map game +- Hold the left-arrow key for 3 seconds +- Release the left-arrow key +- Wait for one second +- Tab out of the game +- Wait for one second +- Tab back into the game +- Congratulations; you now have a scroll bug + +## Why is there a scrollbug? + +Age of Empires II uses the `GetKeyboardState()` and `GetKeyState()` Win32 API functions to determine whether one of the scrolling keys is pressed. + +In the result of these functions, the key stats are reported as a single byte. + +According to the MSDN documentation, + +- Bit 7 of the byte says whether the key is currently pressed +- Bit 0 of the byte toggles each time the key is pressed +- All other bits are not documented + +Age of Empires II wants to check whether the key is currently pressed. To do this it checks whether the byte value is `>= 1` instead of checking if Bit 7 is set (`& 0x80`). + +This works as long as bits 1..6 are zero. + +Unfortunately, wine seems to use bit 2 to provide some other metadata, obviously related to tabbing out of the window, so it doesn't guarantee that bits 1..6 are always 0. + +Windows also seems to sometimes use some of the other bits. + +## Which versions of Age of Empires II have the scrollbug? + +Every single one, i.e.: + +- AoK +- AoC +- Forgotten Empires +- UserPatch +- AoE2:HD +- AoE2:DE + +## How does this fix work? + +This uses the Microsoft Research [Detours](https://github.com/microsoft/Detours) library to inject replacements of `GetKeyState()` and `GetKeyboardState()` into the running Age of Empires II binary. + +The replacement functions mask out bits 1..6 in the results. + +The fix consists of two binaries: + +- `age2_x1_fixed.exe` reads the configuration from `age2_x1_fixed.ini` and calls the `exe` that is specified in the `ini` file with the injector `dll` file that is specified in the `ini` file. +- `fix_scrollbug32.dll` injects fixed versions of `GetKeyState()` and `GetKeyboardState()` into any 32-bit Windows executable it is used with. + +## How do I build this fix? + +- Use Windows (please, somebody port this to winegcc or mingw or _something_ that works on Linux) +- Install Visual C++ Build Tools 2015 +- Get this source code (duh.) +- Get `detours.lib` and `detours.h`, e.g. by building the Detours library from source by invoking `nmake`, or by downloading the binaries from this Github repo. Place these two files in a subfolder `detours/` of this source code +- Call `build.bat` +- You will now have a whole bunch of garbage files, plus + - `fix_scrollbug32.dll` + - `dll_inject.exe` + - `dll_inject.ini` + +## How do I use this fix? + +`dll_inject.exe` can be renamed to whatever name. It is recommended to rename it to `{name_of_the_exe_you_want_to_fix}_fixed.exe`. + +When launched, `dll_inject.exe` will + +- open the `.ini` file with the same name +- in that `.ini` file in the section `[dll_inject]`, read the keys `exe_file` and `dll_file` +- launch the specified `exe` file and inject the specified `dll` file +- wait until the exe file has finished + +If you use the pre-compiled and pre-configured `age2_x1_fixed.exe`, `age2_x1_fixed.ini`, `fix_scrollbug32.dll` you can just place them in the `age2_x1` folder and launch `age2_x1_fixed.exe` instead of `age2_x1.exe`.