Reverse Engineering

Reverse Engineering Pandora’s Box (1)

One game I have quite fond memories of is Microsofts Pandora’s Box (1999), a puzzle game for PC created by the designer of Tetris. The game consists of a total of 361 puzzles of 10 different types, spread over 7 stages with increasing difficulty. Of course, being designed for Windows 95 and 98, the game shows its age: It is hardcoded to use a full-screen resolution of 640×480, writes its save files to the ‘Program Files’ directory, and shows a black screen when the built-in videos are playing, leaving you with audio only.

Wouldn’t it be cool to bring the game to 2023, getting it to run flawlessly under modern Windows (and possibly other operating systems), and making it possible to add more puzzles, while gathering some reverse engineering experience on the way?

The first goal I set is being able to modify the first Focus Point puzzle, specifically, changing the image used by that puzzle.

Setting up the reverse engineering environment

For my reverse engineering efforts I installed Windows 10 (64-bit, as I already knew the games runs fine under x64 Windows) on a virtual machine. Other tools I will be using in this post are:

Installing the game

Let’s get started by installing the game. I used the CD that I still have from back in the day, but the ISO is floating around the web on various abandonware websites if you want to follow along. You also want to grab the 1.0a patch (link to, as the original has long been taken down), as the game will not run on Pentium III and up without it.

Installing the game on a VM

I chose to do the ‘Full’ installation, so all game data is copied to the C: drive. Afterwards, install the 1.0a patch linked earlier.

By changing a single registry key, we can trick the game so that the CD is not required for launching, which is particularly useful when you used a physical disk to install the game. Launch the registry editor, navigate to

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft Games\Pandora's Box\1.0

on 32-bit Windows, or

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft Games\Pandora's Box\1.0

on 64-bit Windows. Change the value of the key ‘CDROMDIR’ to the path where the game was installed, e.g. ‘C:\Program Files (x86)\Microsoft Games\Pandora’s Box’. The game may now be launched without the CD in the (virtual) drive.

First steps

Let’s start by taking a look at the game files we installed:

Installed files

If you have played the game, you can map most directories to the various puzzle types by looking at the directory name:

DirectoryPuzzle type
3DSKINOuter Layer
BGLLens Bender
FNFFind and Fill
FPNFocus Point
IMGImage Hole
JESJesse’s Strips

All the above directories initially contain only sound effects in the WAV file format. After playing a few puzzles, ‘.rim’ files started appearing in some of these directories.

We then have another set of directories containing mostly WAV files:

MUSICMusic played on the map and puzzle screens
SCREENSMenu sound effects

The ‘pandora.htm’ and ‘pandora.mgd’ files in the ‘MUSIC’ directory seem to describe some sort of map or graph. Opening the HTM file in Firefox results in the message ‘(Browser does not support controls.)’. Out of curiosity I tried opening the HTM file under Windows 98 SE/Internet Explorer 5, but it showed the same message.

Finally, we have the ‘HELP’ directory which contains two files: ‘message.m20’ and ‘pandora.m20’. Inspecting them using the ‘string’ command in WSL reveals that they contain text sequences used in the game, like the instructions shown during tutorials.

Jumping back to the root directory, we have the following files:

besttime.datPresumably contains high scores per puzzle
bkant.ttfBook Antiqua font, used in the game
clcd16.dllSafeDisc (copy protection) library
clcd32.dllSafeDisc (copy protection) library
credits.datContains the list of people who developed the game
dplayerx.dllSafeDisc (copy protection) library
EBUSetup.sem? (Empty file)
EULA.RTFEnd User License Agreement
int01.avi to int07.aviTrickster capture videos, played when a chapter is completed
loadbar.pngShown next to the splash screen when starting the game
open.aviOpening movie, played when first starting the game
pandora.eulExact copy of pandora.exe. Interestingly, on the CD this file matches the 1.0a executable, while the 1.0a patch is not even included on my disc!
pandora.exeMain game executable, version 1.0a
pandora.rofGame data (226 MB)
puzzle.datGame data (70 kB)
r<player name>.blySave data, only created after starting the game. Note: if the game is not launched as administrator, these files will instead be written to ‘%LOCALAPPDATA%/VirtualStore/Program Files (x86)/Microsoft Games/Pandora’s Box/’.
r<player name>.plySave data, see above
Readme.rtfGame manual
screens.rofAnother data file (68 MB)
splash.pngGame splash screen
tricks.bmp? (Not a bitmap image despite the file extension)

I determined the above information by inspecting the files with the ‘file’ and ‘strings’ commands under WSL. and by looking at their filenames.

The main files of interest are of course the large .rof files, as we haven’t seen any of the in-game asssets yet. Presumably these assets are combined into the .rof files.

Let’s take a look at the ‘pandora.rof’ file through ‘strings’:

strings pandora.rof | less

In the output we can immediately spot a few level and puzzle names, as well as a lot of PNG files:


Let’s take a look through a hex editor:

Hex dump – first listing

At 0x184..0x23A we immediately spot something that looks like a directory listing. A bit further in the file, at 0x436, we spot another listing, this time of PNG files:

Hex dump – second listing

Let’s write down the following information for now:

  • The first listing starts at 0x184 and is 0xB6 bytes long (including the final null terminator).
  • The first listing contains 19 directory or file names (separated by null bytes).
  • The second listing starts at 0x436 and is 0x14A bytes long.
  • The second listing contains 25 directory or file names.

Let’s look at the start of the file next. As the game is designed to run on 32-bit Windows systems, we can assume all integers are 32 bits/8 bytes long, and the byte order is little endian.

The first integer (13 00 00 00 / 0x13 hexadecimal / 19 decimal) seems to denote the length of the directory index. We validate our hypothesis against the ‘screens.rof’ file: it starts with the sequence ’01 00 00 00′, and indeed contains only one directory named ‘SCREENS’.

Immediately next, at 0x4, we find the value 0xB6. This matches the length of our directory listing.

The next five integers seem to describe the first entry from the listing:

  • 0x23A: this value matches a position immediately after the listing. This is probably the offset where the referred directory or file starts.
  • 0x0 and 0x1: we don’t yet know what these values mean. Let’s ignore them for now.
  • 0x7: this value matches the length of the file name, including the null terminator at the end.
  • 0x0: this value is the offset of the file name relative to the start of the name table.

Again, we can check these hypotheses against either the ‘screens.rof’ file, or more conveniently, the next entry in the structure:

  • 0x580: this again is a offset right after the second listing.
  • 0x0 and 0x1: we still don’t know the meaning of those values, but they are the same as for the first entry.
  • 0x4: matches the length of the name (‘BGL’, including a null terminator).
  • 0x7: indeed matches the offset of the name relative to the name table offset.

We can conclude that the metadata entries thus consist of 5 integers, and as such are 20 bytes long each.

Now that we can parse directory listings, let’s look at the actual files embedded in the .rof file. In the second listing we see that there are PNG files present in. Wikipedia tells us all PNG files start with the sequence ’89 50 4E 47 0D 0A 1A 0A’. Let’s search the file for this sequence:

Hex dump – PNG signature

We get a first match at offset 0x526F2. It seems the contents .rof files are not compressed or encrypted in any way. Furthermore, notice the file starts immediately after the listing, so we know there is no metadata behind the listings.

Because the second file in the listing is also a PNG file, we can search for the signature again. The second signature occurs at 0x82C28. Assuming there is no metadata between the files, we can dump the region 0x526F2…0x82C27 and the result should be a valid PNG image:

Extracted asset (PNG)

And indeed, the range contains an asset from the game. We can also use the offsets (0x526F2 and 0x82C27) and size (subtracting the offset leaves 0x30536) to look for the metadata describing it:

Hex dump – file metadata

The offset is found in between the first and second listing; the size is found immediately thereafter. We can also spot the offset of the second file (0x82C28) at 0x256..0x259, so we know the file entries too are 20 bytes in size.

The two integers between the directory listing and the offset of the first file contain values 0x19 and 0x14A, which again correspond to the number of files and the size of the name table.

Using all the above information, we can write a simple parser in Python that can print the directories and the files in them:

Resulting in the following output:


The files in the puzzle directories (3DSKIN, FNF, FPN etc.) seem to be UI elements only. Looking at the level data, it is a reasonable assumption that the ‘files’ below them are actually be subdirectories.

LEVEL.0 probably contains the tutorial puzzles as there is one puzzle of each type, and LEVEL.01 to LEVEL.07 contain a directory per city (which presumably contain the puzzles for that city) and one to three standalone puzzles for the ‘boss fight’. Interestingly, there is a directory named ‘LEVEL.08.SCALAWAG’, which possibly contains the 40 additional puzzles from the ‘Puzzle Game of the Year Edition‘ re-release. As far as I know however my version of the game is not the Puzzle Game of the Year Edition (it is the European release by Xplosiv), so this raises the question whether the extra puzzles have always been included.

It seems like both directories and files are described by the same 20 bytes of metadata. Let’s look for a directory/file flag. Recall that there is one field that we do not know the meaning of yet. Let’s log the size and the unknown field for every entry:

LEVEL.0 / 0 / 1
  PUZZLE.1.IMH / 0 / 1
  PUZZLE.10.BGL / 0 / 1
  PUZZLE.11.TAN / 0 / 1
  PUZZLE.12.FNF / 0 / 1
  PUZZLE.2.ZOO / 0 / 1
  PUZZLE.3.RTT / 0 / 1
  PUZZLE.4.FSH / 0 / 1
  PUZZLE.5.SKI / 0 / 1
  PUZZLE.6.CHIS / 0 / 1
  PUZZLE.7.JESS / 0 / 1
  PUZZLE.8.RUB / 0 / 1
  PUZZLE.9.SLICE / 0 / 1
LEVEL.01.MAUI / 0 / 1
  CITY.06.CAIRO / 0 / 1
  CITY.10.HONOLULU / 0 / 1
  CITY.16.MADRID / 0 / 1
  CITY.21.NEW YORK / 0 / 1
  CITY.22.PARIS / 0 / 1
  PUZZLE.1.OVL / 0 / 1

Clearly, the unknown field denotes the type of the entry: 0 means file and 1 means directory. Furthermore, all directories have a size of 0, so we can ignore this field when processing a directory.

The final directory metadata structure thus looks as follows:

Let’s rework our script so it can parse an arbitrary mix of directories and files:

And indeed there are a lot of nested directories below the ‘LEVEL’ folders:


In the next post, we will take a look at the following tasks:

  • Extracting the files we just listed;
  • Repacking the .rof file after making changes;
  • Examining the .ZOO file that contains a Focus Point puzzle, and modifying the image embedded in it.

Dealing with the Proxmox Subscription Notice

Over the past few weeks, I have been playing around with Proxmox in a VM. It seems to be an upgrade from my current Fedora + Cockpit based homelab, as the ‘cockpit-machines’ module has some quirks and stability issues. One thing that annoys me however is the subscription notice that is shown when logging in to your Proxmox instance:

Of course, upkeep of open source software is not free. While some projects ask for donations (see e.g. Git), organise fundraisers (GnuPG), sell merchandise and/or books (Lua), Proxmox, like Red Hat for example, offers subscription-based support1. Offering a subscription in itself is no problem in my opinion, but I do have some issues with the above notice:

  1. It is shown on every login, without some sort of ‘Do not show this again’ option;
  2. It is worded in such a way that (to me) implies the product is not fully usable without such a subscription;2
  3. The ‘Community’ subscription is in my opinion quite steeply priced3 for something that does not offer any tangible benefit4.

Having said that, as Proxmox is licensed under the AGPLv3, we can simply fork the component responsible for the subscription notice, disable the notice, and push it to our own package repository. So, without further ado, lets get started!

Setting up the development environment

Note: below instructions are adapted from the Proxmox Developer Documentation and from the pve-common repository.

As recommended in the above documentation I used a new installation of Debian 11 (Bullseye) for development. After installing Debian with the default settings (except GUI disabled and SSH enabled), add the Proxmox repository key:

// You might need to install opengpg before you can run 'apt-key':
# apt install opengpg

# wget -O- "" | apt-key add -

Next, add the Proxmox repositories to the Apt sources:

# echo "deb bullseye pve-no-subscription" > /etc/apt/sources.list.d/proxmox.list

# echo "deb bullseye main" >> /etc/apt/sources.list.d/proxmox.list

Upgrade packages – where necessary – to the Proxmox provided version:

# apt update && apt dist-upgrade -y

Install Git and the ‘devscripts’ package, which provides us with ‘mk-build-deps’ to easily install build dependencies:

# apt install git devscripts

Cloning, modifying and building ‘proxmox-widget-toolkit’

Next, clone the ‘proxmox-widget-toolkit’ repository, which contains the subscription notice:

$ git clone git://

Install the necessary build dependencies using ‘mk-build-deps’:

$ cd proxmox-widget-toolkit

$ mk-build-deps --install --remove --root-cmd sudo
// Accept installation with 'y'.

By grepping for ‘No valid subscription’, we find out that the message is located in src/Utils.js. Let’s comment out the ‘’ call:

Now, build a .deb package for installation on our Proxmox host:

$ make deb

After a few seconds the build completes and we are left with a ‘proxmox-widget-toolkit_3.6.5_all.deb’ file (of course, the version number may differ depending on the current Proxmox version). Copy the package to your Proxmox host, for example using ‘scp’:

$ scp proxmox-widget-toolkit_3.6.5_all.deb root@<Proxmox hostname or IP>:/tmp/

Finally, open a shell on your Proxmox machine, install the package, and restart the web GUI:

# apt install /tmp/proxmox-widget-toolkit_3.6.5_all.deb

# systemctl restart pveproxy

You might need to clear your browser cache because the JavaScript file we modified is probably cached. Finally, go to your Proxmox web GUI, log out and log back in. No message will be shown!

Of course, as soon as Proxmox pushes an update for the ‘proxmox-widget-toolkit’ package, our modifications will be overwritten. In a next post we will investigate 1) how to automate this change whenever a new version of the package is published by Proxmox, and 2) how to serve our custom package to Proxmox through our own repository.

  1. While Red Hat unfortunately does no longer offer a bug-for-bug compatible fork in CentOS, they definitely do contribute a lot to the Linux ecosystem. I was not able to find such a record of upstream contributions by Proxmox. At least a contribution to the Debian project would be appreciated, as most of the packages are downloaded directly from the Debian servers (to my knowledge this is even the case when using the Enterprise repository).
  2. The same fearmongering is used on the Pricing page, which notes that the ‘Community’ subscription provides access to the ‘Complete feature-set’ – implying that this is not the case without a subscription, and also on the ‘Repositories’ page in the web interface, where the enterprise repository (unusable without a subscription) is enabled by default, while the ‘pve-no-subscription’ repository has to be added manually, leaving a new installation without a way to receive updates.
  3. When using for example a second-hand 2P server (they seem to be quite cheap every now and then!) the license sets you back 252€ yearly when including Dutch VAT. For personal/hobby use that is unsustainable. I would love to occasionally make a smaller donation, but such a option is not provided.
  4. According to the Proxmox website the ‘Community’ subscription provides the following benefits:
    • ‘Access to Enterprise repository’ – from the Proxmox forums the only difference to the ‘pve-no-subscription’ repository seems to be that new updates are pushed to the latter first, and only when no issues are raised by the unsubscribed community the update is pushed to the Enterprise repo.
    • ‘Complete feature-set’ – which, as noted above, is misleading as the unsubscribed product also provides all features (as is noted on the forums: ‘There are no artificial limits or missing features when not buying an subscription.’).
    • ‘Community support’ – Support is provided through the Proxmox forums, which of course are also accessible without a subscription.