aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--srb_lib.py52
2 files changed, 30 insertions, 29 deletions
diff --git a/README.md b/README.md
index 44c2d90..f5fbf5a 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-->
# README for Savegame Editor for Snoopy vs. the Red Baron
-Modify an existing savegame file for the PC game [Snoopy vs. the Red Baron (2006)](https://en.wikipedia.org/wiki/Snoopy_vs._the_Red_Baron_(video_game).
+Modify an existing savegame file for the PC game [Snoopy vs. the Red Baron (2006)](https://en.wikipedia.org/wiki/Snoopy_vs._the_Red_Baron_(video_game)).
# tl;dr
@@ -36,10 +36,11 @@ Some common examples.
./srb.py --profile 1 --set-name "newname" --file "Profile 1.sav"
./srb.py --profile 1 --set-money 10000 --file "Profile 1.sav"
- ./srb.py --profile 1 --set-level 5,done --file "Profile 1.sav"
+ ./srb.py --profile 1 --set-level 5,all --file "Profile 1.sav"
+ ./srb.py --profile 1 --set-level 6,none --file "Profile 1.sav"
./srb.py --profile 1 --set-health 4 --file "Profile 1.sav"
-Note that `--set-level 5,done` operates three separate parameters that could be done individually:
+Note that `--set-level 5,all` operates three separate parameters that could be done individually:
--set-level-status 5,general
--set-level-balloons 5,all
diff --git a/srb_lib.py b/srb_lib.py
index a361c16..e671e42 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -20,14 +20,14 @@
# <https://stackoverflow.com/questions/46109815/reversing-the-byte-order-of-a-string-containing-hexadecimal-characters>
# <https://gamefaqs.gamespot.com/pc/930591-snoopy-vs-the-red-baron/faqs/46161>
# Improve:
-# test all functions after trying '<1I' for unsigned int
+# Rewrite as a class so we do not have to pass data around everywhere.
# Documentation:
# winetricks vd=1024x768
# winetricks vd=off
# Dependencies:
import sys, struct
-srb_lib_version = "20240320b"
+srb_lib_version = "20240320c"
# 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.
# "Z<dddddddd" is the start of a profile.
@@ -246,13 +246,13 @@ def _get_data_from_data_object(data_object):
def get_money(data_object,profile_id):
""" Print in decimal the current money value of the profile_id. """
data = _get_data_from_data_object(data_object)
- money_dec = struct.unpack_from('<1i',data,PROFILE_START_POSITION[profile_id]+POS_MONEY)[0]
+ money_dec = struct.unpack_from('<1I',data,PROFILE_START_POSITION[profile_id]+POS_MONEY)[0]
return money_dec
def set_money(data_object,profile_id, money_dec):
""" Using data, set the money given in decimal for the given profile_id. """
data = _get_data_from_data_object(data_object)
- data = srb_pack('<i',data,PROFILE_START_POSITION[profile_id]+POS_MONEY,money_dec)
+ data = srb_pack('<I',data,PROFILE_START_POSITION[profile_id]+POS_MONEY,money_dec)
print(f"after setting money to {money_dec}, we checked and got {get_money(data,profile_id)}")
return data
@@ -277,7 +277,7 @@ def set_weapon(data_object,profile_id,weapon):
ferror(f"Warning: cannot set weapon to {weapon}, because {message}")
return -1
# armed with weapon as the index number, let's change the savegame data
- data = srb_pack('<i',data,PROFILE_START_POSITION[profile_id]+POS_EQUIPPED_WEAPON,weapon["id"])
+ data = srb_pack('<I',data,PROFILE_START_POSITION[profile_id]+POS_EQUIPPED_WEAPON,weapon["id"])
print(f"after setting weapon to {weapon['name']}, we checked and got {get_weapon(data,profile_id)}")
return data
@@ -310,7 +310,7 @@ def get_purchased_weapons(data_object,profile_id,silent=False):
For the given profile, return which weapons are already purchased, as a comma-separated string and a bitmask.
"""
data = _get_data_from_data_object(data_object)
- weapons_purchased = struct.unpack_from('<1i',data,PROFILE_START_POSITION[profile_id]+POS_BYTES_WEAPONS_PURCHASED)[0]
+ weapons_purchased = struct.unpack_from('<1I',data,PROFILE_START_POSITION[profile_id]+POS_BYTES_WEAPONS_PURCHASED)[0]
#print(f"debug: got weapons_purchased 0x{weapons_purchased:0x}")
all_weapons_mask = [i for i in WEAPONS if i["name"] == "all"][0]["p"]
none_weapons_mask = [i for i in WEAPONS if i["name"] == "none"][0]["p"]
@@ -349,7 +349,7 @@ def get_level_status(data_object,profile_id,level,silent=False):
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"])
profile_level_status = data[pos_level_status]
profile_level_balloons = data[pos_level_balloons]
- profile_level_which_balloons = struct.unpack_from('<1i',data,pos_level_which_balloons)[0]
+ profile_level_which_balloons = struct.unpack_from('<1I',data,pos_level_which_balloons)[0]
profile_level_letters = data[pos_level_letters]
if not silent:
print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_status:04x} level status 0x{profile_level_status:x}")
@@ -372,7 +372,7 @@ def set_level_status(data_object,profile_id,level,status):
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)
+ 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.")
@@ -399,7 +399,7 @@ def set_level_balloons(data_object,profile_id,level,count):
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]
+ #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":
@@ -408,8 +408,8 @@ def set_level_balloons(data_object,profile_id,level,count):
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)
+ 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,silent=False):
@@ -425,7 +425,7 @@ def get_levelset_status(data_object,profile_id,levelset,silent=False):
pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
profile_levelset_completed_letters_count = data[pos_levelset_completed_letters_count]
# for some reason bit 1 is not used, so bitshift right 1.
- profile_levelset_completed_letters_mask = struct.unpack_from('<1i',data,pos_levelset_completed_letters_mask)[0] >> 1
+ 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)
profile_levelset_completed_mission_mask = data[pos_levelset_completed_mission_mask]
@@ -467,15 +467,15 @@ def set_level_letters(data_object,profile_id,level,letters):
else:
letter_count = 0
# update letter count for level.
- data = srb_pack('<1i',data,pos_level_letters,letter_count)
+ 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)
+ #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
+ 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}")
@@ -514,9 +514,9 @@ def set_level_letters(data_object,profile_id,level,letters):
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)
+ 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)
+ 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):
@@ -547,7 +547,7 @@ def set_levelset_available_levels(data_object,profile_id,levelset,completed_coun
pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
completed_bitmask = pow(2,completed_count)-1
#print(f"debug: need to set levelset {levelset} available count to {completed_count}, which stored as a bitmask should be {completed_bitmask:07b}")
- data = srb_pack('<1i',data,pos_levelset_completed_mission_mask,completed_bitmask)
+ data = srb_pack('<1I',data,pos_levelset_completed_mission_mask,completed_bitmask)
return data, ""
def get_collected_balloons_for_levelset(data_object,profile_id,levelset,silent=False):
@@ -635,7 +635,7 @@ def get_levelset_info(levelset):
def get_name(data_object,profile_id):
""" Print the name of the profile_id. """
data = _get_data_from_data_object(data_object)
- name_array = struct.unpack_from('<8i',data,PROFILE_START_POSITION[profile_id]+POS_NAME)
+ name_array = struct.unpack_from('<8I',data,PROFILE_START_POSITION[profile_id]+POS_NAME)
#print(f"debug: name_array: {name_array}, type {type(name_array)}")
name_str = ""
for i in name_array:
@@ -662,10 +662,10 @@ def set_name(data_object,profile_id,new_name):
# How I was doing this before:
#data_bytearray = bytearray(data)
#for i in name_array:
- # struct.pack_into('<1i',data_bytearray,PROFILE_START_POSITION[profile_id]+POS_NAME+(4*x),i)
+ # struct.pack_into('<1I',data_bytearray,PROFILE_START_POSITION[profile_id]+POS_NAME+(4*x),i)
# x = x + 1
#data = bytes(data_bytearray)
- data = srb_pack('<8i',data,PROFILE_START_POSITION[profile_id]+POS_NAME+(INT_SIZE*x),*name_array)
+ data = srb_pack('<8I',data,PROFILE_START_POSITION[profile_id]+POS_NAME+(INT_SIZE*x),*name_array)
#print(f"debug: after setting name to {new_name}, we checked and got {get_name(data,profile_id)}")
return data, ""
@@ -702,7 +702,7 @@ def get_plane_stat(data_object,profile_id,stat):
thispos = POS_STUNT
else: # gun
thispos = POS_GUN
- output = struct.unpack_from('<1i',data,PROFILE_START_POSITION[profile_id]+thispos)[0]+1
+ output = struct.unpack_from('<1I',data,PROFILE_START_POSITION[profile_id]+thispos)[0]+1
#print(f"debug: at offset {PROFILE_START_POSITION[profile_id]+thispos:x} got {output:1b}")
return output, ""
@@ -720,7 +720,7 @@ def set_plane_stat(data_object,profile_id,stat,value):
thispos = POS_STUNT
else: # gun
thispos = POS_GUN
- data = srb_pack('<1i',data,PROFILE_START_POSITION[profile_id]+thispos,value-1)
+ data = srb_pack('<1I',data,PROFILE_START_POSITION[profile_id]+thispos,value-1)
return data, ""
def set_purchased_weapons(data_object,profile_id,action,weapons_list):
@@ -746,7 +746,7 @@ def set_purchased_weapons(data_object,profile_id,action,weapons_list):
if final_mask != cur_mask:
print(f"debug: beginning 0x{cur_mask:04x}, b{cur_mask:016b} {cur_weapons}")
print( f"debug: {action:6s} 0x{action_mask:04x}, b{action_mask:016b} {','.join(weapons_list)}")
- data = srb_pack('<1i',data,PROFILE_START_POSITION[profile_id]+POS_BYTES_WEAPONS_PURCHASED, final_mask)
+ data = srb_pack('<1I',data,PROFILE_START_POSITION[profile_id]+POS_BYTES_WEAPONS_PURCHASED, final_mask)
print( f"debug: final 0x{final_mask:04x}, b{final_mask:016b} {get_purchased_weapons(data,profile_id,silent=True)[0]}")
# if we make it to the end
return data, ""
@@ -754,7 +754,7 @@ def set_purchased_weapons(data_object,profile_id,action,weapons_list):
def get_purchased_planes(data_object,profile_id, silent=False):
""" List which planes are purchased already. """
data = _get_data_from_data_object(data_object)
- planes_purchased = struct.unpack_from('<1i',data,PROFILE_START_POSITION[profile_id]+POS_PLANES_PURCHASED)[0]
+ planes_purchased = struct.unpack_from('<1I',data,PROFILE_START_POSITION[profile_id]+POS_PLANES_PURCHASED)[0]
all_planes_mask = [i for i in PLANES if i["name"] == "all"][0]["p"]
none_planes_mask = [i for i in PLANES if i["name"] == "none"][0]["p"]
# short-circuit if all
@@ -797,7 +797,7 @@ def set_purchased_planes(data_object,profile_id,action,planes_list):
if final_mask != cur_mask:
print(f"debug: beginning 0x{cur_mask:04x}, b{cur_mask:016b} {cur_planes}")
print( f"debug: {action:6s} 0x{action_mask:04x}, b{action_mask:016b} {','.join(planes_list)}")
- data = srb_pack('<1i',data,PROFILE_START_POSITION[profile_id]+POS_PLANES_PURCHASED, final_mask)
+ data = srb_pack('<1I',data,PROFILE_START_POSITION[profile_id]+POS_PLANES_PURCHASED, final_mask)
print( f"debug: final 0x{final_mask:04x}, b{final_mask:016b} {get_purchased_planes(data,profile_id,silent=True)[0]}")
# if we make it to the end
return data, ""
bgstack15