Sunday, March 08, 2020

Space Harrier: The protection strikes back!

This is a guest post from Adrian Smethurst

It was back in January 2015 when I first started looking into the Space Harrier code on reports of a ‘bug’ which gave the player ‘extra' lives when they lost a life.  This ‘bug' only affected the game when played in either MAME, on an Enduro Racer converted board or a bootleg Space Harrier board.  It turns out that it wasn’t a bug but rather a time delayed protection mechanism created by Sega to try and hit the arcade operators, who bought bootleg Space Harrier boards back in the mid 80’s, in the pocket.  You can read more about that here.

Space Harrier PCB with Intel 8751 Microcontroller

Fast forward 5 years and a conversation I was having with respected indie game dev and creator of Fortress Craft, Adam Sawkins (ex. Criterion and Codemasters) about Space Harrier, at Arcade Club Leeds.  He asked me why, after the game had been powered up for a while, the enemy shots would start to come at the player faster and faster, to the point where you’d need lightning quick reflexes to simply avoid them.  He also told me that a reboot of the game would reset the enemy shot speed back to normal again, for a short while.  Hearing this news immediately sent my mind drifting off to a time 5 years previously when I’d investigated (and presumably fully defeated) the time delayed protection in Space Harrier.

So that night I went home and stated looking further into the Space Harrier code…

The heart of the protection is built around the ‘in-game’ timer.  This is a 6-byte timer located at address $40020 in main CPU address space.  The first 4 bytes of the timer represent the ‘seconds’ of in-game time played and the final 2 bytes represent the sub-second (frame) timer.  The timer is reset to zero at the start of each new game.  The timer is only incremented during normal gameplay, attract mode gameplay doesn’t increment the timer.  Every vertical blank the sub-second timer is incremented and compared with a value of 61 (yes, it’s a bug!).  If the sub-second timer is greater than 61 then it’s reset to zero and the ‘seconds’ part of the timer is incremented.  This means that, due to the bug, 1 second on the ‘in-game’ timer is actually 62 frames of gameplay, rather than 60.  The code that increments the ‘in-game’ timer is at address $1514 (it also updates the power-on timer located at memory address $40000 at the same time).

During every frame of normal gameplay code at address $4aae checks if the ‘in-game’ timer is at a multiple of $200 (this should, in theory, have been every 512 seconds but due to the bug mentioned above is actually every 529 seconds) and if it is the following code is executed :-

004ACC: move.w  $12444e.l, D0   
004AD2: move.w  $404ee.l, D1
; d0 and d1 never seems to contain a value other than 0 after many hours of gameplay testing
004AD8: eor.w   D1, D0          ; d0 = d1 XOR d0               
004ADA: cmp.w   $40090.l, D0    ; compare d0 with value at $40090.w
004AE0: blt     $4af0           ; branch if d0 < contents of $40090.w

If the 'branch less than’ condition is false (i.e. IF d0 < contents of the word memory @ $40090) then

004AE4: addq.w  #1, $4008e.l    ; trigger ‘previously unknown’ protection
004AEA: addq.w  #1, $400f0.l    ; trigger the 'increase lives' protection

When I was investigating the protection routine 5 years ago I completely failed to consider the first of these 2 instructions and only concentrated my efforts on investigating the second one.  This is because, at the time, I’d only been made aware of the ‘increasing lives’ issue.  So at the time I worked backwards from the ‘number of lives' counter to find out what was increasing the number of lives.  From there my investigations led me back to the instruction at address $4aea.  At that point I wrongly assumed that the ‘increasing lives’ issue was the only side effect of the protection routine.

How wrong I was…

The word value at address $4008e is directly related to the game difficulty.  It’s a value that's set initially by the boot-up code reading the ‘difficulty’ settings from dip switch B and is set as follows :-

HARD            1
HARDEST         2

The code which does this is at address $2d7c.  This value is then subsequently used as part of the calculations in the routine which handles the enemy shots at address $b9d2.  It’s easy to tell that that routine handles enemy shots as if you change the first word of the routine from $08ed to $4e75 (RTS) the enemies will no longer fire shots at the player during gameplay.  The higher the value at address $4008e, the faster the enemy shots head towards the player.

Ordinarily the difficulty value at address $4008e would NEVER change after boot, assuming the game code is running on a genuine Space Harrier board with the 8751 MCU present.

However, as you can see from above, the instruction that I completely ignored 5 years ago, INCREMENTS that value (and hence the difficulty level) every 529 seconds of in-game play.

What this effectively means is this - if you power up the board from cold, ensure the difficulty setting on dip switch B is set to EASY and complete the game (assuming roughly 18 minutes for a full playthrough, although I have seen people complete the game in around 17 minutes) then the difficulty will be at the HARDEST level by the time the game completes.

And it will keep getting harder for each 529 seconds of completed ‘in-game’ time.  This is because the value at $4008e is only reset by either rebooting the game or dropping in and out of service mode.  It ISN’T reset at the start of each new game.

It’s very easy to see the results of a higher value at address $4008e by simply changing it via the MAME debugger and playing the game.  Values of 6 and above (which would represent just 3 or 4 full playthroughs from starting on EASY difficulty) make the game almost impossible to play as the enemy shots head towards the player with such high velocity..

Taking a step back, I feel it’s likely that the 8751 MCU probably exposes a value of 1 at address $40090 (it could in theory be any value between 1 and $7fff for the conditional branch instruction at address $4ae0 to pass but 1 is the most likely value, IMHO).  The updated patch has been added to the Sega Enhanced package here.

I hope that this finally lays to rest the protection in Space Harrier.

1 comment:

TeamEurope said...

The MCU is now decapped! ;-)