Skip to content

Instantly share code, notes, and snippets.

@JeodC
Last active November 24, 2025 18:54
Show Gist options
  • Select an option

  • Save JeodC/b57a307caf97996aa74072986258b7b1 to your computer and use it in GitHub Desktop.

Select an option

Save JeodC/b57a307caf97996aa74072986258b7b1 to your computer and use it in GitHub Desktop.
A guide for creating and distributing wine bottles for use with rocknix

Distributing Wine Bottles for ROCKNIX

If you're new to setting up Wine Bottles in ROCKNIX, then you should read qcs-wine-bottles.md first.

This guide is intended for people who wish to share their functional wine bottles with others, for easier consumption. The system used in this guide is extremely similar to PortMaster and the system used for packaging ports. There are three simple terms used throughout:

  • .winecellar: This is a user-created folder that ideally lives in roms/windows. It is meant to store shared custom binaries to help with running wine bottles (such as tools/splash) and custom wine builds.
  • bottle.json: This json file is similar to port.json in that it holds identifying information about the game it's bundled with. Launch scripts will use jquery to parse this file to correctly configure wine for the game.
  • winecask.json: This json file is a merged combination of all bottle.json files in a GitHub repository. It is only needed if you plan on distributing your wine bottles with Pharos.

Why use bottle.json?

Parsing bottle.json files with jquery to fill out environment variables and download dependencies is much easier to maintain than several custom launch scripts. Launch scripts must still be modified anyway to point to the game directory, executable, and add any additional launch options, but using jquery parsing avoids launch scripts having hundreds of lines to sift through.

Anatomy of bottle.json

What follows is a basic bottle.json file.

{
  // This section is used by Pharos to install the package.
  "version": 1,
  "name": "bottledir.zip",
  "items": [
    "Bottle Name.sh",
    "bottledir"
  ],
  "runner": "default", // The runner defines which binary to use to run the game.
  "deps": [
    // This is an array of dependencies required for wine to run the game.
  ],
  "env": {
    "WINEDEBUG": "-all", // This environment variable defines log level output.
    "WINEARCH": "win64" // This environment variable defines which architecture the wine prefix uses.
  },
  "configdir": [
    // This is an array of the config/save directories used by the game. These are usually documented on pcgamingwiki.
  ],
  // This section is used by Pharos to display the package in the gui.
  "attr": {
    "title": "",
    "desc": "",
    "inst": "Add your game files to the data folder",
    "genres": [
    ],
    "availability": "paid"
  }
}

The entries are then used in launch scripts, for example:

# ================================================
# WINE RUNNER CONFIG
# ================================================

# Function to find the latest runner version
find_runner_dir() {
    # Find any folder under ../.winecellar that matches $1 (case-insensitive)
    # Sorts by version and takes the latest one (tail -n1)
    find "../.winecellar" -maxdepth 1 -type d -iname "*$1*" | sort -V | tail -n1
}

# Runner selection
RUNNER=$(jq -r '.runner // "default"' "$GAMEDIR/bottle.json")
WINEARCH=$(jq -r '.env.WINEARCH // "win64"' "$GAMEDIR/bottle.json")

case "$RUNNER" in
    default)
        # Use PATH wine/box64 with standard .wine prefix
        WINEPREFIX="$HOME/.wine"
        WINE="wine"
        ;;
    proton)
        RUNNER_DIR=$(find_runner_dir "proton")
        WINEPREFIX="$HOME/.proton"
        WINE="$(readlink -f "$RUNNER_DIR/bin/wine")"
        ;;
    wine-wow64)
        RUNNER_DIR=$(find_runner_dir "wine-wow64")
        WINEPREFIX="$HOME/.wine"
        WINE="$(readlink -f "$RUNNER_DIR/bin/wine")"
        ;;
    *)
        echo "Error: Unknown runner '$RUNNER' specified in bottle.json"
        exit 1
        ;;
esac

# Append 32 to prefix if win32 architecture is required
[ "$WINEARCH" = "win32" ] && WINEPREFIX="${WINEPREFIX}32"
# Set BOX based on architecture
BOX=$([ "$WINEARCH" = "win32" ] && echo "box86" || echo "box64")

# Error checking for non-default runners and PATH setup
if [ "$RUNNER" != "default" ]; then
    if [ -z "$RUNNER_DIR" ] || [ ! -x "$WINE" ]; then
        echo "Error: Required runner '$RUNNER' not found or is not executable."
        exit 1
    fi
    
    export PATH="$(dirname "$WINE"):$PATH"
fi

echo "[LAUNCHER]: Using runner '$RUNNER' with WINEPREFIX='$WINEPREFIX' BOX='$BOX' WINE='$WINE'"

# Mapping of dependencies to a file that indicates installation
declare -A DEP_DLL_MAP=(
    [vcrun2022]="drive_c/windows/system32/vcruntime140_1.dll"
    [dxvk]="drive_c/windows/system32/dxgi.dll"
    [vkd3d-proton]="drive_c/windows/system32/d3d12.dll"
    [vcrun2019]="drive_c/windows/system32/vcruntime140.dll"
    [dotnet48]="drive_c/windows/Microsoft.NET/Framework64/v4.0.30319/mscorlib.dll"
)

# Install dependencies from bottle.json
if jq -e '.deps' "$GAMEDIR/bottle.json" >/dev/null 2>&1; then
    echo "[LAUNCHER]: Installing dependencies from bottle.json..."
    jq -r '.deps[]?' "$GAMEDIR/bottle.json" | while read -r dep; do
        marker="${DEP_DLL_MAP[$dep]}"
        if [ -n "$marker" ] && [ -f "$WINEPREFIX/$marker" ]; then
            echo "[LAUNCHER]: $dep already installed (found $marker)"
        else
            echo "[LAUNCHER]: Installing $dep..."
            WINEPREFIX="$WINEPREFIX" winetricks -q "$dep" || {
                echo "[ERROR]: Failed to install $dep"
                exit 1
            }
        fi
    done
fi

# If proton then install dxvk and vkd3d-proton
if [ "$RUNNER" = "proton" ]; then
    if ! WINEPREFIX="$WINEPREFIX" winetricks list-installed | grep -q '^dxvk$'; then
        echo "[LAUNCHER]: Installing DXVK into Proton prefix..."
        WINEPREFIX="$WINEPREFIX" winetricks -q dxvk
    fi
    if ! WINEPREFIX="$WINEPREFIX" winetricks list-installed | grep -q '^vkd3d-proton$'; then
        echo "[LAUNCHER]: Installing vkd3d-proton into Proton prefix..."
        WINEPREFIX="$WINEPREFIX" winetricks -q vkd3d-proton
    fi
fi

# Load bottle env
if command -v jq >/dev/null; then
    while IFS="=" read -r k v; do
        export "$k=$v"
    done < <(jq -r '.env | to_entries | .[] | "\(.key)=\(.value)"' "$GAMEDIR/bottle.json")
else
    echo "Error: jq not found"
    exit 1
fi

# Config Setup
CONFIGDIRS=$(jq -r '.configdir[]?' "$GAMEDIR/bottle.json")
if [ -n "$CONFIGDIRS" ] && [ -n "$WINEPREFIX" ]; then
    mkdir -p "$GAMEDIR/config"
    
    while IFS= read -r dir; do
        SRC="$WINEPREFIX/$dir"
        if [ -d "$SRC" ]; then
            echo "[CONFIG]: Binding $SRC -> $GAMEDIR/config"
            bind_directories "$SRC" "$GAMEDIR/config"
        else
            echo "[CONFIG]: Warning: $SRC does not exist, skipping."
        fi
    done <<< "$CONFIGDIRS"
fi

Launch scripts have not changed much otherwise. It's still advised to use the PortMaster Preamble to make use of its functions and environment variables (like bind_directories and $directory). Local variables come after:

# ================================================
# LOCAL VARIABLES
# ================================================

GAMEDIR="/$directory/windows/bottledir"
EXEC="$GAMEDIR/data/EXECNAME"
BASE=$(basename "$EXEC")

SPLASH="/$directory/windows/.winecellar/tools/splash"
LOG="$GAMEDIR/log.txt"

cd "$GAMEDIR"
> "$LOG" && exec > >(tee "$LOG") 2>&1

# Splash
chmod 777 "$SPLASH"
"$SPLASH" "$GAMEDIR/splash.png" 50000 &

Notice how .winecellar is used here to reference the splash binary, which is then used to display a splash image while the bottle is loading. A full example can be seen with Windswept. In short, the only things that should require modifying in the launch script are:

  • The GAMEDIR
  • EXECNAME
  • The GPTK file used (near the bottom of the script)

Under construction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment