# And patching up lives in Lightening Force

Posted by Tursi, 29 December 2014 · 50 views
After doing the extra work to allow single life games in TF2 and TF3, I was a little disappointed that TF4 doesn't allow it - going from 2 stock ships (3 lives) to 0 (99 lives). So, since I'd already found the menu code and it was above the hack-protected (hopefully) area, I wondered about just changing it to work the same as the other two.
When I last looked at TF4, I found the menu settings for the music test around $FF1A, so the stock ship entry would probably be around there, too. I disassembled the region to try and find a likely candidate, expecting similar patterns to above. This didn't work, so I fell back on the TF2 trick and dumped RAM a couple of times.
This identified $8C96 as the likely candidate, showing the same value as the screen did. The good news was changing this value changed the menu correctly. The bad news was setting an invalid value locked up the menu (perhaps because of the animation), meaning I might need to do a little rewriting to display the numbers like the music test, rather than the fancy spinning numbers.
Anyway, I could breakpoint on writes to that address to find the menu code. Moving right stopped on $FE32, and I disassembled the area to find a somewhat more complex block of code, but eventually found the interesting parts starting at $FE1E:

     moveq #$1,D4		a keypress is confirmed at this point - sets step to '1'
     btst #$2,d0		test joystick left
     beq notleft
     moveq #$-1,d4		set step to -1 if it was left

It then goes through some table lookups and subroutine calls that I presume have something to do with the animation. Finally, we pick up again at $FE78:

     add.w d4,d6		add step to count
     bpl nonegwrap		branch if it didn't go below zero
     moveq #$4,d6		wrap to high value of 4
     bra valueset

    nonegwrap:
     cmp.w #$1,d6		check if we went to 1
     bne notone
     add.w d4,d6		add the step again (2->1 becomes 2->0, 0->1 becomes 0->2)
     bra valueset

    notone:
     cmp.w #$5,d6		check for high value 5
     bne valueset		all tests done if this passed
     moveq #$0,d6		wrap around to 0

    valueset:
     move.w d6,$8c96.w	write the new value back out

Then there is some similar code that again looks like something to do with the animation.
So at the beginning, the hack looks pretty simple. Change the high values, remove the skip over of '1' (which really means 2 lives). The animation will likely break, badly, based on the earlier test, but we can do that much. We'll also need to find (later) where 0 becomes 99.
Unfortunately, we can't use moveq with values as high as 99, so this block needs a rewrite. Luckily we should have lots of space.

     org $fe78
     add.w d4,d6		add the step
     bpl lowokay		if it didn't wrap low, jump ahead
     move.w #$62,d6		if it did, wrap around to 98
     bra valueset		value okay
    lowokay:
     cmp.w #$62,d6		check for high value
     bls valueset		branch if okay
     clr.w d6		reset to zero if not
    valueset:
     move.w d6,$8c96.w	write the value back
     nop			padding to match original code

This looks like this - from $FE78 to $FE95
FE78: dc44 6a00 000a 3c3c 0062 6000 000c bc7c
0062 6300 0004 4246 31c6 8c96 4e71
This works as anticipated - '1' displays as '0' (the lookup tables matched that), and going below zero or above 4 crashes the system so badly the emulator messes up. ;) So now we have to work out how to display the numbers like the music test does, without the animation.
I already have the music test code at $ff1a for selecting, so I took a look at how it displays the number. The music index was stored in $8c98, then the code jumps to $ff76. This does a branch to $11ec8, which appears to be a VDP function that waits for 2 vertical blanks, then returns.
The rest of the function does appear to be a display function:

     lea $b000.w, a0	prepare VDP address
     moveq #$f,d0		prepare mask
     and.w $8c98.w,d0	get last digit
     ori.w #$a000,d0	VDP attributes?
     addi.w #$11,d0		character set offset?
     moveq #$1c,d1		column 28?
     moveq #$11,d2		row 17?
     bsr $123b8		VDP character out

     move.w $8c98.w,d0	get value again
     and.w #$f0,d0		get second digit
     lsr.w #4,d0		shift down
     or.w #$a000,d0		rest same as above
     addi.w #$11,d0
     moveq #$1b,d0		except column 27
     moveq #$11,d2
     bsr $123b8

     move.w $8c98.w,d0	but then what's this?
     bsr $1231c		could be title lookup - part I need is done anyway

So the code appears to be hard-coded, inline. This particular code is BCD, which is not really what I want.
Should be possible to overwrite the animation code with something similar, then.
I decided, too, since TF2 and TF3 are showing how many LIVES you get, that I would make Lightening Force be consistent, and display 1-99, rather than 0-98.

     org $fe96
     bsr $11ec8		wait 2 vblanks
     lea $b000.w, a0	prepare VDP address
     moveq #0,d6		prepare d6
     move.w $8c96.w,d6	make sure it's JUST a word
     addq #1,d6		increment it for display
     divu #10,d6		after storing it, divide by 10 - MSW=remainder, LSW=quotient
     moveq #0,d0
     move.w d6,d0
     ori.w #$a000,d0	VDP attributes?
     addi.w #$11,d0		character set offset?
     moveq #$1b,d1		column 27
     moveq #$0f,d2		row 15
     bsr $123b8		VDP character out

     swap d6
     move.w d6,d0
     ori.w #$a000,d0	VDP attributes?
     addi.w #$11,d0		character set offset?
     moveq #$1c,d1		column 28
     moveq #$0f,d2		row 15
     bsr $123b8		VDP character out

     rts			done

This is placed right after the NOP above. The RTS is at $FED8, which this easily fits in. I've included a BSR $11EC8, which as I mentioned is just a VDP delay, because the code seems to like it.
FE96: 6100 2030 41f8 b000 7c00 3c38 8c96 5246
8cfc 000a 7000 3006 0040 a000 0640 0011
721b 740f 6100 24fc 4846 3006 0040 a000
0640 0011 721c 740f 6100 24e8 4e75
That overwrites the second half of the animation (where it expands), we still need to eliminate the first half, too. For that, we'll just jump over it from $FE32 to $fe78

     org $fe32
     bra $fe78

FE28: replace "41fa" with "6044"
This now works, but, it spins very quickly. We need to change the input from the continuous to the edge triggered one, and we still need to make 0 mean 0. (Other values seem to work). We also need to fix the entry to the configuration screen, which does not display right.
So again, back to the input code. We know a direction is being set at $FE1E, so I had work a little further back to see where it was coming from.
At $FE0E there's this block:

     move.w $8c82.w,d0	get joystick value
     or.w $8c84.w,d0	merge joystick value 2
     and.w #$c,d0		mask to just left and right
     beq $fed8		branch if neither set

I know from TF3 that there is probably a 'just pressed' and a 'held' value on the joystick, and those are probably both of them (for whatever reason). So I just need to see which is which. By loading up the RAM viewer to watch those values, however, I made an even better discovery - at $8CBE is a joystick value which is a keypress with delay then repeat - it's used for the menu up/down. But it should do the trick here perfectly. Besides that, $8c82 was the current value, and I couldn't tell from looking what $8C84 was. Breakpointing in the joystick routine would tell me, but that seems unnecessary.
So all I want to do is change the $8C82 to $8CBE. The OR doesn't seem to cause issues, so for now we'll leave it.
$FE10: change "8C82" to "8CBE"
After that lucky break, the next two issues just require watching the value at certain points - we need to trap the entry to the configuration screen to draw the value correctly, and we need to trap the game start to no longer remap 0 to 99. So, we set a breakpoint on reading $8C96 (while NOT in config). This quickly showed that memory location was changed a lot... $F056 appears to contain the persistent copy of the configuration.
Using that, the entry display code looks like this at $F9E0:

     move.w $f056.w,d6
     lea $feea.l,a0
     move.w d6,d7
     add.w d7,d7
     move.w (a0,d7.w),d7
     move.w #$8000,d0
     moveq #$1c,d1
     moveq #$f,d2
     movea.l (a2,d7.w),a0
     bsr $123cc

Most of that, I believe, are lookups into the animation tables. Since we have a lovely little display routine already at $fe96, we can just BSR to that. The next code reloads A0 and D0, which our function uses, and we can just load D6 in case it wants it later (I doubt it, but we have room).

     org $f9e0
     move.w $f056.w,d6	load d6
     move.w d6,$8c96.w	save it in the workspace
     bsr $fe9A		display ships (skipping over the vblank wait as the VDP is off)
     move.w $f056.w,d6	reload d6 just in case
     bra $fa02		apparent continuation code

F9E0: 3c38 f056 31c6 8c96 6100 04B0 3c38 f056
6010
With that working, all that is left is making sure that 0 does not get translated to 99 (and making sure 0 works as a single-ship game). Set the lives to 00 and exit config. Then set the breakpoint (on $F056, now that we know better), then begin a game.
This triggered after the selection screen, like in TF3, right around A5E. The usual disassembly and look for sense happened. And the answer was actually pretty clear at $a5a:

     move.w $f056.w,$f2f0.w		copy lives config to active lives count
     bne notzero			jump if not zero
     move.w #$63,$f2f0.w		set lives remaining to 99
    notzero:

All we need to do, then, is defeat this test and load - the simplest thing to do is replace the move immediate with a copy of the move.w above it. But there is a small problem.
Remember the security check we found that locked up TF4 on collecting a 1-up? This code is right in the middle of that range.
Unfortunately, there seems to be no way around it if I want this behaviour. I'll be able to test the game through, at least, and hopefully there are no other such checksums.
So, first, the patch is to replace at $A62: "31fc0062" - replace with "31f8f056". That repeats the copy if its zero, no harm done.
The checksum code is at $5cae. It's safest to fix the checksum in case of other tests, rather than defeat the branch, so set a breakpoint at $5cc6. Then you have to play till you get a 1-up (early one in the water stage). The checksum is located at $15ff8, and is a full long. In my case, replace "97BEF5C5" with "87abf5c9". This of course also affects the main checksum.
After all these changes, the main checksum at $018E should be set to "1Ae6".
After I've had a chance to run through each game, I'll post patches, but I think I'm done for tonight ;)