Knowledge Base

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

Adding Frostburgh to Darkflame Lego Universe

I run a private Darkflame Lego Universe instance for myself. There are instructions out in the wild for adding the seasonal map Frostburgh. Here are my steps for the docker-compose based setup that I use, to get Frostburgh as a playable and visitable map.

  1. Download his asset.

    This is from url https://www.mediafire.com/file/a04fq7yah3fbpst/frosty_22Jan24.zip/file. I had to use a browser because that is some landing page and not a direct link.

    $ sha256sum frosty_22Jan24.zip
    39ed16b1b1616c21dcd0691a8bc6edb53260cfd195c84bd4dcf6466f094d5132  frosty_22Jan24.zip
    
  2. Convert original fdb to sqlite using https://fdb.lu-dev.net/

    $ ll
    total 56784
    -r--r--r-- 1 bgstack15 bgstack15 42140012 Nov 26 2011 cdclient.fdb
    -rw-rw-r-- 1 bgstack15 bgstack15 16003072 Nov  8 2024 cdclient.sqlite
    $ sha256sum *
    875d887f806de7c3e0a9c3974f11dc2d5375d3ecfa3b5d3be5314d3da34b3aa4  cdclient.fdb
    8f6ca2963ac3f33e23e5d38eed62f1795dc7d96aed084ea7fa97fb18ad574681  cdclient.sqlite
    
  3. run FROSTY.sql

    This is where I diverge minorly, because on GNU/Linux it's easy to redirect input and run the queries that add the relevant parts to the database. After extracting the zip file, run the queries.

    $ <FROSTY.sql sqlite3 cdclient.sqlite
    
  4. convert sql back to fdb

    Use the same webpage to get it back. My directory now looks like:

    $ sha256sum cdclient.fdb*
    bcb9cabb8274c1fcc0918115268e068b114b51fccbbc7d7cbed1b0810bfd854c  cdclient.fdb
    875d887f806de7c3e0a9c3974f11dc2d5375d3ecfa3b5d3be5314d3da34b3aa4  cdclient.fdb.orig
    
  5. Update client files

    1. Update locale.xml

      On the client, manually update the locale/locale.xml with the contents of the locale.xml.txt from the zip. Add these <phrase> objects where they obviously go, below the last </phrase>, above the <fonts>.

    2. Update res directory

      Copy the res/ directory from the expanded zip to the client directory. This will overwrite a few files.

      cp -pr res/ ~/.wine-lego/drive_c/lego-universe-client/res/
      
    3. Update cdclient.fdb

      Replace the cdclient.fdb with the converted one from step 4.

  6. Update server files

    1. Server locale.xml

      Take that client-updated locale.xml and place on server's client/locale/locale.xml file.

    2. Update res directory

      Copy the from-zip res/ directory to server's client directory.

      ~/darkflameserver/client$ 7za x frosty_22Jan24.zip res
      
    3. Update CDServer.sqlite

      I had updated CDServer.sqlite previously to fix a unicode eror for the Nexus dashboard admin mail function. So I also did this to the server's server/res/CDServer.sqlite:

      <FROSTY.sql sqlite3 ~/darkflameserver/server/res/CDServer.sqlite
      

      Thankfully the server uses sqlite so we do not need to convert this back to fdb.

    4. Prepare the launchpads

      I had to add to my docker-compose file container darkflameserver the volume entry:

         - ./server/vanity:/app/vanity/
      

      And then prepare it on my local filesystem from the default built-in ones.

      sudo docker cp da59f6c5e6c4:/app/vanity ~/darkflamserver/server/
      

      And then fix file permissions. And the format appears to be different now for objects in the vanity dev-tribute.xml. I found https://github.com/DarkflameUniverse/DarkflameServer/tree/v2.2.0/vanity which shows an NPC.xml which was indicated to be modified. Those contents now exist in dev-tribute.xml:

      <object name="Nimbus to Frostburgh" lot="12442">
           <locations>
               <location zone="1200" x="-362.17" y="290.31" z="209.85" rw="0.92" rx="0.0" ry="-0.38" rz="0.0" />
           </locations>
      </object>
      <object name="Frostburgh to Nimbus" lot="12460">
           <locations>
               <location zone="1260" x="-668.58" y="569.1109" z="629.85" rw="0.974" rx="0.0" ry="-0.22" rz="0.0" />
           </locations>
      </object>
      

Now, restart the server and client. After all that I was able to visit Frostburgh from the launchpad placed near the launchpad to Starbase 3001 in Nimbus Station!

Screenshot of the new launchpad to Frostburgh:

Perhaps it would be easy to update the xml file around the holiday season to disable the launchpad, I don't plan on doing that. I guess Frostburgh is here to stay!

Frostburgh is here!

Acetoneiso silent error: A plea for help

I have recently discovered a problem with AcetoneISO (or on launchpad that nobody else seems to have experienced. I wonder if it's disused. Even I have alternatives: just sudo /usr/bin/mount * /mnt/cdrom permissions or cdemu.

The purpose of acetoneiso is to pass a filename to it, which it should then mount, nominally to ~/virtual-drives/1/ and so on. However, nowadays, it fails, on multiple systems that I have tested. It doesn't throw any errors graphically. You have to run acetoneiso from a terminal and then you'll see this:

QIODevice::read (QFile, "/home/bgstack15/virtual-drives/1/readme.txt"): device not open

That's it. And Internet searching has gotten me nowhere. I'm guessing there's some busted thing in QT but I know nothing about QT or how to troubleshoot. I'm guessing GTK_DEBUG=1 won't help here. So if any of you readers know how to help me, please point me in a direction!

Screenshot of acetoneiso

Python error in Radicale led to me fixing my httpd config

When I ran OS updates for the month of November, I realized that my Radicale CalDAV server was not working. It was running, but it had an application error. After doing all the requisite research, I opened a bug report.

Nov 01 11:01:13 server3.ipa.internal.com radicale[8675]: [8675/Thread-2] [INFO] Successful login: 'bgstack15@IPA.INTERNAL.COM'
Nov 01 11:01:13 server3.ipa.internal.com radicale[8675]: [8675/Thread-2] [ERROR] An exception occurred during REPORT request on '/bgstack15/': Error in section 'read-domain-principal' of rights file '/etc/radicale/rights': Replacement index 0 out of range for positional args tuple

To simplify out the examples/boilerplate, my rule for calculating per-user permissions when using GSSAPI (kerberos/ldap) auth was throwing a python error.

[principal-domain]
user: (.+)@IPA.EXAMPLE.COM
collection: {0}
permissions: RW

It appears that whatever new logic happened in the 3.3.0 release revised how the regex capture groups work across the attributes of a permission directive.

It worked before. Or at least, it didn't throw errors before. (Technically I'd been patching out the domain name inside radicale already so this rule wasn't even in use).

So, the Radicale team suggested that I strip the domain name at the reverse proxy level, which inspired me to search, and this time I found a solution!

In my httpd config:

RewriteEngine On
RewriteRule ^/radicale$ /radicale/ [R,L]
<Location "/radicale/">
   ProxyPreserveHost On
   Include conf.d/auth-gssapi.cnf
   # which includes these lines:
   #GSS_NAME returns username@IPA.EXAMPLE.COM which merely needs additional rules in /etc/radicale/rights
   #RequestHeader set X_REMOTE_USER "%{GSS_NAME}e"
   Require valid-user
   AuthName "GSSAPI protected"
   ProxyPass        http://localhost:5232/ retry=20 connectiontimeout=300 timeout=300
   ProxyPassReverse http://localhost:5232/
   RequestHeader    set X-Script-Name /radicale
+   RequestHeader    edit X_REMOTE_USER "^(.*)@.*" "$1"
</Location>

So all I needed to do was treat the now-extant request header as a string variable and do a simple regex manipulation to preserve everything before the at sign.

Now I will no longer need to maintain that ridiculous app/__init__.py one-line patch after every python3 or radicale update.

I am always amused that some complex problems can be solved by a one-line change. It's usually the one-line changes that represent hours and hours and hours of work, eh?

Lego Universe use gamepad controller

If you want to use a usb gamepad controller (no affiliation/kickbacks here: I'm just a happy user) to emulate keypresses for the game of Lego Universe (DarkflameServer), here is how to repeat what I set up.

Install qjoypad. Run, and load this configuration. This is for the default keyboard configuration in-game, which I never tried to change. It might not even be possible.

files/2024/listings/lego-universe-qjoypad.lyt (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# QJoyPad 4.3 Layout File
# Startdate: 2024-10-23-4 15:25
# Author: bgstack15

Joystick 1 {
	Axis 1: gradient, +key 40, -key 38
	Axis 2: gradient, +key 39, -key 25
	Axis 3: +key 11, -key 0
	Axis 4: gradient, maxSpeed 1, tCurve 0, mouse+h
	Axis 5: gradient, maxSpeed 1, mouse+v
	Axis 6: +key 14, -key 0
	Axis 7: gradient, +key 40, -key 38
	Axis 8: gradient, +key 39, -key 25
	Button 1: key 65
	Button 2: key 64
	Button 3: key 50
	Button 4: key 37
	Button 5: key 12
	Button 6: key 13
	Button 7: key 56
	Button 8: mouse 1
	Button 11: rapidfire, mouse 3
}

Screenshot of qjoypad configuration window. This is not enough information to configure your controller for yourself.

The gamepad will have this layout.

Key mapping of XBOX 360-like controller, that is useful for Lego Universe default keybinds

Lego Universe cancel mission

If you are using Darkflame Server to emulate the long-gone Lego Universe MMO, and you accidentally accept a mission you cannot finish, you can use an admin command to remove the mission from your list.

You have to be an admin (aka GM or Mythran) on the server, which is not covered here, to be able to run the macro. It appears DarkFlame added a command for resetting an individual mission.

Screenshot of mission that I will never get to complete.

To determine which mission you need to scrub, search the text in client file locale/locale.xml. You want to grab the id number from the MissionText_[id]_in_progress.

    <phrase id="MissionText_1194_in_progress">
        <translation locale="en_US">Visit 5 Block Yard or Avant Grove Properties today.</translation>
        <translation locale="de_DE">Besuche heute 5 Steinhof- oder Avanthain-Grundstücke.</translation>
        <translation locale="en_GB">Visit 5 Block Yard or Avant Grove Properties today.</translation>
    </phrase>

You can confirm the mission id is correct on explorer.lu (lu:explorer). So with the mission id, you can go chat this in-game:

/gmlevel 8
/resetmission 1194
/gmlevel 0

Screenshot of entering admin chat command to hide the mission.

References

  1. LU Development Network | LU Development Network
  2. Game master levels in DarkflameServer/docs/Commands.md at main · DarkflameUniverse/DarkflameServer
  3. move command resetmission to gmlevel 0 by bgstack15 · Pull Request #1638 · DarkflameUniverse/DarkflameServer

git commit custom timestamp

tl;dr

GIT_AUTHOR_DATE="2024-07-01T15:30:00Z" GIT_COMMITTER_DATE="2024-07-01T15:30:00Z" git commit -m 'in the past'

Explanation

It's not enough to set the commit date, you have to set the author date as well. I also find that it's more believable to use non-zero seconds values, such as:

GIT_AUTHOR_DATE="$( date -d "now - 2 hours" )" GIT_COMMITTER_DATE="$( date -d "now - 2 hours" )" git commit -m 'before the deadline, see the commit timestamp?'

References

This page has way more info about how to do this to multiple commits, such as modifying the entire history of a branch.

  1. How to tweak Git commit timestamps (author and committer dates) - Sling Academy

Lego Universe autologin

I got tired of my Lego Universe game crashing, and having to type my username/password in again. This is not a shared machine, so I wrote a small utility to handle logging in for me.

The first time you use it, it will prompt for username and password. You can trigger this prompting again with parameter --login.

To simulate the clicks and key presses, use parameter --apply.

files/2024/listings/autologin-lego-universe.sh (Source)

#!/bin/sh
# File: autologin-lego-universe.sh
# Location: /mnt/public/Games/lego-universe/
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-10-07-2 15:26
# Title Autologin for Lego Universe client
# Purpose: Given a username and password, auto log in to the Lego Universe/Darkflame client
# History:
# Usage:
#    Env vars:
#       APPLY
#    Parameters:
#       --login Change username and password in conf file
#       --apply Actually run automation
# Reference:
#    https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced/28776166#28776166
#    man xdotool, yad
# Improve:
#    This is slightly buggy. The longer the delays in typing and between tasks, the better it runs.
#    This is only tested against 1920x1080
# Dependencies:
#    dep-devuan: xdotool, yad
#    Lego Universe already running! This script does not know how long it takes to load the username prompt in the game.
# Documentation:
# variables
CONFFILE=~/.config/autologin-lego-universe.conf
autologin_lego_universe() {
   # pixel offset, only tested against 1920x1080
   _type_delay_ms=60
   _step_delay_s=0.3
   _y_offset_username_field=50
   _loop_max_safety=10
   _tmpfile1="$( mktemp )" ; rm "${_tmpfile1}"
   # needs env vars: USERNAME, PASSWORD
   # step 0: find window that matters
   _x=0
   while test ! -f "${_tmpfile1}" ;
   do
      printf '%s ' "Loop ${_x}" 1>&2
      WINDOWID="$( xdotool search "Lego Universe - " )"
      test -n "${WINDOWID}" && touch "${_tmpfile1}"
      sleep 0.5 ;
      _x=$(( _x + 1 ))
      test ${_x} -ge ${_loop_max_safety} && { echo "Fatal! Safety valve: could not find game window after 20 loops. Aborting." 1>&2 ; rm -f "${_tmpfile1:-NOTHINGTODEL}" 1>/dev/null 2>&1 ; return 1 ; }
   done
   echo "Found WINDOWID=${WINDOWID}"
   rm -f "${_tmpfile1:-NOTHINGTODEL}"
   # step 0: bring game window to top. This does not work for me, but maybe it works for somebody else.
   xdotool windowactivate --window "${WINDOWID}" windowfocus --window "${WINDOWID}"
   # step 1: move mouse to username field
   xdotool mousemove --window "${WINDOWID}" $(( 1920 / 2 )) $(( 1080 / 2 - ${_y_offset_username_field} ))
   # step 2: enter username
   xdotool click 1 sleep "${_step_delay_s}" click 1 key ctrl+a type --delay "${_type_delay_ms}" "${USERNAME}"
   # step 3: move to password field
   xdotool sleep "${_step_delay_s}" key Tab
   # step 4: enter password
   xdotool sleep "${_step_delay_s}" sleep "${_step_delay_s}" type --delay "${_type_delay_ms}" "${PASSWORD}"
   # step 5: press enter
   xdotool sleep "${_step_delay_s}" key Return
}
save_field() {
   # usage: save_field USERNAME
   _field="${1}"
   # no error handling. Do not mess up save_field!
   case "${_field}" in
      USERNAME)
         _yad_opts='--text Username'
         _default="${USERNAME}"
         ;;
      PASSWORD)
         _yad_opts='--hide-text --text Password'
         set -x
         _default="${PASSWORD}"
         ;;
      *)
         echo "Error! save_field ${@} is invalid. Needs USERNAME or PASSWORD. Continuing..." 1>&2
         return 1
         ;;
   esac
   # do not quote _yad_opts on next line.
   result="$( yad ${_yad_opts} --entry --entry-text "${_default}" --title "Autologin for Lego Universe" )"
   _response_code=$?
   # write to file
   if test 0 -eq ${_response_code} ;
   then
      sed -i -r -e "/^${_field}=/d" "${CONFFILE}"
      echo "${_field}=\"${result}\"" >> "${CONFFILE}"
   else
      yad --text "Action cancelled."
   fi
   set +x
   unset _default _response_code
}
# if dot-sourced, run main
# BEGIN IF-DOT-SOURCED, so 2683279
sourced=0
if [ -n "$ZSH_VERSION" ]; then
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
# END IF-DOT-SOURCED
if test "${sourced}" = "0" ;
then
   test -f "${CONFFILE}" && . "${CONFFILE}"
   unset _reload
   if test -z "${USERNAME}" || echo " ${@} " | grep -qE " --login " ; then save_field USERNAME ; _reload=1 ; fi
   if test -z "${PASSWORD}" || echo " ${@} " | grep -qE " --login " ; then save_field PASSWORD ; _reload=1 ; fi
   echo " ${@} " | grep -qE " --apply " && APPLY=1
   test -n "${_reload}" && test -f "${CONFFILE}" && . "${CONFFILE}"
   # If you want to be more basic:
   #test -z "${USERNAME}" && { echo "Fatal! Need env var USERNAME. Aborted." 1>&2 ; exit 1 ; }
   #test -z "${PASSWORD}" && { echo "Fatal! Need env var PASSWORD. Aborted." 1>&2 ; exit 1 ; }
   unset _reload
   test -n "${APPLY}" && autologin_lego_universe ;
fi

And you can set your desktop shortcut to run this script then:

files/2024/listings/run-lego-universe-and-autologin.sh (Source)

#!/bin/sh
# Startdate: 2024-10-08-3 14:30
# Purpose: run Lego Universe, and then wait a few seconds, and then auto login.
legouniverse_script="$( find ~/.wine* -iname lego-universe.sh -print -quit )"
"${legouniverse_script}" &
# Hopefully this is long enough
sleep 10
# This if-dot-sourcing does not work so just go ahead and call it dot-sourced and then call the relevant function.
. /mnt/public/Games/lego-universe/autologin-lego-universe.sh
autologin_lego_universe

Nexus Dashboard: vendor fonts

The Nexus Dashboard admin control panel for the Lego Universe server emulator DarkflameServer works really well. It just uses some bog-standard Internet stuff where it pulls in Google fonts. It makes the site look really nice, but for people who don't want to leak the referer and/or are not on the world wide web, you can vendor the font yourself.

In the docker-compose.yml, add to the container darkflamserverweb a volume:

- ${HOME}/web/css:/app/static/css

Then make that directory, and pull down the included url yourself, once.

/* stackrpms,8 This pulls down https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf so I put that here. */
/* @import url(https://fonts.googleapis.com/css?family=Nunito:700); */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  src: url(XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf) format('truetype');
}

So my directory contents are as follows.

$ ls -al web/css
total 220
drwxr-xr-x. 2 bgstack15 admins        72 Oct 11 08:54 ./
drwxr-xr-x. 6 bgstack15 admins        54 Oct 11 08:29 ../
-rw-r--r--. 1 bgstack15 bgstack15 181989 Oct 11 08:28 site.css
-rw-rw-r--. 1 bgstack15 bgstack15  39108 Sep 13  2023 XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf

Cruddy, and silly, but it works. Now I get the nice fonts without telling Google every time I visit the admin console.

Nexus dashboard with nice fonts

gksu: Turn warning back on

Problem

Gksu (yes, the long-gone program throws a notice message if you have sudo NOPASSWD access, indicating you were able to do this operation without a password/authentication. And if you check the box to hide this notice in the future, you won't ever see again.

Solution

Use gconftool-2 to control it. Apparently the gksu uses the slightly-less-deprecated gconf idea (binary registry, sounds familiar? And now replaced with dconf which is different somehow?).

Show the current status.

gconftool-2 --get /apps/gksu/display-no-pass-info

Turn the warnings back on.

 gconftool-2 --set /apps/gksu/display-no-pass-info --type boolean true

Backstory

I was checking on one of my favorite utilities, xdgmenumaker, and it is authored by a guy named gapan. Well, one of his other pinned repositories is salixtools-gtk, which had a nifty user management program. I was setting up a system for an offsite user who will have to manage local users, and I found salixtools-gtk's gtkusersetup program really nice.

I learned that it takes a user with access to /etc/shadow so either a member of Debian group shadow, or some setgid business, or sudo. Or, as the .desktop file for that project suggets, gksu. Which I had forgotten about; I don't normally use root functions from a graphical environment so hadn't needed it. But of course all good things must have ended before I came around, because now "they" want you to use policykit or other loennart-level dumb things.

I got really scared when I couldn't find a ~/.config file for gksu anywhere. Come to find out it's buried in some binary thingy (worse than the xfce4 xml files that only update upon a normal exiting of xfce4) that you can't just use Unixy tools to control. I forget exactly how I learned it was gconf. I think I used strace to read what it was doing or maybe got lucky examining other possible places on my $HOME filesystem. I shouldn't have to search so hard.