Ok, so I found that the NVRAM is read only once at boot-up and presumably the game keeps all data in RAM, writing through to NVRAM when something changes. The table in RAM has an entirely different format. The first entry for the beginner course (default: JUN 5:00:00) is here:
Code: Select all
00106f50h: 01 00 00 00 00 00 42 CC 00 00 00 00 4A 55 4E 00 ; ......BÌ....JUN.
00106f60h: 02 00 00 00 00 00 43 B0 00 00 00 00 4E 41 4F 00 ; ......C°....NAO.
00106f70h: 03 00 00 00 00 00 44 94 00 00 00 00 4B 41 5A 00 ; ......D”....KAZ.
00106f80h: 04 00 00 00 00 00 45 78 00 00 00 00 48 49 44 00 ; ......Ex....HID.
00106f90h: 05 00 00 00 00 00 46 5C 00 00 00 00 4B 59 4F 00 ; ......F\....KYO.
00106fa0h: 01 00 00 00 00 00 21 66 00 00 00 00 4E 49 53 00 ; ......!f....NIS.
00106fb0h: 01 00 00 00 00 00 42 CC 00 00 00 00 52 4E 47 00 ; ......BÌ....RNG.
00106fc0h: 02 00 00 00 00 00 43 B0 00 00 00 00 53 41 4F 00 ; ......C°....SAO.
00106fd0h: 03 00 00 00 00 00 44 94 00 00 00 00 49 53 41 00 ; ......D”....ISA.
00106fe0h: 04 00 00 00 00 00 45 78 00 00 00 00 53 41 54 00 ; ......Ex....SAT.
00106ff0h: 05 00 00 00 00 00 46 5C 00 00 00 00 4A 41 4D 00 ; ......F\....JAM.
00107000h: 01 00 00 00 00 00 21 66 00 00 00 00 4E 49 53 00 ; ......!f....NIS.
Address 0x00106f56 (which contains 0x42cc) is the time.
This is read as a 32-bit word from 0x00106f54 at PC=0x85120:
Code: Select all
0x00085120: 0x81500004 lwz r10,0x04(r16) <-- this will load r10 with the value 0x42cc (5:00:00)
0x00085124: 0xC0300008 lfs f1,0x08(r16) <-- a floating point load, but the value is 0
0x00085128: 0x3821FFE0 addi r1,r1,-0x20
0x0008512C: 0x90610000 stw r3,0x00(r1)
0x00085130: 0xD8210008 stfd f1,0x08(r1)
0x00085134: 0x386A0000 addi r3,r10,0x00
0x00085138: 0xFC200890 fmr f1,f1
0x0008513C: 0x4BFC8379 bl 0x0004D4B4 <-- need to check what this subroutine does
0x00085140: 0xC8210008 lfd f1,0x08(r1)
0x00085144: 0x80610000 lwz r3,0x00(r1)
0x00085148: 0x38210020 addi r1,r1,0x20
0x0008514C: 0x3821FFE0 addi r1,r1,-0x20
0x00085150: 0x90610000 stw r3,0x00(r1)
0x00085154: 0x90810004 stw r4,0x04(r1)
0x00085158: 0x90A10008 stw r5,0x08(r1)
0x0008515C: 0x90C1000C stw r6,0x0C(r1)
0x00085160: 0x38400064 li r2,0x00000064 <-- 0x64 = 100, probably the code for centiseconds
0x00085164: 0x7C6013D6 divw r3,r0,r2
0x00085168: 0x7CC311D6 mullw r6,r3,r2
0x0008516C: 0x7CC60050 sub r6,r0,r6
0x00085170: 0x3840003C li r2,0x0000003C <-- 0x3C = 60, (minutes or seconds)
0x00085174: 0x7C0313D6 divw r0,r3,r2
0x00085178: 0x7CA011D6 mullw r5,r0,r2
0x0008517C: 0x7CA51850 sub r5,r3,r5
0x00085180: 0x7C6013D6 divw r3,r0,r2
0x00085184: 0x7C8311D6 mullw r4,r3,r2
0x00085188: 0x7C840050 sub r4,r0,r4
0x0008518C: 0x90610010 stw r3,0x10(r1)
0x00085190: 0x90810014 stw r4,0x14(r1)
0x00085194: 0x90A10018 stw r5,0x18(r1)
0x00085198: 0x90C1001C stw r6,0x1C(r1)
0x0008519C: 0x80610000 lwz r3,0x00(r1)
0x000851A0: 0x80810004 lwz r4,0x04(r1)
0x000851A4: 0x80A10008 lwz r5,0x08(r1)
0x000851A8: 0x80C1000C lwz r6,0x0C(r1)
0x000851AC: 0x80010010 lwz r0,0x10(r1)
0x000851B0: 0x80410014 lwz r2,0x14(r1)
0x000851B4: 0x1C00003C mulli r0,r0,0x3C <-- 60 used again
0x000851B8: 0x7C420214 add r2,r2,r0
0x000851BC: 0x90410014 stw r2,0x14(r1)
0x000851C0: 0x81010014 lwz r8,0x14(r1)
0x000851C4: 0x80E10018 lwz r7,0x18(r1)
0x000851C8: 0x80C1001C lwz r6,0x1C(r1)
0x000851CC: 0x38210020 addi r1,r1,0x20
0x000851D0: 0x3930000C addi r9,r16,0x0C
0x000851D4: 0x3821FFE0 addi r1,r1,-0x20
0x000851D8: 0x9041001C stw r2,0x1C(r1)
0x000851DC: 0x91210000 stw r9,0x00(r1)
0x000851E0: 0x91010004 stw r8,0x04(r1)
0x000851E4: 0x90E10008 stw r7,0x08(r1)
0x000851E8: 0x90C1000C stw r6,0x0C(r1)
0x000851EC: 0x4BF83C81 bl 0x00008E6C
... lots more code follows ...
The subroutine at 0x4d4b4 is *very* interesting. I had to step through it with the debugger to see what it produces and I don't fully understand how. But, basically, it converts the integral frame count into a floating point number using a pretty weird set of operations. It then takes that and multiplies it by 100.0/57.0, which converts number of frames -> total centiseconds. It then casts that to an integer and returns it in register r0.
Code: Select all
; r3 = frame count
0x0004D4B4: 0x3821FFE0 addi r1,r1,-0x20
0x0004D4B8: 0xD8210000 stfd f1,0x00(r1)
0x0004D4BC: 0xD8410008 stfd f2,0x08(r1)
0x0004D4C0: 0x3821FFF0 addi r1,r1,-0x10
0x0004D4C4: 0x3C40000C li r2,0x000C0000
0x0004D4C8: 0xC0028440 lfs f0,-0x7BC0(r2) <-- some magic value into f2
0x0004D4CC: 0x6C608000 xori r0,r3,0x80000000 <-- frame count | 0x80000000
0x0004D4D0: 0xD8010000 stfd f0,0x00(r1) <-- store 64-bit double to memory
0x0004D4D4: 0x90010004 stw r0,0x04(r1) <-- overwrite lower 32-bits with integer frame count (?!)
0x0004D4D8: 0xC8410000 lfd f2,0x00(r1) <-- load the 64-bit double back to f2
0x0004D4DC: 0xFC420028 fsub f2,f2,f0 <-- this voodoo somehow produces the decimal value of frame count in f2!
0x0004D4E0: 0x38210010 addi r1,r1,0x10
0x0004D4E4: 0xFC401018 frsp f2,f2 <-- round to single precision
0x0004D4E8: 0xEC21102A fadds f1,f1,f2 <-- f1 is 0 before this point (but it may be some other offset?), so now f1 = frame_count
0x0004D4EC: 0x3C40000C li r2,0x000C0000
0x0004D4F0: 0xC0428444 lfs f2,-0x7BBC(r2) <-- f2 = 1.754386 (seems to be 100.0/57.0)
0x0004D4F4: 0xEC2100B2 fmuls f1,f1,f2 <-- f1 = frame_count * (100.0/57.0)
0x0004D4F8: 0xFC00081C fctiw f0,f1 <-- f0 = int(f1)
0x0004D4FC: 0x3821FFF0 addi r1,r1,-0x10
0x0004D500: 0x7C000FAE stfiwx f0,0,r1 <-- f0 (as integer) -> (r1)
0x0004D504: 0x80010000 lwz r0,0x00(r1) <-- r0 = f0 (as integer)
0x0004D508: 0x38210010 addi r1,r1,0x10
0x0004D50C: 0xC8410008 lfd f2,0x08(r1)
0x0004D510: 0xC8210000 lfd f1,0x00(r1)
0x0004D514: 0x38210020 addi r1,r1,0x20
0x0004D518: 0x4E800020 bclr 0x14,0
Resuming execution after the subroutine call, we can now figure out what is going on (remember that r0 holds total centiseconds elapsed):
Code: Select all
0x00085160: 0x38400064 li r2,0x00000064 <-- 0x64 = 100
0x00085164: 0x7C6013D6 divw r3,r0,r2 <-- r3 = total_centiseconds / 100 = whole_seconds
0x00085168: 0x7CC311D6 mullw r6,r3,r2 <-- r6 = whole_seconds * 100 = whole_seconds_in_centiseconds
0x0008516C: 0x7CC60050 sub r6,r0,r6 <-- r6 = total_centiseconds - whole_seconds_in_centiseconds = centiseconds (to display)
0x00085170: 0x3840003C li r2,0x0000003C <-- 0x3C = 60, (minutes or seconds)
0x00085174: 0x7C0313D6 divw r0,r3,r2 <-- r0 = whole_seconds / 60 = whole_minutes
0x00085178: 0x7CA011D6 mullw r5,r0,r2 <-- r5 = whole_minutes* 60 = whole_minutes_in_seconds
0x0008517C: 0x7CA51850 sub r5,r3,r5 <-- r5 = whole_seconds - whole_minutes_in_seconds = seconds (to display)
0x00085180: 0x7C6013D6 divw r3,r0,r2 <-- r3 = whole_minutes/ 60 = whole_hours
0x00085184: 0x7C8311D6 mullw r4,r3,r2 <-- r4 = whole_hours * 60 = whole_hours_in_minutes
0x00085188: 0x7C840050 sub r4,r0,r4 <-- r4 = whole_minutes - whole_hours_in_minutes = minutes (to display)
0x0008518C: 0x90610010 stw r3,0x10(r1)
0x00085190: 0x90810014 stw r4,0x14(r1)
0x00085194: 0x90A10018 stw r5,0x18(r1)
0x00085198: 0x90C1001C stw r6,0x1C(r1)
0x0008519C: 0x80610000 lwz r3,0x00(r1)
0x000851A0: 0x80810004 lwz r4,0x04(r1)
0x000851A4: 0x80A10008 lwz r5,0x08(r1)
0x000851A8: 0x80C1000C lwz r6,0x0C(r1)
0x000851AC: 0x80010010 lwz r0,0x10(r1)
0x000851B0: 0x80410014 lwz r2,0x14(r1)
0x000851B4: 0x1C00003C mulli r0,r0,0x3C <-- 60 used again
...
In Python
Code: Select all
from typing import Tuple
nvram_frames = 0x1eba #0x42cc # 0x2166
def compute_total_centiseconds(num_frames: int) -> int:
return int(float(num_frames) * 100.0 / 57.0)
def compute_display_time(num_frames: int) -> Tuple[int, int, int]:
total_centiseconds = compute_total_centiseconds(num_frames)
whole_seconds = total_centiseconds // 100
centiseconds = total_centiseconds - (whole_seconds * 100)
whole_minutes = whole_seconds // 60
seconds = whole_seconds - (60 * whole_minutes)
whole_hours = whole_minutes // 60
minutes = whole_minutes - (60 * whole_hours)
return (minutes, seconds, centiseconds)
min, sec, csec = compute_display_time(num_frames=nvram_frames)
print(f"{min}:{sec:02d}:{csec:02d}")
I think this yields the same results as the integer version. Specifically, 0x1eba still comes out as 2:18:00 but I am now confident this is correct. To prove this, I overwrote one of the default times with 0x1eba and below is what I got (see the last entry, for NIS)!

- Clipboard01.jpg (97.64 KiB) Viewed 59325 times
So why does yours differ? Can you tell me which version of Supermodel you are using (the specific git revision)? I suspect there is some sort of rounding issue happening on your machine. There were some fixes to the PowerPC rounding code in the last few months, too.