diff options
author | B. Stack <bgstack15@gmail.com> | 2024-04-05 17:53:57 -0400 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2024-04-05 17:53:57 -0400 |
commit | 829bb9a89aefa1a0bba9af959722d8129d689ec6 (patch) | |
tree | 80d15a57aea9e4a4c981162db751e4e202f5135a | |
parent | all: add reset breakables (diff) | |
download | srb_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.png | bin | 0 -> 135799 bytes | |||
-rw-r--r-- | README.md | 30 | ||||
-rwxr-xr-x | srb_tk.py | 132 |
3 files changed, 70 insertions, 92 deletions
diff --git a/.images/scrot-savegame-editor-graphical.png b/.images/scrot-savegame-editor-graphical.png Binary files differnew file mode 100644 index 0000000..603b8ed --- /dev/null +++ b/.images/scrot-savegame-editor-graphical.png @@ -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. @@ -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()) |