aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrb.py14
-rw-r--r--srb_lib.py98
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. """
bgstack15