Hacking a Game That Doesn’t Exist

2020-07-30

Heavily blurred computer graphic

There is a game that exists outside of our reality. It’s been delisted for seven years; you only had two years to buy it. It never had a physical release, so you can’t find a disc of it or anything. Your only hope of playing it today is digging up an Xbox 360 or PlayStation 3 that still has it (or emulating those consoles on a computer).

That is the conclusion you would reach without some careful research. Welcome to Golf.

The Adventure Begins

The game I’m exploring is called 3D Ultra Mini Golf Adventures 2, released in late 2010. It was developed by an unknown Chilean studio called Wanako Games. Wanako’s best-known game is most likely the free promotional game Doritos Crash Course. And if you think we’re done talking about gamer food, think again.

Konami published the game. With franchises like Frogger, Castlevania, and Dance Dance Revolution, they are a staple of the industry. However, in the mid-2010s, they changed focus towards mobile gaming and gambling.

I speculate some initial issue sparked the removal of the game from Xbox and PS3 stores: for example, a software license was expiring, or a contract was about to lapse. Then Konami, which was already losing interest in console games, chose not to put in the effort to update any contracts and just let the game be removed. But if you have a better theory, I would love to hear it.

For brevity, the game will just be called Golf in this post.

Golf is Amazing

Why hack some game from a decade ago? Well, because it rules. It’s one of the most fun party games I’ve ever played. Reviewers were unkind to Golf, partially because it’s a bad single-player game. The utter chaos you can unleash in a four player game doesn’t resemble the vapid one-player experience, even if the courses and gameplay are exactly the same.

Most importantly, and what doesn’t come across in screenshots or even video, is the incredible skill ceiling. Yes, Golf is fun from the very beginning. You can get three friends together and have a blast messing with each other’s shots. But if you keep coming back, you will be rewarded. I’ve played Golf for 50 hours and have not solved it. On first pass, you might call the game bogus, cheap, or unpredictable. But it’s actually quite predictable. It doesn’t play like most games, so it takes time to learn and let the skill gameplay come through.

A friend of mine found a Rooster Teeth series (NSFW) documenting the game, and got us to play it. I was soon hooked. But, two problems appeared:

  1. We had to play it on his beastly gaming computer – the only thing strong enough to emulate a PS3. This lead to many glitches and crashes.
  2. We didn’t have the Fairy Tales add-on. It promised 18 additional holes, another 33% of game content in a brand-new world!

I set out to get us the Fairy DLC. I had no idea where my journey would take me.

For Whom the Taco Bell Tolls

I wanted to get the Fairy DLC playable. I had a copy of the Xbox 360 version of the DLC, but it is incompatible with the PS3 version of the game. There were two options:

  1. Get an Xbox 360 emulator working on my PC.
  2. Hack the 360 DLC to make it work on the PS3 version of the game.

(1) was rejected from the start. Neither I nor my friend could get the 360 emulator working. Before I started on (2), which was a long shot at best, I came across two discs that nobody else knows about.

As the Kids Meal Wiki reports, two demo discs for Golf were released, under the titles 3D Ultra Mini Golf Adventures 2 and 3D Ultra Mini Golf Adventures 2: Reloaded. I found these on eBay and eagerly bought them for $15. (If you’re interested, it seems they aren’t listed often, but also aren’t too expensive either.)

This promo image for the PC demos is not supposed to exist.

This promo image for the PC demos is not supposed to exist.

I started up the game and looked at what was supposed to be impossible: a working, physical copy of Golf.

The main menu.

The main menu.

Sadly, these were both demo discs. Each one features 9 holes (instead of 54) and doesn’t have all the features of the full game. Critically, there’s no multiplayer.

I got to work.

Thinking Outside the Bun

First, I made use of an excellent extractor from AlphaTwentyThree on the XeNTaX forums. Based on their code, it seems that the game files are packaged using the common LZMA standard. The disc is one big file called data.pak, along with a launcher, the game executable, and some DirectX libraries. Extracting data.pak allowed me to see the inner workings of the game.

Interlude: if you have any interest in game hacking, but haven’t tried it before, I suggest you stop reading this and trying to hack Golf on your own. It’s very easy to make progress if you approach it scientifically.

I started by comparing the two discs to each other using WinMerge. Outside of logos (one of the logos said “Reloaded”) and a few other files, the discs were identical.

I also had the thought: I’m a lazy programmer. If I were releasing a game demo, wouldn’t it be easier to remove features in software than try to adjust the file structure of the game?1 Sure enough, the PC demo is very similar to the full paid Xbox 360 version of the game. I then made it my goal to port the Xbox version to the PC. In most cases, if a PC game was also released on 360, the two games are similar. Thus, it made sense to focus on the 360 version and not the PS3 copy.

The first order of business was modifying anything at all. Would Golf have complicated tampering checks that I would have to crack?

Tampered credits, showing "Hello World"

Tampered credits, showing "Hello World"

Nope. I saw that I could open the credits from anywhere in the game. It was a plain XML file, so I changed the text and loaded up the game. It worked perfectly.

I was also getting sick of recompiling the game back into the data.pak container. The QuickBMS readme says:

Please note that often the games are able to load the extracted files directly from their installation folder, sometimes directly maybe by just removing the original archive

I left a data folder with the extracted files in the same folder as the game and launched it. Amazingly, that worked too! I could now modify the game in-place. This also removed size restrictions - replacement files didn’t need to be the same size (or less) as the original files.

With all that out of the way, next up was removing those annoying demo restrictions. Surely this would take a large hack effort.

I unlocked the map editor, which isn't accessible in the demo.

I unlocked the map editor, which isn't accessible in the demo.

Nah. Searching for relevant strings (demo, trial, debug, unlock, etc.) in live game memory with Cheat Engine reveals a function GetPurchaseStatus. The function is referenced in the plain-text Lua scripts that govern much of the game logic.

create "gui/gui/main/text_buttons"  (my.setFast 
	TimeSpace Real  
	OnActivateCommand (() lua "") 
	OnDeactivateCommand (() lua "") 
	OnStepCommand (() lua "-- Unlock Full Game or Download Content#r#nif GetPurchaseStatus() then#r#n#tme.unlock_full_game_text_button.Visible=false#r#n#tme.download_content_text_button.Visible=true#r#nelse#r#n#tme.unlock_full_game_text_button.Visible=true#r#n#tme.download_content_text_button.Visible=false#r#nend")

Initially, I did a quick find-and-replace. if GetPurchaseStatus() became if not GetPurchaseStatus(), making every call to that function return the opposite. I have since made the replacement string true or GetPurchaseStatus(). Because of boolean logic, it will always be true, but it preserves the original logic as much as possible (for later analysis).

Green Field Development

So, we have an unlocked game we can do almost anything with. I don’t have any expertise hacking Windows EXEs, but like I said, lots of game logic is wrapped up in Lua scripts. Specifically, there’s a folder called templates that contains 8 MB of text files. The two game EXEs are only 4 MB, so clearly there’s a lot of logic in templates.

Let’s go for gold - can we get the fairy DLC working in here?

First, I extracted the DLC just like I did the main game. It worked the same way. Luckily, the structure of the DLC is almost identical to the base game. First, I tried simply pasting everything from the DLC into the main game. For once, I was unlucky and it didn’t work. Looking at the folders, the templates_ex01 (expansion pack 01?) folder is brand new for the Fairy DLC. I suspected the key to adding the DLC was in this folder. As an example, here’s /templates_ex01/guipicture.txt:

create "ex01/gui/gui/w4/field"  (set 
	Texture ("ex01/gui/gui/w4/field" 0)  
	CtrlSize (1.2000 1.0000)  
	Description "Haunted Manor Field" 
	Placement "GUI" 
	Name "ex01/gui/gui/w4/field" 
);
end

Clearly, this folder is supplemental to the templates folder. The corresponding file in that folder starts like this:

create "gui/gui/all/themes/haunted_manor/field"  (set 
	Texture ("gui/gui/all/themes/haunted_manor/field" 0)  
	CtrlSize (1.2000 1.0000)  
	Description "Haunted Manor Field" 
	Placement "GUI" 
	Name "gui/gui/all/themes/haunted_manor/field" 
);

It seems that the expansion folder just adds extra content for the game to deal with. I’m guessing the real DLC edits the executable for the game so it knows to look in the templates_ex01 folder. Since I am definitely not a 360/PS3 hacker, that will remain a theory for now.

All these files end with end and a newline, so joining them should be a simple process. Here’s what it is in Python, borrowing heavily from the Python docs:

from filecmp import dircmp
import os

DIR1 = "E:\\golf\\data\\templates"
DIR2 = "E:\\golf\\data\\templates_contentex01"
OUTDIR = "E:\\golf\\test\\"
FOOT = "end"

def print_diff_files(dcmp):
    for name in dcmp.diff_files:
        lp = os.path.join(dcmp.left, name)
        rp = os.path.join(dcmp.right, name)
        with open(lp, "r") as l:
            with open(rp, "r") as r:
                left = l.read()
                right = r.read()
                #"end" should only occur once, but just get the last one just in case
                ind = left.rfind(FOOT)
                out = left[0:ind] + right
                r_ind = right.rfind(FOOT)
                if r_ind == -1:
                    print("~~~~~Failed to find 'end' footer")
                    print(rp)
        out_path = os.path.join(OUTDIR, name)
        with open(out_path, "w+") as newfile:
            newfile.write(out)
    for sub_dcmp in dcmp.subdirs.values():
        print_diff_files(sub_dcmp)

dcmp = dircmp(DIR1, DIR2)
print_diff_files(dcmp)

(Newer, more-refined code for this and more is included in the git repository.)

To my delight, it worked. Even Rooster Teeth, with its millions of subscribers, has never played this DLC, probably because of how hard it is to find.

The Fairy Tales DLC, running on PC

The Fairy Tales DLC, running on PC

Tech Debt Comes Due

Around this time, I knew my habits weren’t sustainable. I was moving fast and breaking things, but I was starting to make discoveries that were being documented poorly. I had a handful of builds on my computer that were all slightly different and I didn’t know what each of them were. I made the very boring, very painful decision to get 100% reproducible builds. Instead of running a find-replace in Notepad++, I would do it in Python. Any other findings could be reproduced in Python, so I and others could easily see how each hack worked.

As I write this blog post, my script is 448 lines long and includes logic to add the DLC to Golf. My investment has paid off handily. I took a seven month break from Golf 2 hacking, but thanks to the work I put in, I could look through my code and pick up where I left off.

Two quality-of-life hacks I implemented were muting the main menu music and skipping the developer logos at the start of the game. Muting the menu was done by creating an empty file with the same filename as the music, and skipping the logos was done by changing the calls to Lua’s native wait() function to be wait(0), i.e., no wait.2

Now, the low-budget menu music doesn’t haunt my dreams, and I can launch the game instantly for testing instead of waiting a few seconds for logos to disappear.

My Next Course?

There’s a lot of console functionality that isn’t working in the PC hack. You can’t save custom levels, costumes, or in-progress tournaments. (Right now you need to do 18 holes in one go.) There’s more research to be done. I think this can be addressed by comparing the remaining differences between the 360 and PC, but there are hundreds or thousands of differences, so it can be tedious work understanding why a file was changed. Just copying everything in from the 360 seems to cause as many problems (crashes) as it solves.

One goal I did put a lot of work into but got nowhere with was the character selection. There are 4 regular characters in the game, plus support for Xbox 360 avatars (fully customizable in their own right). Yet for the life of me, I can’t get two of the characters to be selectable at all. Oddly, in multiplayer all players are forced to the exact same character and costume that player 1 chooses.

I hoped that the first Golf Adventures game would be as good. Unfortunately, it’s too frustrating to play - it did get a PC port, but the controls are all mouse-based and awful. The file structure looks very similar to Golf 2. Maybe it’s possible to port those levels to Golf 2?

The first level of the Golf 1 expansion, pasted into the first level in the Haunted world of Golf 2.

The first level of the Golf 1 expansion, pasted into the first level in the Haunted world of Golf 2.

Seems like it - but the game crashes when you actually putt. I have some ideas about making this work that I might explore in the future.


  1. This is exactly why I won’t do this in any job. You can’t assume anything about your security. :) ↩︎

  2. A more sophisticated crack could skip directly from the first boot to the main menu: the end of each script calls the next, in a chain of about four. You could go directly to the last script from the first and not call the intermediate scripts. I avoided this because I didn’t know if the game is doing anything in the background when it calls the other scripts. It felt safer to just speed them up. ↩︎