Cracking Age of Empires III over shader quality settings

Or how disassemblers and debuggers can save your eyes

Lancelot de Ferrière
22 min readMay 2, 2020

May 1st 2020 — if you’re like me, quarantine is making you want to replay games you haven’t touched in years.
If you’re further like me, you might have a copy of Age of Empires 3 stored somewhere. You might be playing on a Mac, you might not yet have upgraded to Catalina, and you might be eager for some of that Morgan Black action.

So you boot the game, you land on the Main Menu, and you immediately notice something is wrong…
It looks like hot garbage.

If you’re wondering what “hot garbage” looks like -> check out the water. Everything else is also terrible, but it’s less obvious.

So, you hop over to the options, boost everything up…
…And it’s still ugly.

Suspiciously, you notice, the “Shader Quality” options is locked to “Low”.

Attempt #0 — Hacking the settings

At this point, you’ll look for the folder the game has expectedly created somewhere in your Documents, because it’s 2005 and that’s what games do.

What are you looking for, you ask? The file where settings are stored, of course. The interface can lock you out, but you’re a thrifty man, aren’t ya.
So you find the XML you’re looking for, because it’s 2005 and of course it’s XML, and stumble upon a “optiongrfxshaderquality” option set to 0. That looks right, you think, and so you bump that to 100 because one can never go wrong with too much quality. We are in agreement.

You might also notice that this is a terrible use of XML. Thankfully, there are no good uses of it.

Content, you boot the game up. However, patatras (it’s French, look this up), nothing changed. And as you look at the ol’ XML file, the setting is back to 0. Ensemble Studios (RIP) has looked at your craftiness and crapped all over it.

At this point, you might give up. This is a Mac system and you’re unlikely to find a user-patch (well — I’ve looked around a bit. There’s some shady stuff here and there. Might work.) It’s just graphics. It sounds annoying to fix.

But you’re not going to. Because you remember how Age 3 should look like. You remember looking at that beautiful wavy water. At those particle effects. At those huge ships that simply wouldn’t fit in the goddamn fucking frame. Jesus that camera is zoomed in. What were they thinking.

Attempt #1 — Hacking the data

At the top of that Folder in your Documents, there is a log file. The game shows a graphic card, and says it’s picking the “generic dx7” setting. It’s saying you’ve got an Intel GMA 950, which your previous computer indeed had.
This one, though, has an Intel Iris Graphics 6100. Something is indeed wrong.
You take a wild guess that the game is determining your computer’s graphics capabilities by matching it against a database of graphics cards, instead of doing the right thing and checking the capabilities of your card. Because that’s what AAA developers do, and you’ve worked on an open-source RTS game and you know how these things go.

The log file’s content. If you find it funny that Intel is 0x8086, you’re reading the right article.

Incidentally, you find old patch notes. Those confirm your fears.
You look around at some files in the GameData folder, but it’s all .bar and .xmb files, and those are mostly unreadable. You grep for “Intel”, for “dx7”. Nothing happens. You delete some files… Nothing happens. Well, you know that AAA games might want to bundle some resources in weird places, and this is a Mac port of a Windows game, so you know, maybe things are odd.

Still, you end up finding a “render.bar” file, and moving that one crashes at startup. That’s vaguely good. You open that file in Hex Fiend, because that usually can tell you a few things about binary files.
And well, it does. Some of that data is readable text. Some of it is shaders. But most of it is oddly separated by empty spaces…

Hex Fiend’s interface. It’s minimal. It works. The first bytes spell out “ESPN”, which is probably the .bar header.

“Obviously, that’s UTF-16”, you exclaim ! This is originally a Windows game, it makes perfect sense they’d store text in UTF-16, wasting basically half the space of the 30Mb data file. After all, Windows loved UTF-16 (though it shouldn’t), and why not use that and ASCII shaders in the same file. Perfectly sensible idea!
(Not shown: this took me like a day to realise)

Anyways. Hex Fiend actually has an option to parse bytes as UTF-16, so you grep for dx7. That gives you a few hits. You replace all of that with dx9, which also shows up in that data occasionally, you save and you boot up the game.
Welp. The logs don’t even say “dx9”. That’s probably defined elsewhere.

(Imagine the same log screenshot as up top here)

Let’s try something else. You assume the game is detecting graphic cards, and you have Hexadecimal identifiers in the logs (in my case, 0x8086 — Intel, and 0x2773). Grepping for “8086” in Hex Fiend hits somewhere, and some of that text looks encouraging. Intel 9 Series is the GMA 950 series, and it probably didn’t run Age 3 very well.

Some numbers there look like identifiers of cards (2582, 2592), so maybe changing those will do something. You’ve made a copy of the file anyways, so it can’t hurt to try.

Sadly, that too is a dead end. Truth is, you’re looking at the wrong file. Thankfully, I can tell you that because I’ve spent too much time on this and tried way too many things. This bloody thing took upwards of 20 hours. And that’s not counting the time to take all those screenshots…

Anyways, the correct file is “DataP.bar”, and if you replace IDs smartly, the logs will show something completely new:

I’m not sure why this “generic dx7”is not the same as the one above.

Of course, in-game, you’re still stuck on the “Low” setting, so the logs aren’t lying. You might have hoped for “Medium”, but alas, no.

This seems to be as much as you can do from hacking the data.
Time to bring out the big gun.

Attempt #2 — Cracking

So, with all else failing, we’ve only got one last thing to do: change the executable itself (aka “cracking” back in 2005). Nowadays, I suppose we’d just call it hacking, but the word has stuck in the game piracy sphere (not that I’ve ever been anywhere near that, of course).

It’s time for the actually interesting part of this article (yeah we’re over a thousand words in already, but are you not entertained?).

Hopper Disassembler

Our tool of choice for this step will be a disassembler: an application that takes the binary code of the executable and turns it into readable (haha) assembly code. The most famous one is IDA Pro, but it’s crazy expensive and I’m not sure it runs on a Mac, so I’ll go with Hopper. It’s not free (it’s actually 90$), but you can freely use it for 30 minutes at a time with mostly all required functionality.
I won’t go into too many details about how the interface looks, as the Hopper tutorial actually covers that nicely, but I’ll explain what I’m doing, and there’s presumably enough screenshots that you might just learn something.

First, a note: this took me like 5 days to get right, and many unsuccessful attempts were made. I’ll mostly only show the successful path, so it might look kind of magic. But this is hard. Don’t worry if you’re struggling.

And another note: “cracking” is generally illegal and/or done for illegal purposes. This is a rare example of a rather legitimate use case which is also fairly “white hat”, so that’s nice. I do believe it’s technically illegal / against the EULA, but this a obviously a force majeure case. Anyways, please pay for things you enjoy, and only those because anti-consumerism is cool.

Drag and drop the Age 3 app to open it. You just have to pick “x86” instead of powerPC, because yeah, it’s 2005.

Step 1 — Finding the relevant bit.

This can, perhaps surprisingly, be the most tricky part. You have all of the disassembled code, and no idea where to look. True, some games ship with some level of debug symbols, making function names readable, but this one doesn’t. Hopper gives you “sub_a8cbb6” and you’ll have to make do with that.

Thankfully, we do have a head start: our log file. It’s quite likely that writing to the log uses string literals, which basically means it’s ASCII hardcoded in the executable. That’s something you can grep for, because no amount of compiling will delete it (code obfuscation might hide it though. Thankfully, this isn’t the case here).
Grepping for string literals is usually the first thing you’ll do when disassembling something.
So let’s go ahead and type “found vendor” in the Hopper search, and you’ll see it finds something:

Yay, literal strings. Love those things.

On its own, this really wouldn’t move you forward too much. But since the adress of this “literal string” is hardcoded, you can actually find references to it. Hopper does that for you in the green bits on the right : “DATA XREF=sub_1d5908+1252”. Double-clicking on that will take you to the sub_1d5908 procedure, our hero of the day.

There we find assembly code. Unlike what you may think, assembly code is very easy to read. The hard part is understanding it.

Hopper uses ‘Intel Syntax’ by default, which means the first operand is the destination, and the second one is the source. On the highlighted line, you see mov dword [esp+0x1DC8+var_1DC4], aXmlRenderConfi. Let’s unpack that.

Our first assembly line

mov is the MOV instruction, famously turing-complete on x86. It moves (well, it copies) data from A to B, which basically means it reads A and writes it at B.
Given what I’ve told you above, aXmlRenderConfi is A, and dword [esp+0x1DC8+var_1DC4] is B, the destination. Let’s unpack that further.

aXmlRenderConfi is actually Hopper helping us. The truth is, this is an alias for the memory address of the string literal we clicked on a few seconds ago. If you split the window and look at things in Hex-mode (the preferred hacker mode anyways), you’ll see 88 18 83 00 on the right there.

Hopper conveniently highlights the same selected bits on both sides.

If you ‘reverse’ that in just the right way, it’s 0x00831888, the memory address of the string literal (it’s even highlighted in yellow in one of the above screenshots). So that’s one mystery solved: we’re MOV-ing, i.e. writing, the address 0x00831888.

Why is it spelled 88 18 83 00 and not 00 83 18 88 ? Well, this is because of Endianness, a rather arcane topic which usually has little impact. It’s just one of those things that make people go “you know, computers don’t work” (I’ve looked for a citation for this, and failed, but I know it’s out there).
What it means for us is that all numbers we’ll look at today have this problem.

You might have noticed that I’ve said we wrote the address, and this is right: we don’t actually care about the content, which happens to be a NULL-terminated string literal. We’re writing the address because code down the line will refer to that address. It’s a pointer.

What about dword [esp+0x1DC8+var_1DC4] then? This one is trickier.
dword technically means we are operating on “double-words”, aka 16*2 bits. Which means we’re copying 32 bits. Recall: our memory address is 0x00831888, which is 8 hexadecimal characters, and each hexadecimal character can take 16 values, which is 4 bits of data. So that’s 8*4 = 32. By the way, that’s where “32 bits” in computer systems comes from. If we were using 64 bits, our memory address would be written 0x001122334455667788, would be twice as long and 2^32 times bigger, and we would have to copy 16 bytes. That would give much more memory to play with, but it’s also twice the work to copy a pointer, which is why 64 bits wasn’t really twice as fast as 32 bits (this was for you, 1994 boys & girls).
By the way, a more complete explanation of what a “word” is can be found here. Because of course it’s more complicated than that.

Now, what’s that bracketed bit? The brackets mean we’ll compute whatever is inside, and treat it like a pointer. Our MOV will thus write at whatever memory address this evaluates to.
Again, let’s break it down (and don’t worry, once this is done, you’ll basically know how to read assembly code in Intel Syntax).

esp is a Register, which is the closest equivalent of a variable in assembly. The x86 architecture has plenty, they’re somewhat distinct in their usage, but we generally won’t care much. You can find details here. Just know that there are special registers for floating point, and we also have “flags” which are used for comparisons and such.
The rest is simply numbers written as hexadecimals, and Hopper has reprocessed them somewhat to help. var_1DC4 is -0x1DC4 , you can see that at the top of the procedure or in the right-side panel.
Which means [esp+0x1DC8+var_1DC4] evaluates to [esp + 0x1DC8 — 0x1DC4] , which is simply [esp + 4] , which means “take the 32-bit value of register ESP, add 4, and interpret that as a memory address”.

So, to recap, we’re writing the address of our string literal to “esp+4”.
This is correct information, but extremely useless. What does it mean?

This is the tricky bit when reading Assembly code: figuring out what this is supposed to be doing. Here, we have a head start: we know that this is probably logging something. So we can expect a function call that logs stuff. Let’s look around.

There are several similar calls above, and the a call instruction, which calls a procedure. Hopper would usually say it (not sure why it doesn’t here), but what is happening is basically that we’re setting up the arguments for our function call. If you click around the different sub-calls, you’ll see stuff named imp___jump_table___Z22StringVPrintfExWorkerAPcmmPS_PmmPKcS_. This is a name-mangled function name, and “Printf” is enough to now that this indeed prints things. Don’t care about how. We’re in the clear.

A step backward

At this point, you’ve completely lost track of what you were doing: trying to make the game acknowledge that your 2015 graphics card is indeed powerful enough to run a 2005 game. Let’s recap. We’ve found the place where logging takes place. If we’re lucky, this is also the place where the code that checks our settings and our graphics cards capabilities is. And, thankfully, we are lucky (there’s a pretty good chance I couldn’t write this article otherwise).

To get the bigger picture, we’ll use Hopper’s Control Flow Graph feature, which breaks down code in little blocks and draws arrows to represent the various jumps. This is much easier to grok than the actual assembly which is often terribly out of order.

Our logging call is in the middle of a terribly long function, because again, that’s what AAA games do.
This is the point where one would look around for other string literals and Hopper “hints”, hoping to find something. I’ll give you some pointers (eh).
You’ll see at the top of the procedure the rest of the logging, and down below some interesting bits about “forcing dx7” or “Very High” settings and a pixel shader version issue… Which we have.

The log of our “hacked” data said we had pixel shader version 0.0 which was incompatible with the “High” setting. That code lives in the bottom-left of our function, and if you squint you’ll see something peculiar (highlighted in terrible style by yours truly): this is just 4 times the same code, for each of “Very High”, “High”, “Medium” and “Low”. And yes, this took me hours to see.

I’ve highlighted in blue the block where “pixel shader version 0.0 High” is logged. I’ve highlighted other similarities in nice child-friendly colours.

The only differences are that we are writing “0x0”, “0x1”, “0x2” and “0x3” in a bunch of places. This looks suspiciously similar to our Settings file from earlier and the optiongrfxshaderquality setting (well, you might not know that yet. But it does, believe me).

At this point, we could try several things, and indeed I did. But to cut this short — he says, now almost 3000 words in — we’ll do the right thing: figure out why our pixel-shader check fails.
Let’s look for a branch. The logging block is linear, so it must be before.

SEMVER fans, behold the _superior_ version format: floating point numbers.

Indeed, right above that is a jb instruction, a jump instruction (think “goto”). This is a “conditional jump”, and the condition is “if below”. The way Assembly ‘ifs’ work is through the register flags I briefly mentioned before. Just know that usually you simply have to look at the instruction above: that one probably sets the flag.
That instruction is often cmp or test , but here it’s ucomiss .
That’s barbarian. It’s also easy to Google: ucomiss. It’s a compare instruction between floating points, which explains why we seexmm0 : that is a floating point register. It makes sense: the logs says our pixel shader version is “0.0”, which is a floating point value.
So what is over at memory address 0x89034c then?
The hex data is 00 00 00 40, which could leave you dumbfounded. But we know to expect floating points, so let’s interpret that as a floating point: it means 2.0 . Which is a reasonable floating point value for a pixel shader version, as indeed Microsoft has used it for its High-Level Shading Language, which DirectX shaders are written in, and Age 3 is a DirectX game even if the Mac port uses OpenGL. It also makes sense, in 2005, to have a “High” setting that requires the pixel shader version 2.0, so we’re all good.

Now how do we fix this? Well, this compare instruction compares with the value in xmm0 , which is set in the line above: movss xmm0, dword [eax+0xA2A8] . MOVSS is a special “move” instruction for floating-point registers, and we’re writing 32 bits from whatever eax+0xA2A8 evalutes to.

Presumably, that value is not 2.0, more like 0.0.
Let’s fix this.

Actually hacking

We’re finally ready to l33t our way in the game’s High setting. What we want is to take the “red” path in the CFG above, and skip this “wrong pixel shader version” logic. We can achieve that by making the comparison succeed, by reversing the jump condition, but we have one more trick: the code is setup such that deleting the jump will make us take the “red” path.
Let’s simply replace the jump with a nop , an operation that does nothing (you can’t simply delete — or add — data from an executable because it uses offsets everywhere internally, and those would all break).

I recommend switching out of CFG for this, as Hopper kinda freaks out otherwise. If you do it right, you’ll see some red bits in the Hex window.

Those red bits are the changes we’ve made. At this point, if you’ve paid for Hopper, you can simply save the file. But I haven’t, so I’ll open the executable in Hex Fiend, find the proper region (by copying a section from Hopper in the Find tab of Hex Fiend), and modify it manually. Be careful here, you can break everything, so I’d recommend copying the original executable somewhere.

With that done, start the game, hop on to the settings… And Hurray, you can choose “High”. Can’t choose “medium” though. And you can’t use the high shadow settings. But still, progress !

Look at those glorious reflections in the water. Just look at them. LOOK AT THEM.

Improvements

Truth is, there is a better way to fix this. Our solution worked somewhat, but we can do better by making the condition succeed in the proper way: by actually making the game think our graphic card has 2.0 capability.

If you recall, the game loaded up the value at eax+0xA2A8 . We can use Hopper to figure out what is originally written there. What we’re looking for is an instruction where 0xA2A8 appears in the ‘destination’ operand (I use 0xA2A8 because it looks quite specific and we can’t be sure the same register will be used elsewhere). Hopper highlighting is very useful here, as we can simply click on 0xA2A8 and look for yellow bits in the CFG.

Victory is found a fair bit above: we are writing the value of some floating-point register, as expected. I confess I don’t really understand what is happening right before that (best guess: the game checks for texture compression capabilities), but that’s not really important. Let’s write “2.0” and be on our way.

To do that, we’ll use Hopper’s “Assemble instruction”, and then do the same hex-fiend manipulation.

We’re using MOV instead of MOVSS because we’re writing a regular value, not a special floating-point-register value. Also, little endian, so it’s reversed.

You’ll notice that something weird happened: we’ve “eaten” the jb instruction. That’s because our new instruction takes up more bytes than the one before, and as said above we can’t add or delete data from an executable to preserve offsets. So Hopper had no choice but to destroy the jb instruction. This could be a problem, and we might fix it by actually moving the instruction above (we no longer need to set up xmm3 after all). Here, it’ll work anyways because we’re taking the branch we wanted regardless. Luck.

If you start things up… Well, nothing’s really changed. My best guess is that the Intel 9 series is not fast enough and the game complains. It’s no biggie, because what we really want is that “Very High” goodness.

What was the most powerful card around 2004? The NVIDIA GeforceFX 6800. Let’s trick the game into believing that’s what we have.

Down the rabbit hole

Yeah, this is a whole new major section.

What we know is that the game uses Hex codes to identify graphics card, and it’s fetching stuff from data files. We know that this must take place in the function we’ve been looking at (or at least it’d be really weird if it didn’t). So we should be able to find the code doing it. Still, this might be tricky, since we don’t really know what to look for.

We do have one hint: the options are acting weird, with this “Low/High but no Medium” business. So perhaps there is other code handling this “Very High”, “High”, “Medium”, “Low” thing. And indeed, there is, in this very function. It’s much earlier in the CFG, and it looks interesting.

We can assume that the raw data is some form of XML, because that’s quite likely given there’s several other data files that are XML. And XML is basically a bunch of pointers, and attributes. So this code here is likely checking for some XML attributes (indeed below there is a “expected one of ID, Very High …” which looks a lot like a file sanity-check). We can kind of imagine an XML structure like that, with booleans for the shader qualities:

Note that this is really a wild guess. It could look somewhat different.

The very interesting bit is on the left of the CFG (well, it could be on the right for you, but it’s on the left in the above screenshot): we see the same var_CC which is used by the code that logs the device ID. So ebp+var_CC probably refers to our device ID, 0x2773. The weird thing is the first “check” doesn’t show a string literal, but that’s a Hopper bug: the adress 0x0089da8e contains Hex Data 69006400, which is UTF-16 for “id” (the fact it’s UTF16 is probably why Hopper cannot grasp it). And we are looking for IDs.

You know what? Let’s start a debugger and check it out for real.

Debugging the game

Open a terminal window, and start lldb (just type lldb and enter).
First we need to tell LLDB to target Age 3, then we can run the game by calling run . So far, so good. The game runs, nothing useful is gained.

White background, the ascended option for Medium posts

What we need to do is add a breakpoint: we want to stop the execution when we reach the code above to check our assumptions. We don’t have source code, but that doesn’t matter: we can set breakpoints at a memory address. And we do have the address of the code. Let’s break at the MOV call that fetches “id”, its address is 0x001d5e68.
Setting up the breakpoint is as easy as br set -a 001D5E68 . When we run, the game stops there and LLDB shows the disassembly (in AT&T syntax, not Intel, so the operands are reversed, but you can see it’s the same code). You might notice that LLDB is actually cleverer than Hopper here and telling us that we are doing stuff relating to XML and printf-ing things.

Feel like a hacker yet?

To move further, let’s “step instruction”. The shortcut is ni . After a few times, you’ll notice you’re back where you were. This is a loop! It makes perfect sense: we’re probably iterating over some XML. Hopper was in fact showing this, if you follow the CFG you’ll see a (possible) loop.

This means: if (*(ebp+var_CC) == *(ebp+var_28)) { *(ebp+var_1D84)=edi }

Its exit condition is that ebp+var_1D84 must be non-null. And we see some code setting that right where I highlighted the interesting var_CC stuff.

Let’s set a breakpoint in that cmp, and look at things.

On the left: setting a breakpoint in the CMP and resuming execution. On the right, looking at registers and memory.

We’ll look at registers with reg r , and our memory value with mem read $ebp-0x28 . eax indeed contains 0x2773, and the value in memory is now 0x00A0. If you thread c a few times, you’ll see that we’re iterating different IDs, checking for a match. At some point it matches, exits the loop, and the game starts. So now we know how to control this.

Remember the log file: it found both device and vendor. So there’s probably a similar loop for the vendor somewhere else. Indeed, it’s quite similar and right above in the CFG. The loop is slightly simpler, and we’re looking for “vendor”.

This time the comparison is with var_D0 . This is indeed used by the logger for the vendor ID. If we put a breakpoint there and check things out, we see our familiar 0x8086 in eaxand we’re comparing against 0x10DE at the moment. Let’s write that into eax and see what happens.

My oh my.

So 0x10DE is NVIDIA. In fact, looking at the data file would have hinted at that… But that’s just not fun. Now the question is: what’s the identifier of the GeforceFX 6800? We could also use LLDB and just try every identifier, but that’ll take some time (as there are many, I’ve tried for a while). So let’s look into render.bar this time.

I’m assuming this is “XMB”, a binary representation of XML used in Age 3

Well, this is a bit of a messy file. But after the Geforce FX 6800 tags, you can see a bunch of identifiers. One of those is probably a match. Let’s try 00F1.

The simplest way to test this is through LLDB by setting the appropriate breakpoints — I’m skipping, but 00F1 works (thankfully).

At this point, the question becomes “How do we make this permanent?”
It seems like a simple way would be to change the var_CC and var_D0 values to 0X00F1 and 0X10DE, respectively. We just need to get some code-space to do that.

One simple solution is to NOP one of the logging calls, for example the subsystem call. It’ll free up a few dozen bytes, more than needed. Let’s select it all, and NOP it. Then we simply need to MOV some things in our variables: Assemble mov dword [ebp+var_CC], 0xF1 and mov dword [ebp+var_D0], 0x10DE .

Before & After

Let’s start up the game. Now we’re getting somewhere: you can pick from Low, Medium or High, and you can activate High shadows.

“It was a dark and stormy night…”

But we still can’t get Very High, and that makes Afroman unhappy.
What’s going on?

Oh, yeah, that.

Right. As it turns out, the Geforce FX 6800 is supposed to support pixel shader version 3.0, and we’ve configured ourselves for 2.0.
This is a simple fix at this point: we just need to write float 3.0 in ebp+0xA2A8 . That’s 0x40400000.

Finally, the game stops complaining, and we can enjoy the Very High setting.

Weirdly, this looks completely different. Much more toned down colours, and some distance-fog. There’s a slight shadow bias issue (also above, it’s related to the High shadows settings, not the shader quality), which I haven’t solved yet.

And that’s the end of the road. Thank FUCK, and thanks for reading.

As a freebie, since every resource I’ve found online is broken, terrible quality or incomplete, here’s what the settings actually look like in-game.

Medium is acceptable, but the lack of shadows hurts. From what I can tell, Very High mostly adds a completely different LUT (good explanation at “Color Grading” here), distance fog, perhaps even a different lighting model altogether? It looks quite different, which is somewhat strange.

Very-High & High on top, with High shadows. Medium on the bottom-left, with “on” shadows. Low on the bottom-right, with “Off” shadows.

Other links you might find useful

The blog I’ve linked just above is excellent at breaking down modern rendering pipelines: http://www.adriancourreges.com/blog/

Have a look at 0 A.D., an open-source (the libre kind, not the Docker kind) RTS games in the same genre: https://play0ad.com . It needs devs.

If you want to know more about the XMB format, you can look at 0 A.D.’s code for it. I haven’t actually checked, but I’m pretty sure it’s the same format, because the guy who wrote the XMB decoder for Age 3 on Age of Empires heaven (that takes me back, though I was more active on RoN back then) is the same guy who committed those files in 2004 in the 0 A.D. source code. So that’s a fun bit of code paleontology. Thanks for all your work Ykkrosh.

I also distinctly remember reading a great tutorial on how to use Hopper, but I couldn’t find the link again. If somebody knows what I’m talking about…

Finally, I’d like to plug the Cosmic Frontier project: a remake of the classic Escape Velocity series (Override specifically, but Nova-compatible). It’s such a great game, and it’s such a shame it’s not more well known.
They’ve got a kickstarter running, and the lead dev has a very interesting dev blog where they use Hex Fiend to reverse-engineer the data formats of the original game. It’s a great read.
If you think this was crazy, in one article they:

  • Start up an emulator for Classic Mac to be able to use long-deprecated APIs to read the encrypted data file.
  • Reverse-engineers the encryption from the game code
  • Use a hack of the file system to access that data in modern Mac.

Yeah. There are truly dedicated people out there, and I do wish this project succeeds, because EV Nova is perhaps my favourite game of all time.

--

--