From 62c7065e8a411fb63a8d3140b7d7be42997874bb Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Wed, 13 Mar 2024 17:00:50 -0400 Subject: levels, levelsets, and some raw notes Almost fully understand the level info now. Added raw notes about purchased planes; need to implement the get. --- srb.py | 12 ++---- srb_lib.py | 134 +++++++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 89 insertions(+), 57 deletions(-) diff --git a/srb.py b/srb.py index e762b72..f97e01b 100755 --- a/srb.py +++ b/srb.py @@ -9,16 +9,10 @@ # delsum from github # bgconf.py -# WORKHERE: right now, this only just corrects any file. in the future, offer options like --profile 1 --set-money 3045 --give-plane all or --give-plane marcie -# or --take-plan marcie or --set-rank-level 1,general --set-rank-level 1,none --set-rank-level all,general -# or --give-weapon pumpkin --take-weapon bees +# WORKHERE: right now, this only just corrects any file. in the future, offer options like --take-plane all or --give-plane marcie +# or --give-plane marcie or --set-rank-level 1,general --set-rank-level 1,none --set-rank-level all,general # or --give-balloons-level 10 --take-balloons-level 11 -# make chart of level numbers, which ones have balloons # or --reset-level 5 (which zeros out all info about completion of that level) -# --set-plane-health 1 (through 4) -# --set-plane-weapon 1 (through 5) -# --set-plane-stunt 1 (through 4) -# --set-profile-name "asdbdf" import srb_lib, argparse, sys from srb_lib import ferror, debuglev @@ -47,7 +41,7 @@ parser.add_argument("--get-weapon",action="store_true",help="Print currently equ parser.add_argument("--set-weapon",help="Set currently equipped weapon for profile.") parser.add_argument("--get-purchased-weapons",action="store_true",help="Print currently purchased weapon for profile.") parser.add_argument("--add-purchased-weapons",action="append",help="For profile, add these purchased weapons. Can be used multiple times.") -parser.add_argument("--remove-purchased-weapons",action="append",help="For profile, add these purchased weapons. Can be used multiple times.") +parser.add_argument("--remove-purchased-weapons",action="append",help="For profile, remove (un-buy) these purchased weapons. Can be used multiple times.") parser.add_argument("--get-level",help="Print status for this level for profile.") 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.") diff --git a/srb_lib.py b/srb_lib.py index 3873a26..e8adab4 100644 --- a/srb_lib.py +++ b/srb_lib.py @@ -20,13 +20,13 @@ # Dependencies: import sys, struct -srb_lib_version = "20240311a" +srb_lib_version = "20240313a" # 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. -# money is 0x270 bytes after the "Z> 1 + # python trick to reverse a custom-formatted string and convert back to int while reading the string as base 2 + profile_levelset_completed_letters_mask = int(f"{profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}"[::-1],2) profile_levelset_completed_mission_mask = data[pos_levelset_completed_mission_mask] profile_levelset_status = f"b{profile_levelset_completed_mission_mask:06b}" - # WORKHERE: absolute pos 0x2bec, 0x2bf0, 0x2c38 are positions for letters, total letter count, which letters bitmask where 0x1 is not used in the mask, and level 1. - # WORKHERE: get level-info for finished levels and show completed letters, uppercase are collected, lowercase are not collected. show "2/5, MaRcie" - # WORKHERE: count total number of balloons for each level in the levelset. + collected_balloons_for_levelset, message = get_collected_balloons_for_levelset(data,profile_id,levelset,silent=True) + if message != "": + ferror("Unable to count collected balloons.") print(f"Debug: levelset {levelset_obj['id']},\"{levelset_obj['name']:23s}\" has mission mask: b{pow(2,levelset_obj['l'])-1:06b}") - print(f"Debug: pos 0x{pos_levelset_completed_mission_mask:04x} levelset_obj {levelset_obj['id']} completed missions: {profile_levelset_status}") + print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_levelset_completed_mission_mask:04x} levelset_obj {levelset_obj['id']} completed missions: {profile_levelset_status}") + print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_levelset_completed_letters_count:04x} completed letter count: {profile_levelset_completed_letters_count}/{len(levelset_obj['le'])}") + print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_levelset_completed_letters_mask:04x} completed letter mask: b{profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}") + print(f"Debug: collected balloons: {collected_balloons_for_levelset}/{levelset_obj['b']} {levelset_obj['c']}") + completed_letters = list(levelset_obj['le']) + x = 0 + for i in f"{profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}": + completed_letters[x] = completed_letters[x].upper() if int(i) == 1 else completed_letters[x] + x += 1 + completed_letters = ''.join(completed_letters) + print(f"Debug: letters, uppercase is collected: {completed_letters}") + # it comes back as an int, but does it look better as a hex? return profile_levelset_status +def get_collected_balloons_for_levelset(data_object,profile_id,levelset,silent=False): + data = _get_data_from_data_object(data_object) + levelset_obj, message = get_levelset_info(levelset) + if message != "": + ferror(f"For balloon count unable to get levelset for {levelset}.") + return -1, "failed" + levels = [i for i in LEVELS if i["setid"] == levelset_obj["id"]] + x = 0 + profile_balloons = 0 + while x < levelset_obj["l"]: + level_obj = [i for i in levels if i["set_pos"] == x][0] + 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"]) + profile_level_balloons = data[pos_level_balloons] + profile_balloons += profile_level_balloons + x += 1 + if not silent: + print(f"Debug: for levelset {levelset_obj['id']}, collected {profile_balloons} balloons.") + return profile_balloons, "" + def get_level_info(level): """ Returns dictionary of level from LEVELS, searching by id or name. """ try: @@ -387,14 +432,14 @@ def get_level_info(level): if type(level) == str: try: level = [i for i in LEVELS if i["name"].lower() == level.lower()][0] - return level, "valid" + return level, "" 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" + return level, "" except: return -1, f"cannot find level by id {level}" else: @@ -414,8 +459,6 @@ def get_levelset_info(levelset): if type(levelset) == str: try: levelset = [i for i in LEVELSETS if i["name"].lower() == levelset.lower()][0] - # WORKHERE: add letters from the levels referenced here, in order. - #return levelset, "valid" except: return -1, f"cannot find levelset {levelset}" elif type(levelset) == int: @@ -431,12 +474,16 @@ def get_levelset_info(levelset): return -1, f"invalid way to reference levelset: [{type(levelset)}]. Use id or name." # so now we have the dictionary and we need to add the letters and balloons these_levels = [i for i in LEVELS if i["setid"] == levelset["id"]] - x = 0 - while x < len(these_levels): - this_level = [i for i in these_levels if i["set_pos"] == x][0] - levelset["le"] += this_level["l"] - x = x + 1 - return levelset, "valid" + # thankfully every levelset has letters. Not all levelsets have balloons but we can check on only this. + if len(levelset["le"]) == 0: + x = 0 + while x < len(these_levels): + this_level = [i for i in these_levels if i["set_pos"] == x][0] + levelset["le"] += this_level["l"] + # because we only add balloon count under that if statement. + levelset["b"] += this_level["b"] + x = x + 1 + return levelset, "" def get_name(data_object,profile_id): """ Print the name of the profile_id. """ @@ -557,15 +604,6 @@ def set_purchased_weapons(data_object,profile_id,action,weapons_list): # if we make it to the end return data, "" -def get_unlocked_levelsets(data_object,profile_id): - """ Print the unlocked levels. """ - # WORKHERE MAYBE? # stored as binary? - data = _get_data_from_data_object(data_object) - # it always comes as a tuple - unlocked_levelsets = struct.unpack_from('<1?',data,PROFILE_START_POSITION[profile_id]+POS_LEVELSETS_UNLOCKED)[0] - print(f"debug: unlocked_levelsets: {unlocked_levelsets}, type {type(unlocked_levelsets)}") - return unlocked_levelsets - def srb_pack(format_str, data, offset, *new_contents): """ Helper function that accepts data as bytes, instead of requiring bytesarray. """ data_bytearray = bytearray(data) -- cgit