I enjoy gaming and occasionally there’s an application I want to run that is unavailable on Linux. Up until now my primary way of dealing with this was to use multiple wine bottles, one for each game. While this provides a little bit of segmentation for applications, it is not a sandbox.

Something else I dislike is how wine likes to spit out files all over my filesystem. There are ways to stop wine from doing this, such as disabling wine’s winemenubuilder by setting an environment variable with WINEDLLOVERRIDES="winemenubuilder.exe=d".

However I also don’t want my wine bottle having access to my entire system.

I figured this would be a good opportunity to test out flatpak, and figure out how to package and build a wine application with it.

The nice thing about using flatpak is that everything will be isolated in the sandbox. For complex or proprietary applications, flatpak might make packaging possible, when it wasn’t before, or at least was very difficult.

I took a crack at building a flatpak for Path of Exile - which is only available for Windows - that can be installed from my repository. So far everything is working successfully, and it has made installing on multiple Linux systems as simple as installing a regular Linux package.

UPDATE: Since I created this flatpak, the winepak project has appeared. While the information in this post s still useful if you are looking to build a flatpak, if you are looking to install the flatpak, installing Path of Exile from there probably a better option since it supports 64-bit, and Is updated more frequently.

pathofexile

If you’re mostly interested in trying out the flatpak, instructions for installing it are available at the bottom of the post.


Index

Creating the Build

The first step to creating a Flatpack is initializing a ‘build’. A gpg key can be used to sign the resulting package. Alternatively the flag --no-gpg-verify can be given to not require this.

Since Path of Exile is a 32-bit application I will be using the i386 freedesktop platform and sdk.

flatpak build-init pathofexile ca.johnramsden.pathofexile org.freedesktop.Platform/i386/1.6 org.freedesktop.Sdk/i386/1.6

A local repository should also be set up.

flatpak --user remote-add --if-not-exists johnramsden applications

This creates the johnramsden repository in an applications directory.

flatpak-builder

Creating an flatpak is easy with flatpak-builder. Instead of doing the individual steps manually to create a build, flatpak-builder can be used to save the configuration in a json file and build the Flatpak.

Create a json file in reverse DNS format. Mine will be ca.johnramsden.pathofexile.json. The entire file can be found on GitHub.

General Options

To start, add a few self-explanitory values.

{
    "app-id": "ca.johnramsden.pathofexile",
    "runtime": "org.freedesktop.Platform/i386/1.6",
    "sdk": "org.freedesktop.Sdk/i386/1.6",
    "tags": ["proprietary"],
    "command": "pathofexile.sh",

    ...

The command will be a script that will be run at start.

Next set some global build options.

"build-options": {
  "env": {
    "WINEPREFIX": "/var/data/.local/share/pathofexile",
    "WINEDLLOVERRIDES": "mscoree=d;mshtml=d",
    "V": "0"
  }
}

Inside the Flatpak, data that is persistent can be saved in /var/data, the actual location of the data will be ${HOME}/.var/app/ca.johnramsden.pathofexile/, but inside the Flatpak it can be referenced with /var/data.

finish-args

Next specify some finish-args. These are very important and will specify the final permissions the application has on a system.

"finish-args": [
    "--persist=.local/share/pathofexile",
    "--socket=x11",
    "--share=network",
    "--share=ipc",
    "--device=dri",
    "--allow=multiarch",
    "--socket=pulseaudio",
    "--filesystem=/var/log:ro",
    "--env=WINEDEBUG=-all",
    "--env=WINE_RESOLUTION=1920x1080"
]

Several of them are fairly self-explanatory, but I will explain each of them.

  • --persist=.local/share/pathofexile
    • Bindmount the homedir-relative path .local/share/pathofexile to ${HOME}/.local/share/pathofexile during runtime.
    • The actual directory lives at the corresponding path in the per-application directory, i.e. ${HOME}/.var/app/ca.johnramsden.pathofexile/.local/share/pathofexile. This allows a persistent directory to be set for data that will be needed at runtime.
    • Since we set our WINEPREFIX to /var/data/.local/share/pathofexile, this will be the location of our wine bottle.
  • --socket=x11
    • Allow graphical access to the xorg server.
  • --share=network
    • Allow network access.
  • --share=ipc
    • Allow inter-process communication.
  • --device=dri
    • Allow access to graphics direct rendering.
  • --allow=multiarch
    • Allow 64 and 32-bit applications to be built.
  • --socket=pulseaudio
    • Use pulseaudio.
  • --filesystem=/var/log:ro
    • Allow read-only access to /var/log. This is used to check the amount of video card memory in the startup script.
  • --env=WINEDEBUG=-all
    • Do not show wine debugging output, this increases performance.
  • --env=WINE_RESOLUTION=1920x1080
    • Allow users to set the resolution at runtime using an environment variable. Defaults to 1920x1080.
  • --env=VIDEO_MEMORY=
    • Allow users to set the video memory at runtime using an environment variable. If unset, it tries to find it.

modules

The Next Step will be to add modules needed to run the application.

I use the following modules in my application:

  • wine
  • winetricks
  • cabextract
  • wine-gecko
  • wine-mono
  • wget
  • pathofexile-installer
  • icons
  • desktop
  • metadata

Each of them downloads and if necessary compiles the application, installing it into the Flatpak. Special integration exists for autotools, but simple commands such as make install can also be used.

The last four are just installing a script, icons, a desktop file, and AppStream metadata. The only one key to the functioning of the flatpak is the pathofexile-installer module.

The following is an example of the module I use to compile and install wine.

"name": "wine",
"buildsystem": "autotools",
"build-options": {
    "make-args": [ "--silent" ] 
},
"sources": [
    {
        "type": "archive",
        "url": "https://dl.winehq.org/wine/source/3.x/wine-3.4.tar.xz",
        "sha256": "a483247ac93f325d623a463438590b7355ec26c670af15d356b0ce6c46398e93"
    },
    {
        "type": "archive",
        "url": "https://github.com/wine-staging/wine-staging/archive/v3.4.tar.gz",
        "sha256": "c56f5d2706e228fe4f720e9301af30e82b4a7b6c1749d0b6170e60f1cb16fe34"
    },
    {
        "type": "shell",
        "commands": [ "./patches/patchinstall.sh DESTDIR=$(pwd) --all" ]
    }
]

Tthe “pathofexile-installer” module contains the script required to set up the wine bottle, download Path of Exile, and install it into the wine bottle.

#!/bin/bash

export WINEPREFIX="${HOME}/.local/share/pathofexile"
export WINEDEBUG=-all

POE_INSTALLER_NAME="pathofexile_setup.exe"
POE_SETUP="${WINEPREFIX}/${POE_INSTALLER_NAME}"
POE_DOWNLOAD_URL='https://www.pathofexile.com/downloads/PathOfExileInstaller.exe'
POE_RUN_CMD="${WINEPREFIX}/drive_c/Program Files/Grinding Gear Games/Path of Exile/PathOfExile.exe"

WINE_RESOLUTION="${WINE_RESOLUTION:-1920x1080}"
WINE="/app/bin/wine"

XORG_LOG="/var/log/Xorg.0.log"

VERSION_NUM="0.1.3"
VERSION_FILE="${WINEPREFIX}/ca.johnramsden.pathofexile.version"

declare -ra WINE_PACKAGES=(directx9 usp10 msls31 corefonts tahoma win7)
declare -ra WINE_SETTINGS=('csmt=on' 'glsl=disabled')

echo "#############################################"
echo "## Path of Exile Unofficial Flatpak v${VERSION_NUM} ##"
echo "#############################################"
echo

# Set video memory by checking xorg
set_video_memory(){
  if [[ ! -z "${VIDEO_MEMORY}" ]]; then
    echo "Using explicitly set VMEM of ${VIDEO_MEMORY}"
  elif  [ -f "${XORG_LOG}" ]; then

    # Get Video Memory from Xorg logs
    local xorg_vmem
    xorg_vmem="$(sed -rn 's/.*memory: ([0-9]*).*kbytes/\1/gpI' ${XORG_LOG})"

    if [[ ! -z "${xorg_vmem}" ]]; then
        VIDEO_MEMORY=$((${xorg_vmem} / 1024))
        echo "Setting video memory to ${VIDEO_MEMORY}" 
    else
      echo "Unable to find video memory in ${XORG_LOG}."
      echo "Leaving video card memory at default settings."
      echo "To set value explicitly, set the VIDEO_MEMORY environment variable"
      return 1
    fi

  else
    echo "Unable to read Xorg logs from ${XORG_LOG}."
    echo "Leaving video card memory at default settings."
    return 1
  fi 

      tmpfile=$(mktemp VideoMemory.XXXXX.reg)

      cat <<EOF > "${tmpfile}"
Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\\Software\\Wine\\Direct3D]
"VideoMemorySize"="${VIDEO_MEMORY}"

EOF

  "${WINE}" regedit "${tmpfile}" >/dev/null 2>&1
  rm "${tmpfile}"
  return 0
}

set_wine_settings(){
  local my_documents="${WINEPREFIX}/drive_c/users/${USER}/My Documents"

  echo "Installing wine requirements."
  winetricks --unattended "${WINE_PACKAGES[@]}"

  echo "Setting wine settings."
  winetricks --unattended "${WINE_SETTINGS[@]}"

  # Symlink points to wrong location, fix it
  if [[ "$(readlink "${my_documents}")" != "${XDG_DOCUMENTS_DIR}" ]]; then
    echo "Setting game directory to ${XDG_DOCUMENTS_DIR}"
    mv "${my_documents}" "${my_documents}.bak.$(date +%F)"
    ln -s "${XDG_DOCUMENTS_DIR}" "${my_documents}"
  fi

  echo
}

# Run only if POE isn't installed
first_run(){
  set_wine_settings

  echo "${VERSION_NUM}" > "${VERSION_FILE}"

  if [ ! -f "${POE_SETUP}" ]; then
    echo "Downloading Path of Exile installer."
    wget --output-document="${POE_SETUP}" "${POE_DOWNLOAD_URL}"
  fi
  echo "Running Path of Exile installer."
  "${WINE}" "${POE_SETUP}"
}

is_updated(){
  if [ -f "${VERSION_FILE}" ]; then
    last_version="$(cat ${VERSION_FILE})"
  else
    last_version="0"
  fi

  echo "${VERSION_NUM}" > "${VERSION_FILE}"
  
  if [[ "${VERSION_NUM}" == "${last_version}" ]]; then
    return 0
  else
    return 1
  fi
}

# Main function
startup(){

  set_video_memory

  if ! grep -q 'Software\\\\GrindingGearGames\\\\Path of Exile' "${WINEPREFIX}/user.reg" >/dev/null; then
    echo "Path of Exile not installed."
    first_run
  else
    if ! is_updated; then
      echo "Not up to date, re-run wine settings"
      set_wine_settings
    fi
  fi
    
  echo "Setting resolution to ${WINE_RESOLUTION}"
  echo "If resolution was changed from default, game may need restarting"
  winetricks --unattended vd="${WINE_RESOLUTION}" >/dev/null

  echo ; echo "Starting Path of Exile..."
  "${WINE}" "${POE_RUN_CMD}" dbox  -no-dwrite -noasync
}

startup

The script gets run at startup, makes sure Path of Exile is Installed, otherwise it must be the first run, therefore install dependencies and download and install path of Exile.

Doing the install this way, I haven’t actually packaged Path of Exile, i’ve essentially packaged everything required to install it in a wine bottle on the first run.

The flatpak-builder module for it is as follows.

"name": "pathofexile-installer",
"buildsystem": "simple",
"build-commands": [ 
    "install --directory ${WINEPREFIX}",
    "install pathofexile.sh /app/bin"
],
"no-make-install": true,
"sources": [
    {
    "type": "file",
    "path": "pathofexile.sh"
    }
]

Build the Flatpak

Once the package is finished, run flatpak-builder to create the resulting package (gpg signing is optional).

flatpak-builder --user \
                --force-clean \
                --repo=applications \
                --gpg-sign=${GPG_KEY} pathofexile ca.johnramsden.pathofexile.json

When it’s finished, it will be exported to the applications repo.

Remainder of flatpak-builder manifest

My repo containing the build instructions can be found on github. It contains:

.
├── ca.johnramsden.pathofexile.appdata.xml
├── ca.johnramsden.pathofexile.json
├── pathofexile-256.png
├── pathofexile.desktop
├── pathofexile.sh
├── README.md
└── settings.conf

I tried to highlight the interesting parts above.

Install my Flatpak

If you want to try my flatpak without having to build it yourself, add my Flatpak repository.

flatpak --user remote-add \
    --if-not-exists johnramsden http://flatpakrepo.johnramsden.ca/johnramsden.flatpakrepo

Now you should be able to install my flatpaks.

flatpak --user install johnramsden ca.johnramsden.pathofexile

If you are missing the freedesktop platform you may need to add the flathub repository and install it. It should install itself, but you can also explicitly install it.

flatpak --user remote-add \
    --if-not-exists flathub \
    https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install flathub org.freedesktop.Platform//1.6

To change the virtual desktop resolution, set the environment variable WINE_RESOLUTION in the run command.

It defaults to 1920x1080, to set it to 720x480 for example you would change the run command to:

flatpak run --env=WINE_RESOLUTION=720x480 ca.johnramsden.pathofexile

To change the video memory if it’s not detected properly at run time, use the VIDEO_MEMORY environment variable

flatpak run --env=VIDEO_MEMORY=1024 ca.johnramsden.pathofexile 

To make it permanent edit the ~/.local/share/flatpak/exports/share/applications/ca.johnramsden.pathofexile.desktop file.

If you think I’ve made a mistake or would like to submit a pull request, please visit the GitHub repository.

Automated Builds

I also set up an automated system using Travis CI for building the flatpak upon push to github, and a GitHub pages site for distribution. In my next post I will describe how it is configured.