FoxAhead
Warlord
- Joined
- Sep 7, 2017
- Messages
- 198
Hello!
I know that more than 10 years have passed since the Mastermind's patch was presented to run the game on the 64-bit systems. Nevertheless, I want to present you yet another patch.
I was playing Civilization 2 MGE many years on my windows 7 64-bit. And always confusing about input fields not reacting on keyboard strokes like Enter, TAB or Esc. It was little annoying to press 'B' to build city and after that move mouse to hunt OK button rather than just press Enter. I remembered that sometime in the past everything worked fine. Suddenly I took debugger and dive deep in assembly code for a week. Finally I realized that problem was in Mastermind's patch. Yes it allows to run game on 64-bit but the normal key-processing for input fields was broken. And after a while I managed to make my own patch that works without any side effects.
This is Civilization II 64-bit Editbox Patcher.
It eliminates game and editor crashes on 64-bit systems when the game tries to display edit controls (input fields) for entering text (like city name, emperor name, world sizes etc.).
Unlike the existing well known patch by Mastermind it will retain the full functionality of edit controls including the response to the Enter, TAB or Escape keys.
So, the establishing city is now just 'B', 'Enter'. And customizing world size is like '100', 'TAB', '100', 'Enter'. No more mouse clicking.
Supported game versions are:
https://github.com/FoxAhead/Civilization-II-64-bit-Editbox-Patcher/releases
1) Run Civ2x64EditboxPatcher.exe.
2) Click 'Browse...' and select Civ2.exe (or Civ2Map.exe for MapEditor).
3) Some information will be displayed about version and possibility of patching.
4) Click 'Patch!'. Click 'Yes'.
Like Mastermind mentioned the problem was in callback procedure for Editbox control.
The pseudocode for place with error looks like this:
The problem was in hard-coded 0xA which is offset for parameter in extra window memory. Here we are getting two parameters: v17 - pointer to some Style data, and lpPrevWndFunc - pointer to parent callback. The second one is acquired correctly using offset in nIndex. But offset for the first one is hard-coded = 0xA. In 32-bit OS size of extra window memory for 'Edit' window class equals 6 bytes. So plus our two integer (4 bytes) parameters gives total: 6+4*2 = 14 bytes. Call for GetClassLongA(hWnd, GCL_CBWNDEXTRA) returns us this total bytes of extra memory used. To get offset for lpPrevWndFunc you have to subtract 8 from total (14) (which is done correctly). And to get offset for v17 you have to subtract 4 from total (14) which gives you exactly 10 ( = 0xA in hexadecimal). And that works well until 64-bit comes on scene where size of extra window memory for 'Edit' window class equals 8 bytes. So with our two additional integers total size equals 16. And with offset for lpPrevWndFunc being still calculated correctly (16-8) the offset for v17 must be 16 - 4 = 12 (0xC) and not 10 (0xA).
The first simplest solution is to change this single byte from 0xA to 0xC and all will be working fine until... Well, for example you decide to copy this patched file back to 32-bit system (to play on vacation on old notebook with Windows 98 or XP x32). And this patched file will not work on 32-bit OS. So there is no backward compatibility.
The correct way to patch is to achieve code something like this:
Here two first steps are swapped. Firstly we have to get total size of extra memory. And then we can calculate each offset correctly.
And then comes sweet part of juggling assembly code to achieve desired in less opcodes changed. And you have to fit in fixed frame because changing size of EXE is unacceptable. After 6th iteration I managed to make patch changing only 10 bytes.
Before:
After:
P.S.
After almost completing working on patch I found that someone named iunnamed already made another patch explaining exactly the same reason of crashes:
https://www.old-games.ru/forum/threads/sid-meiers-civilization-2.7950/page-9#post-1013691
I inspected it. That patch works good - input fields work correctly. The difference is just it for MGE 5.4.0f game only. Nor for Map Editor nor for ToT nor for other variations of MGE-like family. Also it lacks of GUI. And also it change 17 bytes of EXE.
I made my patcher on Delphi 7 because I wanted:
1) to make it possible run on old systems without external dependencies like .NET. (Tested on Windows 98 SE.);
2) to make GUI;
3) to develop it fast;
Downside of this is size of 400kB. But who cares nowadays about 400kB?
Preamble
I know that more than 10 years have passed since the Mastermind's patch was presented to run the game on the 64-bit systems. Nevertheless, I want to present you yet another patch.
I was playing Civilization 2 MGE many years on my windows 7 64-bit. And always confusing about input fields not reacting on keyboard strokes like Enter, TAB or Esc. It was little annoying to press 'B' to build city and after that move mouse to hunt OK button rather than just press Enter. I remembered that sometime in the past everything worked fine. Suddenly I took debugger and dive deep in assembly code for a week. Finally I realized that problem was in Mastermind's patch. Yes it allows to run game on 64-bit but the normal key-processing for input fields was broken. And after a while I managed to make my own patch that works without any side effects.
Description
This is Civilization II 64-bit Editbox Patcher.
It eliminates game and editor crashes on 64-bit systems when the game tries to display edit controls (input fields) for entering text (like city name, emperor name, world sizes etc.).
Unlike the existing well known patch by Mastermind it will retain the full functionality of edit controls including the response to the Enter, TAB or Escape keys.
So, the establishing city is now just 'B', 'Enter'. And customizing world size is like '100', 'TAB', '100', 'Enter'. No more mouse clicking.
Supported game versions are:
- Civilization II Multiplayer Gold Edition
- Civilization II: Test of Time
Download and source code
https://github.com/FoxAhead/Civilization-II-64-bit-Editbox-Patcher/releases
How to use it
1) Run Civ2x64EditboxPatcher.exe.
2) Click 'Browse...' and select Civ2.exe (or Civ2Map.exe for MapEditor).
3) Some information will be displayed about version and possibility of patching.
4) Click 'Patch!'. Click 'Yes'.
Technical details
Spoiler :
Like Mastermind mentioned the problem was in callback procedure for Editbox control.
The pseudocode for place with error looks like this:
Code:
v17 = GetWindowLongA(hWnd, 0xA);
nIndex = GetClassLongA(hWnd, GCL_CBWNDEXTRA) - 8;
lpPrevWndFunc = GetWindowLongA(hWnd, nIndex);
The problem was in hard-coded 0xA which is offset for parameter in extra window memory. Here we are getting two parameters: v17 - pointer to some Style data, and lpPrevWndFunc - pointer to parent callback. The second one is acquired correctly using offset in nIndex. But offset for the first one is hard-coded = 0xA. In 32-bit OS size of extra window memory for 'Edit' window class equals 6 bytes. So plus our two integer (4 bytes) parameters gives total: 6+4*2 = 14 bytes. Call for GetClassLongA(hWnd, GCL_CBWNDEXTRA) returns us this total bytes of extra memory used. To get offset for lpPrevWndFunc you have to subtract 8 from total (14) (which is done correctly). And to get offset for v17 you have to subtract 4 from total (14) which gives you exactly 10 ( = 0xA in hexadecimal). And that works well until 64-bit comes on scene where size of extra window memory for 'Edit' window class equals 8 bytes. So with our two additional integers total size equals 16. And with offset for lpPrevWndFunc being still calculated correctly (16-8) the offset for v17 must be 16 - 4 = 12 (0xC) and not 10 (0xA).
The first simplest solution is to change this single byte from 0xA to 0xC and all will be working fine until... Well, for example you decide to copy this patched file back to 32-bit system (to play on vacation on old notebook with Windows 98 or XP x32). And this patched file will not work on 32-bit OS. So there is no backward compatibility.
The correct way to patch is to achieve code something like this:
Code:
nIndex = GetClassLongA(hWnd, GCL_CBWNDEXTRA);
v17 = GetWindowLongA(hWnd, nIndex - 4);
lpPrevWndFunc = GetWindowLongA(hWnd, nIndex - 8);
And then comes sweet part of juggling assembly code to achieve desired in less opcodes changed. And you have to fit in fixed frame because changing size of EXE is unacceptable. After 6th iteration I managed to make patch changing only 10 bytes.
Before:
Code:
005D2A01 55 push ebp
005D2A02 8B EC mov ebp, esp
005D2A04 83 EC 1C sub esp, 1Ch
005D2A07 53 push ebx
005D2A08 56 push esi
005D2A09 57 push edi
005D2A0A 6A 0A push 0Ah ; nIndex
005D2A0C 8B 45 08 mov eax, [ebp+hWnd]
005D2A0F 50 push eax ; hWnd
005D2A10 FF 15 2C 7E 6E 00 call ds:GetWindowLongA
005D2A16 89 45 F8 mov [ebp+var_8], eax
005D2A19 6A EE push GCL_CBWNDEXTRA ; nIndex
005D2A1B 8B 45 08 mov eax, [ebp+hWnd]
005D2A1E 50 push eax ; hWnd
005D2A1F FF 15 9C 7E 6E 00 call ds:GetClassLongA
005D2A25 83 E8 08 sub eax, 8
005D2A28 89 45 F4 mov [ebp+nIndex], eax
005D2A2B 8B 45 F4 mov eax, [ebp+nIndex]
005D2A2E 50 push eax ; nIndex
005D2A2F 8B 45 08 mov eax, [ebp+hWnd]
005D2A32 50 push eax ; hWnd
005D2A33 FF 15 2C 7E 6E 00 call ds:GetWindowLongA
005D2A39 89 45 EC mov [ebp+lpPrevWndFunc], eax
005D2A3C 8B 45 0C mov eax, [ebp+Msg]
005D2A3F 89 45 E4 mov [ebp+var_1C], eax
005D2A42 E9 4D 02 00 00 jmp loc_5D2C94
Code:
005D2A01 55 push ebp
005D2A02 8B EC mov ebp, esp
005D2A04 83 EC 1C sub esp, 1Ch
005D2A07 53 push ebx
005D2A08 56 push esi
005D2A09 57 push edi
005D2A0A 6A EE push GCL_CBWNDEXTRA ; nIndex
005D2A0C 8B 45 08 mov eax, [ebp+hWnd]
005D2A0F 50 push eax ; hWnd
005D2A10 FF 15 9C 7E 6E 00 call ds:GetClassLongA
005D2A16 89 C3 mov ebx, eax
005D2A18 2C 04 sub al, 4
005D2A1A 50 push eax ; nIndex
005D2A1B 8B 45 08 mov eax, [ebp+hWnd]
005D2A1E 50 push eax ; hWnd
005D2A1F FF 15 2C 7E 6E 00 call ds:GetWindowLongA
005D2A25 83 EB 08 sub ebx, 8
005D2A28 89 45 F8 mov [ebp+var_8], eax
005D2A2B 8B 45 F4 mov eax, [ebp+var_C]
005D2A2E 53 push ebx ; nIndex
005D2A2F 8B 45 08 mov eax, [ebp+hWnd]
005D2A32 50 push eax ; hWnd
005D2A33 FF 15 2C 7E 6E 00 call ds:GetWindowLongA
005D2A39 89 45 EC mov [ebp+lpPrevWndFunc], eax
005D2A3C 8B 45 0C mov eax, [ebp+Msg]
005D2A3F 89 45 E4 mov [ebp+var_1C], eax
005D2A42 E9 4D 02 00 00 jmp loc_5D2C94
After almost completing working on patch I found that someone named iunnamed already made another patch explaining exactly the same reason of crashes:
https://www.old-games.ru/forum/threads/sid-meiers-civilization-2.7950/page-9#post-1013691
I inspected it. That patch works good - input fields work correctly. The difference is just it for MGE 5.4.0f game only. Nor for Map Editor nor for ToT nor for other variations of MGE-like family. Also it lacks of GUI. And also it change 17 bytes of EXE.
I made my patcher on Delphi 7 because I wanted:
1) to make it possible run on old systems without external dependencies like .NET. (Tested on Windows 98 SE.);
2) to make GUI;
3) to develop it fast;
Downside of this is size of 400kB. But who cares nowadays about 400kB?
Last edited: