aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-04-05 17:53:57 -0400
committerB. Stack <bgstack15@gmail.com>2024-04-05 17:53:57 -0400
commit829bb9a89aefa1a0bba9af959722d8129d689ec6 (patch)
tree80d15a57aea9e4a4c981162db751e4e202f5135a
parentall: add reset breakables (diff)
downloadsrb_lib-829bb9a89aefa1a0bba9af959722d8129d689ec6.tar.gz
srb_lib-829bb9a89aefa1a0bba9af959722d8129d689ec6.tar.bz2
srb_lib-829bb9a89aefa1a0bba9af959722d8129d689ec6.zip
tk: minor code fixes, README: add screenshot
-rw-r--r--.images/scrot-savegame-editor-graphical.pngbin0 -> 135799 bytes
-rw-r--r--README.md30
-rwxr-xr-xsrb_tk.py132
3 files changed, 70 insertions, 92 deletions
diff --git a/.images/scrot-savegame-editor-graphical.png b/.images/scrot-savegame-editor-graphical.png
new file mode 100644
index 0000000..603b8ed
--- /dev/null
+++ b/.images/scrot-savegame-editor-graphical.png
Binary files differ
diff --git a/README.md b/README.md
index 6e949b1..72cede1 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,11 @@ Note that `--set-level 5,all` operates three separate parameters that could be d
Individual letter/balloon control is not implemented at this time.
## Dependencies
-Just python3.
+For the library and cli, just python3.
+
+For the graphical frontend:
+
+ sudo apt-get install python3-tkinter python3-pil.imagetk
## Building
N/A
@@ -67,6 +71,28 @@ The binary contents are stored in little-endian, which makes visual inspection o
![Screenshot of profile selection screen showing three names: "Hexedit," "4fun and," and "Profit."](.images/scrot-hexedit-for-fun-and-profit-srb.png "Screenshot of profiles with fun names")
+## Graphical frontend
+There is an included python tcl/tk frontend:
+
+ ./srb_tk.py
+
+The tool loads the default "Profile 1.sav" file right away. Clicking any widget effects an immediate change to the checksum. To save the file, you must select "Save" from the menu.
+
+![Screenshot of graphical savegame editor](.images/scrot-savegame-editor-graphical.png "Screenshot of the savegame editor with a completed profile on display")
+
+## Credits
+Unless a graphic was taken from the game, any icons come from icons8.
+
+<a target="_blank" href="https://icons8.com/icon/9336/aircraft">Main program icon</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+<a target="_blank" href="https://icons8.com/icon/8Sk1THlBUpC6/hay-bale">Hay Bale</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+<a target="_blank" href="https://icons8.com/icon/23346/wooden-beer-keg">Barrel</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+<a target="_blank" href="https://icons8.com/icon/IjFwYfQ6Hmgc/geyser">Geyser</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+<a target="_blank" href="https://icons8.com/icon/17358/pumpkin">Pumpkin</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+<a target="_blank" href="https://icons8.com/icon/dwzqRjO6XcTN/battery">Battery</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
+
+## License
+GPL 3.0. Contributions welcome!
+
## References
1. <https://bgstack15.ddns.net/blog/posts/2024/03/22/initial-research-for-hacking-savegame-files-for-snoopy-vs-the-red-baron/>
@@ -75,3 +101,5 @@ The binary contents are stored in little-endian, which makes visual inspection o
4. <https://gist.github.com/Lauszus/6c787a3bc26fea6e842dfb8296ebd630>
5. <https://stackoverflow.com/questions/46109815/reversing-the-byte-order-of-a-string-containing-hexadecimal-characters>
6. [Walkthrough for Snoopy vs. the Red Baron](https://gamefaqs.gamespot.com/pc/930591-snoopy-vs-the-red-baron/faqs/46161)
+
+Too many to list here. See the References heading in each file for more.
diff --git a/srb_tk.py b/srb_tk.py
index c141bb6..607daa8 100755
--- a/srb_tk.py
+++ b/srb_tk.py
@@ -13,7 +13,6 @@
# References:
# logout-manager-tcl, lmlib.py
# https://likegeeks.com/python-gui-examples-tkinter-tutorial/#Add_a_Menu_bar
-# icon from https://www.iconarchive.com/show/peanuts-icons-by-ed-mundy/Snoopy-icon.html
# https://www.tcl.tk/man/tcl8.6/TkCmd/bind.htm from https://www.tcl.tk/man/tcl8.6/TkCmd/contents.htm
# https://www.reddit.com/r/learnpython/comments/mavinn/tkinter_remove_optionmenu_icon/
# https://stackoverflow.com/questions/69806038/navigate-to-tkinter-optionmenu-with-tab-key/69806039#69806039
@@ -169,57 +168,35 @@ class App(tk.Frame):
# statusbar variable
self.statustext = tk.StringVar()
- # app label, for vanity
+ # we use this variable later to set "transparent" color on certain widgets.
self.background_color = self.master.cget("bg")
+ # app label, for vanity
self.lbl_app = tk.Label(self.master,text="Savegame editor for Snoopy vs. the Red Baron")
self.lbl_app.grid(row=0,column=0,columnspan=2)
stk.Tooltip(self.lbl_app,text="Also checkout the cli with ./srb.py")
- self.lbl_checksum = tk.Label(self.master,text="Checksum",underline=0)
- self.lbl_checksum.grid(row=1,column=1)
- self.ent_checksum = tk.Entry(state="readonly",textvariable=self.checksum)
- #self.ent_checksum.bind("<Key-Return>",self.load_form_into_data)
- self.ent_checksum.grid(row=1,column=0)
- self.chk_silent = tk.Checkbutton(self.master,text="silent",variable=self.silent)
- self.chk_silent.grid(row=2,column=0)
- self.chk_backgrounds = tk.Checkbutton(self.master,text="frame colors",variable=self.frame_backgrounds)
- self.chk_backgrounds.grid(row=3,column=0)
+ tk.Label(self.master,text="Checksum",underline=0).grid(row=1,column=1)
+ tk.Entry(state="readonly",textvariable=self.checksum).grid(row=1,column=0)
+ tk.Checkbutton(self.master,text="silent",variable=self.silent).grid(row=2,column=0)
+ tk.Checkbutton(self.master,text="frame colors",variable=self.frame_backgrounds).grid(row=3,column=0)
self.frm_profiles = tk.Frame(self.master)
self.frm_profiles.grid(row=4,column=0,columnspan=2)
# investigate radio button key-bind, up-down, to increment/decrement choice?
tk.Label(self.frm_profiles,text="Used").grid(row=0,column=2)
tk.Label(self.frm_profiles,text="Tutorial").grid(row=0,column=3)
- self.rad_prof1 = stk.Radiobutton(self.frm_profiles,value=1,text="Profile 1",variable=self.selected_profile,underline=8)
- self.rad_prof1.grid(row=1,column=0)
- self.chk_prof1_used = tk.Checkbutton(self.frm_profiles,variable=self.prof1_used)
- self.chk_prof1_used.grid(row=1,column=2)
- self.chk_prof1_tut = stk.Checkbutton(self.frm_profiles,variable=self.prof1tut,func=self.load_form_into_data)
- #self.hook_checkbox_enter_keys(self.chk_prof1_tut,self.load_form_into_data)
- self.chk_prof1_tut.grid(row=1,column=3)
+ stk.Radiobutton(self.frm_profiles,value=1,text="Profile 1",variable=self.selected_profile,underline=8).grid(row=1,column=0)
+ tk.Checkbutton(self.frm_profiles,variable=self.prof1_used).grid(row=1,column=2)
+ stk.Checkbutton(self.frm_profiles,variable=self.prof1tut,func=self.load_form_into_data).grid(row=1,column=3)
self.ent_prof1name = stk.Entry(self.frm_profiles,textvariable=self.prof1name,func=self.load_form_into_data,width=11)
- self.ent_prof1name.bind("<Key-Return>",self.load_form_into_data)
- self.ent_prof1name.bind("<Key-KP_Enter>",self.load_form_into_data)
self.ent_prof1name.grid(row=1,column=4)
- self.rad_prof2 = stk.Radiobutton(self.frm_profiles,value=2,text="Profile 2",variable=self.selected_profile,underline=8)
- self.rad_prof2.grid(row=2,column=0)
- self.prof2_used.set(False)
- self.chk_prof2_used = tk.Checkbutton(self.frm_profiles,variable=self.prof2_used)
- self.chk_prof2_used.grid(row=2,column=2)
- self.chk_prof2_tut = stk.Checkbutton(self.frm_profiles,variable=self.prof2tut,func=self.load_form_into_data)
- #self.hook_checkbox_enter_keys(self.chk_prof2_tut,self.load_form_into_data)
- self.chk_prof2_tut.grid(row=2,column=3)
+ stk.Radiobutton(self.frm_profiles,value=2,text="Profile 2",variable=self.selected_profile,underline=8).grid(row=2,column=0)
+ tk.Checkbutton(self.frm_profiles,variable=self.prof2_used).grid(row=2,column=2)
+ stk.Checkbutton(self.frm_profiles,variable=self.prof2tut,func=self.load_form_into_data).grid(row=2,column=3)
self.ent_prof2name = stk.Entry(self.frm_profiles,textvariable=self.prof2name,func=self.load_form_into_data,width=11)
- self.ent_prof2name.bind("<Key-Return>",self.load_form_into_data)
self.ent_prof2name.grid(row=2,column=4)
- self.rad_prof3 = stk.Radiobutton(self.frm_profiles,value=3,text="Profile 3",variable=self.selected_profile,underline=8)
- self.rad_prof3.grid(row=3,column=0)
- self.prof3_used.set(False)
- self.chk_prof3_used = tk.Checkbutton(self.frm_profiles,variable=self.prof3_used)
- self.chk_prof3_used.grid(row=3,column=2)
- self.chk_prof3_tut = stk.Checkbutton(self.frm_profiles,variable=self.prof3tut,func=self.load_form_into_data)
- #self.hook_checkbox_enter_keys(self.chk_prof3_tut,self.load_form_into_data)
- self.chk_prof3_tut.grid(row=3,column=3)
+ stk.Radiobutton(self.frm_profiles,value=3,text="Profile 3",variable=self.selected_profile,underline=8).grid(row=3,column=0)
+ tk.Checkbutton(self.frm_profiles,variable=self.prof3_used).grid(row=3,column=2)
+ stk.Checkbutton(self.frm_profiles,variable=self.prof3tut,func=self.load_form_into_data).grid(row=3,column=3)
self.ent_prof3name = stk.Entry(self.frm_profiles,textvariable=self.prof3name,func=self.load_form_into_data,width=11)
- self.ent_prof3name.bind("<Key-Return>",self.load_form_into_data)
self.ent_prof3name.grid(row=3,column=4)
#self.selected_profile.set(1) # purposefully do not set this value.
@@ -228,13 +205,9 @@ class App(tk.Frame):
self.frm_curprof.grid(row=5,column=0,rowspan=22,columnspan=2)
# most form fields are inside the current profile
self.img_money = get_srb_image("money.png",20,16)
- self.lbl_money = tk.Label(self.frm_curprof,text="Money",image=self.img_money,compound="right")
- self.lbl_money.grid(row=0,column=0)
- self.ent_money = stk.Entry(self.frm_curprof,textvariable=self.money,func=self.load_form_into_data)
- #self.ent_money.bind("<Key-Return>",self.load_form_into_data)
- self.ent_money.grid(row=0,column=1)
- self.lbl_equ_weapon = tk.Label(self.frm_curprof,text="Equipped weapon",compound="right")
- self.lbl_equ_weapon.grid(row=1,column=0)
+ tk.Label(self.frm_curprof,text="Money",image=self.img_money,compound="right").grid(row=0,column=0)
+ stk.Entry(self.frm_curprof,textvariable=self.money,func=self.load_form_into_data).grid(row=0,column=1)
+ tk.Label(self.frm_curprof,text="Equipped weapon",compound="right").grid(row=1,column=0)
# list of names
equippable_weapons = [i["name"] for i in srb_lib.WEAPONS if i["e"]]
self.img_equ_weapon = get_srb_image("balloon-none.png",16,16)
@@ -248,60 +221,38 @@ class App(tk.Frame):
self.img_weapons.append(get_srb_image(i+".png",16,16))
ow.entryconfigure(i,image=self.img_weapons[-1],compound="left")
self.img_health = get_srb_image("health.png",16,16)
- self.lbl_health = tk.Label(self.frm_curprof,text="Health",image=self.img_health,compound="right")
- self.lbl_health.grid(row=2,column=0)
- self.spn_health = stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.health,width=2)
- self.spn_health.grid(row=2,column=1)
+ tk.Label(self.frm_curprof,text="Health",image=self.img_health,compound="right").grid(row=2,column=0)
+ stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.health,width=2).grid(row=2,column=1)
self.img_stunt = get_srb_image("stunt.png",16,16)
- self.lbl_stunt = tk.Label(self.frm_curprof,text="Stunt",image=self.img_stunt,compound="right")
- self.lbl_stunt.grid(row=3,column=0)
- self.spn_stunt = stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.stunt,width=2)
- self.spn_stunt.grid(row=3,column=1)
+ tk.Label(self.frm_curprof,text="Stunt",image=self.img_stunt,compound="right").grid(row=3,column=0)
+ stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.stunt,width=2).grid(row=3,column=1)
self.img_gun = get_srb_image("gun.png",19,16)
- self.lbl_gun = tk.Label(self.frm_curprof,text="Gun",image=self.img_gun,compound="right")
- self.lbl_gun.grid(row=4,column=0)
- self.spn_gun = stk.Spinbox(self.frm_curprof,from_=1,to=5,textvariable=self.gun,width=2)
- self.spn_gun.grid(row=4,column=1)
- self.lbl_pur = tk.Label(self.frm_curprof,text="Purchased: Weapons")
- self.lbl_pur.grid(row=5,column=0)
- self.lbl_pur_weap = tk.Label(self.frm_curprof,text="Planes")
- self.lbl_pur_weap.grid(row=5,column=1)
+ tk.Label(self.frm_curprof,text="Gun",image=self.img_gun,compound="right").grid(row=4,column=0)
+ stk.Spinbox(self.frm_curprof,from_=1,to=5,textvariable=self.gun,width=2).grid(row=4,column=1)
+ tk.Label(self.frm_curprof,text="Purchased: Weapons").grid(row=5,column=0)
+ tk.Label(self.frm_curprof,text="Planes").grid(row=5,column=1)
self.img_potato = get_srb_image("Potato Gun.png",16,16)
- self.chk_pur_weap_potato = stk.Checkbutton(self.frm_curprof,text="Potato Gun",variable=self.pur_weap_potato,func=self.load_form_into_data,image=self.img_potato,compound="left")
- self.chk_pur_weap_potato.grid(row=6,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Potato Gun",variable=self.pur_weap_potato,func=self.load_form_into_data,image=self.img_potato,compound="left").grid(row=6,column=0,sticky="W")
self.img_stinger = get_srb_image("Stinger.png",16,16)
- self.chk_pur_weap_stinger = stk.Checkbutton(self.frm_curprof,text="Stinger",variable=self.pur_weap_stinger,func=self.load_form_into_data,image=self.img_stinger,compound="left")
- self.chk_pur_weap_stinger.grid(row=7,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Stinger",variable=self.pur_weap_stinger,func=self.load_form_into_data,image=self.img_stinger,compound="left").grid(row=7,column=0,sticky="W")
self.img_watball = get_srb_image("Water Balloon Cannon.png",12,16)
- self.chk_pur_weap_watball = stk.Checkbutton(self.frm_curprof,text="Water Balloon Cannon",variable=self.pur_weap_watball,func=self.load_form_into_data,image=self.img_watball,compound="left")
- self.chk_pur_weap_watball.grid(row=8,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Water Balloon Cannon",variable=self.pur_weap_watball,func=self.load_form_into_data,image=self.img_watball,compound="left").grid(row=8,column=0,sticky="W")
self.img_snow = get_srb_image("Snow Blower.png",16,16)
- self.chk_pur_weap_snow = stk.Checkbutton(self.frm_curprof,text="Snow Blower",variable=self.pur_weap_snow,func=self.load_form_into_data,image=self.img_snow,compound="left")
- self.chk_pur_weap_snow.grid(row=9,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Snow Blower",variable=self.pur_weap_snow,func=self.load_form_into_data,image=self.img_snow,compound="left").grid(row=9,column=0,sticky="W")
self.img_fire = get_srb_image("Fire Boomerang.png",12,16)
- self.chk_pur_weap_fire = stk.Checkbutton(self.frm_curprof,text="Fire Boomerang",variable=self.pur_weap_fire,func=self.load_form_into_data,image=self.img_fire,compound="left")
- self.chk_pur_weap_fire.grid(row=10,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Fire Boomerang",variable=self.pur_weap_fire,func=self.load_form_into_data,image=self.img_fire,compound="left").grid(row=10,column=0,sticky="W")
self.img_lightn = get_srb_image("Lightning Rod.png",16,16)
- self.chk_pur_weap_lightn = stk.Checkbutton(self.frm_curprof,text="Lightning Rod",variable=self.pur_weap_lightn,func=self.load_form_into_data,image=self.img_lightn,compound="left")
- self.chk_pur_weap_lightn.grid(row=11,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Lightning Rod",variable=self.pur_weap_lightn,func=self.load_form_into_data,image=self.img_lightn,compound="left").grid(row=11,column=0,sticky="W")
self.img_romanc = get_srb_image("Roman Candles.png",16,16)
- self.chk_pur_weap_romanc = stk.Checkbutton(self.frm_curprof,text="Roman Candles",variable=self.pur_weap_romanc,func=self.load_form_into_data,image=self.img_romanc,compound="left")
- self.chk_pur_weap_romanc.grid(row=12,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="Roman Candles",variable=self.pur_weap_romanc,func=self.load_form_into_data,image=self.img_romanc,compound="left").grid(row=12,column=0,sticky="W")
self.img_pumpkin = get_srb_image("10 Gauge Pumpkin.png",16,16)
- self.chk_pur_weap_pumpkin = stk.Checkbutton(self.frm_curprof,text="10 Gauge Pumpkin",variable=self.pur_weap_pumpkin,func=self.load_form_into_data,image=self.img_pumpkin,compound="left")
- self.chk_pur_weap_pumpkin.grid(row=13,column=0,sticky="W")
- self.chk_pur_plane_marcie = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_marcie,variable=self.pur_plane_bool_marcie,func=self.load_form_into_data)
- self.chk_pur_plane_marcie.grid(row=6,column=1,sticky="W")
- self.chk_pur_plane_sally = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_sally,variable=self.pur_plane_bool_sally,func=self.load_form_into_data)
- self.chk_pur_plane_sally.grid(row=7,column=1,sticky="W")
- self.chk_pur_plane_rerun = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_rerun,variable=self.pur_plane_bool_rerun,func=self.load_form_into_data)
- self.chk_pur_plane_rerun.grid(row=8,column=1,sticky="W")
- self.chk_pur_plane_pigpen = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_pigpen,variable=self.pur_plane_bool_pigpen,func=self.load_form_into_data)
- self.chk_pur_plane_pigpen.grid(row=9,column=1,sticky="W")
- self.chk_pur_plane_woodstock = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_woodstock,variable=self.pur_plane_bool_woodstock,func=self.load_form_into_data)
- self.chk_pur_plane_woodstock.grid(row=10,column=1,sticky="W")
- self.chk_pur_plane_baron = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_baron,variable=self.pur_plane_bool_baron,func=self.load_form_into_data)
- self.chk_pur_plane_baron.grid(row=11,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,text="10 Gauge Pumpkin",variable=self.pur_weap_pumpkin,func=self.load_form_into_data,image=self.img_pumpkin,compound="left").grid(row=13,column=0,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_marcie,variable=self.pur_plane_bool_marcie,func=self.load_form_into_data).grid(row=6,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_sally,variable=self.pur_plane_bool_sally,func=self.load_form_into_data).grid(row=7,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_rerun,variable=self.pur_plane_bool_rerun,func=self.load_form_into_data).grid(row=8,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_pigpen,variable=self.pur_plane_bool_pigpen,func=self.load_form_into_data).grid(row=9,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_woodstock,variable=self.pur_plane_bool_woodstock,func=self.load_form_into_data).grid(row=10,column=1,sticky="W")
+ stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_baron,variable=self.pur_plane_bool_baron,func=self.load_form_into_data).grid(row=11,column=1,sticky="W")
# level and levelsets
self.frm_levels = tk.Frame(self.master)
@@ -400,8 +351,7 @@ class App(tk.Frame):
# end for-i-in-levelsets
# status bar
- self.status = stk.StatusBar(self.master,var=self.statustext)
- #self.grid(expand=True,fill=tk.BOTH)
+ stk.StatusBar(self.master,var=self.statustext)
# and now open the first file
self.func_open(self.current_file.get())
bgstack15