Author Topic: FF7: Why isn't the Countdown Timer hardcoded?  (Read 8608 times)

antd

  • *
  • Posts: 49
    • View Profile
FF7: Why isn't the Countdown Timer hardcoded?
« on: 2013-09-09 18:07:49 »
In the first reactor, after defeating Guard Scorpion, the countdown timer is set to 10 minutes (600 seconds).

I debugged this area of code and it seems a little strange to me.

The timer is set to 600 seconds (0x258) in this way:

Code: (Assembly) [Select]
; Set Countdown Timer to 0x258

; dw_CDSeconds = Countdown Timer RAM address

mov     cl, [edx+eax] ECX=0000040A
mov     [ebp+var_4], cl
mov     al, [ebp+var_4] EAX=0000000A    ; 10!

...
imul    eax, 3Ch                EAX=00000258    ; 10 * 60 = 600
mov     ecx, [ebp+var_4] ECX=00000000
add     ecx, eax         ECX=00000258
mov     [ebp+var_4], ecx ECX=00000258

...
mov     edx, [ebp+var_4] EDX=00000258

...
mov     eax, [ebp+var_4] EAX=00000258   
mov     dw_CDSeconds, eax                       ; 600 seconds for the countdown timer

It does many things in between and I understand the need to push to the stack, etc.

However, why does it go to the trouble of using the expensive imul instruction?
And why calculate 0x258 instead of using it directly as an immediate?

Would it not be more efficient to do something like:
Code: (Assembly) [Select]
mov     eax, 258h
mov     dw_CDSeconds, eax

I'm thinking they didn't do this because the countdown routine will be used in places other than the first reactor, and thus use a different seconds value (not 10 minutes)? >.<

I'm not a programmer. Please explain!
« Last Edit: 2013-09-09 18:15:42 by antd »

sithlord48

  • *
  • Posts: 1635
  • Dark Lord of the Savegame
    • View Profile
    • Blackchocobo
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #1 on: 2013-09-09 18:47:25 »
the countdown is used several times durring the game and is saved in your save game. there are different times used for the different events. so i would say that your guess is correct. also after you beat the guard scorpion you can save. if the timer was hardcoded it would just start back @ 10 min when you loaded your save.

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #2 on: 2013-09-09 19:31:47 »
Because they wrote a "SetCountDownMinutes" method and not a "SetCountDownSeconds". Why? Not really sure. It's a difference of

SetCountDownMinutes( 10 )
vs
SetCountDownSeconds( 10 * 60 )

Which is essentially the same way of doing it. Some compilers will pre-process that 10 * 60 and translate it to 600 and some will calculate it during runtime to be the parameter. Based on how the rest of the code looks I'd say it's the latter. I'm not sure why they didn't just make everything by seconds unless they only wanted to use increments of minutes.

antd

  • *
  • Posts: 49
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #3 on: 2013-09-09 23:22:41 »
Perfect answers, thanks.

Have I understood correctly:
The 60 is just to convert the unit to seconds. The programmer specified the timer in minutes.
The 60 is hardcoded because the timer is always going to be based on seconds.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #4 on: 2013-09-09 23:49:15 »
Because they wrote a "SetCountDownMinutes" method and not a "SetCountDownSeconds". Why? Not really sure. It's a difference of

SetCountDownMinutes( 10 )
vs
SetCountDownSeconds( 10 * 60 )

Which is essentially the same way of doing it. Some compilers will pre-process that 10 * 60 and translate it to 600 and some will calculate it during runtime to be the parameter. Based on how the rest of the code looks I'd say it's the latter. I'm not sure why they didn't just make everything by seconds unless they only wanted to use increments of minutes.

Let's be fair, ff7 has a lot of issues like that.  It's really fucked up.

halkun

  • Global moderator
  • *
  • Posts: 2097
  • NicoNico :)
    • View Profile
    • Q-Gears Homepage
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #5 on: 2013-09-10 10:57:32 »
Keep in mind that the core of the game was also ported from a MIPS CPU using 65316 template code. Things can get a little odd.

sithlord48

  • *
  • Posts: 1635
  • Dark Lord of the Savegame
    • View Profile
    • Blackchocobo
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #6 on: 2013-09-10 14:58:14 »
the times are all stored differently . the play time and description time are stored in seconds (4 bytes) . the countdown time is  stored in 3 bytes for HH:MM:SS (Possibly has a 4th byte for padding) . The snowboard times are stored in 4 bytes  uses MM:SS:TENTHS::PADDING instead(snowboard times may only use 3 bytes as well just is currenly in FF7tk as 4 not reading or writing the 4th byte ).

« Last Edit: 2013-09-10 15:08:32 by sithlord48 »

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #7 on: 2013-09-10 16:01:53 »
Snowboard is stored in milliseconds.  If I remember its 3 bytes but not sure.

sithlord48

  • *
  • Posts: 1635
  • Dark Lord of the Savegame
    • View Profile
    • Blackchocobo
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #8 on: 2013-09-10 19:58:04 »
its 4 bytes one is padding . but it might be in milliseconds i have to check the display code. the raw vaules only show me show much :D. the game is super inconsistent with that kind of stuff...

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #9 on: 2013-09-10 20:14:36 »
Yeah 1 of the bytes is padding.  pretty sure it's milliseconds... had to update them for Weapon mod back to the international values.  problem is, the minigame timer uses timegettime, and so it's unfair..  if the minigame doesnt run at a precise 60fps you are losing or gaining time.  I may have asked this before... NFITC, how hard would it be to change the timer to use frame based calculations instead? (same for submarine minigame).
« Last Edit: 2013-09-10 20:16:37 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #10 on: 2013-09-10 21:26:37 »
NFITC, how hard would it be to change the timer to use frame based calculations instead? (same for submarine minigame).

Not too hard at all I think. There seems to be once instance of 444h that influences a game's timer. That indicates that a single frame of a 60fps display region has passed. GameTimerFraction is incremented by 444h each time this is run and when it exceeds 65535 the GameTimer (in seconds) gets incremented by 1 and GameTimerFraction gets AND'd by FFFFh so the remaining fraction is preserved. Since I don't see GameTimerFraction modified anywhere else I'll have to assume that this is the only place this happens (There's another suspect function that alters GameTimer at 0x6CCDA5, but that's not referenced anywhere) and that it runs four times per frame during battle and twice per frame during field events.

Anyway, here's the code:
Code: [Select]
.text:0040ABD1 loc_40ABD1:                             ; CODE XREF: fptc+44j
.text:0040ABD1                 mov     ecx, GameTimerFraction
.text:0040ABD7                 add     ecx, 444h
.text:0040ABDD                 mov     GameTimerFraction, ecx
.text:0040ABE3                 mov     edx, GameTimerFraction
.text:0040ABE9                 shr     edx, 10h
.text:0040ABEC                 test    edx, edx
.text:0040ABEE                 jz      short loc_40AC0F
.text:0040ABF0                 mov     eax, GameTimer
.text:0040ABF5                 add     eax, 1
.text:0040ABF8                 mov     GameTimer, eax
.text:0040ABFD                 mov     ecx, GameTimerFraction
.text:0040AC03                 and     ecx, 0FFFFh
.text:0040AC09                 mov     GameTimerFraction, ecx

Here's how it should read if you want it to be per frame:

Code: [Select]
.text:0040ABD1 loc_40ABD1:                             ; CODE XREF: fptc+44j
.text:0040ABD1                 mov     cl, GameTimerFraction
.text:0040ABD7                 inc     cl
.text:0040ABDD                 mov     GameTimerFraction, cl
.text:0040ABE3                 mov     dl, GameTimerFraction
.text:0040ABE9                 cmp     dl, 3Ch
.text:0040ABEE                 jl      short loc_40AC0F
.text:0040ABF0                 mov     eax, GameTimer
.text:0040ABF5                 add     eax, 1
.text:0040ABF8                 mov     GameTimer, eax
.text:0040AC09                 mov     GameTimerFraction, 0

The addresses are now off, but you get the idea. The Countdown timer is located right below this script, but it doesn't have to be modified if you don't want it to be. I don't see where the snowboard game's timer is. Can you point me to it?
« Last Edit: 2013-09-10 21:31:20 by NFITC1 »

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #11 on: 2013-09-10 23:54:53 »
Cool!

There is a timegettime call for snowboard at 0x72D0E6
call dword ptr [007B6374]

If I am following you right, since the code is simply iterating at that point, essentially you just remove the timegettime and do a normal increment instead?

How do I get it to increment correctly in milliseconds (which Snowboard uses)?  I am probably going way off base here and missing something, but...

if the function is called 60 times a second (which is the correct framerate for snowboard btw), then the calc

1000 / 60 won't work, because you will have to choose increment of 16 or 17, and best result is 20 extra milliseconds for every 1 second (60*17).

I must be missing some really easy calculation here?  Basically, how do I update the millisecond timer to work properly, when the number of loops is 60 a second.
« Last Edit: 2013-09-11 14:16:55 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #12 on: 2013-09-11 14:31:39 »
There never was a timegettime call to calculate gametime. It's probably because they anticipate frame lag so they have a specific frame timer count. If you want to base it around the timegettime function it'll take adding a function and a holder address.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #13 on: 2013-09-11 14:38:45 »
It uses timegettime for the delta time (difference between frames) yes :) the problem with this is, it means even if the game runs slow, the timer will remain constant which is unfair.  What I need is for snowboard to be completely based on the frames. So if 60 frames have been played, no matter how fast/slow, that will be precisely 1 second. The issue with snowboard and sub game is that the game play is advancing by frames, but the timer is always being true (because of timegettime call) to realtime.  This isn't fair to the player.  If for whatever reason, there is ANY drop in frame rate, they are at a disadvantage, because the timer will say they have played x milliseconds, when in fact, for part of that, there was no frame advancement in actual game play.  You can see the place where it adds on the difference in time in the same area I mentioned.  (add ecx,edx.  Right before cmp [edx+10],005B8D7F)

I can get around this by simply adding 17 to the timer every loop (17*60 = 1 second), but this means 1 second is 1020 milliseconds instead of 1000.  I am unsure in assembly how to fix this. A really easy way around it would be to just fiddle the code so that the milliseconds are actually the frame count... but this is cheating and looks crap (although it's more correct when you consider milliseconds is bollocks at 60fps).

« Last Edit: 2013-09-11 14:50:00 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #14 on: 2013-09-11 15:29:26 »
In that function you pointed me to it looks like the time is saved in milliseconds (at least for the snowboard game). If we consider 0x926920 to be the base of the array data[] then we can follow what's happening.


Code: [Select]
data[60h] = timegettime();
if ( data[64h] == 0 )
    data[64h] = data[60h]; //delta is now 0
data[10h] += data[60h] - data[64h];
if ( data[10h] > 5999999 )
    data[10h] = 5999999; //upper time limit of 99:59.999
data[64h] = data[60h]

//Then a method is called at 0x72D207 which takes data[10h] as one of the parameters which parses that value into minutes, seconds, and milliseconds which gets stored at 0xDD86B0. Then irrelevant stuff happens.

data[64h] = timegettime //for delta later

So you could just have data[10h] += 17 each frame and it would still calculate milliseconds correctly because it's all stored in milliseconds. Then lag wouldn't make the timer look like it's going too fast.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #15 on: 2013-09-11 15:34:49 »
Yeah that looks spot on.

Although it is stored in milliseconds (so it would still work out from games perspective), I am a little bit of a perfectionist... and adding 17 would surely mean that every 50 seconds, the actual time is out by 1 second?  because 17*60 =1020 and not 1000.  Or are you saying that's just a price that has to be paid here?  I'd prefer it to actually be as close as possible... I am guessing to do this would require more work, and to work with floating point calculations?

« Last Edit: 2013-09-11 15:37:06 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #16 on: 2013-09-11 15:47:25 »
I'm saying that 17*60 = 1020 which the game would then translate as 1.020 seconds. Then adding another "frame" would make it 1.037 seconds. You're not losing anything because the data[10h] IS the timer stored in milliseconds that the game later separates. It's not truncating those .02 seconds.

The biggest catch is that some times are unobtainable. You can't get 1.000 or 2.000 or even 1:00.000. It would always be a multiple of 17 milliseconds.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #17 on: 2013-09-11 15:54:11 »
Oh yeah, I get that!   ;D 

Code: [Select]
mov edx,00000011
mov ecx,[eax+10]
add ecx,edx


It's just that what the person sees on screen and his actual time that he tells people about isn't... true.  It's not right, even if it is now completely fair between players.  When he sees a time of 51 seconds on screen, he's actually played 50.. even though in terms of calculation and play it will work that way on every play through.

Is that right?

What I was asking before was if it was possible to make the milliseconds not only calculate ok, but be true. So that when you have played 50 seconds, it still shows as close to 50 as possible.  There has to be a calculation that will do that using rounding and floating point?  or some other method?

=====

and yeah, I went 17 in hex above just then.  :-P  Corrected.
« Last Edit: 2013-09-11 16:16:04 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #18 on: 2013-09-11 17:06:10 »
Ack! I see what you're saying now. Yes, that would make the timer move faster than it should, wouldn't it? In that case, you could make data[60h] be a floating value and add 16.66667 and set data[10h] to the whole number equivalent to that each time.

Code: [Select]
if ( data[64h] == 0 )
     (float)data[64h] = (float)1000/(float)60; //get the right amount of precision here.
(float)data[60h] += (float)data[64h]
data[10h] = data[60h].toInt? //Admittedly, I'm not sure how to do this part.

I ran a "simulation" of this and the precision loss gets extreme as the timer gets higher. 52.000 is actually 52.00099. I understand how floating points work and this is probably the best you could hope for.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #19 on: 2013-09-11 17:10:37 »
Only prob there is I dunno how floating point code works in assembly (don't know how to use the registers and so forth)  I have a very basic grasp of that. 

The other way of doing this is to just increment by 1, and then make the second clock on for every 60 "milli" which would be basically the frame count.  I did this, and changed the code further down to parse it right.  Works a charm, but I think your idea would be best for making it look good.  Seeing milli with 0-59 doesn't look too good even if its 100% precision.  :-D

Tell you what, it would work better if I could make the millisecond format be 2 digits and not 3.  Even some TV studios do their countdowns that way (they use frame count for the smaller unit, like 25 for PAL).

Or...  just do away with time altogether and do a basic frame counter.  But not sure how to code that into the display.  It looks dodgy that code and format.


edit:

looks like making it 2 digits isnt too hard... just shift the digit write like at 72D458 to be edx+6 for example.

So yeah, sorry about hijacking this thread.  That's what I'm gonna do... just use frames for the 3rd number.    :evil:
« Last Edit: 2013-09-11 17:50:22 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #20 on: 2013-09-11 18:12:39 »
Code: [Select]
int __stdcall sub_72D485(int TimerValue, int DD86B0)
{
  minutes = TimerValue / 60000;
  DD86B0 = TimerValue / 60000 / 10;                      //X0:00.000
  DD86B4 = minutes - 10 * DD86B0;                        //0X:00.000
  seconds = (TimerValue - 60000 * minutes) / 1000;
  DD86B8 = seconds / 10;                                 //00:X0.000
  DD86C2 = seconds - 10 * DD86B8;                        //00:0X.000
  milliseconds = TimerValue - 60000 * v3 - 1000 * v4;
  DD86C6 = milliseconds / 100;                           //00:00.X00
  DD86D0 = (milliseconds - 100 * (DD86C6)) / 10;         //00:00.0X0
  DD86D4 = milliseconds - 100 * DD86C6 - 10 * DD86D0;    //00:00.00X
}

Those addresses populate the structure at 0x9568A0, which starts out as "00:00.000" in ff7text.

I think if you change the line:
Code: [Select]
.text:0072D473                 mov     [eax+8], dlto
Code: [Select]
.text:0072D473                 mov     [eax+8], 0h
Then it will either error or not display the millisecond count because it terminates the string early.

DLPB_

  • Banned
  • *
  • Posts: 11006
    • View Profile
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #21 on: 2013-09-11 18:32:06 »
Nice!

Turns out when it doesnt get written and stays 00 it doesnt appear anyway.

{============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 (60 now not 1000)
72D495 = B9 10 0E 00 00 (3600 frames in 1 minute)
72D4C6 = 6B C9 3C 90 90 90 (60 frames)
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
{============

That's a full list of changes I've made.  Just supposing I wanted to have the display purely as frame count (so frames are literally the time in a 5 digit display), how would that work?  By the looks of it, it updates every digit individually and the code could get messy.


A few other things need editing to make this all work proper also.
« Last Edit: 2013-09-11 18:56:23 by DLPB »

nfitc1

  • *
  • Posts: 3011
  • I just don't know what went wrong.
    • View Profile
    • WM/PrC Blog
Re: FF7: Why isn't the Countdown Timer hardcoded?
« Reply #22 on: 2013-09-11 18:58:42 »
The answer is pretty long. Can we take this to a new thread, appropriately titled what it is we're talking about? :)