diff options
-rwxr-xr-x | srb.py | 3 | ||||
-rw-r--r-- | srb_lib.py | 70 |
2 files changed, 27 insertions, 46 deletions
@@ -384,7 +384,4 @@ else: if args.checksum: f = args.file - #for f in args.file: - if debuglev(1,debuglevel): - ferror(f"Fixing checksum for file {f}") srb_lib.correct_file(f,debuglevel) @@ -25,24 +25,36 @@ # Dependencies: import sys, struct -srb_lib_version = "20240320a" +srb_lib_version = "20240320b" # Table of byte positions of values in the savegame file, minus the first four bytes which are the checksum. Due to zero-indexing of python lists, but for ease of usage, we will always put a zero as the value of index 0. That is, profile 1 will use index 1 of the list. # "Z<dddddddd" is the start of a profile. -CHECKSUM_LENGTH = 0x4 +INT_SIZE = 0x4 # unsigned; no negative numbers used in this game or data structure +CHECKSUM_LENGTH = INT_SIZE PROFILE_START_POSITION = [0, 0x10, 0x142C, 0x2848] + +# There are additional values stored, including Options but you can change those in the menu so are not implemented in this tool. +POS_BYTES_WEAPONS_PURCHASED = 0x028C +POS_LEVEL_BALLOONS_MULTIPLIER_LEVELSET = 0x238 POS_MONEY = 0x270 POS_HEALTH = 0x274 # 0-3 is available levels -POS_STUNT = 0x280 # 0-3 is available levels POS_GUN = 0x27C # 0-3 is available levels, although 4 is octo-gun +POS_STUNT = 0x280 # 0-3 is available levels POS_EQUIPPED_WEAPON = 0x284 +POS_PLANES_PURCHASED = 0x288 POS_PROFILE_IN_USE = 0x290 POS_NAME = 0x294 +POS_LEVELSET_COMPLETED_MISSIONS_MASK = 0x2B8 +POS_TUTORIAL_COMPLETED = 0x2D0 # bool +POS_LEVEL_START = 0x2D4 +POS_LEVELSET_COMPLETED_LETTERS_COUNT = 0x3A0 +POS_LEVEL_LETTERS = 0x3E8 +POS_LEVEL_LETTERS_MULTIPLIER_LEVELSET = 7 +POS_LEVEL_BALLOONS = 0x4B4 +POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL = 0x50 -NAME_CHARS = " ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!?" +NAME_CHARS = " ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!?" # in order -POS_BYTES_WEAPONS_PURCHASED = 0x028C -# Woodstock Missile and Bottle Rockets are always available to use. # for equipped-weapon # id = value of equipped byte # e = equippable @@ -66,7 +78,6 @@ WEAPONS = [ {"id":0xF,"e":False,"p":0x6F60,"name":"all"}, # custom for this library ] -POS_PLANES_PURCHASED = 0x288 PLANES = [ {"p":0x0000,"name":"none"}, {"p":0x0100,"name":"marcie"}, @@ -91,17 +102,6 @@ LEVELSETS = [ {"id":5,"l":2,"c":"none" ,"b":0,"le":"","name":"Flying Fortress"}, ] -INT_SIZE = 0x4 -# relative to player profile -POS_LEVEL_START = 0x2D4 -POS_LEVEL_BALLOONS = 0x4B4 -POS_LEVEL_BALLOONS_MULTIPLIER_LEVELSET = 0x238 -POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL = 0x50 -POS_LEVELSET_COMPLETED_LETTERS_COUNT = 0x3A0 -POS_LEVEL_LETTERS = 0x3E8 -POS_LEVEL_LETTERS_MULTIPLIER_LEVELSET = 7 -POS_LEVELSET_COMPLETED_MISSIONS_MASK = 0x2B8 - # pos_r = relative position for level status # set_pos = level number within the levelset. Necessary for certain position calculations including letters. LEVELS = [ @@ -156,8 +156,6 @@ LEVEL_STATUSES = [ {"b":0x64,"name":"general"}, # depends on all secondary objectives, all letters, all balloons, under time, at least 50/100 health, not lost any lives. ] -POS_TUTORIAL_COMPLETED = 0x2D0 # bool - # DEFINE FUNCTIONS def ferror(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) @@ -177,22 +175,15 @@ def read_file(filename): with open(filename, 'rb') as f: # Read the entire file file_contents = f.read() - checksum = file_contents[0:4] - file_contents_without_checksum = file_contents[4:] + checksum = struct.unpack_from('<1I',file_contents,0)[0] + file_contents_without_checksum = file_contents[CHECKSUM_LENGTH:] return checksum, file_contents_without_checksum def write_file(filename, checksum, data): """ Write the checksum and data to the file. We are assuming the checksum is already correct. """ - print(f"debug: checksum(type={type(checksum).__name__})={checksum}") - if type(checksum) == int: - checksum = checksum - elif type(checksum) == str: - checksum = int(checksum) - elif type(checksum) == bytes: - checksum = int.from_bytes(checksum) + #print(f"debug: checksum(type={type(checksum).__name__})={checksum}") with open(filename, 'wb') as f: - f.write(struct.pack('>L',checksum)) - #f.write(struct.pack('>I',int(checksum))) + f.write(struct.pack('<L',checksum)) f.write(data) ######## start embedded file @@ -821,16 +812,9 @@ def calculate_checksum(data): # The polynomial appears to be a standard one like used for Ethernet, but I am uncertain of the xor is standard or not. # I learned all this only with <https://github.com/8051Enthusiast/delsum> # crc width=32 poly=0x4c11db7 init=0x0 xorout=0x235b4b9c refin=false refout=false out_endian=little - csum = hex(crc_poly(data, 32, 0x4C11DB7, crc=0x0, ref_in=False, ref_out=False, xor_out=0x235b4b9c)) - # comes in as ceb434d4, but needs to be d434b4ce, and bytearray is convenient way to swap it like that - # trim off the '0x' from the string - print(f"debug: Got csum={csum}") - # pad any odd-digit-count hex value with a leading 0 for bytearray.fromhex() which is not very smart. - csum = ("0" if len(str(csum)) % 2 else "") + str(csum)[2:] - csum = bytearray.fromhex(csum) - csum.reverse() - # and back to bytes because that is how we will want to use it. - return bytes(csum) + csum = crc_poly(data, 32, 0x4C11DB7, crc=0x0, ref_in=False, ref_out=False, xor_out=0x235b4b9c) + # Note that this value will be stored little-endian in the file. That detail does not matter here. + return csum def correct_file(filename, debuglevel = 0): """ Given a savegame file, calculate the correct checksum and store that sum instead of whatever is there. """ @@ -839,10 +823,10 @@ def correct_file(filename, debuglevel = 0): ran = False if csum != cs: if debuglev(5,debuglevel): - ferror(f"Stored checksum is {cs} and will update it to {csum}") + ferror(f"Stored checksum is 0x{cs:04x} and will update it to 0x{csum:04x}") write_file(filename,csum,data) ran = True else: if debuglev(2,debuglevel): - ferror(f"Stored checksum is still correct, {csum}. Skipping {filename}") + ferror(f"Stored checksum is still correct, 0x{csum:04x}. Skipping {filename}") return ran |