sidneys1.com Open in urlscan Pro
2607:ff18:80::5ba4  Public Scan

Submitted URL: https://t.co/2p4FaLYqrT
Effective URL: https://sidneys1.com/reverse-engineering/2023/03/16/reverse-engineering-a-win95-game-II.html
Submission: On March 19 via manual from US — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

Sidneys1.com
TagsCategoriesSeries


REVERSE ENGINEERING A WINDOWS 95 GAME


REVERSING (UNDOCUMENTED) SETTINGS

Mar 16, 2023 • Sidneys1 • programming • reverse-engineering • ghidra

This post has multiple parts:
 * Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I)
 * Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings
   (Part II)



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 disassembling and reverse engineering the binary to
identify an undocumented settings file format.

--------------------------------------------------------------------------------

As our reverse engineering tool of choice, we’ll be using the National Security
Agency’s Ghidra. This powerful tool allows us to disassemble the MATHINV.EX_
binary that is bundled on the disk. The first bit of information we get when
ingesting the binary in Ghidra is an “Import Results Summary” dialog, with
information about the binary itself. Here’s some excerpts:

Compiler:                     visualstudio:unknown
Debug Misc:                   Release/sspyth.exe
PDB File:                     sspyth.pdb
PE Property[FileDescription]: SSPYTH MFC Application


Interesting - the project in visual studio seems to have originally been called
“sspyth”, short for “S.S. Pythagoras”, the name of the protagonist’s ship within
the game. Let’s try and identify the entrypoint. This is a Windows MFC program,
which means the actual entrypoint is “runtime code” that will identify the main
MFC module within the program and initialize it. So instead of looking for this
enrtypoint (which Ghidra finds for us and names entry), we will try and find the
main MFC module initializer by searching for something we know happens early in
the program’s execution.

When first run, the game checks that DirectX, DirectPlay, and the game CD are
inserted. Using Ghidra’s Search → For Strings... tool we’ll find the “Please
insert CD” message.



Clicking the result will select the data in the CodeBrowser, and right-clicking
the automatically created symbol allows us to click References → Find references
to s_Please_insert... to find all references to this particular value within the
codebase. Doing so brings up one result at 0x0042cb86. Clicking the result takes
us to the relevant address. The disassembly shows us a function called
FUN_0042ca2f(CWinApp *param_1), which we’ll renamed to CWinAppEntrypoint. As
this function is not called anywhere else in the code, we can be fairly
confident that this is only called by runtime code that gets its address
programmatically.

Disassembly of CWinAppEntrypoint (click to expand).

void CWinAppEntrypoint(CWinApp *param_1) {
  int iVar1;
  undefined4 *puVar2;
  FILE *_File;
  undefined4 local_28c;
  BYTE local_21c [264];
  char local_114 [260];
  void *pvStack_10;
  undefined *puStack_c;
  undefined4 local_8;

  local_8 = 0xffffffff;
  puStack_c = &LAB_0042cc3d;
  pvStack_10 = ExceptionList;
  ExceptionList = &pvStack_10;
  CWinApp::Enable3dControlsStatic(param_1);
  CWinApp::LoadStdProfileSettings(param_1,4);
  FID_conflict:__mbscpy((char *)local_21c,&DAT_00495378);
  FUN_0042d603(s_Version_0049537c,local_21c);
  iVar1 = _strcmp(s_1.00-Rel_00495384,(char *)local_21c);
  if (iVar1 != 0) {
    AfxMessageBox(s_Game_not_installed,_run_the_setu_00495390,0x10,0);
    FUN_0042cc47();
    return;
  }
  FID_conflict:__mbscpy((char *)local_21c,&DAT_004953bc);
  FID_conflict:__mbscpy(local_114,&DAT_004953c0);
  GetPrivateProfileStringA
            (s_MazePath_004953dc,s_pakpath_004953d4,&DAT_004953d0,local_114,0x104,
             s_.\3d.ini_004953c4);
  puVar2 = (undefined4 *)_strlen(local_114);
  if (puVar2 == (undefined4 *)0x0) {
    FUN_0042d603(s_pakpath_004953e8,local_21c);
    FID_conflict:_strcat((char *)local_21c,s_game.pak_004953f0);
    while (_File = FID_conflict:__wfopen((char *)local_21c,&DAT_004953fc), _File == (FILE *)0x0)  {
      iVar1 = AfxMessageBox(s_Please_insert_the_Math_Invaders_C_00495400,0x11,0);
      if (iVar1 == 2) {
        FUN_0042cc47();
        return;
      }
    }
    puVar2 = (undefined4 *)_fclose(_File);
  }
  AfxSetAllocStop(0x53b0);
  local_8 = 0;
  if (puVar2 == (undefined4 *)0x0) {
    local_28c = 0;
  }
  else {
    local_28c = FUN_0042e186(puVar2);
  }
  local_8 = 0xffffffff;
  *(undefined4 *)(param_1 + 0x1c) = local_28c;
  FUN_0042e2e0(*(int **)(param_1 + 0x1c));
  FUN_0042cc47();
  return;
}


Alright! We can already see some useful things here. FUN_0042d603 gets a value
from the game’s Registry key, so that line just checks that the program is
installed. In fact, we can just rename FUN_0042d603 to GetValueFromRegistry.
Further down we see a GetPrivateProfileStringA call. I had to look this function
up as it’s somewhat esoteric, but it and the whole GetPrivateProfile* still
supported in today’s Win32 API!

> Retrieves a string from the specified section in an initialization file.
> GetPrivateProfileStringA function (winbase.h) - Win32 apps | Microsoft Learn

This description undersells this singular function call - when called the
GetPrivateProfileXxx family of APIs will open and read a given *.ini file, parse
it, and return the value in the specified [section] and key=. If the given file
does not exist, it will return the default value.

And, using Ghidra’s Symbol Tree, we can find all calls to the
GetPrivateProfileXxx APIs and the parameters used. Doing so provides us with
this list of parameters, expected to be found in .\3d.ini (relative to the CWD).
These are mostly loaded in another function called by CWinAppEntrypoint:
FUN_0042e2e0, which we can rename to LoadSettings:

[MazePath]
pakpath =       ; String
datapath =      ; String
diskpath =      ; String
lastfile =      ; String
room =          ; String
usepakfile = 0  ; Integer. In practice it is used as a boolean,
                ; where 0 is FALSE, and anything else is TRUE.

[Render]
fullscreen = 1  ; Integer
winsize = 10    ; Integer
textdetail = 10 ; Integer


Let’s see if this works. Let’s just create a C:\MathInvaders\3d.ini and as a
simple test, we’ll set [Render]→fullscreen to 0, and…



Well… Sort of. Ok, the game doesn’t actually run, and there’s a weird white
space at the bottom of the window. But we’ve proven it works! But what’s
intriguing to me is the [MazePath] section of the config… I wonder what we could
use those settings for. In particular, the fullscreen setting is loaded into a
global variable that we’ll call gFullscreen - this factors into to code
processing some very interesting strings about an “editor mode”… I wonder if we
can activate that?

if (gFullscreen == 0) {
	if (*(int *)(param_1 + 0x334) == 0) {
		_sprintf(local_104,s__Math_Invaders_-_NO_ACTIVE_LEVEL_0049585c);
	}
	else {
		__splitpath(&DAT_0049c7c8,local_1fc,local_1f4,local_12c,local_10c);
		_sprintf(local_104,s__Math_Invaders_-_'%s'_004957f4,local_12c);
		if (*(int *)(param_1 + 0x3714) == 0) {
			FID_conflict:_strcat(local_104,s__-_***_EDITOR_MODE_***_00495810);
		}
		iVar1 = CSplitterWnd::IsTracking((CSplitterWnd *)(param_1 + 0x2e0));
		if (iVar1 == 0) {
			FID_conflict:_strcat(local_104,s__-_Running..._0049584c);
		}
		else {
			FID_conflict:_strcat(local_104,s__-_Paused,_press_'p'_to_resume._00495828);
		}
	}
	CWnd::SetWindowTextA(param_1,local_104);
}


Next time!

--------------------------------------------------------------------------------

This post has multiple parts:
 * Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I)
 * Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings
   (Part II)

--------------------------------------------------------------------------------

Like what you read? Consider buying me a coffee.

Disqus Comments

Please enable JavaScript to view the comments powered by Disqus.


SIDNEYS1.COM

 * Contact
 * admin@sidneys1.com
 * sidneys1@proton.me
 * Matrix Chat
 * SimpleX Chat
 * Alternative Access
 * Sidneys1.com on Tor
 * Sidneys1.com on IPFS
 * Sidneys1.com on GitHub Pages

 * Sidneys1

 * Sidneys1
 * Sidneys1

A home for all my ramblings on subjects such as programming, cybersecurity,
photography, videography, video games, and whatever else I see fit.

Tor Snowflake

> Snowflake is a system that allows people from all over the world to access
> censored websites and applications. Similar to how VPNs assist users in
> getting around Internet censorship, Snowflake helps you avoid being noticed by
> Internet censors by making your Internet activity appear as though you're
> using the Internet for a regular video or voice call.

— The Tor Project