From 7a26a5f51722ec9a8af334df385dbe5fd4847e32 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Sat, 9 Mar 2024 22:12:16 -0500 Subject: progress for day --- srb.py | 14 +++++++-- srb_lib.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/srb.py b/srb.py index ed96c4b..800d1c5 100755 --- a/srb.py +++ b/srb.py @@ -23,7 +23,11 @@ import srb_lib, argparse, sys from srb_lib import ferror, debuglev -parser = argparse.ArgumentParser(description="Cli tool for manipulating savegame files for srb.exe") +parser = argparse.ArgumentParser(description="Cli tool for manipulating savegame files for srb.exe", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=f"""LEVELS include {[i['name'] for i in srb_lib.LEVELS]+list(range(0,len(srb_lib.LEVELS)))} +WEAPONS include {[i for i in srb_lib.WEAPONS if i != "undefined"]+list(range(0,16))} +""") parser.add_argument("-V","--version",action="version",version="%(prog)s " + srb_lib.srb_lib_version) parser.add_argument("-d","--debug",nargs='?',default=0,type=int,choices=range(0,11),help="Set debug level") parser.add_argument("--profile",type=int,choices=range(1,4),help="Profile in user menu.") @@ -34,6 +38,7 @@ parser.add_argument("--get-weapon",action="store_true",help="Print currently equ #choices=[i for i in srb_lib.WEAPONS if i != "undefined"]+list(range(0,16)) #parser.add_argument("--set-weapon",choices=[i for i in srb_lib.WEAPONS if i != "undefined"],help="Print currently equipped weapon for profile.") parser.add_argument("--set-weapon",help="Print currently equipped weapon for profile.") +parser.add_argument("--get-level",help="Print status for this level for profile.") parser.add_argument("--checksum",action=argparse.BooleanOptionalAction,default=True,help="Correct checksum. Default is to do this. It happens at the end of everything else.") parser.add_argument("file",default="Profile 1.sav") args = parser.parse_args() @@ -49,9 +54,10 @@ if debuglev(8,debuglevel): # common parameters profile_id = args.profile -print(f"profile_id={profile_id}") +#print(f"profile_id={profile_id}") -if not profile_id and (args.get_money or args.set_money or args.get_weapon or args.set_weapon): +# WORKHERE: new actions that need --profile must be added here. +if not profile_id and (args.get_money or args.set_money or args.get_weapon or args.set_weapon or args.get_level): ferror("Warning: Cannot perform most actions without --profile. Not all tasks may run.") else: if args.get_money: @@ -76,6 +82,8 @@ else: pass else: srb_lib.write_file(args.file,0,data) + if args.get_level: + print(f"Profile {profile_id} has level {args.get_level} status {srb_lib.get_level_status(args.file,profile_id,args.get_level)}") if args.checksum: f = args.file diff --git a/srb_lib.py b/srb_lib.py index f3a1deb..dc69614 100644 --- a/srb_lib.py +++ b/srb_lib.py @@ -29,6 +29,11 @@ PROFILE_START_POSITION = [0, 0x10, 0x142C, 0x2848] POS_MONEY = 0x270 POS_EQUIPPED_WEAPON = 0x284 +# stinger purchased is relative 0x28C, value 0x40 (b01000000) or 64 +# water-balloon-gun is relative 0x2D, value 0x0001 +# stinger and potato gun is relative 0x28C, value 0x60, so potato gun is (b00100000) or 64 +#POS_STINGER = 0x028C + WEAPONS = [ "undefined", # 0x0 "undefined", # secondary weapon is a single machine gun?! @@ -48,6 +53,57 @@ WEAPONS = [ "undefined", # 0xF ] +# WORKHERE: position is for where the balloon info is stored for this levelset, if it is stored here and not per-level. +LEVELSETS = [ + {"id":0,"name":"Aerodrome Island","pos":0x0}, + {"id":1,"name":"Woods of Montsec","pos":0x0}, + {"id":2,"name":"Front Lines of Verdon","pos":0x0}, + {"id":3,"name":"Mines of the Matterhorn","pos":0x0}, + {"id":4,"name":"Verdon Gorge","pos":0x0}, + {"id":5,"name":"Flying Fortress","pos":0x0}, +] + +# relative to player profile +POS_LEVEL_START = 0x2D8 +# each level position is POS_LEVEL_START + (4*level["pos_r"]) +# the relative position is because each level set gets 7 (or 6 plus 1 blank) slots, but not all levelsets have 6 levels. +LEVELS = [ + {"id":0, "pos_r":0, "setid":0,"name":"Defend Island"}, + {"id":1, "pos_r":1, "setid":0,"name":"Recover Plans"}, + {"id":2, "pos_r":2, "setid":0,"name":"Protect the Trucks"}, + {"id":3, "pos_r":3, "setid":0,"name":"Attack of the U-Boats"}, + {"id":4, "pos_r":4, "setid":0,"name":"Cripple Outpost Island"}, + {"id":5, "pos_r":5, "setid":0,"name":"Sink the Battleship"}, + {"id":6, "pos_r":7, "setid":1,"name":"Rerun's Challenge"}, + {"id":7, "pos_r":8, "setid":1,"name":"Eliminate Tree Village"}, + {"id":8, "pos_r":9, "setid":1,"name":"Tree Chopper"}, + {"id":9, "pos_r":14,"setid":2,"name":"Trench Warfare"}, + {"id":10,"pos_r":15,"setid":2,"name":"Recover Allied Base"}, + {"id":11,"pos_r":16,"setid":2,"name":"Giant Tank"}, + {"id":12,"pos_r":21,"setid":3,"name":"Surprise Attack"}, + {"id":13,"pos_r":22,"setid":3,"name":"Derail the Train"}, + {"id":14,"pos_r":23,"setid":3,"name":"Enter the Mines"}, + {"id":15,"pos_r":24,"setid":3,"name":"Explore the Mines"}, + {"id":16,"pos_r":25,"setid":3,"name":"Destroy Driller Boss"}, + {"id":17,"pos_r":28,"setid":4,"name":"Navigate the Canyon"}, + {"id":18,"pos_r":29,"setid":4,"name":"Destroy Circus City"}, + {"id":19,"pos_r":30,"setid":4,"name":"Circus Aircraft Carrier"}, + {"id":20,"pos_r":35,"setid":5,"name":"Rescue Allies"}, + {"id":21,"pos_r":36,"setid":5,"name":"Battle the Red Baron"} +] + +# hex values +# WOKRHERE: profile 2 level statuses are off somehow?! they are 4 bytes closer to front than the calculation shows. +LEVEL_STATUSES = { + "59": "all-balloons,all-letters,sergeant", + "5B": "all-balloons,all-letters,colonel", + "5D": "all-balloons,all-letters,colonel", + "60": "all-balloons,all-letters,colonel", + "62": "all-balloons,all-letters,colonel", + "63": "all-balloons,all-letters,colonel", + "64": "all-balloons,all-letters,general" +} + # DEFINE FUNCTIONS def ferror(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) @@ -196,12 +252,46 @@ def get_weapon_info(weapon): except: return -1, f"cannot find weapon {weapon}" # must be an incorrect name elif type(weapon) == int: - if weapon not in range(0,16): + if weapon in range(0,16): + return weapon, WEAPONS[weapon] + else: return -1, f"invalid index {weapon}; use 0-15" # must be <0 or >15 + return -1, f"invalid way to reference weapon: [{type(weapon)}]. Use index or name." + +def get_level_status(data_object,profile_id,level): + """ WORKHERE: not sure how to use this, but making sure my position in the data works. """ + data = _get_data_from_data_object(data_object) + level_obj, message = get_level_info(level) + if message != "valid" or level == -1: + ferror(f"Unable to get level status for {level}.") + print(f"Debug: got level_obj {level_obj} and message {message}") + profile_level_status = data[PROFILE_START_POSITION[profile_id]+POS_LEVEL_START+(4*level_obj["pos_r"])] + # it comes back as an int, but does it look better as a hex? + return hex(profile_level_status) + +def get_level_info(level): + """ Returns dictionary of level from LEVELS, searching by id or name. """ + try: + # if it is an integer, make sure it shows up as one in the next check + level = int(level) + except: + pass + if type(level) == str: + try: + level = [i for i in LEVELS if i["name"].lower() == level.lower()][0] + return level, "valid" + except: + return -1, f"cannot find level {level}" + elif type(level) == int: + if level in range(0,len(LEVELS)+1): + try: + level = [i for i in LEVELS if i["id"] == level][0] + return level, "valid" + except: + return -1, f"cannot find level by id {level}" else: - return weapon, WEAPONS[weapon] - else: - return -1, f"invalid way to reference weapon: [{type(weapon)}]. Use index or name." + return -1, f"invalid level index {level}; use 0-{len(LEVELS)}" + return -1, f"invalid way to reference level: [{type(level)}]. Use id or name." def calculate_checksum(data): """ Return the 4-byte checksum used by the game for the provided data. """ -- cgit