Viewing Issue Advanced Details
|ID||Category [?]||Severity [?]||Reproducibility||Date Submitted||Last Update|
|07581||Gameplay||Major||Always||Mar 1, 2020, 22:30||Mar 10, 2020, 06:38|
|Tester||cmonkey||View Status||Public||Platform||MAME (Official Binary)|
|Assigned To||Resolution||Fixed||OS||MacOS X|
|Version||0.219||Fixed in Version||0.220||Build||64-bit|
|Fixed in Git Commit||Github Pull Request #|
|Summary||07581: sharrier, sharrier1: Constantly increasing game difficulty due to time delayed protection|
I (along with Chris Hardy and a few others) originally investigated Space Harrier's time delayed protection 5 years ago in January 2015 as part of ID 00995. At that time the only effect that I was made aware of was the 'increasing lives when you lose a life' issue. As I wasn't aware of anything else as regards 'weird' behaviour in the game I concentrated all my efforts on purely understanding the cause of the 'increasing lives' issue, and nothing else.
Winding the clock forward 5 years, I was recently talking with Adam Sawkins (creator of Fortress Craft and ex. Critereon/Codemasters dev) about Space Harrier and he asked me why, after the cabinet had been powered up for a while, the enemy shots became increasingly faster and faster, to the point it was virtually impossible to avoid them. He mentioned that rebooting the game reset the enemy shot speed back to normal but the enemy shot speed would gradually get faster again, until the board had to be rebooted again. The cabinet in question was running Space Harrier roms on a converted Enduro Racer board set (i.e. with the Space Harrier Enhanced romset and no i8751 MCU). This immediately got me thinking whether it was related to the time delayed protection I'd investigated 5 years ago as that issue only manifested itself when running on a board with no MCU (and also in MAME due to lack of MCU simulation at that time).
So I started looking into the code again and it turns out that I only half defeated the protection back in 2015 and the other half has remained, until now.
The protection is driven by the in-game timer which only ticks during normal gameplay (i.e. not in attract mode or main menu). This timer is 6-bytes long and is located at address $40020 in main CPU address space. The first 4 bytes represents 'seconds' and the final 2 bytes are the sub-second (frame) counter. The timer is zeroed at the start of each game. Code at $1514 updates both the power-on timer (at $40000) and the in-game timer (if we're in-game). Due to a bug in the timer updating code the 'seconds' are only updated every 62 frames, rather than every 60 frames.
One of the routines (at $4aae) that's called every frame by the main game loop checks if the in-game timer is at a multiple of $200 (in theory this should be every 512 seconds but due to the bug in the timer update code it's actually every 529 seconds) and if it is the following code is executed :-
004ACC: 3039 0012 444E move.w $12444e.l, D0 ; never seems to contain a value other than 0 after many hours of play testing
004AD2: 3239 0004 04EE move.w $404ee.l, D1 ; never seems to contain a value other than 0 after many hours of play testing
004AD8: B340 eor.w D1, D0 ; d0 = d1 XOR d0
004ADA: B079 0004 0090 cmp.w $40090.l, D0 ; this is *likely* to be the protection address
004AE0: 6D00 000E blt $4af0 ; branch if d0 < contents of $40090
If the conditional branch fails then the following two instructions are executed :-
004AE4: 5279 0004 008E addq.w #1, $4008e.l ; increase difficulty by increasing the speed of enemy shots
004AEA: 5279 0004 00F0 addq.w #1, $400f0.l ; trigger the 'increase lives' protection
The second instruction here is the one that I concentrated all my efforts on 5 years ago, I completely ignored the first instruction. After further investigation it's clear that it is also part of the protection.
The word value at $4008e is set during early boot by code at $2d7c which reads the game difficulty settings from dip switch B. It's value is set as follows :-
It's a value that used as part of the calculations to determine enemy shot speed by the routine at $b9d2 (you can easily tell this routine is related to enemy shots by changing the first word of the routine to $4e75 (RTS), doing so will stop the enemies from firing shots at the player). Higher value = faster enemy shots
This value would ordinarily NEVER change after it has been initially set, assuming the i8751 MCU is present. The absence of the MCU means the value increments for every 529 seconds of in-game play. The only way to reset the value is to reboot the board or drop in/out of service mode.
The slow increase of this value means that if you start a new game from cold boot with the difficulty set to EASY/MEDIUM on the dips then by the time you finish the game the difficult will effectively be on HARDEST (assuming roughly 17 mins 40 seconds to beat the game). And it will keep on getting harder with each completed 529 seconds of in-game time. Once the value reaches 6 it takes lightning fast reflexes to avoid the enemy shots.
Taking a step backwards, I have a feeling that the MCU exposes a value of 1 at address $40090. It could in theory be any value between 1 and $7fff for the conditional branch at $4ae0 to pass, but i'd imagine that 1 was the more likely. The reason why I think $40090 is the protection address is because it's the next word, sequentially, to $4008e and it makes sense to group protection related values together, from a source point of view.
The only 2 'unknowns' at the moment are the values at $12444e (shared ram with sub cpu) and $404ee. There's no direct reference to those addresses (via either byte, word or long) in either the main or sub cpu programs. I've played the game for several hours in MAME with r/w watchpoints set on both addresses and the watchpoints have never been hit. I've never observed any value other than zero at those two locations. But that's not to say they're not referenced indirectly, by an offset to a base register, somewhere deep in the code. I've got a feeling they could just be red herrings put there in an attempt to try and throw the bootleggers off course if/when they tried to investigate this back in the day.
I'd be interested to hear MAMEdev's views on this.
|Steps To Reproduce||
1) Set difficulty to EASY/MEDIUM via dip switch B and observe the word value at $4008e after boot, it should be zero
2) Start a game and play 529 seconds of gameplay, you can use CONTINUE if you run out of lives, the in-game timer won't be reset when you CONTINUE
3) Observe the word value at $4008e increment to 1 after 529 seconds of gameplay (via visual inspection of a memory window or setting a watchpoint)
4) Keep playing for further 529 second segments and watch the value slowly increment, whilst observing the enemy shots get faster and faster toward the player
5) Start a new game and observe the value at $4008e is NOT reset by starting a new game
** you can shorten step 2 by simply setting the word @ $40022 to $1ff/$3ff/$5ff/$7ff/etc and resuming execution, the protection will kick in one second later
|Affected Sets / Systems||sharrier, sharrier1|
|There are no relationship linked to this issue.|
Mar 1, 2020, 23:31
Sounds like typical arcade rank control to me.
If I have to guess, the rank should decrease (by another table that the difficulty dip sets up?) when player loses a life.
Finally it looks weird to me that easy and medium shares the exact same behaviour given your wording.
Mar 9, 2020, 22:24
I guess this can be closed now after the latest commit from OG :-