EarthBound Zero/RAM map: Difference between revisions

From Data Crystal
Jump to navigation Jump to search
(the checksum is figured out!)
m (Xkeeper moved page EarthBound Zero:RAM map to EarthBound Zero/RAM map: normalize subpages and titles)
 
(11 intermediate revisions by one other user not shown)
Line 22: Line 22:
<pre>
<pre>
# replace with the values from the RAM
# replace with the values from the RAM
ram_x = 0x1980
ram_x = 0x8019
ram_y = 0x2dc0
ram_y = 0xc02d


real_x = ((byte1 << 8) | (byte0 & 0xC0)) >> 2
byte0, byte1 = ram_x.to_bytes(2, "little")
real_y = ((byte3 << 8) | (byte2 & 0xC0) - 0x2000) >> 2
byte2, byte3 = ram_y.to_bytes(2, "little")
 
real_x = ((byte0 << 8) | (byte1 & 0xC0)) >> 2
real_y = ((byte2 << 8) - 0x1e00 | (byte3 & 0xC0)) >> 2
 
print((real_x, real_y))
</pre>
</pre>


=SRAM (save data)=
=SRAM (save data)=
===Unknown===
==Entire SRAM==
There are multiple saves throughout the save RAM, three for the saved data and one for the last save.  The last save is the one that was saved last, and unless an error occurred during saving, should be identical to the save slot that was last saved.
===Save RAM===
* 0000-13FF: '''Unknown'''
* 0000-13FF: '''Unknown'''
===Checksum===
* 1400-16FF: Last save
* 1700-19FF: Save slot 1
* 1A00-1CFF: Save slot 2
* 1D00-1FFF: Save slot 3
==Saves==
Each save has this format:
===Slot metadata===
The 2-byte checksum is calculated by subtracting each byte after it (0x300 for each save, so 0x29E bytes to subtract), then running it through mod 0x10000.
The 2-byte checksum is calculated by subtracting each byte after it (0x300 for each save, so 0x29E bytes to subtract), then running it through mod 0x10000.
Here is an example Python program that calculates it:
Here is an example Python program that verifies it:
<pre>
<pre>
def calculate_checksum(handle, base = 0x1700):
def calculate_checksum(handle, slot = 0): # 0 = last save
     handle.seek(base)
     handle.seek(0x1400 + slot * 0x300)
     checksum = int.from_bytes(handle.read(2), "little")
     ram_checksum = int.from_bytes(handle.read(2), "little") # read the checksum from the RAM
 
    # calculate it manually
     calculated_checksum = 0x0
     calculated_checksum = 0x0
     for number in range(0x300 - 0x2):
     for number in range(0x300 - 0x2):
         calculated_checksum = (calculated_checksum - int.from_bytes(handle.read(2), "little")) % 0x100 ** 2
         calculated_checksum = (calculated_checksum - int.from_bytes(handle.read(2), "little")) % 0x100 ** 2 # this is ran for each byte other then the checksum
 
    assert ram_checksum == calculated_checksum, "checksum is invalid" # throw an error if the checksum doesn't match
     return calculated_checksum
     return calculated_checksum
calculate_checksum(open("file.sav", "rb"))
calculate_checksum(open("file.sav", "rb"))
</pre>
</pre>
* 1400-1401: Checksum
* 1400-1401: Checksum
* 1402: Slot number
*:$B0 for slot 1, $B1 for slot 2 and $B2 for slot 3
* 1403: Slot state
*:Normally $7E, deleted slots are $00.  It's possible to recover an accidentally deleted save slot by changing this back
===Main data===
===Main data===
* 1402-1403: '''Unknown'''
* 1404-1407: Position
* 1404-1407: Position
* 1408-140B: Current party members
* 1408-140B: Current party members
*:Each number corresponds to an ally in [[EarthBound Zero:Ally data|Ally data]], 0 means empty and skips to the next value.
*:Each number corresponds to an ally in [[EarthBound Zero:Ally data|Ally data]], 0 means empty and skips to the next value
* 140C-140F: '''Unknown'''
* 140C-140F: Last save position
*:Right before showing the XP needed to level up for each character (during Dad's phone call), the game copies the position to this address, meaning that this is always the position you were when you last phoned Dad (regardless of whether you saved or not); changing it doesn't affect the position when loading the save
* 1410-1411: Current money
* 1410-1411: Current money
* 1412-1414: Money in bank
* 1412-1414: Money in bank
Line 71: Line 92:
* 1984-16AF: '''Unknown'''
* 1984-16AF: '''Unknown'''
* 16B0-16CF: Items in storage
* 16B0-16CF: Items in storage
===Unknown===
* 16D0-16FF: '''Unknown'''
* 16D0-1FF0: Other
==Ally state==
==Ally state==
Each ally's state is stored at a place corresponding to the character, see the above section
Each ally's state is stored at a place corresponding to the character, see the above section

Latest revision as of 02:41, 24 January 2024

Chip tiny.png The following article is a RAM map for EarthBound Zero.

RAM

RAM Purpose
0x0006 Part of an instruction for "copy protection". $E5 on checksum failure. $00 to bypass. "Correct"/non-hack value unknown.
0x0015 Used for assigning area to objects?
0x0060 HP target value for stat increase during level up
0x0611 Looks like the first EXP byte, but changing it even mid-battle will result in insta-death. Anti-cheat?
0x0818-B The X and Y coordinates, stored strangely; see below

Coordinates

The coordinates in RAM to not correspond to coordinates in the rest of the game. To convert it, run this Python code (in this example, bytes $818-B are `80 19 C0 2D`):

# replace with the values from the RAM
ram_x = 0x8019
ram_y = 0xc02d

byte0, byte1 = ram_x.to_bytes(2, "little")
byte2, byte3 = ram_y.to_bytes(2, "little")

real_x = ((byte0 << 8) | (byte1 & 0xC0)) >> 2
real_y = ((byte2 << 8) - 0x1e00 | (byte3 & 0xC0)) >> 2

print((real_x, real_y))

SRAM (save data)

Entire SRAM

There are multiple saves throughout the save RAM, three for the saved data and one for the last save. The last save is the one that was saved last, and unless an error occurred during saving, should be identical to the save slot that was last saved.

Save RAM

  • 0000-13FF: Unknown
  • 1400-16FF: Last save
  • 1700-19FF: Save slot 1
  • 1A00-1CFF: Save slot 2
  • 1D00-1FFF: Save slot 3

Saves

Each save has this format:

Slot metadata

The 2-byte checksum is calculated by subtracting each byte after it (0x300 for each save, so 0x29E bytes to subtract), then running it through mod 0x10000. Here is an example Python program that verifies it:

def calculate_checksum(handle, slot = 0): # 0 = last save
    handle.seek(0x1400 + slot * 0x300)
    ram_checksum = int.from_bytes(handle.read(2), "little") # read the checksum from the RAM

    # calculate it manually
    calculated_checksum = 0x0
    for number in range(0x300 - 0x2):
        calculated_checksum = (calculated_checksum - int.from_bytes(handle.read(2), "little")) % 0x100 ** 2 # this is ran for each byte other then the checksum

    assert ram_checksum == calculated_checksum, "checksum is invalid" # throw an error if the checksum doesn't match
    return calculated_checksum
calculate_checksum(open("file.sav", "rb"))
  • 1400-1401: Checksum
  • 1402: Slot number
    $B0 for slot 1, $B1 for slot 2 and $B2 for slot 3
  • 1403: Slot state
    Normally $7E, deleted slots are $00. It's possible to recover an accidentally deleted save slot by changing this back

Main data

  • 1404-1407: Position
  • 1408-140B: Current party members
    Each number corresponds to an ally in Ally data, 0 means empty and skips to the next value
  • 140C-140F: Last save position
    Right before showing the XP needed to level up for each character (during Dad's phone call), the game copies the position to this address, meaning that this is always the position you were when you last phoned Dad (regardless of whether you saved or not); changing it doesn't affect the position when loading the save
  • 1410-1411: Current money
  • 1412-1414: Money in bank
  • 1415-141F: Unknown
  • 1420-1430: Your name (the player name, not Ninten's name)
  • 1431-143F: Unknown

Ally status

This is documented below.

  • 1440-147F: Ninten
  • 1480-14BF: Ana
  • 14C0-14FF: Lloyd
  • 1500-153F: Teddy
  • 1540-157F: Pippi
  • 1580-15BF: EVE
  • 15C0-15FF: Flying Man

Other

  • 1600-1688: Unknown
  • 1689-1983: Favorite food name
  • 1984-16AF: Unknown
  • 16B0-16CF: Items in storage
  • 16D0-16FF: Unknown

Ally state

Each ally's state is stored at a place corresponding to the character, see the above section

  • 00: Unknown (unused?)
  • 01: Status condition
    • Bit 0: Cold
    • Bit 1: Poisoned
    • Bit 2: Puzzled
    • Bit 3: Confused
    • Bit 4: Sleeping
    • Bit 5: Paralyzed
    • Bit 6: Stone
    • Bit 7: Unconscious (can be applied even when HP isn't at 0)
  • 02: Unknown
  • 03-04: Max HP
  • 05-06: Max PP
  • 07-08: Offense
  • 09-0A: Defense
  • 0B: Fight
  • 0C: Speed
  • 0D: Wisdom
  • 0E: Strength
  • 0F: Force
  • 10: Level
  • 11-13: Experience
  • 14-15: Current HP
  • 16-17: Current PP
  • 18-19: Unknown
  • 20-27: Items
  • 28: Weapon
  • 29: Coin
  • 2A: Ring
  • 2B: Pendant
  • 30-37: Learned PSI
  • 38-3E: Name
  • 3F: Unknown (unused?)