RoboCop – The Future of Copy Protection

7 min read Original article ↗

Arcades in the late 80’s / early 90’s were a big deal, the cutting edge of gaming. So big in fact that bootleg versions of top of the line games would appear on the black market. A quick dip into MAME and you’ll find a raft of bootleg PCB’s listed and supported.

In the home market, copy protection systems ( DRM for you young folk ) were common place. Various methods were employed. Could be the disk format, a code wheel or manual check as well as some very clever code obfuscation techniques. RoboCop 3 on the Amiga even had a hardware dongle, but that’s another story. Reverse engineering those protections, particularly on the Amiga, has been a hobby of mine for many years. Well it seems the arcade was no stranger to protecting itself too.

In the last couple of years I’ve taken to doing full “ish” disassembly projects of old arcade games which run on a 68000 CPU. So far I’ve done Shinobi, Golden Axe and Zero Wing, all of which are either ported to the Neo Geo or about to be 😉

During the Golden Axe port, I spotted some strange things on the Neo Geo side. Each time a player used a magic spell, all the sprites on screen would suddenly disappear. Also enemies just didn’t attack you, it was like they couldn’t see you!. Turns out that Sega threw a custom IC on the board which did comparison and multiplication functions.

The chip was mapped into a few areas of the 68000 address space, allowing the CPU to write values and get a result. In the case of the disappearing sprites, the code wasn’t even using the result of the chip, it was just storing a sprite value on the IC and reading it back later. In the case of the enemies unwilling to engage, it was using the chips comparator function as a collision detection. The result is, if you manage to bootleg the board but don’t have access to the custom Sega IC, then you’re copy of Golden Axe starts to behave badly.

Which brings me onto my current disassembly project.. The Future of Law Enforcement, RoboCop.

Most 68000 based arcade systems are pretty similar in their approach with the Motorola taking charge and an audiocpu, usually a Z80, dealing with the comms to the audio chips. However I noticed while debugging the game in MAME that there is a sub CPU, namely a HuC6280. I wasn’t sure what it’s purpose was until I started pulling apart the original code.

A jump table is a fairly common programming technique in assembly. There’s a bunch of different ways to do it but in the case of RoboCop it’s a simple list of 32bit addresses. All the other jump tables I’d found so far all looked normal, with the addresses pointing to obvious routines. However this one looked… weird!

You’ll notice some addresses there are in red, that’s because they are pointing to an odd number. With the 68000 being a 16-bit, all instructions need to be aligned to even addresses. If you jump to an odd address, the CPU with throw an exception and effectively crash.

So in the example above, if the index value for this jump table was 4, it would read $2A5D, try to execute code at that address and promptly die on its arse.

So what is feeding the index value? Well it seems this is where the HuC6280 CPU comes into play.

Both the CPU’s have a shared memory area. The 68000 starts off the process by writing the value #$1 into the IO_ProtectReq address. The HuC6280 sees this and prepares to send a stream of numbers back. When the first number is ready, it stores it in the shared address IO_ProtectResult and signals that the value is ready by writing a #$2 into IO_ProtectReq. The 68000 then writes #$ff into IO_ProtectNext which signals to the HuC6280 that it wants the next number in the list. When it’s ready the HuC6280 writes then next value into IO_ProtectResult and increments the IO_ProtectNext to say confirm it’s ready. The process repeats until a jump id of #$2 is returned, at which point the routine bails and normal service resumses.

But where are these numbers coming from? Is there a pattern? Can that pattern change?

To monitor the process I setup two special breakpoints on the MAME debugger.

-- print a line to confirm entry into the routine
bp 3eee,1,{ printf "--------------" ; g }
-- print the value in register D0 
bp 3f08,1,{ printf "D0=%08X",D0 ; g }

The debug window then happily prints each value as it comes in from the HuC6280.

I ran it on a couple of different levels, and it seems like it’s the exact same sequence of numbers every time. But I wanted to be sure, so I debugged the sub-CPU during the call-and-response process and found this.

As you can see, the number sequence matches what we’re receiving back in into the 68000.

Armed with this info, I can annotate the jump list with the order each function is called. The first number is the index value and the second number is the order they are executed in.

Currently I’m still disassembling the ROM so I’ve not replaced this function yet, but it should be a simple list of jsr instructions in the correct order. Something like this…

jsr sub_4170
jsr sub_2CB2
jsr sub_3262
...

Case closed… or is it?

Once you find a thread of protection, it’s good to just check some cross references in case there’s more and.. well obviously there is!

The HuC6280 is constantly incrementing IO_ProtectNext after each operation. After a quick hunt and I found this.

This piece of code is sat after the one of the intro sequences. So if the HuC6280 CPU is missing or broken, the number never goes up by one and the code gets stuck here forever. This one is simple, just comment out these three lines of code and you’re done.

The next bit I’m still not 100% sure on yet but here goes. This looks like it stores a bunch of the player values and a large chunk of data into the shared ram.

Then at some point later, it reads them back in.

As a quick test I disabled the ProtectStoreVals routine while half way through a level and found the PlayerLife value wasn’t going down. This seems to suggest it was just reading the previously saved values every time. Then disabling the ProtectReadVals routine put it back to what seemed like normal operation. Hopefully I can just disable both of these to circumvent this. We’ll find out in due course.

What I find most fascinating about RoboCop is the game doesn’t run at full frame rate. Load it up into MAME, hit the debugger and tap F8 for frame advance. You’ll notice the game only updates the screen every other frame! Shocking!

I did wonder why on earth such powerful hardware would need two frames to render, but looking at the above two functions I’m inclined to think it’s actually the copy protection!.

The loop at the bottom of both routines repeats the following two instructions 1,200 times!

move.w (a0)+,d0
move.b d0,(a1)+

Those instructions cost 16 cycles but with a dbf loop its more like 26. So our total is 1200 * 2 * 26 = 62,400 CPU CYCLES! Every bloody game frame! No wonder it can’t run at 60 FPS!

This part needs a bit more attention before I can safely knock it off the list, but it seems TurboCop might be an option 🙂 ( I’m kidding btw, the task of changing all the motion deltas to be half is just a terrible idea! )

Update

We have a part 2… https://hoffman.home.blog/2025/12/26/robocop-breaking-the-law/