aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrb.py167
-rw-r--r--srb_lib.py122
2 files changed, 265 insertions, 24 deletions
diff --git a/srb.py b/srb.py
index 5d33efe..1687d12 100755
--- a/srb.py
+++ b/srb.py
@@ -4,15 +4,12 @@
# Purpose: frontend for srb_lib
# History:
# Usage:
+# tl;dr: ./srb.py --profile 1 --unlock-everything --buy-everything "Profile 1.sav"
# Reference:
# blog posts 2024-03
# delsum from github
# bgconf.py
-
-# WORKHERE:
-# or --reset-level 5 (which zeros out all info about completion of that level)
-# --set-level-letters 0,none 0,all
-# --set-level 0,done 5,new # sets balloons 10, status general, letters all
+# WORKHERE: still some confusion around the --unlock-everything --lock-everything and the levelset available levels.
import srb_lib, argparse, sys
from srb_lib import ferror, debuglev
@@ -46,8 +43,10 @@ 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, balloons, letters for this level for profile. Example value to pass: 0,all 15,none")
parser.add_argument("--set-level-status",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("--set-level-letters",action="append",help="Set collected letters for level for profile. Examples: 0,all 15,none")
parser.add_argument("--get-levelset",help="Print status for this levelset for profile.")
parser.add_argument("--set-levelset-available-levels",action="append",help="Set number of available levels in this levelset for profile.")
parser.add_argument("--get-name",action="store_true",help="Print name for profile.")
@@ -60,7 +59,11 @@ parser.add_argument("--set-health",type=int,choices=range(1,5),help="Set stat he
parser.add_argument("--get-stunt",action="store_true",help="Print stat stunt for profile.")
parser.add_argument("--set-stunt",type=int,choices=range(1,5),help="Set stat stunt for profile.")
parser.add_argument("--get-gun",action="store_true",help="Print stat gun for profile.")
-parser.add_argument("--set-gun",type=int,choices=range(1,6),help="Set stat gun for profile.")
+parser.add_argument("--set-gun",type=int,choices=range(1,6),help="Set stat gun for profile. 5 is octo-gun which normally unlocks at 100% completion of the game.")
+parser.add_argument("--lock-everything",action="store_true",help="Set all levels to completed with rank general, all letters, and all balloons.")
+parser.add_argument("--unlock-everything",action="store_true",help="Set all levels to not completed. Take all letters and balloons.")
+parser.add_argument("--buy-everything",action="store_true",help="Give all purchasable items: planes, weapons, plane stats.")
+parser.add_argument("--unbuy-everything",action="store_true",help="Remove all purchasable items: planes, weapons, plane stats.")
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()
@@ -78,8 +81,8 @@ 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 or args.set_level_status or args.set_level_balloons or args.set_levelset_available_levels):
+# 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 or args.set_level_status or args.set_level_balloons or args.set_levelset_available_levels or args.set_level_letters or args.unlock_everything or args.lock_everything or args.buy_everything or args.unbuy_everything or args.set_level):
ferror("Warning: Cannot perform most actions without --profile. Not all tasks may run.")
else:
if args.get_money:
@@ -225,13 +228,159 @@ else:
try:
levelset_num, count = l.split(",")
except:
- ferror(f"Warning! Skipping un-parseable level completion spec {l}. Please use \"0,general\" format.")
+ ferror(f"Warning! Skipping un-parseable levelset available spec {l}. Please use \"0,all\" format.")
if levelset_num != -1:
data, message = srb_lib.set_levelset_available_levels(args.file,profile_id,levelset_num,count)
if (type(data) == int and data == -1) or message != "":
ferror(f"Failed to set profile {profile_id} level {levelset_num} available levels to {count} because {message}")
else:
srb_lib.write_file(args.file,0,data)
+ if args.set_level_letters:
+ for l in args.set_level_letters:
+ level_num = -1
+ try:
+ level_num, count = l.split(",")
+ except:
+ ferror(f"Warning! Skipping unparseable level letter spec {l}. Please use \"0,all\" format.")
+ if level_num != -1:
+ data, message = srb_lib.set_level_letters(args.file,profile_id,level_num, count)
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to set profile {profile_id} level {levelset_num} available levels to {count} because {message}")
+ else:
+ #print(f"good?")
+ srb_lib.write_file(args.file,0,data)
+ if args.unlock_everything:
+ data = srb_lib._get_data_from_data_object(args.file)
+ # counting backwards helps the levelset available levels count.
+ for i in range(len(srb_lib.LEVELS)-1,0,-1):
+ data, new_status, message = srb_lib.set_level_status(data,profile_id,i,"general")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_status, {message}")
+ break
+ data, message = srb_lib.set_level_balloons(data,profile_id,i,"all")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_balloons, {message}")
+ break
+ data, message = srb_lib.set_level_letters(data,profile_id,i,"all")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_letters, {message}")
+ break
+ for i in range(len(srb_lib.LEVELSETS)-1,0,-1):
+ data, message = srb_lib.set_levelset_available_levels(data,profile_id,i,"all")
+ if message != "":
+ ferror(f"Failed on levelset loop {i}, {message}")
+ break
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to unlock everything as indicated above.")
+ else:
+ srb_lib.write_file(args.file,0,data)
+ if args.lock_everything:
+ data = srb_lib._get_data_from_data_object(args.file)
+ # counting backwards helps the levelset available levels count.
+ for i in range(len(srb_lib.LEVELS)-1,0,-1):
+ data, new_status, message = srb_lib.set_level_status(data,profile_id,i,"none")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_status, {message}")
+ break
+ data, message = srb_lib.set_level_balloons(data,profile_id,i,"none")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_balloons, {message}")
+ break
+ data, message = srb_lib.set_level_letters(data,profile_id,i,"none")
+ if message != "":
+ ferror(f"Failed on loop {i}, set_level_letters, {message}")
+ break
+ for i in range(len(srb_lib.LEVELSETS)-1,0,-1):
+ data, message = srb_lib.set_levelset_available_levels(data,profile_id,i,"none")
+ if message != "":
+ ferror(f"Failed on levelset loop {i}, {message}")
+ break
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to lock everything as indicated above.")
+ else:
+ srb_lib.write_file(args.file,0,data)
+ if args.buy_everything:
+ data, message = srb_lib.set_purchased_weapons(args.file,profile_id,"add",["all"])
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to remove purchased weapons ['all'] because {message}")
+ else:
+ data, message = srb_lib.set_purchased_planes(data,profile_id,"add",["all"])
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to add purchased planes ['all'] because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"health",4)
+ if data == -1 or message != "":
+ print(f"Failed to set health to 4 because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"stunt",4)
+ if data == -1 or message != "":
+ print(f"Failed to set stunt to 4 because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"gun",5)
+ if data == -1 or message != "":
+ print(f"Failed to set gun to 5 because {message}")
+ # srb_lib.write_file(args.file,0,data)
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to buy everything as indicated above.")
+ else:
+ srb_lib.write_file(args.file,0,data)
+ if args.unbuy_everything:
+ data, message = srb_lib.set_purchased_weapons(args.file,profile_id,"remove",["all"])
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to remove purchased weapons ['all'] because {message}")
+ else:
+ data, message = srb_lib.set_purchased_planes(data,profile_id,"remove",["all"])
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to remove purchased planes ['all'] because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"health",1)
+ if data == -1 or message != "":
+ print(f"Failed to set health to 1 because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"stunt",1)
+ if data == -1 or message != "":
+ print(f"Failed to set stunt to 1 because {message}")
+ else:
+ data, message = srb_lib.set_plane_stat(data,profile_id,"gun",1)
+ if data == -1 or message != "":
+ print(f"Failed to set gun to 1 because {message}")
+ # srb_lib.write_file(args.file,0,data)
+ if (type(data) == int and data == -1) or message != "":
+ ferror(f"Failed to unbuy everything as indicated above.")
+ else:
+ srb_lib.write_file(args.file,0,data)
+ if args.set_level:
+ for i in args.set_level:
+ level_num = -1
+ status = ""
+ cont = True
+ try:
+ level_num, status = i.split(",")
+ except:
+ ferror(f"Warning! Unable to set level spec {i}. Use \"0,all\" or \"15,none\" syntax.")
+ cont = False
+ if status not in ["all","none"]:
+ ferror(f"Warning! Unable to set level {level_num} to {status}. Use all or none.")
+ cont = False
+ if level_num != -1:
+ data, new_status, message = srb_lib.set_level_status(args.file,profile_id,level_num,"general" if status == "all" else "none")
+ if message != "":
+ ferror(f"Failed on {i}, set_level_status, {message}")
+ cont = False
+ if cont:
+ data, message = srb_lib.set_level_balloons(data,profile_id,level_num,status)
+ if message != "":
+ ferror(f"Failed on {i}, set_level_balloons, {message}")
+ cont = False
+ if cont:
+ data, message = srb_lib.set_level_letters(data,profile_id,level_num,status)
+ if message != "":
+ ferror(f"Failed on {i}, set_level_letters, {message}")
+ cont = False
+ if (type(data) == int and data == -1) or message != "" or not cont:
+ 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.checksum:
f = args.file
diff --git a/srb_lib.py b/srb_lib.py
index 64d323e..933ff6c 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -128,7 +128,7 @@ 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.
+# INCOMPLETE: 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",
@@ -147,6 +147,7 @@ LEVELS = [
# While there are other bitmasks for most ranks, general is only ever 0x64.
LEVEL_STATUSES = [
+ {"b":0x00,"name":"none"},
{"b":0x47,"name":"corporal"},
{"b":0x59,"name":"sergeant"},
{"b":0x5B,"name":"colonel"},
@@ -351,14 +352,14 @@ def get_purchased_weapons(data_object,profile_id,silent=False):
return ','.join(weapons_list), weapons_mask
def get_level_status(data_object,profile_id,level,silent=False):
- """ WORKHERE: need to write set methods for this. """
+ """ Display all sorts of useful info about a level for the profile. The return value is not as useful as the printed info. """
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}.")
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']}.")
+ ferror(f"Unable to get levelset info for {level_obj['setid']}.")
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"])
@@ -374,14 +375,13 @@ def get_level_status(data_object,profile_id,level,silent=False):
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:
+ if message != "" or level_obj == -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}")
@@ -390,9 +390,21 @@ def set_level_status(data_object,profile_id,level,status):
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}")
+ #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)
+ levelset_available_levels = get_levelset_available_levels(data,profile_id,level_obj["setid"])
+ print(f"debug: levelset {level_obj['setid']} currently has {levelset_available_levels} available levels.")
+ # if setting to any completed status, if the levelset available levels is less than this level, then make it this.
+ if levelset_available_levels < (level_obj["set_pos"] + 1) and status not in ["none"]:
+ data, message = set_levelset_available_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"] + 1)
+ if message != "":
+ return -1, -1, f"Unable to set levelset available levels to minimum of this level set_pos {level_obj['set_pos']}"
+ # decrement the available levels in the levelset if clearing out this level and the available levels is exactly this one.
+ if levelset_available_levels == (level_obj["set_pos"] + 1) and status in ["none"]:
+ data, message = set_levelset_available_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"])
+ if message != "":
+ return -1, -1, f"Unable to decrement levelset available levels."
return data, current_status, ""
def set_level_balloons(data_object,profile_id,level,count):
@@ -419,13 +431,14 @@ def set_level_balloons(data_object,profile_id,level,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. """
+def get_levelset_status(data_object,profile_id,levelset,silent=False):
+ """ Display all sorts of useful info about a levelset for the profile. The return value is not as useful as the printed info. """
data = _get_data_from_data_object(data_object)
levelset_obj, message = get_levelset_info(levelset)
if message != "" or levelset == -1:
ferror(f"Unable to get levelset status for {levelset_obj['setid']}.")
- print(f"Debug: got levelset {levelset_obj}")
+ if not silent:
+ print(f"Debug: got levelset {levelset_obj}")
pos_levelset_completed_letters_count = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_LETTERS_COUNT+(INT_SIZE*2*levelset_obj["id"])
pos_levelset_completed_letters_mask = pos_levelset_completed_letters_count + INT_SIZE
pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
@@ -439,21 +452,100 @@ def get_levelset_status(data_object,profile_id,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: 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']}")
+ if not silent:
+ 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: 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}")
+ if not silent:
+ 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 set_level_letters(data_object,profile_id,level,letters):
+ """ Set collected letters for given level to all or none. """
+ data = _get_data_from_data_object(data_object)
+ level_obj, message = get_level_info(level)
+ if message != "" or level == -1:
+ return -1, f"Unable to get level status for {level}."
+ levelset_obj, message = get_levelset_info(level_obj["setid"])
+ if message != "":
+ return -1, f"For set_level_letters unable to get levelset for {level_obj['setid']}."
+ pos_level_letters = PROFILE_START_POSITION[profile_id]+POS_LEVEL_LETTERS+(INT_SIZE*level_obj["set_pos"])+(INT_SIZE*POS_LEVEL_LETTERS_MULTIPLIER_LEVELSET*level_obj["setid"])
+ if letters not in ["all","none"]:
+ return -1, f"Cannot set level letters to {letters}. Use all or none."
+ if letters == "all":
+ letter_count = len(level_obj["l"])
+ else:
+ letter_count = 0
+ # update letter count for level.
+ data = srb_pack('<1i',data,pos_level_letters,letter_count)
+ pos_levelset_completed_letters_count = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_LETTERS_COUNT+(INT_SIZE*2*levelset_obj["id"])
+ #print(f"debug: setting abs 0x{CHECKSUM_LENGTH+pos_levelset_completed_letters_count:04x} to {letter_count}")
+ #data = srb_pack('<1i',data,pos_levelset_completed_letters_count,letter_count)
+ # prepare to update the levelset completed letters count and mask
+ pos_levelset_completed_letters_mask = pos_levelset_completed_letters_count + INT_SIZE
+ # need to get current letter bitmask for levelset
+ # convert the left-1-bitshifted value to useful, left-to-right mask
+ profile_levelset_completed_letters_mask = struct.unpack_from('<1i',data,pos_levelset_completed_letters_mask)[0] >> 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)
+ print(f"debug: levelset {levelset_obj['id']} before changes has letters mask {profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}")
+ levels_to_check = [i for i in LEVELS if i["setid"] == levelset_obj["id"]]
+ #print(f"need to check levels {levels_to_check}")
+ levelset_letters_count = 0
+ x = 0
+ bitmask_pos = 0
+ while x < len(levels_to_check):
+ ltc = [i for i in levels_to_check if i["set_pos"] == x][0]
+ this_level_count = data[PROFILE_START_POSITION[profile_id]+POS_LEVEL_LETTERS+(INT_SIZE*ltc["set_pos"])+(INT_SIZE*POS_LEVEL_LETTERS_MULTIPLIER_LEVELSET*ltc["setid"])]
+ #print(f"debug: level {ltc['id']} has count {this_level_count}")
+ levelset_letters_count += this_level_count
+ if ltc["id"] == level_obj["id"]:
+ this_bitmask_pos = bitmask_pos
+ bitmask_pos += len(ltc["l"])
+ x += 1
+ # so now that this_bitmask_pos is populated with the location in the levelset letter mask of this level's letters, we need to manipulate it.
+ #print(f"debug: need to manipulate letter mask {profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b} at position {this_bitmask_pos} length {len(level_obj['l'])}")
+ new_letter_mask = list(f"{profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}")
+ #print(f"debug: this_bitmask_pos={this_bitmask_pos},len(level_obj['l'])={len(level_obj['l'])}, thing={(['1'] if letters == 'all' else ['0'])*len(level_obj['l'])}")
+ # I could not get this pythonic thing working the way I wanted. I think the slice manipulation that uses len(level_obj["l"]) was not working....
+ #new_letter_mask[this_bitmask_pos:len(level_obj["l"])+1] = (["1"] if letters == "all" else ["0"]) * len(level_obj["l"])
+ # ... so I just run it per spot individually.
+ x = 0
+ while x < len(level_obj["l"]):
+ new_letter_mask[this_bitmask_pos+x] = "1" if letters == "all" else "0"
+ x += 1
+ #print(f"debug: intermediate, new_letter_mask={new_letter_mask}")
+ new_letter_mask = "".join(new_letter_mask)
+ new_letter_mask_int = int(new_letter_mask,2)
+ #print(f"debug: new_letter_mask updated list is str {new_letter_mask}, int {new_letter_mask_int}")
+ #print(f"debug: total letters collected for levelset {levelset_obj['id']}: {levelset_letters_count}")
+ print(f"debug: new letters bitmask for levelset {levelset_obj['id']}: {new_letter_mask_int:0{len(new_letter_mask)}b}")
+ # bitmask needs to be reversed and left-1-bitshifted
+ new_letter_mask_final = int(f"{new_letter_mask_int:0{len(levelset_obj['le'])}b}"[::-1],2) << 1
+ print(f"debug: so that final bitmask should be {new_letter_mask_final:016b}")
+ # set levelset letter count
+ data = srb_pack('<1i',data,pos_levelset_completed_letters_count,levelset_letters_count)
+ # set levelset letter mask
+ data = srb_pack('<1i',data,pos_levelset_completed_letters_mask,new_letter_mask_final)
+ return data, ""
+
+def get_levelset_available_levels(data_object,profile_id,levelset):
+ data = _get_data_from_data_object(data_object)
+ levelset_obj, message = get_levelset_info(levelset)
+ if message != "":
+ return -1, f"For set_levelset_available_levels unable to get levelset for {levelset}."
+ pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
+ return data[pos_levelset_completed_mission_mask]
+
def set_levelset_available_levels(data_object,profile_id,levelset,completed_count):
data = _get_data_from_data_object(data_object)
levelset_obj, message = get_levelset_info(levelset)
bgstack15