Reverse Engineering a Windows 95 Game
Editor Mode, and Conclusion
- Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I)
- Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings (Part II)
- Reverse Engineering a Windows 95 Game – Editor Mode, and Conclusion (Part III)
I recently rediscovered an obscure 1997 Simon & Schuster / Marshall Media edutainment game for Windows 95 that I played
as a kid: Math Invaders. In this part, we’ll investigate whether we can
enter an “editor mode”, hinted at within the strings
contained within the program. There’s even a
✨surprise ending✨ that I didn’t see coming!
Here’s where we left off, investigating the disassembly of a function that references a mysterious string:
*** EDITOR MODE ***
. Cleaning the disassembly up and commenting to be a bit to be more readable gives us:
// Because `param_1` from the disassembly (aka `this`) is passed to
// `CWnd::SetWindowTextA` at the end, this function probably belongs
// to a class inheriting `CWnd`.
class CGameWnd : public CWnd;
void CGameWnd::_updateWindowTitle() {
// Don't do anything if we're in fullscreen mode.
if (gFullscreen) return;
char buffer[256];
if (this->unknown_334 != 0) {
// If the variable at offset 0x334 is not 0/FALSE/NULL,
// we have a file loaded.
if (this->unknown_3714 == 0) {
// If the variable at offset 0x3714 is 0/FALSE/NULL,
// we're in "editor mode"... whatever that is!
// Append text indicating this to the buffer.
strcat(buffer, " - *** EDITOR MODE ***");
}
}
// Set the window title to the contents of the buffer.
this->SetWindowTextA(buffer);
}
Ok! So to activate editor mode, we need to 1) not be fullscreen, 2) have CGameWnd->unknown_334
be non-zero,
and 3) have CGameWnd->unknown_3714
be zero. Enabling fullscreen (via the 3d.ini file described in
part II) no longer seems to crash my game (that must have been a mistake of mine!). The game starts and plays in
fullscreen, and the title even updates between “Paused, Press ‘p’ to resume.” and “Running…” when we press
P!
But try as I might, no amount of reverse engineering is allowing me to toggle the
unknown_3714
variable. No code even exists (that I can find) to change it, except during initialization or
loading of levels, when it’s always set to TRUE
. So I have a theory: there was an editor mode, but its functionality
has been “removed” behind something like #ifdef EDITOR
.
Well then! Without a deus ex machina, it looks like we’ll never break into the “editor mode”. I reached out to the community to see if anyone knew more about the game itself, or had heard of a source code leak for this nearly 30 year old game. A few people were even so kind as to search Usenet for me. But nothing was turning up. A few months passed and I’d pretty much given up on ever finishing this part III post.
Vindication!
You could try downloading this SSK@YoUTHinkYOUrecLeVeRdONtYou?/SSPYTH.zip
Its password seems to be the file name.
Months after I’d given up I received an encrypted message, sent to the SimpleX address listed in my website footer. It
didn’t contain much more than a Freenet hash. I hurriedly installed a client and accessed the hash — the file
downloaded — the password worked — and inside? Beautiful, wonderful source
code, with “last modified” dates ranging from December 1995 to January 1997! And sure enough, there are several
instances of #ifdef EDITOR
that block the “Editor Mode” from being used, as I suspected! In fact, editor mode is
implemented as a completely separate static library, that is only linked into the executable when editor mode is to be
used. No wonder none of the relevant code can be found when reverse engineering the released binary!
Now that we have source code and can fully analyze the game in the ground truth, let’s poke around and see what we can find. The source code is laid out as follows:
- 📁 3DLIB - The core 3D engine.
- 📄 Various Assembly, and C++ source and header files.
- 📁 EditLib - Editor Mode functionality.
- 📄 Various C++ source and header files.
- 📁 RES - The game icon in BMP and ICO formats.
- 🔨 Various Microsoft Developer Studio (Visual C++) files.
- 📄 Various C++ source and header files.
- 📄 Some example save files.
- ⚙️ SSPYTH.EXE - A compiled binary.
- 🔧 3D.INI - An example configuration file.
Cheat Codes
The game has a few cheat codes! They can be activated by typing the code in during play, as it keeps a buffer of the last 20 key presses. All cheat codes begin with K and are committed with L:
Cheat Code | Message | Description |
---|---|---|
KEY1-4L | Add Key | Gives the specified key (1-4). |
KWN0-9L | Add Weapon | Gives the specified weapon (0-9) with max ammo. |
KVURL | Add Strength, Shield | Sets max strength, max shield, and dons the spacesuit. |
KHJ1-6L | None | Gives the specified item (1-6), however items 2, 3, and 6 are not allowed to be given via this cheat. The items 1-6 are: “health pack”, “light divider”, “time warper”, “drainer field”, “ultra drainer field,” and “reflection”. |
KYHRL | Add Everything | Adds all keys, weapons, and allowed items. |
KNNL | Problem Debug Mode ON Problem Debug Mode OFF |
Toggles a mode in which math problems’ expected answers are printed. |
K01-27L | None | Go to level 01-27. |
Building a 27 Year Old Game
Of course, the pièces de résistance of having access to the source code: editor mode! Let’s see what it takes to get it working. First, we’ll need to get the source code building. Thanks to the lovely project DOSBox-X, emulating older Windows operating systems is as simple as following a guide. I also sourced the following disk images: Windows 95 OSR21, Visual C++ 4.2, the DirectX 1.0 SDK, and the ActiveMovie SDK2 (this disk contains many other interesting installers as well). If you’re planning on building up such a virtual machine yourself, you’ll have to find your own product keys, sorry.
Breezing through the guide leaves us with a fully functional Windows 95 install, with Visual C++ 4.2 (which includes the
Microsoft Developer Studio IDE - which we’ll need), the DirectX SDK, and the ActiveMovie SDK. Our source code contains
an .mdp
file, which is a Developer Studio project, so let’s open it and build the default project with
Ctrl+B!
--------------------Configuration: 3dlib - Win32 Debug--------------------
Compiling...
decomp.cpp
fatal error C1083: Cannot open source file: 'C:\Sspyth\3dlib\decomp.cpp': No such file or directory
Error executing cl.exe.
Sspyth.exe - 1 error(s), 4 warning(s)
Alright, some errors, but nothing we can’t solve. The first thing we notice is we’re missing a file 3DLIB\DECOMP.CPP. Poking around, we find there’s a file named 3DLIB\aviDECOMP.CPP. A simple file rename gets us past this error. Re-running the build gives us another error, now in the linking process. It can’t seem to find the 3DLib and ActiveMovie libraries:
e_frame.cpp
.\.\ztest.hpp(9) : fatal error C1083: Cannot open include file: 'strmif.h': No such file or directory
That’s as easy as adding the full path to the ActiveMovie SDK’s include directory to the compiler path, and adding the
lib\StrmBase.lib file and the 3DLib output file to the linker properties. Our project now builds, and we
can verify that the game runs! Well, it tells us Game not installed, run the setup program., but commenting
out a few lines in CSspythApp::InitInstance()
fixes that:
// Check for game installed
// char buffer[260];
// strcpy(buffer, "");
// GetRegString("Version", buffer, 20);
// if (strcmp(SSP_VERSION, buffer)) {
// AfxMessageBox("Game not installed, run the setup program.", MB_OK | MB_ICONSTOP);
// return FALSE;
// }
Installing the game and pointing the pakpath
setting in 3D.INI to the install directory allows the game
to load assets and run!
Editor Mode
So what do we need to enable editor mode? Let’s create a new build configuration just for this use case. We already know
we have to add /D EDITOR
to the compiler settings, and doing so builds… and fails. Why now?
sspyth.obj : error LNK2001: unresolved external symbol "public: void __thiscall CEditFrame::EditDoor(void *)"(?EditDoor@CEditFrame@@QAEXPAX@Z)
sspyth__/Sspyth.exe : fatal error LNK1120: 7 unresolved externals
Error executing link.exe.
Sspyth.exe - 8 error(s), 0 warning(s)
Ah, EditLib! Let’s add that to our linker options as well and try again. This time the build succeeds and
we can run the game as before. Now, how do we activate it? We know so far that: 1) the game has to be in windowed mode,
so we set fullscreen=0
in 3D.INI; and 2) there are some mystery values in the main window class that must be
set just-so to be “in editor mode”. Thankfully now we can look at actual code! It turns out our
CGameWnd::_updateWindowTitle()
decompilation above is actually named CMainFrame::ShowPauseState()
:
void CMainFrame::ShowPauseState(void)
{
if (g_FullScreen)
return;
char buffer[256];
if (m_game.m_pscene != NULL) {
char drive[5],directory[200], name[30], extension[5];
_splitpath(FileName, drive, directory, name, extension);
sprintf(buffer, " S.S. Pythagoras - '%s' ", name);
if (!m_game.m_GameMode)
strcat(buffer, " - *** EDITOR MODE ***");
if (m_game.Paused())
strcat(buffer, " - Paused, press 'p' to resume.");
else
strcat(buffer, " - Running...");
} else {
sprintf(buffer, " S.S. Pythagoras - NO ACTIVE LEVEL");
}
SetWindowText(buffer);
}
void CGame::Update(CKeyboard& keys) {
if (keys.KeyDownWasUp('G')) {
m_GameMode = !m_GameMode;
GetMainFrame()->ShowPauseState();
}
}
Awesome, my guesses were really close. unknown_334
is m_game.m_pscene
, and unknown_3714
is m_game.m_GameMode
.
Let’s see if I’m right, and m_GameMode
is changed with a #ifdef EDITOR
-surrounded key input. m_GameMode
is only
changed in two places in the code, both in GAME.CPP. The first is during initialization, where it is set to
TRUE
. The second place is further down the file, in CGame::Update(CKeyboard&)
:
Ok, this isn’t surrounded by #ifdef EDITOR
… I suspect again that the “final” version of the game saw a few code
changes that weren’t included in the copy of the code I have. But a little digging shows that m_GameMode
alone has no
real effect, because just a little further down is the code to actually perform editor mode:
////////////////////////////////////////// EDITOR
#ifdef EDITOR
if (keys.KeyDownWasUp('I')) {
GetApp()->ShowEditFrame();
GetApp()->m_editframe->OnInsertButton();
}
#endif
So, we need to press G to switch the game mode, and then I (I suspect for Insert or Inspect) will show the editor controls! Once editor mode is active you can also press E to Edit door and entity instances, or T to edit the focused object’s Type. Let’s give it a go:
Next Steps
If there’s ever going to be a follow-up to this three-part post, there’s a few things I’d like to try:
- Add modern WASD keyboard controls — the current control scheme is A/Z for forward/back, and Shift/X for left/right.
- Get the game running on modern versions of Windows — I’d like to do this by getting it running “well” on each newer OS starting with Windows XP and moving forward.
- Properly handle texture files when extracting — PAKrat (from part I) doesn’t extract the PCX textures correctly.
-
The 3rd release of Windows 95, “OEM Release 2”, added support for FAT32 drives. ↩
-
Alternative source in disk 4 of Storm #1 - Internet Archive. ↩
- Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I)
- Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings (Part II)
- Reverse Engineering a Windows 95 Game – Editor Mode, and Conclusion (Part III)
Webmentions ♥️0
No webmentions were found.
Webmention support enabled by webmention.io and Webmentions for Jekyll.
-
{% for webmention in webmentions %}
-
{{ webmention.content }}
{% endfor %}
No bookmarks were found.
{% endif %}-
{% for webmention in webmentions %}
- {% endfor %}
No likes were found.
{% endif %}-
{% for webmention in webmentions %}
-
{{ webmention.content }}
{% endfor %}
No links were found.
{% endif %}-
{% for webmention in webmentions %}
- {{ webmention.title }} {% endfor %}
No posts were found.
{% endif %}-
{% for webmention in webmentions %}
-
{{ webmention.content }}
{% endfor %}
No replies were found.
{% endif %}-
{% for webmention in webmentions %}
- {% endfor %}
No reposts were found.
{% endif %}-
{% for webmention in webmentions %}
- {% endfor %}
No RSVPs were found.
{% endif %}-
{% for webmention in webmentions %}
-
{% if webmention.author %} {% endif %}{% if webmention.content %} {{ webmention.content }} {% else %} {{ webmention.title }} {% endif %}
{% endfor %}
No webmentions were found.
{% endif %}
Comments from Mastodon
You can leave a comment by replying to this Mastodon post from any ActivityPub-capable social network that can exchange replies with Mastodon.
Comment support inspired by Cassidy James (@cassidy@blaede.family) and some code borrowed from Julian Fietkau (@julian@fietkau.social).