This is the (6502) assembler algorithm that handles the checksum for Lufia & The Fortress of Doom (thanks to KingMike over at RHDN again for his help):
$00/9429 A0 FC 03 LDY #$03FC A:0070 X:616E Y:0000
$00/942C A6 1F LDX $1F [$00:001F] A:0070 X:616E Y:03FC
$00/942E A9 02 65 LDA #$6502 A:0070 X:0000 Y:03FC
$00/9431 18 CLC A:6502 X:0000 Y:03FC
$00/9432 7D 08 00 ADC $0008,x[$70:0008] A:6502 X:0000 Y:03FC
$00/9435 E8 INX A:4AE7 X:0000 Y:03FC
$00/9436 E8 INX A:4AE7 X:0001 Y:03FC
$00/9437 88 DEY A:4AE7 X:0002 Y:03FC
$00/9438 D0 F7 BNE $F7 [$9431] A:4AE7 X:0002 Y:03FB
$00/943A AA TAX A:592C X:07F8 Y:0000
$00/943B 7A PLY A:592C X:592C Y:0000
$00/943C AB PLB A:592C X:592C Y:0000
$00/943D 28 PLP A:592C X:592C Y:0000
$00/943E 60 RTS A:592C X:592C Y:0000
Here is the same code again, with some comments I have added to make it a bit more readable (sorry, my 6502 assembler isn't very good, so feel free to let me know of any mistakes I have made):
A0 FC 03 LDY #$03FC ; load Y immediate with value 0x03FC
A6 1F LDX $1F [$00:001F] ; load X with value at address 0x1F
A9 02 65 LDA #$6502 ; load Accumulator with 0x6502
18 CLC ; clear the carry flag
7D 08 00 ADC $0008,x[$70:0008] ; add with carry starting at address 0x0008
E8 INX ; increment the X register
E8 INX ; increment the X register (again)
88 DEY ; decrement the Y register
D0 F7 BNE $F7 [$9431] ; repeat until Y hits 0 (branch not equal)
AA TAX ; transfer the accumulator into X
7A PLY ; pull the Y register off of the stack
AB PLB ; pulls a byte off of stack into data bank
28 PLP ; pull processor status off of the stack
60 RTS ; return from subroutine
And I tell you that to tell you this...
I have written this small program below to give you a better feeling for how the checksum algorithm works for this game. I have included comments to try and make it as straight forward as possible (and yes, the while loop should be placed in a separate function with the proper variables just passed in to it -- I have coded it this way on purpose to show the program and logic flow here).
/* begin file -- main.cpp */
// File name: main.cpp
// Author: Vegetaman
// Date: February 24, 2011
// Purpose: Lufia Checksum
#include <iostream>
#include <fstream>
#define SIZE_OF_SRAM 0x2000 // SRAM is 8K large
#define LUFIA_SRAM_FILE "C:\\Lufia.srm" // SRAM file location
#define HALF_OF_2K 0x03FC // half of 2048 - 8
#define FILE00_OFFSET 0x0008 // 8 bytes in
#define FILE01_OFFSET 0x0808 // 2K + 8 bytes in
#define FILE02_OFFSET 0x1008 // 4K + 8 bytes in
#define COUNTER_ROLLOVER_VALUE 0xFFFF // max 16-bit value
using namespace std;
int main(int argc, char *argv[])
{
FILE *filePtr; // file pointer
unsigned char ArrayOfSRAM[SIZE_OF_SRAM]; // array of char or bytes
unsigned short accumulator; // is a 16-bit unsigned integer
unsigned short register_x; // will be just like the register X
unsigned short register_y; // will be just like the register Y
filePtr = fopen(LUFIA_SRAM_FILE, "r"); // open file Lufia.srm -- read only
// NOTE: I am reading the SRAM into an array so I don't have to do all of my
// operations from the file, which would be really slow and wasteful...
fread(ArrayOfSRAM, sizeof(char), SIZE_OF_SRAM, filePtr); // load the array
fclose(filePtr); // always close your file handle
// begin the routine of calculating the first 16-bit little endian checksum
register_x = FILE00_OFFSET; // load the first offset into register X
register_y = HALF_OF_2K; // load 0x03FC into register Y
accumulator = 0x6502; // load the accumulator with the base value 6502
while(register_y != 0) // while Y does not equal 0 (will run 1020 times)
{
accumulator += ArrayOfSRAM[register_x]; // get the first/low byte
register_x++; // increment X
accumulator += ArrayOfSRAM[register_x] << 8; // get the second/high byte
register_x++; // increment X
accumulator &= 0xFFFF; // discarding the carry flag
register_y--; // decrement Y
}
// some code to throw the checksum up on the console so that it can be seen
cout << "Checksum for FILE00: " << dec << accumulator << endl;
cout << "Little Endian HEX: " << hex << (accumulator & 0xFF) << " ";
cout << hex << ((accumulator >> 8) & 0xFF) << endl << endl;
// prepare to calculate the second little endian 16-bit checksum
register_x = FILE01_OFFSET; // load the first offset into register X
register_y = HALF_OF_2K; // load 0x03FC into register Y
accumulator = 0x6502; // load the accumulator with the base value 6502
while(register_y != 0) // while Y does not equal 0 (will run 1020 times)
{
accumulator += ArrayOfSRAM[register_x]; // get the first/low byte
register_x++; // increment X
accumulator += ArrayOfSRAM[register_x] << 8; // get the second/high byte
register_x++; // increment X
accumulator &= 0xFFFF; // discarding the carry flag
register_y--; // decrement Y
}
// some code to throw the checksum up on the console so that it can be seen
cout << "Checksum for FILE01: " << dec << accumulator << endl;
cout << "Little Endian HEX: " << hex << (accumulator & 0xFF) << " ";
cout << hex << ((accumulator >> 8) & 0xFF) << endl << endl;
// prepare to calculate the third and final little endian 16-bit checksum
register_x = FILE02_OFFSET; // load the first offset into register X
register_y = HALF_OF_2K; // load 0x03FC into register Y
accumulator = 0x6502; // load the accumulator with the base value 6502
while(register_y != 0) // while Y does not equal 0 (will run 1020 times)
{
accumulator += ArrayOfSRAM[register_x]; // get the first/low byte
register_x++; // increment X
accumulator += ArrayOfSRAM[register_x] << 8; // get the second/high byte
register_x++; // increment X
accumulator &= 0xFFFF; // discarding the carry flag
register_y--; // decrement Y
}
// now throw the last checksum up on the console so it can also be seen
cout << "Checksum for FILE02: " << dec << accumulator << endl;
cout << "Little Endian HEX: " << hex << (accumulator & 0xFF) << " ";
cout << hex << ((accumulator >> 8) & 0xFF) << endl << endl;
// now, hold the program up until the user is done reading...
cout << "Press ENTER to continue..."; // prompt user
getchar(); // wait for "ENTER" key
return 0; // exit with success
}
/* end of file -- main.cpp */
If you wish to run this code on your own platform (it should compile with Visual Studio/Visual C++.NET and run as a console program, work with Bloodshed Dev-C++, and gcc/gpp -- for all you Windows, Mac, and Linux fans out there respectively), make sure you make the following line of code match your file path for where your Lufia SRAM (.srm) file is located (tested with the [!] version of the game):
#define LUFIA_SRAM_FILE "C:\\Lufia.srm" // SRAM file location
Have a suggestion? Drop me a line at: vegetaman@gmail.com