--------------------------------------------------------------------------------
| Ultima: Quest of the Avatar
| Save State Hacking Guide
| by John Ratliff
|
| The most recent version of this guide can always be found at
| http://games.technoplaza.net/hack4u/sram-hacking.txt
|
| Copyright (C) 2004-2005 emuWorks (http://games.technoplaza.net/)
|   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
|   http://www.gnu.org/licenses/fdl.html
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
| 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 http://games.technoplaza.net/hack4u/. 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
     it.

 - 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
     guidance.

 - 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
 example.

 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
   http://www.gnu.org/licenses/fdl.html

 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
 curious.

 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
 slphabet.

 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
 $21.

 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.0.

--------------------------------------------------------------------------------
| 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
* http://games.technoplaza.net/
*
* 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
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* 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 */
       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");
       fclose(f);

       return -1;
   }

   fclose(f);

   /* 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) {
           printf("\n");
       }
   }

   /* 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");
       fclose(f);

       return -1;
   }

   fclose(f);

   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
 http://games.technoplaza.net/hack4u/sanity.zip

--------------------------------------------------------------------------------
| 7.0 Contact Information
--------------------------------------------------------------------------------

 The author, John Ratliff, can be contact at
 http://www.technoplaza.net/feedback.php.