Subscribe to me on YouTube 
Icons by neorelic
 

NHL94 hack

 

►Presentation

Mid summer, I received an email from clockwise, a fan of the game and webmaster of NHL91.com.
He was looking for someone to code 6 buttons support on his favorite game.
At this time, I was very busy but the project was interesting :
I didn't code for genny for a long time and a new kind of challenge could help to restart...
2 months later, I re-installed my development environment and was ready to try.

 

►Disasm disasm and still disasm!

To add 6 buttons support, I first thought I had to find the joypad reading method.
Thanks to IDA and my M.IDA script, it was quite easy : load the game.bin on IDA, tell it to handle it like a 68k binary file, run the M.IDA IDC script, press G key and jump to 0xA10003.

Here you are : IDA tells you when 0xA10003 (DATA1) is called, jump to any of the cross reference to find the wanted method.
On NHL94, I have 3 methods which make a call to 0xA10003
0xF6D5E => from the way it writes bytes to DATA2 & CTRL2, I quickly understood it was the multitap detector method
0x1A430 => it could be the one but I also notice it branches (bsr) to 0x1A498, inside the 3rd method
0x1A470 => the good one! How did I know that ? Because this kind of code rings a bell to any genny developer :

move.b  #0,(a0)
nop
nop
move.b  (a0),d0         ; d0 = 00SA00DU
move.b  #$40,(a0) ; '@'
nop
nop
move.b  (a0),d1         ; d1 = 01CBRLDU
It's easy to see here a joypad input reading method

 

►Wait, I don't find it on <put-your-game-name-here> !

Welcome to the asm nightmare!
IDA is good, but what it can't understand is when developer use some strange tricks to optimize rom size (more on this here)
The worst trick I know about is the data-following-call trick (or whatever it is called)
jsr     unk_11B92       ; tiles info is following function call
ori.b   #5,d6
ori.b   #$1E,d0
moveq   #6,d1
what does the hell mean this code ?
If you ever browse unk_11B92 function, you notice a strange thing like movea.l 4(sp),a1 which mean a1 = address of code following method call (where ori.b #5,d6 is coded, in our example)
With the help of SpritesMind members, I finally understood how to translate this code to :
jsr     loc_11B92       ; tiles info is following function call
; ---------------------------------------------------------------------------
dc.w 6
dc.b $BD ; ¢
dc.b   5
dc.b 0
dc.b   0
dc.b $70 ; p
dc.b $1E
; ---------------------------------------------------------------------------
moveq   #6,d1

So, the arguments or the data used by loc_11B92 function are following its call.
Why is it a nightmare ?
Because IDA automatically converts this as code, and so could understands jump or branch when there isn't any.
IDA then continues to disasm the jump function and it's become worst : wrong disasm could overwrite good one and IDA could miss very useful jumps (like the one to the joypad reading method!)

The other confusing method is the jump array.
It's a good method while coding, but IDA (or any other disassembler) couldn't understand it, since it's up to the developer to code it his own way.
The good news is it's easy to find it: search for jmp (a0) code
Of course, you still have to understand how a0 is initiated.
If you tell IDA to continue analysis on this data, you'll see a lot of new code coming and so on.
At 0x11A78, you'll find an example of jump array :
move.b  (a1)+,d0
ext.w   d0
bgt.w   loc_11A94       ; if MSB=0
neg.w   d0
asl.w   #2,d0
movea.l #off_11AF4,a2
movea.l (a2,d0.w),a2
jsr     (a2)
bra.w   loc_11ADC
a2 is a pointer to jump array, you could be sure you'll find an array of method pointers at 0x11AF4 :
off_11AF4:
 dc.l locRTS 
 dc.l sub_11B28
 dc.l sub_11B3C
 dc.l sub_11B4C
 dc.l loc_11B5A
 dc.l loc_11B68
 dc.l loc_11B76
 dc.l loc_11B84
 dc.l loc_11B18
you have to convert each data as long and tell IDA it's a method

 

►Add 6 buttons support

I then add to inject the 6 buttons method at 0x1A49A.
The 3 buttons method is only from 0x1A49A to 0x1A4AE, which give me very few bytes to add Chilly Willy's code.
So I had to add this code 'somewhere' on the rom then call it.
I replaced the original code by a jsr <my_6button_method> and any needed nop instruction.
If I don't forget to end my 6 buttons method by a rts, it will work (yeah yeah! I forgot it the first try!)

Next came a new problem related to asm : long, far, near jump
I knew you can't assemble code at an adress (like 0x400) and simply move it anywhere you want.
Why ? because bsr mycode is assembled as bsr (pc+12) for example and so, if you move the code at mycode to somewhere else, it won't work anymore.
So I had to code like if I was at the wanted address...

I think a good idea is to add your code at the end of the rom, don't even try to find 'unused' space on the rom, you'll waste your time.
I launched my hex editor, moved to the end of the rom and scrolled up until I found something else than a bunch of 0xFF.
0xFF (or 0x00) is the byte used to pad the rom to a 128K round size.
The last byte of useful data in a rom is so where these 0xFF bytes start to fill the rom...and it's where we can add anything we want (gfx, code, hidden message to your girlfriend,....)
NHL94 ends at 0xFFB10 so I had to make my code start at 0xFFB20.

I spent a lot of times trying to use GenDevKit to compile/assemble my code at the right address but finally found one working method (again with some help of SpritesMind's members! )
I added a section in the md.ld like this
  .joy 0x000FFB20:
  {
    joy6.o
  } >rom =0xFF
and gave the name of the section to link to in my joy6.s like this
	.section joy
	.global get_pad
get_pad:

I updated Chilly Willy, made some tests, pasted the result binary data to 0xFFB20 and updated code at 0x1A49A with a jsr 0xFFB20
Or, in raw data, 4e b9 00 0f fb 20.
Houra! The game is still working, with Y working as a B button

 

►Make a good use of the other buttons

Here came the fun part....you have you find which memory address to update or method to call when X, Y or Z are pressed...
To do this, no problem : just continue disasm until you find it....
It's why I almost disasmed and commented every first screen of the game up to the game itself....
It took me about 4/5 of the time I spent of this hack...yes! it is laborious! Be ready for that!
Every time I was able to identify a var, I named and commented it (ex : p1_score, p1_joypad, p2_teamIdx...)
This way, when I finally came to the 0xFFBF06 byte, I knew it was initialize with 0x11 and, spying it with GensKMod watcher, I found it decrement to 0x00 when p1 press B long enought to switch to goal :
0xFFBF06 is the P1 long press counter
0xFFBF07 is the P2 long press counter
0xFFBF08 is the P3 long press counter
0xFFBF09 is the P4 long press counter

I added another jump (to know which player I'm ready input for), made a better use of the MSB of 0xFFBF06 and finally, when Y is pressed, I was able to change the wanted long press counter to 0x01...
After return of this method, other part of the code decrements the counter if B (or Y) is pressed and selects the goal if counter reach 0x00.
How it exactly do that ? I don't know...and I don't need to know so it doesn't bother!

 

►Final words

The hack is done : so many hours spent for 326 bytes and 2 redirections !
I really hope you'll enjoy hacking a game like I did on this one.
Every game is different, some are basics, without annoying asm tricks, while some others use only jump arrays!
I'm only sure of one point : if you don't know how the genesis works, it will be hard for you to make a hack as simple as this one (yes, it's a SIMPLE one!).
And, of course, you need to be, at least, able to read asm 68k.
Don't be afraid, I'm not an asm coder myself but I'm able to read asm code...and I knew a lot of great guys who can help me when I'm REALLY stuck !

I join the current IDA disassembly and the source of my hack to this article, if you want to try yourself...or add another hack ;)
The patches are available on NHL91.com.

Good luck !