hide results

    Save State Hacking Guide by jdratlif

    Version: 1.2 | Updated: 03/21/06 | Search Guide | Bookmark Guide

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

    FAQ Display Options: Printable Version