SNES Development
Lufia & The Fortress of Doom

The following information is pertaining to Estpolis Denki aka エストポリス伝記 aka Lufia & The Fortress of Doom.

SRAM

SRAM Checksum Algorithm

To calculate the checksum for each save slot, you start with the first offset in your save slot, and add up the little endian 16-bit numbers.

So, say for SLOT00, your first 16 bytes looked like this:

46 69 6C 65 30 30 E8 63 01 04 E5 E5 E5 E5 E5 E5

So, your first four bytes: ”46 69 6c 65”, are the HEX equivalent of the word ”File” (if you don’t believe me, look up the ASCII character table, and find that hex values 0x41 - 0x5A are upper case characters and hex 0x61 - 0x7A are lower case characters of the alphabet (A-Z and a-z respectively)).

Then, your next two bytes: ”30 30”, are the HEX equivalent of the printable ASCII characters 00 (the ASCII table defines hex values 0x30 - 0x39 as the textual representations of the characters 0 - 9).

Now we come to the little endian checksum part, which can be complicated, so please bear with me:

Then, the next two bytes are what your checksum is currently calculated as. Since it is a 16 byte value, to get your checksum as a decimal number, you take the first number, ”0xE8”, and convert it to a decimal – 232. Now, you take the second number, ”0x63”, and convert it to a decimal – 99.

But! Before you can add these two numbers together, you have multiply 99 by 256, since the number that exists there would actually be 0x6300. Anyway, 99 times 256 is 25344. Then you add 232 to that, and you get: 25576. That is your checksum value.

Why did I show you that complicated method? Because, we’re dealing with little endian encoding. That means that “0x63” is your high order byte, and ”0xE8” is your low order byte. The 16 bit value that is your checksum is not 0xE863, but it is actually 0x63E8. This little tidbit of information becomes important when we start going through the save slot to compute the checksum.

Now, if we were to start computing the checksum for this save slot, we would begin after the checksum bytes with the first ”0x01”. This is the low order byte, so the decimal equivalent here is 1. Now, we take the high order byte, which is “0x04”. Remember, this is just like saying “0x0400” for the purposes of computing a checksum. So, convert the byte to a decimal – in this case, 4, and then multiply it by 256 – 1024; before adding back in 1. So, your first value in your checksum is 1025. You will continue in this manner until you reach the end of that save slot.

Except, there is a caveat: what happens when your checksum value goes over 0xFFFF (the biggest 16-bit number, aka. 65535). Well, your counter technically rolls back over to 0, and starts counting up again – and you just throw away the extra.

So you’ve added up your save slot and you find out that it still doesn’t match the two bytes at the end of “File00”? Well, that’s why the assembler code up above was important – it not only told us how the checksum algorithm works, but it also told us the base value to begin the checksum calculation off of. In this case, it is 6502. You can either start with this value from the onset, or you can add it on as the last step in the process, but regardless, the value this yields will be the two bytes that get stored away as your checksum for this save slot. Then you just have to rinse and repeat this process for the next two save slots! (by the way, 6502 is a decimal value – not in hex).

$00/9429 A0 FC 03    LDY #$03FC              A:0070 X:616E Y:0000 ; load Y immediate with value 0x03FC
$00/942C A6 1F       LDX $1F    [$00:001F]   A:0070 X:616E Y:03FC ; load X with value at address 0x1F
$00/942E A9 02 65    LDA #$6502              A:0070 X:0000 Y:03FC ; load Accumulator with 0x6502
$00/9431 18          CLC                     A:6502 X:0000 Y:03FC ; clear the carry flag
$00/9432 7D 08 00    ADC $0008,x[$70:0008]   A:6502 X:0000 Y:03FC ; add with carry starting at address 0x0008
$00/9435 E8          INX                     A:4AE7 X:0000 Y:03FC ; increment the X register
$00/9436 E8          INX                     A:4AE7 X:0001 Y:03FC ; increment the X register (again)
$00/9437 88          DEY                     A:4AE7 X:0002 Y:03FC ; decrement the Y register
$00/9438 D0 F7       BNE $F7    [$9431]      A:4AE7 X:0002 Y:03FB ; repeat until Y hits 0 (branch not equal)
$00/943A AA          TAX                     A:592C X:07F8 Y:0000 ; transfer the accumulator into X
$00/943B 7A          PLY                     A:592C X:592C Y:0000 ; pull the Y register off of the stack
$00/943C AB          PLB                     A:592C X:592C Y:0000 ; pulls a byte off of stack into data bank
$00/943D 28          PLP                     A:592C X:592C Y:0000 ; pull processor status off of the stack
$00/943E 60          RTS                     A:592C X:592C Y:0000 ; return from subroutine

Below is a C++ program that accurately handles the checksum algorithm described above:

// 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
}

SRAM Structure

Save slot 0 is at SRAM 0x0000, spot 1 is at 0x0800, spot 2 is at 0x1000 and there is extra space at 0x1800.
On an interesting note though, where the fourth save slot would be, the SRAM file I was working with says “Estpolis Biography Neverland Co.”. A perfect 32 bytes starting at SRAM location 0x1800.

OffsetDescription
0x000 - 0x005“FILE0?” where 0 is the ? for this file location in SRAM.
0x006 - 0x007The 16 bit little endian checksum.
0x113 - 0x1165 bytes for the Hero’s name plus a null terminator (0x00).
0x119 - 0x11E5 bytes for the Hero’s name a second time plus a 0x00.
0x11F - 0x1245 bytes for Lufia’s name (once she joins) plus a 0x00.
0x125 - 0x12A5 bytes for Aguro’s name (once he joins) plus a 0x00.
0x12B - 0x12F5 bytes for Jerin’s name (once she joins) plus a 0x00.
0x131This byte says how many party members you currently have.
0x132 - 0x134Three bytes that contain the amount of gold your party has.
0x13E - 0x1B5120 bytes that hold the item information.
0x1B6Hero’s Level
0x1B7Lufia’s Level
0x1B8Aguro’s Level
0x1B9Jerin’s Level
0x1C6 - 0x1C7Hero’s HP
0x1C8 - 0x1C9Lufia’s HP
0x1CA - 0x1CBAguro’s HP
0x1CC - 0x1CCJerin’s HP
0x1CE - 0x1CFHero’s MP
0x1D0 - 0x1D1Lufia’s MP
0x1D2 - 0x1D2Aguro’s MP
0x1D3 - 0x1D4Jerin’s MP
0x1D6 - 0x1F5Hero’s Magic Spells (32 bytes for 32 spells)
0x1F6 - 0x215Lufia’s Magic Spells (32 bytes for 32 spells)
0x216 - 0x235Jerin’s Magic Spells (32 bytes for 32 spells)
0x307Hero’s Equipped Weapon
0x308Lufia’s Equipped Weapon
0x309Aguro’s Equipped Weapon
0x30AJerin’s Equipped Weapon
0x30BHero’s Equipped Armor
0x30CLufia’s Equipped Armor
0x30DAguro’s Equipped Armor
0x30EJerin’s Equipped Armor
0x30FHero’s Equipped Shield
0x310Lufia’s Equipped Shield
0x311Aguro’s Equipped Shield
0x312Jerin’s Equipped Shield
0x313Hero’s Equipped Helm
0x314Lufia’s Equipped Helm
0x315Aguro’s Equipped Helm
0x316Jerin’s Equipped Helm
0x317Hero’s Equipped Shoes
0x318Lufia’s Equipped Shoes
0x319Aguro’s Equipped Shoes
0x31AJerin’s Equipped Shoes
0x31BHero’s Equipped Ring
0x31CLufia’s Equipped Ring
0x31DAguro’s Equipped Ring
0x31EJerin’s Equipped Ring

Items

You get a maximum of 5 pages of items, with 12 items a page, for a maximum of 60 items. The way the data is stored is that the first byte of the pair identifies what the item is, while the second byte of the pair identifies the quantity of that item that you have.

ValueItem Name
00Empty Slot
01Knife
02Club
03Mace
04Dagger
05Long Knife
06Short Sword
07Rod
08Gladius
09Glass Robe
0ABrone Sword
0BStaff
0CScimitar
0DRapier
0ELong Sword
0FLong Staff
10Axe
11Spear
12Morning Star
13Catwhip
14Battle Axe
15Hammer Rod
16Trident
17Silver Rod
18Silver Sword
19Buster Sword
1AZircon Rod
1BGreat Axe
1CGrand Blade
1DZircon Axe
1EZircon Sword
1FBroad Sword (cursed)
20Broad Rod (cursed)
21Luck Blade (cursed)
22Gloom Pick (cursed)
23Dual Blade
24Dress
25Cloth
26Cloth Armor
27Robe
28Tan Armor
29Tan Robe
2ALight Armor
2BLight Robe
2CChain Mail
2DChain Cloth
2EPlate Cloth
2FBrone Armor
30Quilted Silk
31Half Mail
32Brone Robe
33Silver Armor
34Silver Robe
35Plate Mail
36Zircon Robe
37Zircon Armor
38Clear Silk
39Bracelet
3ATan Shield
3BWood Shield
3CBuckler
3DWood Wrist
3EKite Shield
3FRound Shield
40Round Wrist
41Brone Shield
42Tower Shield
43Large Shield
44Silver Wrist
45Silver Plate
46Zircon Wrist
47Zircon Plate
48Cloth Helm
49Tan Helm
4AHair Band
4BWood Helm
4CGlass Cap
4DBrone Helm
4ERed Beret
4FIron Helm
50Plate Cap
51Plate Helm
52Glass Beret
53Silver Helm
54Sakret
55Zircon Beret
56Zircon Helm
57Sandal
58Cloth Shoes
59Tan Shoes
5ASpike Shoes
5BHeeled Shoes
5CWind Shoes
5DWind Heels
5EKnife Shoes
5FNeedle Heels
60Sonic Shoes
61Sonic Heels
62Sword Shoes
63Cat Heels
64Mach Shoes
65Mach Heels
66Power Ring
67HiPower Ring
68Daze Ring
69Hi Daze Ring
6AMind Ring
6BSonic Ring
6CMach Ring
6DUndead Ring
6EGhost Ring
6FDragon Ring
70Sea Ring
71Fly Ring
72Water Ring
73Fire Ring
74Ice Ring
75Electro Ring
76Flash Ring
77Flame Ring
78Water Ring
79Blast Ring
7AFrost Ring
7BMight Armor
7CMight Shield
7DMight Helmet
7EGloom Ring
7FGloom Voice
80Dummy
81Brone Breast
82Carbo Sword
83Carbo Plate
84Carbo Shield
85Carbo Helm
86Carbo Cap
87Gloom Guard
88Diamond Ring
89Engage Ring
8AMonster Ring
8BBlue Ring
8CYellow Ring
8DRed Ring
8EPurple Ring
8FGreen Ring
90White Ring
91Black Ring
92Heavy Ring
93Wave Ring
94Potion
95Hi Potion
96Ex Potion
97Hi Magic
98Ex Magic
99Antidote
9ASweet Water
9BFoul Water
9CAwaken
9DStone Cure
9EMystery Pin
9FShriek
A0Swing Wing
A1Magic Guard
A2Power Gourd
A3Mind Gourd
A4Power Potion
A5Spell Potion
A6Speed Potion
A7Mind Potion
A8Great Potion
A9Float
AASmoke Ball
ABArror
ACMid Arrow
ADBig Arrow
AEArrows
AFHi Arrows
B0Ex Arrows
B1Dragon Arrows
B2Sleep Arrow
B3Puzzle Arrow
B4Stun Arrow
B5Gloom Arrow
B6Bomb
B7Hi Bomb
B8Ex Bomb
B9Miracle
BARevive
BBPear Cider
BCSour Cider
BDLime Cider
BEPlum Cider
BFApple Cider
C0Hair Band
C1Brooch
C2Earring
C3Necklace
C4Stuffed Bear
C5Stuffed Dog
C6Stuffed Pig
C7Emerald
C8Opal
C9Goblet
CAEar Tip
CBEmpty Bottle
CCGown
CDRibbon
CEFry Pan
CFSmall Knife
D0Pot
D1Chop Block
D2Apron
D3Dragon Egg
D4Crown
D5Secret Map
D6Miracle Gem
D7Silver Wick
D8Royal Statue
D9Silver Tarot
DAGolden Pawn
DBCrown Jewels
DCWind Flute
DDEscape
DEMagic Jar
DFDragon Tooth
E0Grilled Newt
E1Poison Pin
E2Might Sword
E3Straw Doll
E4Long Nail
E5Bomb
E6Alumina
E7Power Oil
E8Elven Bow
E9Artea’s Bow
EAMight Bow
EBDummy
ECDummy
EDDummy
EEDummy
EFFree Door
F0Sheran Key
F1Letter
F2Dais Key
F3Shrine Key
F4Pirate Key
F5Light Key
F6Oil Key
F7Green Jade
F8Red Sapphire
F9Blue Jade
FAPurple Newt
FBGlasdar Key
FCMagic Flavor
FDFairy Kiss
FENot Used
FFNot Used

Note that there is some oddity in this list, such as two items named “Bomb”, and at least 5 “Dummy” items, and then there’s “Free Door” on top of that.

Not only that, but several of these items, such as “Sheran Key”, go into your Scenario page, not your item list. But, I digress – you can add them in to your SRAM file anyway.

Magic

You have a maximum amount of space for spells of 32 bytes. However, the way that magic works is that the spell listing must end with a call to 0x00 for “END OF LIST”. Otherwise, if you use up all 32 slots in Hero’s Magic list, he will also have every spell of Lufia’s in his spell casting ability as well! And likewise, if you fill up all 32 of Lufia’s spells, you can spill over into Jerin’s territory. And if you fill up Jerin’s… Well, you’ll probably crash something, but just beware if you go to edit the game in this manner. Though with this method, you can actually give your Hero EVERY spell in the game when the team consists of only you and Lufia! Also, notice that there is no space reserved for Aguro to have magic, so you cannot just give him some spells to let him cast away.

ValueDescription
00END OF LIST
01Flash
02Bolt
03Thunder
04Spark
05Flame
06vulcan
07Dew
08Water
09Flood
0ABang
0BBlast
0CSunder
0DFrost
0EBlizzard
0FGlacier
10Perish
11Succumb
12Drowsy
13Fright
14Drain
15Dread
16Deflect
17Bounce
18Absorb
19Fake
1ATrick
1BConfuse
1CBravery
1DCourage
1EShield
1FProtect
20Mirror
21Statue
22Strong
23Stronger
24Champion
25Boost
26Valor
27Poison
28Stun
29Dead
2ARally
2BStone
2CWaken
2DWarp
2EEscape
2FFloat
30Elf
31Defake
32Figual
33Paraiz
34Elegion
35Elegi
36Elegio
37Absobl

In case you do not recognize some of the last magic spells on that list, that is because they must have been in there for test purposes. Without going too much off topic, here is what it appears that these extra magic spells do:

  • Defake - Agility Down
  • Figual - Confuse
  • Paraiz - Paralyze
  • Elegion - Thunder Spell (all enemies)
  • Elegi - Flash Spell (all enemies)
  • Elegio - Bolt Spell (all enemies)
  • Absobl - Absorb Magic

The plus of some of these spells is that they cost only 1 or no MP at all to cast, meaning you can give them to your low level party and completely rule the entire game – never mind the great items you could give yourself as well.

Dictionary Reference Key

The dictionary is located at 0x054E19 - 0x0553CC.

Ì, .z .& .îone.. a fake ..?. ..   rich ‡ gemstone.mines. Ì .o ran .‹..` ago.

Can’t read some of those characters? Well, that’s because the stuff that doesn’t make sense has to do with punctuation and pointers to other words that get put in place.

The statement that should be making is this:

But, why would anyone
make a fake ruby?
Medan was rich in gemstone
mines. But they ran out
years ago.

A few words are there that you can make out (between some townsperson and the Princess in Medan). Such as ”,”, “one”, “a fake”, ”?”, “rich”, “gemstone mines.”, “ran”, “ago.”. But what about the questionable things you can’t see. Let’s take the first part, the “But, why would anyone make a fake ruby, which looks like this, and compare it against the dictionary file of the game (0x54A50):

cc 2c 20 0c 7a 20 0c 26 20 0c ee 6f 6e 65 05 0c 7f 20 61 20 66 61 6b 65 20 0c 19 3f

Ì, .z .& .îone.. a fake ..?. 

cc    -> refers to an upper case "But"
2c    -> comma (",")
20    -> blank space
0c 7a -> refers to a lower case (0c is "lower case"/0d is "upper case") "why"
20    -> blank space
0c 26 -> refers to a lower case "would"
20    -> blank space
0c ee -> refers to a lower case "any"
6f    -> letter "o"
6e    -> letter "n"
65    -> letter "e"
05    -> line/carriage return
0c 7f -> refers to a lower case "make"
20    -> blank space
61    -> letter "a"
20    -> blank space
66    -> "f"
61    -> "a"
6b    -> "k"
65    -> "e"
20    -> blank space
0c 19 -> refers to a lower case "ruby"
3f    -> "?"

But, why would anyone
make a fake ruby?

After that, there is a ”04 A0” code which I can only assume is some sort of “close textbox” and “open new text box” (and possibly a character sprite/on screen location to hook it to). But then we get to the second phrase:

0b 08 20 a0 20 72 69 63 68 20 87 20 67 65 6d 73 74 6f 6e 65 05 6d 69 6e 65 73 2e 20 cc 20 0c 6f 20 72 61 6e 20 0c 8b 05 0c 60 20 61 67 6f 2e

..   rich ‡ gemstone.mines. Ì .o ran .‹..` ago.

0b 08                   -> dictionary for the town name "Medan"
20                      -> blank space
a0                      -> lower case "was" (notice it is not referenced by any 0c/0d type calls)
20                      -> blank space
72                      -> "r"
69                      -> "i"
63                      -> "c"
68                      -> "h"
20                      -> blank space
87                      -> lower case "in"
20                      -> blank space
67 65 6d 73 74 6f 6e 65 -> letters for "gemstone"
05                      -> carriage/line return
6d 69 6e 65 73          -> letters for "mines"
2e                      -> "."
20                      -> blank space
cc                      -> upper case "But" (as 8c refers to lower case)
20                      -> blank space
0c 6f                   -> lower case "they"
20                      -> blank space
72 61 6e                -> letters for "ran"
20                      -> blank space
0c 8b                   -> lower case "out"
05                      -> carriage/line return
0c 60                   -> lower case "years"
20                      -> blank space
61 67 6f                -> letters for "ago"
2e                      -> "."

Medan was rich in gemstone
mines. But they ran out
years ago.

Then there’s more words after that, because she’s long winded, but you get the idea… It took me almost 8 hours of trial and error to figure out how the dictionary words were stored, as well as what numbers called them (which I figured out by comparing the dictionary against known phrases). The upper case/lower case was a bit harder to figure out as well. There’s still some extra data that doesn’t make sense yet. Also, there’s a lot of words that are in the dictionary but they don’t both to make a call to use them (like “one” in the above example”). There must be a reason for this, but I have yet to figure it out.

Some things are a little more hidden, such as a character is referenced by “07 0X” with Hero being ”07 00” up to Gades as ”07 0b”. Also, while to get lowercase/uppercase for the one set of dictionary words it seems to be dependent on the “0c” vs. “0d” call, the other section of dictionary words has two memory addresses that are separate for lower case or upper case (though the dictionary only exists once, it just loops back on itself I guess). For example, in the non-0c/0d dictionary, the word “there” is referenced by ”8e”, while “There” is referenced by ”ce”. Meaning they’re exactly 0x40 difference.

Now, for the dictionaries themselves, the character names start around ”0xe800”, the town names around ”0xe850”, there’s one of 16 words (such as (’s) and (“Welcome”)). These start at ”0x54a50”. Then the next dictionary (the one that has double calls to it, for being lower case/upper case) starts at ”0x54ac0” (it contains 80 items, which makes for 160 words between upper/lower case). Then the main dictionary (called by 0c/0d for lower/upper case) starts near ”0x54c10”. Now, there’s two types of dictionary calls here.

Early on names:

0xe850:    8f e8 95 e8 9b e8

8f e8 refers to memory location "0xe88f", which contains the name "Alekia"
95 e8 refers to memory location "0xe895", which contains the name "Chatam" 
9b e8 refers to memory location "0xe89b", which contains the name "Sheran"

These places are called by text boxes by ”0b xx”, where ”0b 01” is Alekia, ”0b 02” is Chatam, ”0b 03” is Sheran and so on (there’s an extra space ’.’ in this list which makes the names start at 1 instead of 0).

Later on words:

0x54ac0:    42 cb 45 cb 48 cb

Here we have a bit of a problem, as memory location ”0xcb42” is way back in the program and nowhere near what we want. So all of these later dictionary entries need to have the hex value ”0x48000” added to them so that they point to the proper place. The unique item grabber program I made for Diablo I uses a pointer offset like this too, so it is not all that uncommon for larger programs, especially later on in the data when dealing with 16 bit little endian pointers.

42 cb + 48000H = "0x54b42" which contains the word "the"
45 cb + 48000H = "0x54b45" which contains the word "you"
48 cb + 48000H = "0x54b48" which contains the word "to"

In case you didn’t notice, the pointer to the next word tells you where to stop. At least, that’s my guess…

Important Codes

ValuesDescription
04Text Box Close (?)
05Line Return
07Character Name Call
0BPlace Name Call
0CLower Case Dictionary
0DUpper Case Dictionary
20Blank Space (” “)
2EPeriod (.)
2BDouble Period (..)
20 - 7FReserved Character Symbols
00 - 0FReserved Flag Calls and Specials
80 - A9New Textbox (?) (Tie to Character/Position?)
??Time to Wait Between Boxes (?)
ValueName
07 00Hero
07 01Lufia
07 02Aguro
07 03Jerin
07 04Maxim
07 05Selan
07 06Guy
07 07Artea
07 08Daos
07 09Erim
07 0AAmon
07 0BGades
ValuesName
1C E8Lufia
21 E8Aguro
26 E8Jerin
2B E8Maxim
30 E8Selan
35 E8Guy
38 E8Artea
3D E8Daos
41 E8Erim
45 E8Amon
49 E8Gades
4E E8<- End of List
Value AValue BNames
008E E8(00)
018F E8Alekia
0295 E8Chatam
039B E8Sheran
04A1 E8Treck
05A6 E8Lorbenia
06AE E8Grenoble
07B6 E8Kirof
08BB E8Medan
09C0 E8Surinagal
0AC9 E8Belgen
0BCF E8Jenoba
0CD5 E8Ruan
0DD9 E8Ranqs
0EDE E8Odel
0FE2 E8Lyden
10E7 E8Arus
11EB E8Platina
12F2 E8Carbis
13F8 E8Bakku
14FD E8Linze
1502 E9Marse
1607 E9Herat
170C E9Soshette
1814 E9Epro
1918 E9Arubus
1A1E E9Frederia
1B26 E9Forfeit
1C2D E9Makao
1D32 E9Elfrea
1E38 E9Elfrea
XX3E E9<- End of List

Offset: 048000 (hex)

ValuesLink #Dictionary
1072 CA’s
1174 CAed
1276 CAing
1379 CAI’m
147C CAI’ll
1580 CAI’ve
1684 CAAlumina
178B CASinistral
1894 CADual
1998 CAFalcon
1A9E CAGlasdar
1BA5 CAWelcome
1CAC CARaile
1DB1 CALilah
1EB6 CAReyna
1FBB CAShaia
XXC0 CA<- End of List
LCUCLink #Dictionary
80C042 CBthe
81C145 CByou
82C248 CBto
83C34A CBit
84C44C CBof
85C54E CBthat
86C652 CBis
87C754 CBin
88C856 CBand
89C959 CBwhat
8ACA5D CBthis
8BCB61 CBgo
8CCC63 CBbut
8DCD66 CBare
8ECE69 CBthere
8FCF6E CBno
90D070 CBbe
91D172 CBwe
92D274 CBso
93D376 CBdo
94D478 CBfor
95D57B CBhave
96D67F CBcan
97D782 CBme
98D884 CBknow
99D988 CBdon’t
9ADA8D CBhe
9BDB8F CBif
9CDC91 CBmy
9DDD93 CBhere
9EDE97 CByes
9FDF9A CBon
A0E09C CBwas
A1E19F CBisland
A2E2A5 CBwith
A3E3A9 CBabout
A4E4AE CByour
A5E5B2 CBcome
A6E6B6 CBget
A7E7B9 CBsee
A8E8BC CBcan’t
A9E9C1 CBwill
AAEAC5 CBright
ABEBCA CBnow
ACECCD CBlet
ADEDD0 CBok
AEEED2 CBat
AFEFD4 CBtake
B0F0D8 CBjust
B1F1DC CBup
B2F2DE CBreally
B3F3E4 CBplease
B4F4EA CBwell
B5F5EE CBnot
B6F6F1 CBall
B7F7F4 CByou’re
B8F8FA CBgood
B9F9FE CBwant
BAFA02 CCfour
BBFB06 CCtower
BCFC0B CCas
BDFD0D CCfrom
BEFE11 CCback
BFFF15 CCby
XXXX17 CC<- End of List
"0c" -> lc
"0d" -> uc
ValueLink #Dictionary
0019 CEsomething
0122 CEmonsters
022A CEprofessor
0333 CEdoom
0437 CEpieces
053D CElorbenia
0645 CEvibration
074E CEbasement
0856 CEvillage
095D CEgoing
0A62 CEunderstand
0B6C CEaround
0C72 CEyou’ll
0D78 CEshould
0E7E CEdangerous
0F87 CEthink
108C CEpeople
1192 CEcave
1296 CEfind
139A CEcastle
14A0 CEagain
15A5 CEgold
16A9 CEwon’t
17AE CEpower
18B3 CEnorth
19B8 CEruby
1ABC CEblade
1BC1 CEreturn
1CC7 CEbattle
1DCD CEwhere
1ED2 CEwhen
1FD6 CEyou’ve
20DC CEtogether
21E4 CEthrough
22EB CEstill
23F0 CEdoesn’t
24F7 CEsapphires
2500 CFyourself
2608 CFwould
270D CFanything
2815 CFnever
291A CFbecause
2A21 CFdidn’t
2B27 CFbeen
2C2B CFnothing
2D32 CFlever
2E37 CFhello
2F3C CFalright
3043 CFthanks
3149 CFlittle
324F CFdescendant
3359 CFcourse
345F CFonly
3563 CFbefore
3669 CFtown
376D CFsome
3871 CFremember
3979 CFkingdom
3A80 CFrecently
3B88 CFgreat
3C8D CFwish
3D91 CFstrong
3E97 CFwithout
3F9E CFtreasure
40A6 CFtime
41AA CFthought
42B1 CFstrange
43B8 CFlooking
44BF CFheard
45C4 CFover
46C8 CFtoday
47CD CFtell
48D1 CFlook
49D5 CFlike
4AD9 CFbelieve
4BE0 CFrestored
4CE8 CFhelp
4DEC CFfather
4EF2 CFalways
4FF8 CFwonder
50FE CFmust
5102 D0matter
5208 D0king
530C D0everything
5416 D0country
551D D0we’re
5622 D0sorry
5727 D0night
582C D0need
5930 D0happened
5A38 D0found
5B3D D0everyone
5C45 D0shop
5D49 D0even
5E4D D0thank
5F52 D0someone
6059 D0years
615E D0world
6263 D0vibrations
636D D0true
6471 D0saying
6577 D0returned
667F D0princess
6787 D0isn’t
688C D0aren’t
6992 D0worried
6A99 D0they’re
6BA0 D0destroy
6CA7 D0one
6DAA D0lately
6EB0 D0wouldn’t
6FB8 D0they
70BC D0said
71C0 D0leave
72C5 D0couldn’t
73CD D0things
74D3 D0sure
75D7 D0many
76DB D0enough
77E1 D0hope
78E5 D0give
79E9 D0too
7AEC D0why
7BEF D0who
7CF2 D0then
7DF6 D0stay
7EFA D0rubies
7F00 D1make
8004 D1maberia
810B D1long
820F D1cinnamon
8317 D1careful
841E D1attacked
8526 D1anyway
862C D1she
872F D1more
8833 D1three
8938 D1these
8A3D D1south
8B42 D1out
8C45 D1first
8D4A D1after
8E4F D1sacrifice
8F58 D1reward
905E D1man
9161 D1how
9264 D1fine
9368 D1cooper
946E D1knights
9575 D1west
9679 D1stop
977D D1magic
9882 D1level
9987 D1could
9A8C D1ahead
9B91 D1used
9C95 D1him
9D98 D1called
9E9E D1wrong
9FA3 D1we’ll
A0A8 D1place
A1AD D1floor
A2B2 D1bring
A3B7 D1brant
A4BC D1such
A5C0 D1ship
A6C4 D1mean
A7C8 D1islands
A8CF D1into
A9D3 D1came
AAD7 D1already
ABDE D1wonderful
ACE7 D1southeast
ADF0 D1northwest
AEF9 D1commander
AF02 D2apologize
B00B D2whatever
B113 D2wanted
B219 D2them
B31D D2switch
B423 D2sapphire
B52B D2might
B630 D2later
B735 D2forest
B83B D2fight
B940 D2city
BA44 D2bridge
BB4A D2way
BC4D D2got
BD50 D2old
BE53 D2mark
BF57 D2feel
C05B D2down
C15F D2did
C262 D2we’ve
C367 D2small
C46C D2seems
C571 D2other
C676 D2light
C77B D2haven’t
C882 D2has
C985 D2forgive
CA8C D2were
CB90 D2spirit
CC96 D2shrine
CD9C D2happen
CEA2 D2under
CFA7 D2things
D0AC D2supposed
D1B4 D2spiritual
D2BD D2southwest
D3C6 D2shouldn’t
D4CF D2possible
D5D7 D2medicine
D6DF D2left
D7E3 D2knows
D8E8 D2important
D9F1 D2care
DAF5 D2away
DBF9 D2alone
DCFE D2surrounded
DD08 D3say
DE0B D3repair
DF11 D3problem
E018 D3much
E11C D3memory
E222 D3girlfriend
E32C D3girl
E430 D3gets
E534 D3defeat
E63A D3better
E740 D3anytime
E847 D3young
E94C D3raise
EA51 D3piron
EB56 D3yeah
EC5A D3wait
ED5E D3best
EE62 D3any
EF65 D3tunnel
F06B D3second
F171 D3order
F276 D3mountain
F37E D3memories
F486 D3makes
F58B D3made
F68F D3live
F793 D3kill
F897 D3items
F99C D3information
FAA7 D3hey
FBAA D3her
FCAD D3grief
FDB2 D3disappeared
FEBD D3daughter
FFC5 D3business
XXCD D3<- end of list

SRAM details algorithm description, C++ checksum program and text dictionary compression by Vegetaman. SRAM Checksum traced and described by KingMike.