Add resolution handling to Logical Stones
Problem
The heap corruption is gone, but the game is still unusable because the UI was not designed using responsive technologies and it appears broken using today’s common aspect ratios. Logical Stones lacks resolution handling - it goes full screen and uses the current resolution of your display, which just looks weird at the widespread 1920x1080 resolution:
Investigation
First, I looked for the location in the code where the main game window is created:
{
[...]
WndClass.style = 32;
WndClass.lpfnWndProc = sub_41599C;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIconA(0, (LPCSTR)0x7F00);
WndClass.hCursor = LoadCursorA(0, (LPCSTR)0x7F00);
WndClass.hbrBackground = (HBRUSH)GetStockObject(4);
WndClass.lpszMenuName = 0;
WndClass.lpszClassName = "LGStones";
RegisterClassA(&WndClass);
dword_44797C = GetSystemMetrics(0);
dword_447980 = GetSystemMetrics(1);
v4 = CreateWindowExA(
0,
"LgStones",
"Logical Stones Game",
0x90880000,
0,
0,
dword_44797C,
dword_447980,
0,
0,
hInstance,
0);
[...]
}
As you can see dword_44797C
and dword_447980
are the global variables which contain the width and height of your current display obtained by calling GetSystemMetrics
with parameters SM_CXSCREEN
and SM_CYSCREEN
respectively.
In order to fix the resolution of the game, we need to take care of the above-mentioned variables and constants.
Solution
Instead of setting the values of dword_44797C
and dword_447980
based on the values reported by GetSystemMetrics
a custom value can be used by patching the game binary. I used OllyDbg to apply the changes.
Binary patching
[...]
PUSH 0
CALL <JMP.&USER32.GetSystemMetrics>
MOV DWORD PTR DS:[44797C],EAX
PUSH 1
CALL <JMP.&USER32.GetSystemMetrics>
MOV DWORD PTR DS:[447980],EAX
ADD ESP,-4
PUSH 0
PUSH EBX
PUSH 0
PUSH 0
PUSH EAX
PUSH DWORD PTR DS:[44797C]
PUSH 0
PUSH 0
PUSH 90880000
PUSH LgStones.00415843 ; ASCII "Logical Stones Game"
PUSH LgStones.00415857 ; ASCII "LgStones"
PUSH 0
CALL <JMP.&USER32.CreateWindowExA>
[...]
The CreateWindowExA
function expects the width of the window to be located at 0x0044797C
and the height of the window to be in the EAX
register.
The task is to initialize dword_44797C
, dword_447980
and EAX
with hard coded constant values which can be easily modified later. The default resolution is going to be 1024x768 which used to be a very common resolution with 4:3 aspect ratio back then.
The hexadecimal value of 1024 is moved to EAX
then copied to dword_44797C
. The same applies to the height - the hexadecimal value of 768 is moved to EAX
then copied to dword_447980
. Now the global variables are initialized and EAX
contains the height of the window as it is expected by CreateWindowExA
.
MOV EAX,400
MOV DWORD PTR DS:[44797C],EAX
MOV EAX,300
MOV DWORD PTR DS:[447980],EAX
NOP
NOP
NOP
NOP
ADD ESP,-4
PUSH 0
PUSH EBX
PUSH 0
PUSH 0
PUSH EAX
PUSH DWORD PTR DS:[44797C]
PUSH 0
PUSH 0
PUSH 90880000
PUSH LgStones.00415843 ; ASCII "Logical Stones Game"
PUSH LgStones.00415857 ; ASCII "LgStones"
PUSH 0
CALL <JMP.&USER32.CreateWindowExA>
Configurable resolution
I have created a small utility which can directly overwrite our hard-coded 1024x768 resolution. When you start the tool you will be asked about the desired width of the game window. The height is automatically calculated to match the 4:3 aspect ratio required by the UI.
#include <iostream>
#include <fstream>
#include <string>
static const size_t WINDOW_WIDTH_OFFSET = 0x00014CC7;
static const size_t WINDOW_HEIGHT_OFFSET = 0x00014CD1;
template <typename T>
void patch(std::ofstream& executable, size_t offset, const T& value)
{
executable.seekp(offset, std::ofstream::beg);
executable.write(reinterpret_cast<const char*>(&value), sizeof(T));
executable.flush();
}
int main() {
std::ofstream executable("LgStones.exe", std::ofstream::in | std::ofstream::out | std::ofstream::binary);
if (executable) {
std::cout << "Width of the game window: ";
std::string width_str;
std::getline(std::cin, width_str);
uint32_t width = std::stoi(width_str);
uint32_t height = uint32_t(width / 4.0f * 3.0f);
std::cout << "Height of the game window: " << height << std::endl;
patch<uint32_t>(executable, WINDOW_WIDTH_OFFSET, width);
patch<uint32_t>(executable, WINDOW_HEIGHT_OFFSET, height);
std::cout << "Done!" << std::endl;
}
else {
std::cerr << "Couldn't find LgStones.exe!" << std::endl;
}
system("pause");
return 0;
}
Downloads
You may download and play the game, including all the fixes for free.
The source code for LgStonesAllocator
and LgStonesResolutionChanger
are also available.
Are you stuck on a planet? Let me know in the comments!