Save State Hacking Guide by jdratlif

Version: 1.2 | Updated: 03/21/06 | Printable Version

| Ultima: Quest of the Avatar
| Save State Hacking Guide
| by John Ratliff
| The most recent version of this guide can always be found at
| Copyright (C) 2004-2005 emuWorks (
|   Permission is granted to copy, distribute and/or modify this document
|   under the terms of the GNU Free Documentation License, Version 1.2
|   or any later version published by the Free Software Foundation;
|   with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
|   Texts.  A copy of the license can be found at

| Table of Contents

  1.0 Introduction
  2.0 Revision History
  3.0 Credits & Thanks
  4.0 Copyright
  5.0 SRAM Offsets
    5.1  Phases of the Moon
    5.2  The Hero's Name
    5.3  Virtues
    5.4  Party Members
    5.5  Stones & Runes
    5.6  Magic
    5.7  Herbs, Gold, & Tools
    5.8  Inventory
    5.9  Character Stats
    5.10 Starting Location
    5.11 Pirate Ships & The Balloon
    5.12 The Whirlpool
    5.13 Treasure Offsets
    5.14 Miscellaneous Offsets
  6.0 The Sanity Algorithm
  7.0 Contact Information
| 1.0 Introduction

  This document is a guide to hacking the save state (SRAM or battery backed
  RAM) of Ultima: Quest of the Avatar for the Nintendo Entertainment System.
  Ultima: Quest of the Avatar was released for the NES in 1990. The copyrights
  are attributed to Origin Systems (creators of the Ultima series) in 1985 for
  the original version, and to FCI and Pony Canyon in 1990 for the NES version.
  This guide refers to the NES version released in the US in 1990. I do not know
  if it was released in Japan or Europe. If it was, this information may or may
  not apply to those games. Since I only owned the US version, I can only
  comment on that version.
  This guide is not specific to any one NES emulator. It modifies the SRAM
  (aka battery backed RAM, non-volatile RAM, etc.) that the game used
  internally to save the game on the real cartridge. Because of this, these
  methods should work on any emulator that supports SRAM. Any decent emulator
  should do this. A short list includes FCE Ultra, Nester, Nestopia, Nesten. I
  have never used Jnes, Fakenes, or VirtuaNES, but I would guess they do this
  also as they are highly regarded.
  I have only tested this information with FCE Ultra, which is the emulator I
  use. This is primarily because FCE Ultra is one of two current emulators that
  runs in Windows and includes a nice debugger which is very useful for finding
  the information here.
  Modifying the information directly (by hand with a hex editor for instance) is
  difficult because of a complicated sanity algorithm Ultima uses to check the
  integrity of the SRAM. Because the original battery in the cartridge can only
  last so long, it was necessary to develop an algorithm they were sure would
  confirm the data in the SRAM was legit. Unfortunately, it is not a simple
  algorithm to compute by hand. Fortunately, I've written a nice program that
  will do it for you. You may be reading this guide as part of that program, but
  if not, you can find it at It is
  available for Windows, Linux, Mac OS X, and FreeBSD.
  The sanity algorithm is described in section 6.0.
| 2.0 Revision History

  Version 1.2 - March 21, 2006
    - minor typo corrections
    - updated the Ultima alphabet table
    - added information on joined party members
    - added information on starting location
    - added information on the balloon location
    - added information on the pirate ship locations
    - added information on the whirlpool location
    - added information on treasure offsets
  Version 1.1 - August 3, 2005
    - minor corrections (typos mostly)
  Version 1.0 - December 4, 2004
    - initial version of this guide
| 3.0 Credits & Thanks

  This guide would not be possible without the work of several people I feel
  should be awarded much accolates for their efforts.
  - Tony Hedstrom
      Without Tony, I would have never discovered the sanity algorithm used by
      the game. I had originally given up on it until I talked with him about
  - Xodnizel
      Creator of FCE Ultra. Without FCE Ultra's debugger, it is quite likely I
      wouldn't have been able to find most of the offsets that lead to this
      guide and certainly not the sanity algorithm. It's a shame Xodnizel
      doesn't intend to continue FCE Ultra's development.
  - Julian Smart, Vadim Zeitlin, Robert Roebling, and all the wxWidgets authors
      Without wxWidgets, hack4u would have been written in Java. While it would
      have been just as nice IMO, many people dislike Java and it wouldn't be as
      useful to people.
      In addition, Vadim and Julian have been invaluable resources in learning
      the wxWidgets nuances and I wouldn't have gotten very far without their
  - sp, bbitmaster, DahrkDaiz, and Vystrix Nexoth
      Creators of FCEUXD SP. The PPU viewer was very helpful in learning the
      remainder of the Ultima character alphabet.
| 4.0 Copyrights
  It really disgusts me when authors of documents like this say you can't post
  their stuff, or that you can do so only with their prior approval. Maybe this
  is solely because they want to make sure their guide is up-to-date in all the
  distributed locations, but I suspect far more evil intent.
  Since I can't do anything about their copyrights, I'll just have to lead by
  This document is Copyright (C) 2004-2006 emuWorks 
    Permission is granted to copy, distribute and/or modify this document
    under the terms of the GNU Free Documentation License, Version 1.2
    or any later version published by the Free Software Foundation;
    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
    Texts.  A copy of the license can be found at
  Basically, it is free documentation in much the same way software under the
  GNU General Public License is free software. You can modify it, redistribute
  it, sell it, publish it, etc.
| 5.0 SRAM Offsets

  Finally, we get to the good stuff.
  Ultima: Quest of the Avatar for the NES included, like many games for the NES,
  an SRAM (aka battery backed RAM).
  This SRAM was a portion of writable memory inside the cartridge that when the
  NES was off was saved by a battery also contained inside the cartridge. This
  is similar the way a PC saves it's CMOS information today. That's the reason
  your PC's clock still works when your computer is off.
  The SRAM was an area of memory $2000 bytes long. All numbers in this guide
  that begin with a $ are hex numbers, i.e. numbers in the hexadecimal number
  system, base 16. Decimal numbers, like we use in practice, are base 10. I
  don't plan to include a decimal->hex tutorial here, so if you don't understand
  hex, you'll probably need to find one. There are many on the web.
  Fortunately, the parts of the SRAM we care about are in three small $200 byte
  sections, and 9 other bytes contained elsewhere.
  The NES accessed the SRAM at the CPU addresses $6000-$7FFF. When I refer to
  the CPU address, it will always be in this range. The SRAM address with be in
  the range $0000-$1FFF. $0000 SRAM = $6000 CPU.
  The SRAM was used by Ultima to save three different games. Each game used
  $200 (512) bytes of memory. The three SRAM addresses start at $1A00, $1C00,
  and $1E00. They end at $1BFF, $1DFF, and $1FFF. The nine other bytes are used
  to sanity check the SRAM, and will be discussed later.
  Since each game is identical in storage location, I will use relative SRAM
  offsets. In other words, if I refer to address $27 in game 1, I really mean
  SRAM address $1A27. Just add the start address + the relative offset to get
  the SRAM address. You can then add the starting CPU address if you want the
  actual CPU address, but this is probably not useful unless you're just
  Here are the relative offsets in the SRAM that I know about. There are many
  bytes unaccounted for. Some may be unused, and some may just be
  uninteresting for purposes of save state hacking. There will be a more
  detailed explanation after I present the list.
  $00       Sanity Byte
  $01       Moon Phases
  $02-$06   Hero's Name
  $0B       Characters Who've Agreed to Join the Party
  $0C-$13   Virtues
  $14       Avatarhood
  $15-$18   Party Members
  $19       Stones
  $1A       Runes
  $1B-$1E   Magic
  $1F-$26   Herbs
  $27-$28   Gold
  $29       Torches
  $2A       Gems
  $2B       Key
  $2C       Oil
  $2D       Sextant
  $2E       Scale
  $2F       Flute
  $30       Candle
  $31       Book
  $32       Bell
  $33       Wheel
  $34       Horn
  $35       Skull
  $36       Key of Truth
  $37       Key of Courage
  $38       Key of Love
  $39-$3E   Mage Inventory
  $3F-$44   Bard Inventory
  $45-$4A   Fighter Inventory
  $4B-$50   Druid Inventory
  $51-$56   Tinker Inventory
  $57-$5C   Paladin Inventory
  $5D-$62   Ranger Inventory
  $63-$68   Shepherd Inventory
  $69-$70   Levels
  $71-$80   Current HP
  $81-$90   Max HP
  $91-$98   Current MP
  $99-$A0   Max MP
  $A1-$A8   Strength
  $A9-$B0   Intelligence
  $B1-$B8   Dexterity
  $B9-$C8   Experience
  $D2-DD    Found Treasure
  $ED       Capture Pirate Ships
  $EE       Next Pirate Ship to Overwrite
  $F0-$F1   Balloon Location
  $F2-$F3   Whirlpool Location
  $F4-$FB   Pirate Ship Location(s)
  $107      Starting Location
  These are all the ones I know about, or cared enough to make a note of anyway.
  I haven't noted the spells you can learn in Spells Unlimited. I thought it was
  enough just to have the spells.
| 5.1 Phases of the Moon
  There are two moons in the world of Ultima: Trammel and Felucca. Trammel is
  the one on the left, while Felucca is on the right. Respectively, they
  determine where the Moongate is, and where it will lead.
  There are $18 (24 = 8 cities * 3 destinations each) possible phases. The
  relative offset $01 determines which of these it is.
  Here are all the possible values the offset $01 can have and their meanings.
  $00 Moonglow - Moonglow (Black Stone)
  $01          - Britain
  $02          - Jhelom
  $03 Britain - Yew
  $04         - Minoc
  $05         - Trinsic
  $06 Jhelom - Skara Brae
  $07        - Magincia
  $08        - Moonglow
  $09 Yew - Britain
  $0A     - Jhelom
  $0B     - Yew
  $0C Minoc - Minoc (Shrine of Spirituality)
  $0D       - Trinsic
  $0E       - Skara Brae
  $0F Trinsic - Magincia
  $10         - Moonglow
  $11         - Britain
  $12 Skara Brae - Jhelom
  $13            - Yew
  $14            - Minoc
  $15 Magincia - Trinsic 
  $16          - Skara Brae
  $17          - Magincia
  This value is not that useful since the moon phases change often enough it is
  usually easier to wait for a specific phase in-game than to alter the state.
| 5.2 The Hero's Name

  In Ultima, like most RPG's, you have a Hero and several supporting characters.
  The Hero is the only character that can have a name selected by the player.
  The game determines that the person who leads the party is the Hero. Other
  than that, the Hero is just like any of the other character classes. If your
  hero is the Paladin class, then the game's built-in Paladin (Dupre) will not
  join you. This is because the game stores information according to class, not
  according to individual player.
  Well, enough about the hero, how do you change his (or her) name.
  The offsets $02-$06 (5 characters) will do this. You can also use $07 for a
  sixth character (like the name Mariah for the built-in Mage), but the game
  will only allow you to give the character 6. Some number of offsets past $07
  can also be used for the hero, but this will mess up the status screen, so I
  don't recommend more than 6, 5 if you want to stay within the game's limits,
  but the only real reason I see for this is that there's no space between the
  last character in the name and the status info in battle. The mage Mariah has
  this problem, too, so it doesn't seem to hurt the game any.
  Ultima's alphabet for names is simple. I will list all the symbols I know, but
  some may not be valid for names. The legitimate alphabet for character names
  includes the letters A-Z and the lowercase a-z, the numbers 0-9, the dash -,
  the exclamation point !, a space (represented by an underscore _), and the
  NUL character which signifies termination of a name with less than 5
  characters. My save state editor program hack4u limits you to the game
  provided character alphabet.
  Legitimate character alphabet:
  A-Z = $91-$AA
  a-z = $D1-$EA
  -   = $D0
  !   = $90
  _   = $BC     (SPC in the game becomes an underscore)
  NUL = $00     (this signifies a name with less than 5 (or 6) characters).
  Other possible symbols:
  Circle (small)                 = $BF
  /                              = $C0
  .                              = $C1
  ,                              = $C2
  Dot                            = $C3
  "                              = $C4
  Degree mark (circle)           = $C5
  ,                              = $C6
  Key symbol                     = $C7
  Star                           = $C8
  + (Plus Symbol)                = $C9
  Ankh Symbol                    = $CA
  x (not the same as alphabet x) = $CB
  :                              = $CC
  ;                              = $CD
  Status Screen Alphabet:
  These are the capital letters used on the status screen to indicate class,
  MP, HP, and status ailments. These letters are distinct from the name
  N                     = $63
  E                     = $64
  S (Shepherd)          = $65
  W                     = $66
  G                     = $67
  P (Paladin or Poison) = $68
  D (Druid)             = $69
  M (Mage or MP)        = $6A
  B (Bard)              = $6B
  F (Fighter)           = $6C
  H (HP)                = $6D
  T (Tinker)            = $6E
  R (Ranger)            = $6F
  I'm not sure what N, E, W, and G are used for. The other letters may have
  additional uses I'm unaware of.
  I always name my hero either Dominic or Liam, depending upon how many
  characters I'm allowed. Since we are restricted to 5, Liam it is.
  Liam is spelled $9C $D9 $D1 $DD $00. That fills the offsets $02-$06.
| 5.3 Virtues

  The virtue system in Ultima introduced with Quest of the Avatar was extremely
  popular and unique.
  There are eight virtues: Honesty, Compassion, Valor, Justice, Sacrifice,
  Honor, Spirituality, and Humility. They are always numbered from 0-7.
  The game represents everything in refernece to these eight virtues. The cities
  Moonglow, Britain, Jhelom, Yew, Minoc, Trinsic, Skara Brae, and Maginicia. The
  cities are always numbered 0-7, too. The characters Mage, Bard, Fighter,
  Druid, Tinker, Paladin, Ranger, and Shepherd are also represented 0-7. All
  these values are tied together. Moonglow is the city of Honesty and Mages.
  The virtues are tracked all throughout the game in the offsets $0C-$13.
  $0C = Honesty, $13 = Humility. Here is the full list:
  $0C Honesty
  $0D Compassion
  $0E Valor
  $0F Justice
  $10 Sacrifice
  $11 Honor
  $12 Spirituality
  $13 Humility
  I'm not 100% what the exact values represent, but the higher the number, the
  better (more moral) your character is.
  I believe the values range between $00-$64 (0-100). $64 means you have partial
  avatarhood, $63 is the best you can be without having partial avatarhood,
  $32 (50) is where you start, and so on.
  However, avatarhood is measured two ways (probably for simplicitiy in the
  program). Offset $14 is also used to measure partial avatarhood in a virtue.
  Offset $14 is what is known as a bit list or flagset, etc. It has many names.
  Basically, $14 is a single byte, which has 8 bits of data. Since there are
  eight virtues, this is perfect. We can turn a bit on (1) to represent you have
  partial avatarhood in this virtue, and off (0) to represent you do not have
  partial avatarhood in this virtue.
  Bits are represented from right to left. So if we have a list of bits
  10011010, the bit 7 is on (1), and bit 0 is off (0). Knowing that virtues are
  represented 0-7, we know that Honesty is bit 0, and Humility is bit 7.
  If all the bits are on, you are the avatar. But if you virtue values don't
  correspond, you will lose your avatar status very easily. My program,
  hack4u, will keep both values in sync automatically.
  Let's say the game has just started. You have $32 (50) in offsets $0C-$13 and
  $0 (no partial avatarhoods) in $14. To give yourself partial avatarhood in
  honesty, we can change $0C to $64 and $14 to $1. If after that, we want to get
  partial avatarhood in Honor, we can set offset $11 to $64 and offset $14 to
  Here is how I got the value for $14.
  Starting value = $1 (%00000001 in binary -- binary numbers start with % signs)
  We want to turn on bit 5 (honor = 5). So we binary OR the starting value with
  (1 LSH 5 = %00100000). Then we get %00100001 which is $21 in hex. If you don't
  understand binary math operations, you should look for a tutorial, or just
  use my program. That's even easier.
| 5.4 Party Members

  Ultima IV can support four characters in your party as one time. The
  characters are one of the following: Mage, Bard, Fighter, Druid, Tinker,
  Paladin, Ranger, and Shepherd. You can also have no one in your party at a
  certain location, but to avoid confusing the game, it's best if the empty
  slots are at the end.
  If you read the section on Virtues, you know that the characters, like the
  virtues are always numbered 0-7 (Mage - Shepherd). But as a special case where
  there can be no one, 0 becomes no one. So each number is shifted up by 1.
  Mage = 1, Bard = 2, ..., Shepherd = 8.
  The first member is the hero, so $15 not just the leader, but is also the
  hero. You cannot rearrange the hero's location. If you put the hero's
  character in another slot, he (or she) will stop being the hero and will lose
  their hero abilities such as being able to equip Exotic Armor or Sword of 
  Paradise if they are the Avatar.
  You can also have more than one of the same character in your party. However,
  they are all tracked with the same stats. This means if one character dies,
  they all die. But on the plus side, if one gains experience, they all gain
  experience. Other than this quirk, I didn't notice any adverse affects in the
  game, but that doesn't mean there aren't any. There are generally some issues
  to be expected when the game is forced to use invalid data it never expected
  was possible.
  Since members in your party are not tracked by the same value as people who
  have agreed to join you, if you put characters who have not joined your party
  in your formation, you will see them in their hometowns. You can even ask them
  to join you as if they were not already in your party. If your party is not
  full and they agree to join you, you will have a duplicate in your party. If
  your party is full, they will go to the hostel in Castle Britannia. But since
  that member is already in your party, they won't really be there.
  In addition, if you part with people at the hostel who didn't join you, they
  will not show up in the hostel until you've asked them to join you in their
  hometown. However, if you split with them, they will appear in the hostel
  until you leave the room and can rejoin your party immediately.
  Just set the values to 0-8 (0 = no one, 1-8 = Mage - Shepherd)
  $15 First Member (Hero)
  $16 Second Member
  $17 Third Member
  $18 Fourth Member
  As for members who have agreed to join your party, this is tracked by another
  flagset variable, $0B. Since no one is not an option here, we go back to the
  standard values 0-7 Mage - Shepherd. This affects which characters will appear
  in their hometown or in the hostel if they are not in your party. Assuming you
  had picked the Mage as your hero, and asked Iolo (Bard), Dupre (Paladin), and
  Jaana (Druid) to join you, $0B would have a value of $2B.

| 5.5 Stones & Runes

  As with character's, virtues, and towns, there are eight stones and eight
  runes. They all correspond to a particular virtue.
  Blue Stone of Honesty
  Yellow Stone of Compassion
  Red Stone of Valor
  Green Stone of Justice
  Orange Stone of Sacrifice
  Purple Stone of Honor
  White Stone of Spirituality
  Black Stone of Humility
  The stones are at offset $19 while the runes are at offset $1A. Just like
  partial avatarhood, they are represented by a bitlist. You can read more about
  bitlists in the Virtues section 5.3.
| 5.6 Magic

  Magic in the game is tracked using four bitlists at $1B, $1C, $1D, and $1E.
  They can each hold eight spells, but only some values are used. You can read
  more about bitlists in the Virtues section 5.3.
  Offset $1B represents the following spells:
    Bit 0 Light
    Bit 1 Missile
    Bit 2 Awaken
    Bit 3 Cure
    Bit 4 Wind
    Bit 5 Heal
    Bit 6 Fire
    Bit 7 Exit
  Offset $1C
    Bit 0 Dispel
    Bit 1 View
    Bit 2 Protect
    Bit 3 Ice
    Bit 4 Blink
    Bit 5 Energy
    Bit 6 Quick
    Bit 7 (Unused)
  Offset $1D
    Bit 0 Sleep
    Bit 1 Reflect
    Bit 2 Negate
    Bit 3 (Unused)
    Bit 4 Destroy
    Bit 5 Jinx
    Bit 6 Squish
    Bit 7 Gate

  Offset $1E
    Bit 0 Tremor
    Bit 1 Life
    Bit 2 (Unused)
    Bit 3 Defeat
    Bit 4 (Unused)
    Bit 5 (Unused)
    Bit 6 (Unused)
    Bit 7 (Unused)

  If you think I've missed a spell or this information is incorrect, please
  contact me.
| 5.7 Herbs, Gold, & Tools

  The number of herbs you can have is represented in the offsets $1F-$26. There
  are eight herbs: Ash, Ginseng, Garlic, Silkweb, Moss, Pearl, Fungus, and
  Manroot. Ash = $1F, Manroot = $26. The rest are in-between, respectively.
  Just change the number to how much you want. $00 = 0, $63 = 99. This is the
  most the game would normally let you have, though I haven't seen any adverse
  affect if you have more, say $FF = 255.
  Gold is one of the more difficult values because it's one of the few values
  that is represented by a word (2 bytes = 16 bits). Anytime you have a value
  greater than a single byte (usually), you start needing a way to store such
  a number in memory. There are two primary ways of doing this. They are known
  as little-endian, and big-endian. The NES (MOS 6502) and the PC (Intel x86)
  are little-endian. Macs (MC68000 and PPC) and Suns (Sparc) are big-endian.
  What is endian? It's how you store a multi-byte number. Do you store the most
  significant part first (like humans do normally, 8001), which is big-endian,
  or do you store the least significant part first, which is little-endian.
  Though I would personally prefer big-endian since it looks more correct for
  us, the NES used little-endian, and we can't do anything about that now.
  So, if the NES has a word value (2 bytes) starting at $26, it might read
  something like $9001 ($27 = $90, $28 = $01). Now, $0190 is 400, the amount of
  gold you start with. So, to look correct for us, we need to swap $27 and $28.
  So, if we want 9999 gold, which is $270F hex, we want to put $0F in $27 and
  $27 in $28.
  Complexities like this are just another reason to use my hack4u program.
  Tools come in two formats, have/have not, and quantity. The quantity tools
  are torches, gems, and oil. You can have 0 (none) up to 99 (max). You might
  be able to have 255 like Herbs, but we haven't tested it.
  So, for the three quantity tools, just change the offset to the number you
  want, $00 - $63 (or possibly $FF).

  $29 = Torches, $2A = Gems, and $2C =  Oil
  All the other tools are have/have not tools. You either have them, or you
  don't. If you don't have them, there's a $0 value in the offset. If you have
  them, there is a $1.
  The have/have not tools are $2B, and $2D-$38. You can look them up in section
| 5.8 Inventory

  In Ultima, there are eight characters. Each ones inventory is tracked
  separately. They can have 6 items (weapons, bows, and armors) and they can
  have one of each kind equipped.
  So there are six offsets for each character, and they start at $39. Refer to
  section 5.0 for the full list for each character.
  Here are the possible values.
  $00 Nothing
  $01 Sling
  $02 Bow
  $03 X-Bow
  $04 +1 Bow
  $05 Dagger (Has nyone ever heard of this? I haven't)
  $06 Staff
  $07 Club
  $08 Axe
  $09 Sword
  $0A +1 Sword
  $0B +2 Sword
  $0C +1 Axe
  $0D Wand
  $0E +2 Axe (Axe of Legend)
  $0F Sword of Paradise
  $10 Cloth
  $11 Leather
  $12 Chain
  $13 Ring
  $14 +1 Cloth
  $15 Plate
  $16 +1 Chain
  $17 +1 Plate
  $18 Robe
  $19 Exotic Armor
  That will give your character the item. If you want it equipped, add $80 to
  the value. $81 = Equipped Sling. $99 = Equipped Exotic Armor.

| 5.9 Character Stats

  Character stats include current level, current/max HP, current/max MP,
  strength, intelligence, dexterity and experience.
  Of those values, current/max HP and experience are word values. See section
  5.7 on Gold for more information on the implications of word values as opposed
  to single byte values.
  For each range of offset, they apply to the characters in sequence Mage,
  Bard, Fighter, Druid, Tinker, Paladin, Ranger, and Shepherd. This means
  the level offset for the Mage is $69, for Bard $6A, for Fighter, $6B, etc.
  I'm not entirely sure, but I believe valid HP values are between 200 and 800,
  while valid experience values are between 0 and 9999. All the rest of the
  values range between 0 and 99. Going outside these ranges may yield
  unexpected results.
| 5.10 Starting Location

  The starting location value determines in which town the player starts out.
  There are eight inns in the game, one for each city of virtue except Magincia,
  and the village of Vesper.
  Offset $107
    Moonglow   = $05
    Britain    = $06
    Jhelom     = $07
    Yew        = $08
    Minoc      = $09
    Trinsic    = $0A
    Skara Brae = $0B
    Vesper     = $0E
  Any other value puts you in Britain's inn.
| 5.11 Pirate Ships & The Balloon

  In Ultima, you can find a balloon to fly in, and steal ships from pirates. The
  game has one balloon, and keeps track of up to 4 stolen pirate ships you've
  comandeered. For each ship after 4 you take, one will vanish. You may lose all
  your ships if you travel by moongate or use the Gate spell. I'm not sure
  exactly when the game erases your ships during gate travel.
  The locations of the balloon and the pirate ships are stored in two bytes
  using the game's internal latitude and longitude coordinates.
  Balloon Longitude = $F0
  Balloon Latitude  = $F1
  The Balloon will go back to its starting location anytime you exit the dungeon
  Hyloth from the Britannia entrance. The starting location is at (243,233).
  The four pirate ship locations are stored as follows:
  Ship 1 = ($F4,$F5) (Longitude,Latitude)
  Ship 2 = ($F6,$F7)
  Ship 3 = ($F8,$F9)
  Ship 4 = ($FA,$FB)
  Because there are four potential comandeered pirate ships, there are some
  extra offsets for pirate ships. To have one of the ships, the offset $ED must
  have one of it's lower 4 bits set. For ship 1 to exist, bit 0 must be on, ship
  2 is bit 1, and so on for the four possible ships.
  The offset $EE keeps track of the next ship. In other words, if you already
  have ship 1, then the next ship you steal should be ship 2. This number varies
  between 0 and 3 for the 4 possible pirate ships.
| 5.12 The Whirlpool

  There are three ways to get to the village Cove in the game. One is the
  balloon. Another is by ship without a whirlpool (yes, there are places you can
  catch a pirate ship and sail to Cove sans whirlpool). Finally, there is taking
  your ship into the Whirlpool.
  Before today, I had never seen the whirlpool, though I've heard of it. I've
  always used one of the other two methods for getting to Cove.
  I'm not entirely sure how the whirlpool works. It moves, but it has a starting
  location that may or may not change. Since I've never seen it before, I don't
  know if it changes or not. I assume that it does because it seems stupid to
  save a value in SRAM that never changes.
  The starting location of the whirlpool is at ($F2,$F3) (Longitude, Latitude).
  It moves around this spot, but doesn't seem to stray very far. I'm not sure
  how useful it is to change the location or if there are any side effects. I
  didn't notice any when I tested it. But I only tried it out for a short time.
| 5.13 Treasure Offsets

  Treasure offsets determine whether or not a found item will be there when you
  go to look for it. For example, if you've already found the rune of honor,
  then it won't be there if you research the field in Trinsic. Nearly every
  treasure has a single bit that tracks whether it will be there or not. The
  only exceptions I have found are the three keys.
  Rune of Honesty      = $D4 bit 4
  Rune of Compassion   = $D4 bit 5
  Rune of Valor        = $D4 bit 6
  Rune of Justice      = $D4 bit 7
  Rune of Sacrifice    = $D5 bit 0
  Rune of Honor        = $D5 bit 1
  Rune of Spirituality = $D5 bit 2
  Rune of Humility     = $D5 bit 3
  Blue Stone           = $D6 bit 7
  Yellow Stone         = $D7 bit 0
  Red Stone            = $D7 bit 1
  Green Stone          = $D7 bit 2
  Orange Stone         = $D7 bit 3
  Purple Stone         = $D7 bit 4
  White Stone          = $D7 bit 5
  Black Stone          = $D7 bit 6
  Horn                 = $D6 bit 3
  Flute                = $D5 bit 5
  Scale                = $D5 bit 4
  Candle of Love       = $D5 bit 7
  Book of Truth        = $D6 bit 0
  Bell of Courage      = $D6 bit 1
  Mondain's Skull      = $D6 bit 4
  Sword of Paradise    = $DD bit 7
  Exotic Armor         = $DE bit 0
  Robe                 = $D5 bit 6
  Gave Scale to Zircon = $D2 bit 3
  Axe of Legend        = $D2 bit 5
| 5.14 Miscellaneous Offsets

  These offsets don't seem to fit in anywhere else. I'm not sure how useful they
  are, but here they are anyway.
  Dungeon Keeper's Wall Smashed:
  For each of the 6 dungeons that have stones (all except Hyloth), there is a
  guardian who keeps the stone behind a wall. If you answer the question
  correctly, the wall crumbles. These offsets track whether the wall is still
  present or not.
  Deceit (Honesty)     = $DD bit 1
  Despise (Compassion) = $DD bit 2
  Destard (Valor)      = $DD bit 3
  Wrong (Justice)      = $DD bit 4
  Covetous (Sacrifice) = $DD bit 5
  Shame (Honor)        = $DD bit 6

| 6.0 The Sanity Algorithm

  Because the battery can wear out, the SRAM values cannot be trusted. They may
  not retain their proper values, which would result in invalid or corrupt
  saves. Unfortunately, there is no way to prevent it. But we can attempt to
  detect is by using what are known as sanity algorithms. Sometimes they are
  called checksums, CRCs, MD5 sums, etc, but each of those other names is
  specific to unique process. None of these processes are used by ultima. They
  did something far more complex.
  To keep the game thinking the SRAM is good, since it doesn't let you play the
  game if it thinks it's not, we need to duplicate the process it uses to
  create sanity values that it can check. Here is how it works.
  Take the $200 bytes used by the particular save game.
  You need a value which will be the sanity byte. Set this value to 0. You also
  need a counter value. Set this value to 0.
  For every byte $001 to $1FF ($200 bytes = $000 - $1FF), do the following
  1) XOR (binary exclusive OR) this number with one of the following numbers
     $55, $AA, $33, $CC, $A5, $5A, $BB, $99. The number depends upon your
     counter. Counter at 0 = $55, Counter at 7 = $99.
  2) Add the XORed value to the sanity value.
  3) If the sanity value exceeds $FF (255), drop the higher numbers.
     $123 = $23. $1FE = $FE. etc.
  4) Increment the counter.
  5) If the counter is > 7, set the counter to 0.
  6) Repeat steps 1-5 for each byte $001 - $1FF
  Once you are done, you will have the primary sanity value. This value should
  be stored in the SRAM at offset $00.
  But you're not done yet. We need two secondary sanity values. To get the
  first, XOR the primary with $AA. To get the second, XOR the primary with $55.
  Now, you have three sanity values. For game one, store then in the SRAM at
  addresses $1900, $1903, and $1906. For game two, it's $1901, $1904, and $1907.
  For game three, it's $1902, $1905, and $1908. These are the nine bytes thar
  are not part of the $200 byte game data.
  If you want to hack the SRAM, but don't want to generate the SRAM values, you
  can use this program I wrote. It will take an NES Ultima SRAM file, generate
  the sanity values and write them to the SRAM file. Then you can see your
  modifications. Of course I would recommend my hack4u program as a complete
  solution, but if you want to play with values you can't modify with hack4u,
  this is a good alternative.
------------------------------ begin sanity.c ----------------------------------
 * sanity
 * Copyright (C) 2004 emuWorks
 * sanity is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * sanity is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with sanity; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

#include <stdio.h>

#define SRAM_SIZE 0x2000
#define SANITY_OFFSET 0x1900

#define GAME_SIZE 0x200
#define GAME_OFFSET 0x1A00

static const unsigned char SANITY_XORS[] = {
    0x55, 0xAA, 0x33, 0xCC,
    0xA5, 0x5A, 0xBB, 0x99

unsigned char getSanity(const char *game) {
    int counter = 0;
    int offset;
    unsigned char sanity = 0;
    /* For every byte $001 to $1FF, do the following */
    for (offset = 1; offset < GAME_SIZE; offset++) {
        /* XOR the number with one of the following numbers */
        char xor = (game[offset] ^ SANITY_XORS[counter]);
        /* Add the XORed value to the sanity value */
        sanity += xor;
        /* If the sanity value exceeds $FF, drop the higher numbers */
        /* this is done automatically since our sanity value is a char */
        /* increment the counter */
        /* if the counter > 7, set the counter to 0 */
        counter &= 0x7;
        /* repeat steps 1-5 for each byte */
    return sanity;

int main(int argc, char *argv[]) {
    FILE *f;
    char sram[SRAM_SIZE], *game;
    int game_number;
    unsigned char primary, first, second;
    /* check for the SRAM argument */
    if (argc != 2) {
        fprintf(stderr, "syntax: sanity ultima_sram_file\n");
        fprintf(stderr, "example: sanity ultima.sav\n");
        return -1;
    /* make sure we can open the file for reading */
    if ((f = fopen(argv[1], "rb")) == NULL) {
        fprintf(stderr, "error: unable to open %s for reading.\n", argv[1]);
        return -1;
    /* make sure we were able to read a full SRAM file */
    if (fread(sram, SRAM_SIZE, 1, f) != 1) {
        fprintf(stderr, "error: unable to read SRAM file data.\n");
        return -1;
    /* print the three sanity values for each game */
    for (game_number = 0; game_number < 3; game_number++) {
        /* find the game data within the SRAM */
        game = (sram + GAME_OFFSET + (game_number * GAME_SIZE));
        /* generate sanity values */
        primary = getSanity(game);
        first = (primary ^ 0xAA);
        second = (primary ^ 0x55);
        /* alter the SRAM with the new sanity values */
        game[0] = primary;
        *(sram + SANITY_OFFSET + game_number) = primary;
        *(sram + SANITY_OFFSET + game_number + 3) = first;
        *(sram + SANITY_OFFSET + game_number + 6) = second;
        /* display those sanity values */
        printf("primtary sanity for game %d = $%02X\n", 
               (game_number + 1), (unsigned int)primary);
        printf("first sanity check for game %d = $%02X\n", 
               (game_number + 1), (unsigned int)first);
        printf("second sanity check for game %d = $%02X\n", 
               (game_number + 1), (unsigned int)second);
        if (game_number < 2) {
    /* write these values back to the save file */
    if ((f = fopen(argv[1], "wb")) == NULL) {
        fprintf(stderr, "error: unable to open %s for writing.\n", argv[1]);
        return -1;
    if (fwrite(sram, SRAM_SIZE, 1, f) != 1) {
        fprintf(stderr, "error: unable to write SRAM file data.\n");
        return -1;
    return 0;
-------------------------------- end sanity.c ----------------------------------

  Written in ANSI C and under the GNU GPL, it should compile under any platform.
  I have put up a windows binary at
| 7.0 Contact Information

  The author, John Ratliff, can be contact at