aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrb.py39
-rw-r--r--srb_lib.py91
2 files changed, 109 insertions, 21 deletions
diff --git a/srb.py b/srb.py
index 81c8b5f..9674a66 100755
--- a/srb.py
+++ b/srb.py
@@ -46,6 +46,8 @@ parser.add_argument("--get-purchased-planes",action="store_true",help="Print cur
parser.add_argument("--add-purchased-planes",action="append",help="For profile, add these purchased planes. Can be used multiple times.")
parser.add_argument("--remove-purchased-planes",action="append",help="For profile, remove (un-buy) these purchased planes. Can be used multiple times.")
parser.add_argument("--get-level",help="Print status for this level for profile.")
+parser.add_argument("--set-level",action="append",help="Set completion status for this level for profile. Example value to pass: \"0,general\"")
+parser.add_argument("--set-level-balloons",action="append",help="Set balloon status for this level for profile. Example value to pass: \"0,all\" or \"0,none\"")
parser.add_argument("--get-levelset",help="Print status for this levelset for profile.")
parser.add_argument("--get-name",action="store_true",help="Print name for profile.")
parser.add_argument("--set-name",help="Set name for profile.")
@@ -76,7 +78,7 @@ profile_id = args.profile
#print(f"profile_id={profile_id}")
# 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 or args.get_name or args.set_name or args.get_profile_in_use or args.get_purchased_weapons or args.get_tutorial_completed or args.add_purchased_weapons or args.remove_purchased_weapons or args.get_health or args.get_stunt or args.get_gun or args.set_health or args.set_stunt or args.set_gun or args.get_levelset or args.get_purchased_planes or args.add_purchased_planes or args.remove_purchased_planes):
+if not profile_id and (args.get_money or args.set_money or args.get_weapon or args.set_weapon or args.get_level or args.get_name or args.set_name or args.get_profile_in_use or args.get_purchased_weapons or args.get_tutorial_completed or args.add_purchased_weapons or args.remove_purchased_weapons or args.get_health or args.get_stunt or args.get_gun or args.set_health or args.set_stunt or args.set_gun or args.get_levelset or args.get_purchased_planes or args.add_purchased_planes or args.remove_purchased_planes or args.set_level or args.set_level_balloons):
ferror("Warning: Cannot perform most actions without --profile. Not all tasks may run.")
else:
if args.get_money:
@@ -103,6 +105,41 @@ 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.set_level:
+ for l in args.set_level:
+ level_num = -1
+ try:
+ level_num, completion = l.split(",")
+ except:
+ ferror(f"Warning! Skipping un-parseable level completion spec {l}. Please use \"0,general\" format.")
+ if level_num != -1:
+ completion_names = [i["name"] for i in srb_lib.LEVEL_STATUSES]
+ if completion not in completion_names:
+ ferror(f"Warning! Skipping invalid level completion spec {l}; for completion, use one of {completion_names}")
+ else:
+ # so we have a valid level completion spec.
+ data, new_status, message = srb_lib.set_level_status(args.file,profile_id,level_num, completion)
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to set profile {profile_id} level {level_num} status to {completion} because {message}")
+ else:
+ srb_lib.write_file(args.file,0,data)
+ if args.set_level_balloons:
+ for l in args.set_level_balloons:
+ level_num = -1
+ try:
+ level_num, balloons = l.split(",")
+ except:
+ ferror(f"Warning! Skipping un-parseable level completion spec {l}. Please use \"0,general\" format.")
+ if level_num != -1:
+ if balloons not in ["none","all"]:
+ ferror(f"Warning! Skipping invalid balloons for level {l}; options are all or none.")
+ else:
+ data, message = srb_lib.set_level_balloons(args.file,profile_id,level_num,balloons)
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to set profile {profile_id} level {level_num} balloons to {balloons} because {message}")
+ else:
+ srb_lib.write_file(args.file,0,data)
+
if args.get_levelset:
print(f"Profile {profile_id} has levelset {args.get_levelset} status {srb_lib.get_levelset_status(args.file,profile_id,args.get_levelset)}")
if args.get_name:
diff --git a/srb_lib.py b/srb_lib.py
index 253a3be..b659028 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -130,20 +130,28 @@ LEVELS = [
# hex values
# WORKHERE: I think balloons and letters merely count as one of the types of requirements. there can up to 5 secondary objectives in a level (maybe the levels without balloons though?). This is probably a bitmask for secondary objectives (including all-balloons,all-letters),health,time.
# Definitely 0x64 means the whole level is 100% complete, rank General.
-LEVEL_STATUSES = {
- "4A": "3-balloons,no-letters,under-time,good-health,corporal",
- "47": "2-balloons,no-letters,under-time,good-health,corporal",
- "59": "all-balloons,all-letters,under-time,good-health,sergeant",
- "5B": "all-balloons,all-letters,under-time,good-health,colonel",
- "5B": "9-balloons,no-letter,under-time,good-health,colonel",
- "5D": "all-balloons,all-letters,under-time,good-health,colonel",
- "5E": "all-balloons,no-letter,under-time,good-health,colonel",
- "60": "all-balloons,all-letters,under-time,good-health,colonel",
- "61": "all-balloons,all-letters,under-time,2/3obj,good-health,colonel",
- "62": "all-balloons,all-letters,under-time,good-health,colonel",
- "63": "all-balloons,all-letters,under-time,good-health,colonel",
- "64": "all-balloons,all-letters,under-time,good-health,general"
-}
+#LEVEL_STATUSES = {
+# "4A": "3-balloons,no-letters,under-time,good-health,corporal",
+# "47": "2-balloons,no-letters,under-time,good-health,corporal",
+# "59": "all-balloons,all-letters,under-time,good-health,sergeant",
+# "5B": "all-balloons,all-letters,under-time,good-health,colonel",
+# "5B": "9-balloons,no-letter,under-time,good-health,colonel",
+# "5D": "all-balloons,all-letters,under-time,good-health,colonel",
+# "5E": "all-balloons,no-letter,under-time,good-health,colonel",
+# "60": "all-balloons,all-letters,under-time,good-health,colonel",
+# "61": "all-balloons,all-letters,under-time,2/3obj,good-health,colonel",
+# "62": "all-balloons,all-letters,under-time,good-health,colonel",
+# "63": "all-balloons,all-letters,under-time,good-health,colonel",
+# "64": "all-balloons,all-letters,under-time,good-health,general"
+#}
+
+# While there are other bitmasks for most ranks, general is only ever 0x64.
+LEVEL_STATUSES = [
+ {"b":0x47,"name":"corporal"},
+ {"b":0x59,"name":"sergeant"},
+ {"b":0x5B,"name":"colonel"},
+ {"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
@@ -342,7 +350,7 @@ def get_purchased_weapons(data_object,profile_id,silent=False):
print(f"debug: currently have 0x{weapons_mask:04x} b{weapons_mask:016b}, {weapons_list}")
return ','.join(weapons_list), weapons_mask
-def get_level_status(data_object,profile_id,level):
+def get_level_status(data_object,profile_id,level,silent=False):
""" WORKHERE: need to write set methods for this. """
data = _get_data_from_data_object(data_object)
level_obj, message = get_level_info(level)
@@ -351,7 +359,8 @@ def get_level_status(data_object,profile_id,level):
levelset_obj, message = get_levelset_info(level_obj["setid"])
if message != "" or levelset_obj == -1:
ferror(f"Unable to get levelset status for {level_obj['setid']}.")
- print(f"Debug: for input {level} found level_obj {level_obj} and message {message}")
+ if not silent:
+ print(f"Debug: for input {level} found level_obj {level_obj} and message {message}")
pos_level_status = PROFILE_START_POSITION[profile_id]+POS_LEVEL_START+(INT_SIZE*level_obj["pos_r"])
pos_level_balloons = PROFILE_START_POSITION[profile_id]+POS_LEVEL_BALLOONS+(level_obj["setid"]*POS_LEVEL_BALLOONS_MULTIPLIER_LEVELSET)+(POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL*level_obj["set_pos"])
pos_level_which_balloons = pos_level_balloons + INT_SIZE
@@ -360,14 +369,56 @@ def get_level_status(data_object,profile_id,level):
profile_level_balloons = data[pos_level_balloons]
profile_level_which_balloons = struct.unpack_from('<1i',data,pos_level_which_balloons)[0]
profile_level_letters = data[pos_level_letters]
- print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_status:04x} level status 0x{profile_level_status:x}")
- print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_balloons:04x} collected balloon count: {profile_level_balloons}/{level_obj['b']}")
- print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_which_balloons:04x} which balloons: b{profile_level_which_balloons:010b}")
- print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_letters:04x} letter count: {profile_level_letters}/{len(level_obj['l'])}")
+ if not silent:
+ print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_status:04x} level status 0x{profile_level_status:x}")
+ print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_balloons:04x} collected balloon count: {profile_level_balloons}/{level_obj['b']}")
+ print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_which_balloons:04x} which balloons: b{profile_level_which_balloons:010b}")
+ print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_letters:04x} letter count: {profile_level_letters}/{len(level_obj['l'])}")
# WORKHERE: show which balloons are collected already for this level?
# it comes back as an int, but does it look better as a hex?
return hex(profile_level_status)
+def set_level_status(data_object,profile_id,level,status):
+ data = _get_data_from_data_object(data_object)
+ level_obj, message = get_level_info(level)
+ if message != "" or level == -1:
+ ferror(f"Unable to get level status for {level}.")
+ current_status = get_level_status(data, profile_id, level, silent=True)
+ print(f"Before changing, level {level} has status {current_status}")
+ bits = 0x0
+ try:
+ bits = [i for i in LEVEL_STATUSES if i["name"]==status][0]["b"]
+ except:
+ return data, "", f"unable-to-set-level-completion-status-{status}"
+ print(f"debug: will try to set bits {bits:02x}")
+ data = srb_pack('<1i',data,PROFILE_START_POSITION[profile_id]+POS_LEVEL_START+(INT_SIZE*level_obj["pos_r"]),bits)
+ current_status = get_level_status(data, profile_id, level, silent=True)
+ return data, current_status, ""
+
+def set_level_balloons(data_object,profile_id,level,count):
+ data = _get_data_from_data_object(data_object)
+ level_obj, message = get_level_info(level)
+ if message != "" or level == -1:
+ ferror(f"Unable to get level status for {level}.")
+ # simplify the task; does a user really need to set individual balloons?!
+ if count not in ["all","none"]:
+ return -1, f"Can only set balloons to all or none for a level."
+ pos_level_balloons = PROFILE_START_POSITION[profile_id]+POS_LEVEL_BALLOONS+(level_obj["setid"]*POS_LEVEL_BALLOONS_MULTIPLIER_LEVELSET)+(POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL*level_obj["set_pos"])
+ pos_level_which_balloons = pos_level_balloons + INT_SIZE
+ #profile_level_balloons = data[pos_level_balloons]
+ #profile_level_which_balloons = struct.unpack_from('<1i',data,pos_level_which_balloons)[0]
+ #print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_balloons:04x} collected balloon count: {profile_level_balloons}/{level_obj['b']}")
+ #print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_which_balloons:04x} which balloons: b{profile_level_which_balloons:010b}")
+ if count == "all":
+ target_count = 10
+ target_which = 0x3FF # b1111111111, or bitmask that is 10 bits long
+ else:
+ target_count = 0
+ target_which = 0x0
+ data = srb_pack('<1i',data,pos_level_balloons,target_count)
+ data = srb_pack('<1i',data,pos_level_which_balloons,target_which)
+ return data, ""
+
def get_levelset_status(data_object,profile_id,levelset):
""" WORKHERE: need to make set methods. """
data = _get_data_from_data_object(data_object)
bgstack15