aboutsummaryrefslogtreecommitdiff
path: root/srb.py
blob: 4225e14b9123144d9f24bbe28b677d02116fa3ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#!/usr/bin/env python3
# File: srb.py
# Location: https://bgstack15.ddns.net/cgit/srb_lib
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-03-08-6 15:28
# Title: Frontend for savegame editor for Snoopy vs. the Red Baron
# Project: srb_lib
# Purpose: frontend for srb_lib
# History:
# Usage:
#    tl;dr: ./srb.py --profile 1 --unlock-everything --buy-everything "Profile 1.sav"
# Reference:
#    bgconf.py
# Improve:
#    still some confusion around the --unlock-everything --lock-everything and the levelset available levels.
# Documentation: README.md
# Dependencies:

import srb_lib, argparse, sys
from srb_lib import ferror, debuglev

HELP_WEAPONS = ""

for w in srb_lib.WEAPONS:
   HELP_WEAPONS += str(w["id"]) + "," + w["name"] + ','
HELP_WEAPONS = HELP_WEAPONS.rstrip(",")

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 {HELP_WEAPONS}
NAME_CHARS include "{srb_lib.NAME_CHARS}"
""")
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.")
parser.add_argument("--get-money",action="store_true",help="Print current money for profile.")
parser.add_argument("--set-money",type=int,help="Set money for profile.")
parser.add_argument("--get-weapon",action="store_true",help="Print currently equipped weapon for profile.")
# choices seems to be too strict here for the numbers. We can live with just the
#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="Set currently equipped weapon for profile.")
parser.add_argument("--get-purchased-weapons",action="store_true",help="Print currently purchased weapons 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, remove (un-buy) these purchased weapons. Can be used multiple times.")
parser.add_argument("--get-purchased-planes",action="store_true",help="Print currently purchased planes for profile.")
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.")
parser.add_argument("--set-name",help="Set name for profile.")
parser.add_argument("--get-profile-in-use",action="store_true",help="Print if profile is in use.")
parser.add_argument("--get-tutorial-completed",action="store_true",help="Print if profile has completed the tutorial.")
parser.add_argument("--set-tutorial-completed",choices=["True","False"],help="Set tutorial-completed for profile.")
parser.add_argument("--get-health",action="store_true",help="Print stat health for profile.")
parser.add_argument("--set-health",type=int,choices=range(1,5),help="Set stat health for profile.")
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. 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()
debuglevel = 0
if args.debug:
   debuglevel = args.debug

if debuglev(1,debuglevel):
   ferror("debug level", debuglevel)
if debuglev(8,debuglevel):
   ferror(args)

# common parameters
profile_id = args.profile

#print(f"profile_id={profile_id}")

# 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:
      money = srb_lib.get_money(args.file, profile_id)
      print(f"Profile {profile_id} has {money} money.")
   if args.set_money:
      if args.set_money > 0xFFFF:
         ferror(f"Warning: Do not set money higher than 65535. While it can technically work, there's no need for that in-game anyways. Continuing...")
      else:
         data = srb_lib.set_money(args.file, profile_id, args.set_money)
         srb_lib.write_file(args.file,0,data)
   if args.get_weapon:
      print(f"Profile {profile_id} has weapon {srb_lib.get_weapon(args.file, profile_id)}")
   if args.set_weapon:
      try:
         args.set_weapon = int(args.set_weapon)
      except:
         pass
      data = srb_lib.set_weapon(args.file, profile_id, args.set_weapon)
      if type(data) == int and data == -1:
         # error is printed in the function
         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.set_level_status:
      for l in args.set_level_status:
         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:
      print(f"Profile {profile_id} has name {srb_lib.get_name(args.file,profile_id)}")
   if args.set_name:
      data, message = srb_lib.set_name(args.file, profile_id, args.set_name)
      if (type(data) == int and data == -1) or message != "":
         ferror(f"Failed to set profile {profile_id} name to {args.set_name} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.get_profile_in_use:
      print(f"Profile {profile_id} in use is {srb_lib.get_profile_in_use(args.file,profile_id)}")
   if args.get_purchased_weapons:
      print(f"Profile {profile_id} has weapons {srb_lib.get_purchased_weapons(args.file,profile_id)}")
   if args.add_purchased_weapons:
      data, message = srb_lib.set_purchased_weapons(args.file,profile_id,"add",args.add_purchased_weapons)
      if (type(data) == int and data == -1) or message != "":
         ferror(f"Failed to add purchased weapons {args.add_purchased_weapons} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.remove_purchased_weapons:
      data, message = srb_lib.set_purchased_weapons(args.file,profile_id,"remove",args.remove_purchased_weapons)
      if (type(data) == int and data == -1) or message != "":
         ferror(f"Failed to add purchased weapons {args.remove_purchased_weapons} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.get_tutorial_completed:
      print(f"Profile {profile_id} completed-tutorial is {srb_lib.get_tutorial_completed(args.file,profile_id)}")
   if args.set_tutorial_completed:
      thisbool = False if args.set_tutorial_completed == "False" else True
      data, newstatus = srb_lib.set_tutorial_completed(args.file,profile_id,thisbool)
      srb_lib.write_file(args.file,0,data)
   if args.get_health:
      stat, message = srb_lib.get_plane_stat(args.file,profile_id,"health")
      # error is printed in the function, so if not any error, run
      if not (stat == -1 or message != ""):
         print(f"Profile {profile_id} has health level {stat}")
   if args.set_health:
      data, message = srb_lib.set_plane_stat(args.file,profile_id,"health",args.set_health)
      if data == -1 or message != "":
         print(f"Failed to set health to {args.set_health} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.get_stunt:
      stat, message = srb_lib.get_plane_stat(args.file,profile_id,"stunt")
      # error is printed in the function, so if not any error, run
      if not (stat == -1 or message != ""):
         print(f"Profile {profile_id} has stunt level {stat}")
   if args.set_stunt:
      data, message = srb_lib.set_plane_stat(args.file,profile_id,"stunt",args.set_stunt)
      if data == -1 or message != "":
         print(f"Failed to set stunt to {args.set_stunt} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.get_gun:
      stat, message = srb_lib.get_plane_stat(args.file,profile_id,"gun")
      # error is printed in the function, so if not any error, run
      if not (stat == -1 or message != ""):
         print(f"Profile {profile_id} has gun level {stat}")
   if args.set_gun:
      data, message = srb_lib.set_plane_stat(args.file,profile_id,"gun",args.set_gun)
      if data == -1 or message != "":
         print(f"Failed to set gun to {args.set_gun} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.get_purchased_planes:
      print(f"Profile {profile_id} has planes {srb_lib.get_purchased_planes(args.file,profile_id)}")
   if args.add_purchased_planes:
      data, message = srb_lib.set_purchased_planes(args.file,profile_id,"add",args.add_purchased_planes)
      if (type(data) == int and data == -1) or message != "":
         ferror(f"Failed to add purchased planes {args.add_purchased_planes} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.remove_purchased_planes:
      data, message = srb_lib.set_purchased_planes(args.file,profile_id,"remove",args.remove_purchased_planes)
      if (type(data) == int and data == -1) or message != "":
         ferror(f"Failed to add purchased planes {args.remove_purchased_planes} because {message}")
      else:
         srb_lib.write_file(args.file,0,data)
   if args.set_levelset_available_levels:
      for l in args.set_levelset_available_levels:
         levelset_num = -1
         try:
            levelset_num, count = l.split(",")
         except:
            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
   srb_lib.correct_file(f,debuglevel)
bgstack15