Users browsing this thread: 1 Guest(s)
FF4 SNES SRM Checksum
02-13-2017, 12:34 AM
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=1numel(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);
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=1numel(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);
02-13-2017, 04:00 AM
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:
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:
@assasin: Sounds right, different way to compute the same checksum. Your math impresses me.
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.
02-13-2017, 06:37 AM
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.
02-13-2017, 03:21 PM
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.
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.
02-13-2017, 04:38 PM
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.
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.
02-13-2017, 05:34 PM
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!
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!
02-13-2017, 07:10 PM
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
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
02-13-2017, 07:37 PM
glad it's working, and that i could stumble into helping.. 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.
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.
03-01-2017, 01:37 PM
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.
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.
04-27-2017, 12:26 AM
(This post was last modified: 05-02-2017, 10:22 PM by Grandirus.
Edit Reason: All fine.
)
(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.
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.
« Next Oldest | Next Newest »
Users browsing this thread: 1 Guest(s)