Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Make flashing text box in python tkinter

One of the useful features of my recently-announced tkstackrpms python library is a function that flashes an entry widget (text box).

files/2024/listings/demo-flashing-entry.py (Source)

#!/usr/bin/env python3
# Startdate: 2024-04-08-2 13:44
# Purpose: demo of the cool features of tkstackrpms
# Reference:
#    [python - How to make a flashing text box in tkinter? - Stack Overflow](https://stackoverflow.com/questions/27533244/how-to-make-a-flashing-text-box-in-tkinter/27533595#27533595)
# demo: flashing entry widget
import tkinter as tk
import tkstackrpms as stk
class App(tk.Frame):
   def __init__(self, master):
      super().__init__(master)
      self.master.title("Demo of tkstackrpms features")
      imgicon = stk.get_scaled_icon("battery",24,"default", "","apps")
      self.master.tk.call("wm","iconphoto",self.master._w,imgicon)
      self.grid()
      # Variables
      self.name1 = tk.StringVar()
      self.name2 = tk.StringVar()
      self.name3 = tk.StringVar()
      # Widgets
      tk.Label(self.master,text="If an A is in a text box when you tab out or press Enter,\nit will flash to indicate there is a problem.").grid(row=0,column=0,columnspan=3)
      self.ent_name1 = stk.Entry(self.master,textvariable=self.name1,func=self.validate_inputs)
      self.ent_name1.grid(row=1,column=0)
      self.ent_name2 = stk.Entry(self.master,textvariable=self.name2,func=self.validate_inputs)
      self.ent_name2.grid(row=1,column=1)
      self.ent_name3 = stk.Entry(self.master,textvariable=self.name3,func=self.validate_inputs)
      self.ent_name3.grid(row=1,column=2)
   # Functions
   def validate_inputs(self, arg1 = None, arg2 = None, arg3 = None):
      if "a" in self.name1.get():
         stk.flash_entry(self.ent_name1,["red","white","blue","white","red","white","red","white"],333)
      if "a" in self.name2.get():
         stk.flash_entry(self.ent_name2,["red","white","blue","white","red","white","red","white"],333)
      if "a" in self.name3.get():
         stk.flash_entry(self.ent_name3,["red","white","blue","white","red","white","red","white"],333)
# main
root = tk.Tk()
app_tk = App(root)
app_tk.mainloop()

I haven't bothered to learn how to make an animated GIF showcasing this, sorry. I bet this logic could be hooked up to the function called by the invalidcommand parameter of the entry widget, but I've used this where I had complex logic that evaluated a whole form so I didn't try the per-widget stuff.

New python3 library: tkstackrpms

I have collected my small additions/modifications to Tkinter in a single library: python3-tkstackrpms.

  • New widgets: Tooltip, Statusbar
  • Extended widgets:
  • Spinbox with mousewheel support
  • Checkbox that calls function on Enter/Space/MouseButton1
  • Entry that calls function on Enter/FocusOut
  • Radiobutton that allows keyboard navigation through all related (by same variable) radiobuttons: Home, End, Up, Down.
  • Poor-man's Gtk 3.0 icon name to path resolution
  • Load image from svg
  • Function flash_entry that cycles through a list of colors, at a provided duration per color. Useful for flashing an error.

If those aren't interesting to you, you can ignore this. But these have helped me in at least two projects, so it was time to make my own library.

There's a proper setup.cfg but I haven't bothered to figure out how to upload this to pypi. But I did make a dpkg for tkstackrpms.

Over the next few posts, I'll be demonstrating some of the useful features.

Reaction to Don't Require People to Change "Source Code" to Configure Your Programs

One of the blogs I read, Chris's Wiki :: blog, had an recent post titled Chris's Wiki :: blog/programming/ConfigureNoSourceCodeChanges. I considered posting a comment but had too much to say.

So after you read that article, read mine.

I have been intrisically doing this for quite some time, in various ways! I've written an overly-complicated template shell script that would load a series of conffiles, such as ~/.config/appname.conf, /etc/appname/appname.conf, and so on.

Nowadays, I use a process that just dot-sources ~/.config/appname.conf and then continues to define variables if not yet defined. Yes, I hardcode ~/.config rather than parse the fd.o XDG Base Directory spec.

Even if I have a single-instance deployment of some custom script, I tend to use some process like this. I don't want new versions of the script to affect the local configuration. It also makes it easier if I ever want to duplicate this script elsewhere: I can have the same script, and just local configs that are different.

And then of course if you get to the point of dpkg or rpm packages, those package managers handle config files for you. I prefer the rpm style, where it drops in a new file, /etc/appname/appname.conf.rpmnew and leaves it to you to deal with it entirely. But dpkg isn't so terrible. I feel it's more complicated and I've had to write complicated snippets to handle the config stuff to just always keep my config file instead of the package maintainer's version.

Graphical frontend for Savegame editor for Snoopy vs. the Red Baron

This is part 4 of my 3-part series on hacking the savegame file for Snoopy vs. the Red Baron. Previous posts include:

  1. Initial research for hacking savegame files for Snoopy vs. the Red Baron (2024-03-22)
  2. The checksum for the savegame file for Snoopy vs. the Red Baron (2024-03-26)
  3. Savegame editor for Snoopy vs. the Red Baron (2024-03-30)

I spent way too much time on this, and have developed a graphical frontend in tcl/tk for this savegame editor! It loads up "Profile 1.sav" by default so you can immediately make changes. You just have to remember to select "Save" from the File menu before quitting again.

It's in the same repository as the rest of the source code.

I had a lot of fun writing it, probably more fun than actually playing the game. Contributions welcome!

Freeipa sudorule all users mount -av

This rule demonstrates the users category "all" which is deceptively on the sudorule-mod command and not add-user one.

I want my users to be able to get to /mnt/public and some systems (on wireless networks) wait for the user session to start before mounting /mnt/public. And sometimes the autofs daemon is misbehaving, so /net/public (which mounts the same nfs export) isn't always available. Sometimes I just want to run sudo mount -av. If something shouldn't be mounted with that, then use flag noauto in /etc/fstab, but I have decided anything in /etc/fstab is allowed to be mounted by all users.

ipa sudorule-add "all-users-mount-av"
ipa sudorule-add-host "all-users-mount-av" --hostcat="all"
ipa sudorule-mod "all-users-mount-av" --usercat="all"
ipa sudorule-add-runasuser "all-users-mount-av" --users 'root'
ipa sudocmd-add --desc="mount -av" "/usr/bin/mount -av"
ipa sudorule-add-allow-command "all-users-mount-av" --sudocmds "/usr/bin/mount -av"
ipa sudorule-add-option "all-users-mount-av" --sudooption '!authenticate'
ipa sudorule-mod "all-users-mount-av" --desc="all users may run mount -av on any system"

And now a random user can run the comand I've been needing for months!

$ sudo -l -U user1
Matching Defaults entries for public on server8:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty,
    env_keep+="ftp_proxy http_proxy https_proxy no_proxy", env_keep+="FTP_PROXY HTTP_PROXY HTTPS_PROXY NO_PROXY",
    env_keep+="DEBUG DEBUG_LEVEL DRYRUN VERBOSE", env_keep+="DRYRUN VERBOSE", env_keep+="DRYRUN VERBOSE MYA_PREFIX
    DEBUG AUTOMOUNT_USER"

User user1 may run the following commands on server8:
(root) NOPASSWD: /usr/bin/mount -av

Hm, maybe I should clean up the duplicate DRYRUN, VERBOSE flags. Ah well, different problem for a different day.

Powershell: Invoke-WebRequest with custom cookie

In Powershell 7.3 on MacOS, you can run Invoke-WebRequest (iwr) with a -Headers that includes "Cookie" and it works. I'm guessing the Windows implementation of iwr does not let you do this, in Powershell 5.1 or 7.4.

$s = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$c = New-Object System.Net.Cookie('AppCookie',$value,'/','example.com')
$s.Cookies.Add($c)

$Response = Invoke-WebRequest "https://example.com/path/to/endpoint" -WebSession $s

References

There's a few places out on the Internet that describe how to set cookies for a request.

  1. HTTP requests with PowerShell’s Invoke-WebRequest – by Example - David Hamann
  2. Powershell Invoke-WebRequest with a cookie | GripDev

Savegame editor for Snoopy vs. the Red Baron

tl;dr

./srb.py --profile 1 --unlock-everything --buy-everything "Profile 1.sav"

Article

Previous posts in this 3-part series:

  1. Initial research for hacking savegame files for Snoopy vs. the Red Baron (2024-03-22)
  2. The checksum for the savegame file for Snoopy vs. the Red Baron (2024-03-26)

I have finished my Savegame Editor for Snoopy vs. the Red Baron! Go check out the source code now.

This python tool lets you query, and set, values for the different user profiles in the savegame file. You can change the username, or give yourself a lot of money, or mark levels completed with rank "General," and so on.

All of this is possible because of the checksum I learned in part 2.

You can use my savegame editor to change a profile name:

./srb.py --profile 1 --set-name "blogger" "Profile 1.sav"

You can purchase any specific plane by name even if you don't have the letters for it. (Not terribly useful on PC, but still.)

./srb.py --profile 1 --add-purchased-plane "rerun" "Profile 1.sav"

Reset balloons for a level.

./srb.py --profile 1 --set-level-balloons 5,none "Profile 1.sav"

You can read information too; you don't just have to set values.

./srb.py --profile 2 --get-levelset 0 "Profile 1.sav"

I would call this script as of the time of this writing as minimum viable product. It doesn't always respect debuglevel, or have a great layout of the output. But it does the bare minimum I wanted it to be able to do.

Happy cheating at a kid's game! Contributions welcome.

The checksum for the savegame file for Snoopy vs. the Red Baron

This is part 2 of my research for hacking the savegame file to Snoopy vs. the Red Baron. See part 1 for more context.

The program delsum has a few commands. You can tell it to guess which checksum algorithm is used if you give it enough samples and the checksums.

So I had to prepare the checksums.

for word in ~/Documents/Snoopy\ vs.\ the\ Red\ Baron/Profile\ 1/*sav ; do printf '%s,' "$( xxd -p -l 4 "${word}" )" ; done > ~/checksums1

Then given those existing checksums, use those same files (hopefully the shell globbing hasn't changed the order of those files in the past 8 seconds...):

$ cd ~/Documents/Snoopy\ vs.\ the\ Red\ Baron/Profile\ 1
$ ~/Downloads/delsum reverse --extended-search --start 4 --model 'crc width=32 init=0' --checksums "$( cat ~/foo1 | sed -r -e 's/,$//;' )" *sav
crc width=32 poly=0x4c11db7 init=0x0 xorout=0x235b4b9c refin=false refout=false out_endian=little

And that is the important part! It solved it. It took less than a second! It felt instantaneous. That's the magic information I hadn't been able to find after about 12 hours of research across the past 5 days.

I spent some time in Ghidra looking for this polynomial, literal 0x41c1db7 and I found it. I forget the offset, but it's in the binary. It was inside some dumb FUN_01234982748() type function that returns void. So clearly decompiling has its limits.

So, with this whole derived specification of a checksum, I hacked up test1.sav by changing how much money I have in-game. I used vim with :%!xxd to get the hex dump (maybe I should research a proper hexeditor again), make the 2-byte change at offset 0x284 (little-endian, of course). Reverse it with :%!xxd -r, and save the file.

Then I derived the checksum I'll have to insert back into the file:

$ ~/Downloads/delsum check -m 'crc width=32 poly=0x4c11db7 init=0x0 xorout=0x235b4b9c refin=false refout=false out_endian=little' --start 4 test1.sav
1cc2e3b4

So then I opened up the file again, xxd again, put that as the first 4 bytes, reversed xxd again and saved it. I replaced the main file, and told the game to reload. Nope, corrupted.

After some brief checking, I learned my test1.sav was one byte larger. Vim had of course saved the newline on the end. So, a :set binary and :set noeol later, I could save the file. And then the game can load my hacked savegame file!

I intend to write a small python tool to facilitate making this process easier. I might even add a small frontend to make it easier to set the various in-game attributes, like profile name, settings, achievements/unlockables, etc. We'll see.

Initial research for hacking savegame files for Snoopy vs. the Red Baron

This is the story of my meanderings to learn how to hexedit the savegame file for PC game Snoopy vs. The Red Baron.

The game has a short campaign (<20 hours of gameplay) where you unlock more weapons, earn money to buy those unlocked weapons and upgrades, and defeat the final boss.

The game appears to use a video-game-console-style savegame file, in a binary format. After some very basic hexediting with vim and :%!xxd (and :%!xxd -r before returning it) it became fairly obvious where the username/profile names were kept, along with some basic things like current money balance, and progress on certain levels.

I eventually learned that no changes I made would work, because the game would think the file is corrupt. The first four bytes are a checksum, and I spent about 10 hours total researching the checksum.

I started by estimating how many bytes are in the checked payload. The sum is 4 bytes, and the file is always 15,444 bytes, so I started with the assumption that the checked data is everything else but the payload, including the 0x0a at the end.

I tried writing some little functions in python, and I even found an implementation of Fletcher's checksum for python, but none of those worked. I even noted that the money of a player is stored in little-endian format, so I spent a bunch of time reformatting the output of every type of algo I tried. I don't really know how little-endian works, other than "1234 5678" would end up as "3412 7856" or maybe "7856 3412". sigh

Since I couldn't guess the checksum, I then decided to investigate the binary file, named srb.exe. I've heard of Ghidra, and even had come across an article recently about using it in wine. I probably found it on Ycombinator's Hacker News.

I realized that ghidra would be in Kali Linux, which is close enough to Devuan that I just needed to manually install the kali keyring dpkg, and then add the right repository and then apt-get install ghidra: Seamless!

I got ghidra and gdb running, and was able to sort of hook up ghidra to the remote gdb thing running wine running srb.exe. I found a loose spot in the binary where I could put a stub for the getpid-linux-i386 function, offset 0x401042. I couldn't get breakpoints to work, so I gave up on the debugger. I spent hours in the codebrowser, and I poked around the few ReadFile and WriteFile kernel calls (which is the first time in my life that I've looked up Windows programming references, excluding powershell which isn't the same). Ghidra is really cool! I could see how somebody who knows what he's doing could do so much more with it. It almost made me appreciate having to install java on my system for it.

I gave ollydbg a try, after undoing the minor damage its dependent package kali-defaults wreaked on my system files. I didn't understand ollydbg as much as I did ghidra (which was still only like a 2% understanding for that one), and also the game would always crash when I ran it in the ollydbg debugger.

As a side note, I spent a lot of time changing in winecfg to emulate a virtual desktop, so I could have the game up while hacking the savegame file. The game itself has a handy option to reload the savegame file, so I could make any changes, replace "Profile 1.sav" (hardcoded, which is easy to see in the binary), and tell it to reload. Upon which, of course, it would say the file was corrupted and it would have to delete it. The game, being a flying sim, struggled with when my mouse hit the edge of my real screen, because I must have forgotten to bind the mouse to the virtual-desktop-bound. So in between hacking the savegame file, I'd play a little, for which I needed full screen again. I'm guessing there's a winetricks command that simplifies that, but whatever.

So, after enjoying reading decompiled assembly, which would just name variables like "puVar12" and "param_3_00", and renaming a few of those, and still not finding what I was looking for, I switched back to researching the checksums.

Of course I have copies of the one 15kb-sized file, at various points in my progress of the gameplay. I was wondering if I just needed to brute-force the stuff. I felt it was going to be a crc32, because isn't that the most basic checksum (command cksum) around?

I learned about a sourceforge-hosted project (which used to be cool), and since the project was from the same timeframe as when SF was cool, there was a chance: CRC RevEng. I spent maybe an hour investigating this tool, but I think I was missing some key info, like a "polynomial," as well as I think it couldn't handle the 15kb payload. All the samples in the example were given on the command line as short string literals and not filenames. I briefly checked if the bashism < <( cat "Profile 1.sav") would work. It did not.

So I found a few other tools in a discussion on the Reverse Engineering Stack Exchange. I looked at hashdb but that focuses on malware checksums. So the second tool, delsum seemed very interesting. I don't know if I'd be able to compile rust, but thankfully a binary release exists.

And there this story should end. (Because I was successful, with delsum.) Tune in next time for more information about hexediting the savegame, and the checksum involved.

Change default audio input on Devuan with Pulseaudio

I switched to pulseaudio recently, which means some of my workflows have changed.

I learned how to use pulseaudio command lines to set my default input. Apparently the default gets reset when I unplug my headset.

files/2024/listings/use-mic.sh (Source)

1
2
3
4
#!/bin/sh
# Startdate: 2024-02-25-1 20:36
# https://askubuntu.com/questions/14077/how-can-i-change-the-default-audio-device-from-command-line
pacmd set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo

Pacmd worked well with autocomplete, and I already knew my correct info name was "analog stereo."

And now, so monitor when my headphones get plugged in, I had to install acpid for this daemon which I've added to my ~/.fluxbox/startup script.

files/2024/listings/daemon-use-mic.sh (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
# startdate: 2024-02-26-2 08:07
# Reference:
#    https://askubuntu.com/questions/1267949/how-do-i-automatically-switch-pulseaudio-input-to-headset-upon-connection/1284966#1284966
# Dependencies:
#    acpid, pulseaudio

# just in case we drop use-mic.sh
index="$( pacmd list-sources | grep -E 'index|ports|analog-input-headset-mic' | grep -E '\*\sindex:\s+[0-9]' | awk '{print $NF}' )"

acpi_listen | while IFS= read -r line ;
do
   echo "${line}" | grep -qE "HEADPHONE plug" 1>/dev/null 2>&1 && /usr/local/bin/use-mic.sh ;
   # or we could run "pacmd set-source-port ${index} analog-input-headset-mic
done

References

Weblinks

  1. sound - How can I change the default audio device from command line? - Ask Ubuntu
  2. 18.04 - How do I automatically switch PulseAudio input to headset upon connection? - Ask Ubuntu