Qhimm.com Forums
Miscellaneous Forums => Scripting and Reverse Engineering => Topic started by: DLPB_ on 2013-09-11 19:05:57
-
Working with NFITC1 to make these 2 games (Submarine should be dead easy), use frame based timers, and get rid of timegettime altogether. Problem with the timers as they are is that they work in realtime. If you get any changes in frame rate in play, it doesn't affect the timer, leading to unfair times.
There are 4 main ways to address the snowboard timer:
1. Change the calculation to use floating point addition for the counter (+16.66667)
2. Make it so the millisecond display is actually a frame counter (I have done this below to some extent)
3. Instead of conventional time, make the timer based entirely on frame count with 5 digits.
4. Add on 16, 16, 18 in repeating pattern. I decided to go with this one in the end.
NFITC1 was just in the process of explaining how number 3 would work...
This is where I am with number 2.
{============SNOWBOARD CHANGE
{in play time format
9568A0 = 00 00 1A 00 00 1B 00 00 00
{time format
9568B0 = 1F 1F 1A 1F 1F 1B 1F 1F 00
{best format
9568D0 = 2D 2D 27 2D 2D 22 2D 2D 00
{Increment by 1
72D11D = BA 01 00 00 00 90
{time calcs
72D4D8 = B9 3C 00 00 00
72D495 = B9 10 0E 00 00
72D4C6 = 6B C9 3C 90 90 90
72D50B = 6B C9 3C 90 90 90
72D51E = 6B C9 3C 90 90 90
{write to 1st/2nd digit
72D458 = 88 4A 06
72D473 = 88 50 07
{============
-
Number 3:
A structure pointed to at 0x926290 (0xDD83B8) contains a dword value of a timer that traditionally stores the timer in seconds that is incremented by a delta of two timegettime function calls (which return the system time in milliseconds). This is accomplished by storing timegettime at the beginning in this structure then storing timegettime in another section. Bypassing all this is easily possible.
.text:0072D0E6 call ds:timeGetTime
.text:0072D0EC mov ecx, off_926290
.text:0072D0F2 mov [ecx+60h], eax
.text:0072D0F5 mov edx, off_926290
.text:0072D0FB cmp dword ptr [edx+64h], 0
.text:0072D0FF jnz short loc_72D112
.text:0072D101 mov eax, off_926290
.text:0072D106 mov ecx, off_926290
.text:0072D10C mov edx, [ecx+60h]
.text:0072D10F mov [eax+64h], edx
.text:0072D112
.text:0072D112 loc_72D112: ; CODE XREF: sub_72D0C0+3Fj
.text:0072D112 mov eax, off_926290
.text:0072D117 mov ecx, off_926290
.text:0072D11D mov edx, [eax+60h]
.text:0072D120 sub edx, [ecx+64h]
.text:0072D123 mov eax, off_926290
.text:0072D128 mov ecx, [eax+10h]
.text:0072D12B add ecx, edx
.text:0072D12D mov edx, off_926290
.text:0072D133 mov [edx+10h], ecx
.text:0072D136 mov eax, off_926290
.text:0072D13B cmp dword ptr [eax+10h], 5999999
.text:0072D142 jbe short loc_72D151
.text:0072D144 mov ecx, off_926290
.text:0072D14A mov dword ptr [ecx+10h], 5999999
.text:0072D151
.text:0072D151 loc_72D151: ; CODE XREF: sub_72D0C0+82j
.text:0072D151 mov edx, off_926290
.text:0072D157 mov eax, off_926290
.text:0072D15C mov ecx, [eax+60h]
.text:0072D15F mov [edx+64h], ecx
That's the code it traditionally uses. Reducing it to use only frames as its counter it becomes:
.text:0072D0E6 mov eax, off_926290
.text:0072D128 mov ecx, [eax+10h]
.text:0072D12B inc ecx
.text:0072D12D mov edx, off_926290
.text:0072D133 mov [edx+10h], ecx
.text:0072D136 mov eax, off_926290
.text:0072D13B cmp dword ptr [eax+10h], 99999
.text:0072D142 jbe short loc_72D162
.text:0072D144 mov ecx, off_926290
.text:0072D14A mov dword ptr [ecx+10h], 99999
Tada! Now it will count per frame with an upper limit of 99999 frames (max time of 27:46.65. If you take this long you're doing something HORRIBLY wrong). That doesn't solve everything, however as now we have to change the display. It's located at 0x72D485 which is a long function that I'm about to overwrite. This function is used in four places so it's probably holding times for the other mini-games as well. Specifically the highway chase and submarine games.
.text:0072D485 ; int __stdcall formatTimeString(int TimerValue, int DestinationMemory)
.text:0072D485 formatTimeString proc near ; CODE XREF: sub_72994E+4Ep
.text:0072D485 ; sub_72D207+2Ep ...
.text:0072D485
.text:0072D485 var_10 = dword ptr -10h
.text:0072D485 var_C = dword ptr -0Ch
.text:0072D485 var_8 = dword ptr -8
.text:0072D485 var_4 = dword ptr -4
.text:0072D485 TimerValue = dword ptr 8
.text:0072D485 DestinationMemory= dword ptr 0Ch
.text:0072D485
.text:0072D485 push ebp
.text:0072D486 mov ebp, esp
.text:0072D488 sub esp, 10h
.text:0072D48B mov [ebp+var_10], ecx ;This is a pointless? line
.text:0072D48E mov eax, [ebp+TimerValue]
.text:0072D493 xor edx, edx
.text:0072D495 mov ecx, 10
.text:0072D49A div ecx
.text:0072D4B0 mov ecx, [ebp+DestinationMemory]
.text:0072D4B3 mov [ecx+10h], edx ;TimerValue % 10
.text:0072D49C mov [ebp+Result], eax ;now a tenth of what it was before
.text:0072D49F mov eax, [ebp+Result] ;This seems a little ridiculous too, but it's actually not
.text:0072D493 xor edx, edx
.text:0072D495 mov ecx, 10
.text:0072D49A div ecx
.text:0072D4B0 mov ecx, [ebp+DestinationMemory]
.text:0072D4B3 mov [ecx+0Ch], edx ;(TimerValue / 10) % 10
.text:0072D49C mov [ebp+Result], eax ;now a tenth of what it was before
.text:0072D49F mov eax, [ebp+Result]
.text:0072D493 xor edx, edx
.text:0072D495 mov ecx, 10
.text:0072D49A div ecx
.text:0072D4B0 mov ecx, [ebp+DestinationMemory]
.text:0072D4B3 mov [ecx+8h], edx ;(TimerValue / 100) % 10
.text:0072D49C mov [ebp+Result], eax ;now a tenth of what it was before
.text:0072D49F mov eax, [ebp+Result]
.text:0072D493 xor edx, edx
.text:0072D495 mov ecx, 10
.text:0072D49A div ecx
.text:0072D4B0 mov ecx, [ebp+DestinationMemory]
.text:0072D4B3 mov [ecx+4h], edx ;(TimerValue / 1000) % 10
.text:0072D49C mov [ebp+Result], eax ;now a tenth of what it was before
.text:0072D49F mov eax, [ebp+Result]
.text:0072D493 xor edx, edx
.text:0072D495 mov ecx, 10
.text:0072D49A div ecx
.text:0072D4B0 mov ecx, [ebp+DestinationMemory]
.text:0072D4B3 mov [ecx], edx ;(TimerValue / 10000) % 10
.text:0072D577 mov esp, ebp
.text:0072D579 pop ebp
.text:0072D57A retn 8
.text:0072D57A sub_72D485 endp
Because this is accessed in other places, it'd be safest to write this to some unused memory block. Now we have the digits stored in order in the DestinationMemory block in big-endian decimal. So we need to force this into the display string:
.text:0072D3EA ; int __stdcall sub_72D3EA(int DestinationString, int FormattedTimeValue, char FF7TextOffset)
.text:0072D3EA sub_72D3EA proc near ; CODE XREF: sub_72D207+7Bp
.text:0072D3EA ; sub_72D333+60p ...
.text:0072D3EA
.text:0072D3EA var_4 = dword ptr -4
.text:0072D3EA DestinationString= dword ptr 8
.text:0072D3EA FormattedTimeValue= dword ptr 0Ch
.text:0072D3EA FF7TextOffset = byte ptr 10h
.text:0072D3EA
.text:0072D3EA push ebp
.text:0072D3EB mov ebp, esp
.text:0072D3ED push ecx
.text:0072D3EE mov [ebp+var_4], ecx ;I have no idea what purpose this serves
.text:0072D3F1 movsx eax, [ebp+FF7TextOffset]
.text:0072D3F5 mov ecx, [ebp+FormattedTimeValue]
.text:0072D3F8 mov edx, [ecx]
.text:0072D3FA add edx, eax
.text:0072D3FC mov eax, [ebp+DestinationString]
.text:0072D3FF mov [eax], dl
.text:0072D401 movsx ecx, [ebp+FF7TextOffset]
.text:0072D405 mov edx, [ebp+FormattedTimeValue]
.text:0072D408 mov eax, [edx+4]
.text:0072D40B add eax, ecx
.text:0072D40D mov ecx, [ebp+DestinationString]
.text:0072D410 mov [ecx+1], al
.text:0072D413 movsx edx, [ebp+FF7TextOffset]
.text:0072D417 mov eax, [ebp+FormattedTimeValue]
.text:0072D41A mov ecx, [eax+8]
.text:0072D41D add ecx, edx
.text:0072D41F mov edx, [ebp+DestinationString]
.text:0072D422 mov [edx+2], cl
.text:0072D425 movsx eax, [ebp+FF7TextOffset]
.text:0072D429 mov ecx, [ebp+FormattedTimeValue]
.text:0072D42C mov edx, [ecx+0Ch]
.text:0072D42F add edx, eax
.text:0072D431 mov eax, [ebp+DestinationString]
.text:0072D434 mov [eax+3], dl
.text:0072D437 movsx ecx, [ebp+FF7TextOffset]
.text:0072D43B mov edx, [ebp+FormattedTimeValue]
.text:0072D43E mov eax, [edx+10h]
.text:0072D441 add eax, ecx
.text:0072D443 mov ecx, [ebp+DestinationString]
.text:0072D446 mov [ecx+4], al
.text:0072D449 movsx edx, [ebp+FF7TextOffset]
.text:0072D44D mov eax, [ebp+FormattedTimeValue]
.text:0072D450 mov ecx, [eax+14h]
.text:0072D453 add ecx, edx
.text:0072D455 mov edx, [ebp+DestinationString]
.text:0072D458 mov [edx+5], cl
.------------------------------------------------------------ chunk erased
.text:0072D47F mov esp, ebp
.text:0072D481 pop ebp
.text:0072D482 retn 0Ch
.text:0072D482 sub_72D3EA endp
FF7TextOffset in this case should be 10h because that's where the '0' character is in its text map.
This function, again, is referenced in other locations so it might be best to write this somewhere else too.
So the formatting string needs to be changed to:
.data:009568A0 TimerText db '00000', 00 ; DATA XREF: sub_72D207+73o
Just a null-terminated string of 5 zeroes. This forces the display of the time to have leading zeroes, but that's good for the leaderboard for all the text to be aligned.
-
Impressive. Most impressive. G-Bike doesnt have a timer though! :)
-
G-Bike doesnt have a timer though! :)
Does it not? Then why is it referenced in FIVE different places? The only minigame I could think of that uses timers is snowboard and submarine. Why are there three other invocations?
EDIT:
That's probably not the end of that conversation. I don't know how the rankings and leaderboards are displayed and it will probably need some editing as well. Make those changes and tell me what's wrong and I can fix it.
-
I'm probably gonna try using floating point to get this done so that we don't need to mess about with display and what not, but that might prove difficult. I'm looking at how Delphi compiles it...
The gbike may have once been intended to use a timer, and maybe it does internally, but there is no display for it in game.
The frame count code above is interesting nonetheless!
As for the ranks, they are just a small table with 4 byte millisecond times for each rank on each course. (with 1 that is not used per course... was removed outside of Japan). If I use the above way, I'll fix it up!
International difficulty... If I got it right.
00926470 = 20 CB 00 00 F0 D2 00 00 C0 DA 00 00 60 EA 00 00
00926480 = E8 FD 00 00 E0 28 01 00 90 5F 01 00 FF FF FF FF
00926490 = D0 01 01 00 70 11 01 00 28 1D 01 00 E0 28 01 00
009264A0 = 80 38 01 00 A0 86 01 00 C0 D4 01 00 FF FF FF FF
009264B0 = 70 11 01 00 F8 24 01 00 80 38 01 00 08 4C 01 00
009264C0 = 18 73 01 00 B0 AD 01 00 D0 FB 01 00 FF FF FF FF
http://stackoverflow.com/questions/8804770/how-to-divide-floating-point-number-in-x86-assembly
This might help me.
-
Another way to do this, which is a bit "ugh" but I'm gonna use it:
Use integer values and cheat. Have it log which loop it is on and add as follows
loop 1: add 16
loop 2: add 16
loop 3: add 18 and loop now equals 1 again. Repeats process.
16+16+18= 50
16.6666*3 = 50.
This makes sure that every 3rd loop the correct millisecond value is being written.
edit
And here it is. Give it a check for me if you have time and see if it can be made simpler.
72D0E6 = A1 90 62 92 00 8B 48 10 8A 15 AF 68 95 00 80 FA 01 7F 07 83 C1 10 FE C2 EB 05 83 C1 12 B2 00 EB 2B 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 88 15 AF 68 95 00 89 48 10 81 78 10 7F 8D 5B 00 76 1E
Run through
mov eax,[00926290]
mov ecx,[eax+10] // move current timer to ecx
mov dl,[009568AF] // move current flag to dl
cmp dl,01
jg 0072D100 //if flag is not 0 or 1
add ecx,10 // else add 16
inc dl // and increment flag
jmp 0072D105 // and jump to mov [009568AF],dl
add ecx,12 // add 18 if flag is 2
mov dl,00 // reset flag
mov [009568AF],dl //place flag value to memory.
mov [eax+10],ecx // finally place new time to memory
This will zero the flag on game start.
726029 = C7 40 10 00 00 00 00 C6 05 AF 68 95 00 00 90 90 90 90 90 C6 40 68 00
-
OK so pending any fixes that NFITC1 notices need making with the above code, the snowboard timer is fixed. Next is the submarine timer, which runs on much the same idea (except this time it already runs based on frame count), and starts
0x77E7CD
A lot of this seems to be redundant, but I am really not sure about it..
Firstly, the minigame isn't running at 30fps (probably due to the main game timer bug that Dziugo was rewriting and then got busy. Really hope he is still around to fix that.), so even when you get the code perfect, the timer isn't going to run at a true speed until it is.
In any case, this is what I have so far:
mov ecx,[00E7476C] // flag for pause and end of game
cmp ecx,00
jg // skip adding 2 to ebp-3c if game is paused or ended
mov [ebp-3C],00000002
mov ecx,[00980DC8]
add ecx,[ebp-3C]
mov [00980DBC],X // needed for game over screen timer. Not sure what value it needs. Probably 2.
mov [00980DC8],ecx
mov edx,[00980DD0]
sub edx,[00980DC8]
mov [00980DCC],edx
mov eax,[00980DC8]
Honestly, I don't know what's going on further down. I can't understand why a value of 2 seems to be correct either (it might not be!). The game is running at 30fps (which is correct for this minigame. On PSX the frames just get doubled) though this time and not 60, so that probably has something to do with it. Help needed. :-D
The parsing calculation seems to be at 0x7928E9
AH GOOD!! I've finally found where that damn delay is! I need my weapon mod to have subs able to fire straight away. The delay before they can fire is set at 0x77E794
edit
Sub game seems to use frame count from the get go... so that makes this far less complicated. I think this timer is fixed pending any corrections. And there should be some because I cobbled together this fix and have no idea if I've totally buggered something up somewhere.