Lex Talionis on Retro Handhelds with Portmaster

Hi everyone. I love all the rom hacks that people have been posting here, and just made an account to share that I’ve been able to get Lex Talionis games working on my retro handheld via PortMaster. To my knowledge this hasn’t been done publicly yet, but let me know if there’s something that exists out there already.

I only have the TrimUI Smart Pro using muOS, but in principle this should work with any device and OS capable of running PortMaster. I would certainly expect the TrimUI Brick and Anbernic xx devices running muOS to work. Performance isn’t perfect, but definitely playable – I’m getting on average 40-50 fps with rare slowdowns outside of loading. Some LT games run super well at 60 fps, even.

I’ll try to outline my process and help other people who might want try this. Making an actual port on PortMaster seems to require a lot more work, and I’d have to wait for permission from all the creators of the games I’d be including, so for now I’ll just leave this as a guide. If you’re fairly familiar with Linux/Python (or even just Python), you can probably just skip to the TL;DR at the bottom.

I: Setting up the LT engine

  1. Get a retro handheld with an OS that supports PortMaster, and make sure PortMaster is installed. Again, I’m personally using muOS.
  2. Ensure Westonpack 0.2 is installed in PortMaster under Options > System > Runtime Manager. If you’ve been using PortMaster there’s a good chance you already have it installed.
  3. Ensure Python is installed on your retro handheld. You can do this with python --version after ssh-ing into your handheld. Lex Talionis uses Python 3.11.7, but muOS comes preinstalled with 3.12.x which has been working fine for me. If you need to install Python, your options are probably ssh or to cross compile an appropriate Linux build of Python and copy it over. Someone else definitely knows more about this than me.
  4. It’s probably a good idea to also install Lex Talionis on your computer to troubleshoot with. Instructions can be found here in the official Lex Talionis documentation (sorry, I can’t post links).
  5. Copy the files for Lex Talionis (from step 4) onto your SD card. This can be anywhere, but I put this in a folder named LT on the root of my SD card. If you didn’t do step 4, you can also just download the Lex Talionis engine from gitlab.
  6. Install the necessary packages for Lex Talionis’ engine onto your device/SD card. These are found in requirements_engine.txt in your Lex Talionis folder, and are pygame-ce 2.3.2, pyinstaller 6.2.0, typing-extensions 4.8.0. You can do this several ways:
    1. I personally used ssh to install them. To do this, ssh onto your device and make sure you have pip by typing python -m ensurepip. Then you can just install these packages by typing python -m pip install package_here==version_here, or you can navigate to your Lex Talionis directory and use requirements_engine.txt to install them all in one go.
    2. I also tried installing the latest version of pygame-ce, which seems to work just as well. There might be some performance benefits to doing this (I’m not sure I noticed any though), but it may have worse compatability (again I’m not sure I noticed this either).
    3. You’ll get a warning about installing them as the root user, but I chose to ignore this my first time doing this and didn’t run into any issues. The “proper way” to set this up is to use a virtual environment – if you know how to do that then probably do that. I won’t cover that here.
    4. You could also install the packages (again for Linux) on a different machine. I did not try this, but look up how to use pip to install a cross platform package.

The Lex Talionis engine should be working on our device now, but we can’t quite test it yet. Let’s get an LT game running.

II: Getting a game to run in the engine

  1. Download your favorite Lex Talionis game (that does not use a custom engine!) and locate the .ltproj folder in the game files. In the directory you find the .exe or .bat to run the game, there should also be another folder. The .ltproj folder should usually be in that folder.
  2. Copy the .ltproj folder to your Lex Talionis folder. I would recommend doing this first on your computer.
  3. Then open up run_engine.py in any text/code editor (eg. Notepad++/Sublime) and find a try block towards the bottom of the script that calls the functions find_and_run_project(), main(), and test_play(). This should be lines 80-82.
  4. Comment out (or delete) find_and_run_project() and test_play() and uncomment the main() function (in Python we comment with #, there should be examples to follow in the file already). Also change the argument of the main() function to the name of the .ltproj folder from step 1 in quotes (single or double are fine, and do not include .ltproj). So for example milieu.ltproj we would have the line main(‘milieu’). Do note that games sometimes use a different name for their .ltproj folder.
  5. Check to see that the game runs. You can do this by opening Command Prompt, navigating to your Lex Talionis folder, and typing python run_engine.py.
    1. I found that many releases don’t run straight away. Just copy over whatever files are needed from a working game (I just use default.ltproj). Usually this is credit.json in the game_data directory of your .ltproj folder, but sometimes also the fonts folder in resources or something else entirely. The engine will give you useful error messages.
    2. I’m assuming these discrepancies are due to many games being released on earlier versions of the Lex Talionis engine, and every game I’ve tried has worked by just using the default files.
    3. I would love to include the correct credit.json file, but I haven’t spent to time to figure it out and how to create said file.
    4. For Milieu specifically, I found that the game wasn’t naming its saves correctly. To fix this, go into constants.json in the game_data folder and look for a game_nid field. Change this to anything unique.
  6. Once you have the game running (with any additional files), copy the new run_engine.py and .ltproj folder into the Lex Talionis folder on your SD card.
  7. Create a new folder in the Lex Talionis folder on your SD card called conf. You can leave it empty. In fact this shouldn’t even be needed as long as we remove the corresponding lines in a later .sh file. I didn’t bother going through that process though.
  8. Create a new file in the Lex Talionis folder on your SD card and call it run.sh. Inside a text/code editor paste the following, and make sure to save with Unix end of lines (so probably use at least something like Notepad++). You can load your venv here if you’re doing that:
    run.sh
    #!/bin/bash
    
    python “./run_engine.py”
    
  9. Also in the Lex Talionis folder of your SD card create a file called controls.gptk and paste the following contents. Feel free to read the PortMaster page for documentation to change this file to your liking – these are just some modified defaults I pulled of the page. In particular, I included the first line because I was experiencing some doubled inputs. This makes inputs wait longer before repeating. And save with Unix end of lines. I’m not actually sure this does anything.
    controls.gptk
    repeat_delay = 1000
    
    start = enter
    guide = enter
    
    a = x
    b = z
    x = c
    y = a
    
    l1 = rightshift
    l2 = home
    l3 = mouse_right
    
    r1 = leftshift
    r2 = end
    r3 = mouse_left
    
    up = up
    down = down
    left = left
    right = right
    
    left_analog_up = w
    left_analog_down = s
    left_analog_left = a
    left_analog_right = d
    
    right_analog_up = end
    right_analog_down = home
    right_analog_left = left
    right_analog_right = right
    
    Update: I’ve been editing the engine in \app\engine\fluid_scroll.py by changing speed= and slow_speed= to fairly high values (200 and 4, respectively) to control scrolling speed in menus. To do this for the minimap, I’ve been editing \app\engine\cursor.py and changing the arguments in line 25 where FluidScroll is called. I have it at frame2m(4), 3.25, again to slow down the scrolling speed.
  10. Somewhere in the ROM folder of your SD card, create an .sh file with the contents below. I’ll call it Lex Talionis.sh for now. This should probably be with your other PortMaster .sh files, especially if your OS has strict folder requirements. If you didn’t put your Lex Talionis folder on the root of your SD card, or changed any of the file/folder names in previous steps, make sure you check that GAMEDIR, CONFDIR, EXECUTABLE, and CONTROLSCHEME are correct (towards the top of the file). Again, make sure we have Unix end of lines.
    Lex Talionis.sh
    #!/bin/bash
    
    XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
    
    if [ -d "/opt/system/Tools/PortMaster/" ]; then
      controlfolder="/opt/system/Tools/PortMaster"
    elif [ -d "/opt/tools/PortMaster/" ]; then
      controlfolder="/opt/tools/PortMaster"
    elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then
      controlfolder="$XDG_DATA_HOME/PortMaster"
    else
      controlfolder="/roms/ports/PortMaster"
    fi
    
    source $controlfolder/control.txt
    [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
    get_controls
    
    GAMEDIR=/$directory/LT/
    CONFDIR="$GAMEDIR/conf/"
    EXECUTABLE="run.sh"
    CONTROLSCHEME="controls"
    
    mkdir -p "$GAMEDIR/conf"
    
    cd $GAMEDIR
    
    > "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1
    
    export XDG_DATA_HOME="$CONFDIR"
    export LD_LIBRARY_PATH="$GAMEDIR/libs.${DEVICE_ARCH}:$LD_LIBRARY_PATH"
    export SDL_GAMECONTROLLERCONFIG="$sdl_controllerconfig"
    
    # Mount Weston runtime
    weston_dir=/tmp/weston
    $ESUDO mkdir -p "${weston_dir}"
    weston_runtime="weston_pkg_0.2"
    if [ ! -f "$controlfolder/libs/${weston_runtime}.squashfs" ]; then
      if [ ! -f "$controlfolder/harbourmaster" ]; then
        pm_message "This port requires the latest PortMaster to run, please go to https://portmaster.games/ for more info."
        sleep 5
        exit 1
      fi
      $ESUDO $controlfolder/harbourmaster --quiet --no-check runtime_check "${weston_runtime}.squashfs"
    fi
    if [[ "$PM_CAN_MOUNT" != "N" ]]; then
        $ESUDO umount "${weston_dir}"
    fi
    $ESUDO mount "$controlfolder/libs/${weston_runtime}.squashfs" "${weston_dir}"
    
    if [ -f "${controlfolder}/libgl_${CFW_NAME}.txt" ]; then
      source "${controlfolder}/libgl_${CFW_NAME}.txt"
    else
      source "${controlfolder}/libgl_default.txt"
    fi
    
    $GPTOKEYB "${EXECUTABLE}" -c "./${CONTROLSCHEME}.gptk" & # xbox360 tag runs the app with virtual xbox360 controller, but is currently broken for muOS
    pm_platform_helper "$GAMEDIR/${EXECUTABLE}"
    $ESUDO env HOME=$HOME CRUSTY_SHOW_CURSOR=0 $weston_dir/westonwrap.sh headless noop kiosk crusty_glx_gl4es \
    XDG_DATA_HOME="$GAMEDIR" WAYLAND_DISPLAY= \
    ./${EXECUTABLE}
    
    #Clean up after ourselves
    $ESUDO $weston_dir/westonwrap.sh cleanup
    if [[ "$PM_CAN_MOUNT" != "N" ]]; then
        $ESUDO umount "${weston_dir}"
    fi
    pm_finish
    

Now we’re ready to run the game. Eject your SD card, put it into your retro handheld, and run the Lex Talionis however your OS runs ports via PortMaster. Be sure to run it in performance mode if your OS has options for that. Booting it up can sometimes take a bit especially the first time, but hopefully it does run and we can quit out for now with either select or hotkey+start. I want to just go over some other tweaks I’ve made to improve my experience:

III: Extras

  1. Go into the saves folder in the Lex Talionis directory and edit config.ini so that debug=0. This removes the debug menu from our game, and also makes it so the escape key (select) does not quit out of the game. You can also change any other settings you want here, but I would recommend doing keybindings in game.
  2. In the Lex Talionis directory, edit \app\engine\engine.py. Look for a line (for me line 41) that says
    return pygame.display.set_mode(size, pygame.FULLSCREEN if cf.SETTINGS['fullscreen'] else 0)
    
    and change the 0 to pygame.SCALED so that it reads
    return pygame.display.set_mode(size, pygame.FULLSCREEN if cf.SETTINGS['fullscreen'] else pygame.SCALED)
    
    This will make it so that your game is integer scaled and doesn’t stretch out to the aspect ratio of your screen. I’m not aware of a method for preserving aspect ratio without integer scaling. There’s a bunch of other flags/options you could try here, such as vsync=1; consult the pygame-ce documentation.
  3. I’ve found that the using the smallest screen size in the settings results in a slight boost in performance. But since I’m using pygame.SCALED in the previous step, I still see the largest possible integer scaled image. Honestly I can’t even tell the difference, if there is any, between having the game render a larger image or just scaling it up afterward.
  4. To add more Lex Talionis games, simply repeat the steps above to copy over your .ltproj folder and create new run_engine.py, run.sh, and Lex Talionis.sh files. Obviously you’ll have to name these something else. In fact, I renamed these all to reflect the name of the game I’m running. You’ll have to edit run.sh to run the corresponding .py file and Lex Talionis.sh to have the correct EXECUTABLE file. Or if you’re familiar with some coding, go ahead and pass along an argument through these files. In any case the good news is that the engine is shared between all of these games.
  5. A few games (eg. Blade and Claw or The Unbroken Thread) will default to displaying “The Lion Throne” on the title screen. Certainly not game breaking, but if you want their intended blank title screen, in game_project.ltproj/resources/custom_sprites (make the folder if it doesn’t exist), copy in a blank/transparent 400x400 png. It probably can be any (small) size, but to be safe I simply copied over the one that is used in Milieu.
  6. For games running the legacy engine (eg. Snow and Fire or Absolution), I’ve been able to download the file main.py from the old build of LT on gitlab (sorry I can’t post links, but it shows up if you Google lex talionis gitlab). Then copy main.py to the directory with the folders Code, Data, Saves, etc. and do the steps above, running main.py instead of run_engine.py. I’ve only booted these games and briefly looked at Chapter 1 though, so YMMV.
  7. To get custom engines running, you’ll need a new installation of the Lex Talionis engine. Get a working version of the engine on your computer, and copy that into a different directory on your SD card. You do not need to install the Python packages again, but everything else you will need to repeat. Akira Sou has a nice video on how to set up Embrace of the Fog, for example (I’m not actually aware of other games with custom engines, so please let me know what else is out there). I’m getting substantially worse performance on Embrace of the Fog though, around 20-30 fps in the actual chapters.
  8. Out of the things I’ve tested, the only game not really running is Myriad Fortunes. I had to mess around with some of the game data to even get into the casino, and the menus are still wonky/slightly glitchy. I’m assuming it’s got some engine modifications present… If anyone knows how I can get a working engine for Myriad Fortunes I’d be interested.

TL;DR: For custom OSes with Python and PortMaster, install the requirements for the LT engine on your handheld and you can run LT games with the westonpack runtime. Copy over the .ltproj folder of your favorite LT game into your LT engine, and on a PC check that it runs by running run_engine.py (step 5 under section II for some troubleshooting). Take a look at the run.sh, controls.gptk, and Lex Talionis.sh scripts above (steps 8-10 under section II), and any tweaks/tips as needed (section III).

Wow that was quite a bit of text but I promise the process isn’t all that difficult. I did go through everything a while ago and tinkered a lot along the way, and this guide is my first draft at recreating the steps from memory, so if something doesn’t work please let me know and I’ll help troubleshoot/fix the guide. I’d love to hear feedback on how this works on other devices and OSes as well. Update: I’ve got this working on Knulli, but performance is a bit worse than MuOS. Something like 10-20% lower fps.

And finally, thanks to the developers of Lex Talionis for all their work, as well as all the people here making rom hacks and fan games, Lex Talionis or otherwise. I don’t want to post any files, but if I get the okay from the devs of Lex Talionis and the makers of LT games, I could upload a zip of all the files you would be creating above, in an almost-ready-to-run state.

7 Likes