Users browsing this thread: 2 Guest(s)
FF4 SNES SRM Checksum

#11
Posts: 7
Threads: 1
Thanks Received: 0
Thanks Given: 0
Joined: Feb 2017
Reputation: 0
Status
None
I wrote a quick MATLAB script (code below) that generates the correct checksum based on your comments and from the assembly you sent. It mostly works save for that pesky initial checksum value. The initial value at address $41 unfortunately does not seem to be 0, and further, it seems to depend on either the save slot or some combination of information contained within. Luckily the initial value is always a fairly low number (> -100 or so signed... ~>65460 if unsigned). Also, the same initial value for slot 0 on one file isn't necessarily the same initial value for slot 0 on another file.

Is there something from your ROM assembly source that specifies what goes into address $41? It's got to be deterministic and probably calculated during some part of the save routine.

I forgot how much I disliked assembly code (and all the direct, indirect, absolute, immediate, etc. addressing schemes)! If I had some easy way of getting the disassembly from the ROM and being able to debug it, I could look into it as well at some point. What sort of debugging/disassembly setup are you using?

*** MATLAB script follows in case anybody else would like to use it ***

% Open the FF2 SNES SRM save file
fid = fopen('FF4_in_use.srm', 'r');

% User selects which save slot to use (0, 1, 2, or 3)
slot_offset = 0;

% Position file pointer at correct byte offset in file
fseek(fid, (slot_offset*2048), 'bof');

% Read 2kB save slot data. Cast as 8-bit Unsigned Integer
bytes = fread(fid, 2048, 'uint8=>uint8');

% Store off checksum in file
checksum = (uint32(bytes(2045))*256) + uint32(bytes(2046));

% Apply starting offset (LDA $41) - Seems to be based on slot number?
if(slot_offset == 0)
D8acc = intmax('uint16') - 81;
end
if(slot_offset == 1)
D8acc = intmax('uint16') - 22;
end
if (slot_offset == 2)
D8acc = intmax('uint16') - 71;
end
if (slot_offset == 3)
D8acc = intmax('uint16') - 72;
end

% Compute 16-bit data values and accumulate. Loop 0x7fa times
% NUMber of ELements in BYTES - 6 = 0x7fa
for i=1Sadnumel(bytes)-6)
% Handle initial word 0x00bb
if(i==1)
a = 0;
b = bytes(i);
c = uint32(uint32(a)*256) + uint32(b);
% Handle other words 0xaabb
else
a = bytes(i-1);
b = bytes(i);
c = uint32(uint32(a)*256) + uint32(b);
end
% Look at sanity check hex value of 0xaabb
d = dec2hex(c, 4);
% Accumulate running checksum
D8acc = uint32(D8acc) + uint32©;
end

while (D8acc >= 65536)
D8acc = D8acc - 65536;
carry = 1;
end

% Get final calculated checksum and compare to save slot checksum
E8acc = dec2hex(D8acc, 4);
checksum_hex = dec2hex(checksum, 4);
if (E8acc == checksum_hex)
disp('CHECKSUMS MATCH');
else
disp('CHECKSUMS DO NOT MATCH');
end

fclose(fid);
  Find
Quote  

#12
Posts: 127
Threads: 8
Thanks Received: 21
Thanks Given: 12
Joined: Jan 2012
Reputation: 13
Status
None
The disassembly I posted before was made by me. I posted the full notes I have but if anyone has any FFIV disassembly I would be happy to get a copy.

I use bsnes+ for breakpoints, geiger's disassembler is also awesome but I'm using Mac Os X. I have a home made disassembler for auto generating the assembly code.

I took a look at $41, it is reset in two places at least:
Code:
$15c9ce when the game starts, I think this is where the RAM is cleared.
$018cb8 when a menu is opened (when loading opens and when main menu is opened)

I don't know what it does but I'm suspecting it's a high byte of $40, $40 is being reset all the time in the entry routine. I think it serves as some sort of deep game timer.

I think $41 (and maybe $40 aswell) is stored in the SRAM somewhere since the value is variable. Maybe next to the game timer?

This is all a lot of guesswork at the moment for me, I'll try to look into this later on.

Rough disassembly of the entry point for FFIV:
Code:
008000     20 2C 80     JSR $802C
008003     6B         RTL 
008004     20 1A BA     JSR $BA1A
008007     6B         RTL 
008008     20 BF C2     JSR $C2BF
00800B     6B         RTL 
00800C     20 92 95     JSR $9592
00800F     6B         RTL 
008010     20 5E 80     JSR $805E
008013     6B         RTL 
008014     20 95 CC     JSR $CC95
008017     6B         RTL 
008018     20 1A BA     JSR $BA1A
00801B     6B         RTL 
00801C     20 BB 82     JSR $82BB
00801F     6B         RTL 
008020     20 92 D7     JSR $D792
008023     6B         RTL 
008024     20 95 CC     JSR $CC95
008027     6B         RTL 
008028     20 6E 80     JSR $806E
00802B     6B         RTL 

00802C     8B         PHB 
00802D     0B         PHD 
00802E     7B         TDC 
00802F     8F 00 41 00     STA $004100.l
008033     A9 7E         LDA #$7E
008035     48         PHA 
008036     AB         PLB 
008037     20 45 80     JSR $8045
00803A     A9 80         LDA #$80
00803C     8F 00 21 00     STA $002100.l
008040     7B         TDC 
008041     EB         XBA 
008042     2B         PLD 
008043     AB         PLB 
008044     60         RTS 

008045     20 3A 87     JSR $873A
008048     20 7D 8C     JSR $8C7D
00804B     BA         TSX 
00804C     CA         DEX 
00804D     CA         DEX 
00804E     8E 65 1A     STX $1A65.w
008051     20 FB 87     JSR $87FB
008054     20 7E 94     JSR $947E
008057     4C 3F 87     JMP $873F
00805A     5C 00 80 03     JML $038000
00805E     48         PHA 
00805F     A9 00         LDA #$00
008061     8F 40 01 00     STA $000140.l
008065     22 03 FD 14     JSL $14FD03
008069     A9 00         LDA #$00
00806B     EB         XBA 
00806C     68         PLA 
00806D     60         RTS 


@assasin: Sounds right, different way to compute the same checksum. Your math impresses me.
  Find
Quote  

#13
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
argh, guess i was a bit off..  i forgot that the bottom half's sums can carry into the top half, and then the top half's into the bottom half via Carry Flag.
Quote  

#14
Posts: 7
Threads: 1
Thanks Received: 0
Thanks Given: 0
Joined: Feb 2017
Reputation: 0
Status
None
Looks like I'm not able to find any type of magic offset stored in the SRAM file. I've tried looping through both all 8-bit words and all 16-bit words as the initial value with no repeatable luck. I get a few matches in various places but nothing that is consistent from one slot to the next or one file to the next.

I just found Geiger's debugger. Is there a way to set a trigger (breakpoint) on when the user selects to save a file? Or do you absolutely have to know the address that you want to break at? Is there also a traceback feature? I saw the "Trace From" option, but again it seems to imply that you need to know starting and ending address points.
  Find
Quote  

#15
Posts: 127
Threads: 8
Thanks Received: 21
Thanks Given: 12
Joined: Jan 2012
Reputation: 13
Status
None
I think you can break when addresses get read or written (aswell as executed). SRAM should be at $700000 so a breakpoint for write in that range should give you the save routine.

For a SNES memory map see this link:
https://en.wikibooks.org/wiki/Super_NES_..._map#LoROM

I found the checksum routine by having a read breakpoint for $7007FE.

I think the whole save slot is copied as a big chunk from WRAM though so I'm guessing $41 would be shadowed into the save slot? Then again you said you tested the whole SRAM file, so it's probably not there, I'm confused to how this might work.

Save routine is a good starting point though, good idea.
  Find
Quote  

#16
Posts: 7
Threads: 1
Thanks Received: 0
Thanks Given: 0
Joined: Feb 2017
Reputation: 0
Status
None
Aha! I got the debugger working and quickly figured out my goofs. 1) I had neglected to increment the 16-bit accumulated value when there was a carry. (I knew the C in ADC wasn't there for kicks!) 2) I was incorrectly adding a word at the beginning of the data block.

After tweaking my code, it works every time. The best part? That mysterious initial value? It's 0. No need to investigate all of that $41 business.

Interestingly, my disassembly addresses were slightly off from what yours were. Maybe that's down to a tool difference?

Thank you so much for the quick debugging/breakpoint how-to. It saved the day!
  Find
Quote  

#17
Posts: 127
Threads: 8
Thanks Received: 21
Thanks Given: 12
Joined: Jan 2012
Reputation: 13
Status
None
Awesome to hear! 

Thanks for asking an interesting question and being an avid hacker yourself. On this forum we love to answer questions.

The disassembly difference could be the tool, could also be that I used a japanese ROM. In retrospect that was stupid of me and I should note the version of the ROM when posting disassemblies. Sadly my emulators don't display that information conveniently, I can say my ROM had CRC32: CAA15E97 for anyone curious to compare.

@assassin: At least you pointed out the importance of the carry flag that turned out to be the problem. Either you knew what was up or the cosmos has it's ways.

I'm a happy hacker now Smile
  Find
Quote  

#18
Posts: 200
Threads: 1
Thanks Received: 10
Thanks Given: 0
Joined: Oct 2015
Reputation: 18
Status
None
glad it's working, and that i could stumble into helping.. Wink  but why would they add in Var $41 instead of just a 0000h constant?  all to save 1 byte?  does the variable (along with $42) get zeroed right before the game save/load or checksum functions?  or are you going off of never seeing it receive another value?  in the latter case, it is a lot harder to prove a negative.


FF6, in comparison, has an always-zero value (at least when running in Bank C3, the main menus), presumably to save space on loads, but it's at a more intuitive address $0000.
Quote  

#19
Posts: 127
Threads: 8
Thanks Received: 21
Thanks Given: 12
Joined: Jan 2012
Reputation: 13
Status
None
I made a python script for this if anyone wants:
https://github.com/mogue/SNES-SRM-Checks...sum_fix.py

The repository also includes FF5 and FF6 SRM fixing.

@ assassin: I'm not sure how to prove the value stays zero, it's zeroed when the menu opens. FF5 checksum uses a similar initial RAM value that gets zeroed when the menu opens but is located elsewhere. All my testing suggest these values stay zero.
  Find
Quote  
[-] The following 2 users say Thank You to m06 for this post:
  • Grandirus (05-02-2017), madsiur (03-01-2017)

#20
Posts: 1
Threads: 0
Thanks Received: 0
Thanks Given: 0
Joined: Apr 2017
Reputation: 0
Status
None
(03-01-2017, 01:37 PM)m06 Wrote: I made a python script for this if anyone wants:
https://github.com/mogue/SNES-SRM-Checks...sum_fix.py

The repository also includes FF5 and FF6 SRM fixing.

Hi.
There is some way to wrote this as Visual Basic 6 language?
I'm trying to make and SRM Editor.
Already made a Save State Editor, but now, I'm entering in SRM files.
Have done one for "Zelda, A Link To The Past" SRM, and another to "Phantasy Star IV".
Any help will be welcome.

EDIT: Success with FF5 code.
Do some google searchs about python, and now I have some code to test with FF5 SRM Files.
Now, I'm stuck in FF4 code.
My code do close results with the checksum (checksum is C443, my result is C4C4)
I'm continue to find out where this diference enter in the final result.

EDIT 2: I figured out how to made it work in VB6 code.
Now I'm going to FF6 checksum quest, hahahahaha.  Laugh

EDIT 3: Got it to FF6 too.
All working fine.
Now, I will made the convertions, from Save State editors to SRAM editors.

EDIT 4: Thanks to m06 scripts, I figured out how to make and VB6 function to fix checksums for FF4, FF5 and FF6.
FF4 and FF5 worked fine, with game config options (can change window colors and other options in the editor).
FF6 wil take some time to update the current project that I have.
Well, thanks m06.
  Find
Quote  
[-] The following 1 user says Thank You to Grandirus for this post:
  • seibaby (04-27-2017)



Forum Jump:

Users browsing this thread: 2 Guest(s)


Theme by Madsiur2017Custom Graphics by JamesWhite