[
    {
        "UpdateStageName": "Instance Directory Creation",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "CreateDirectory",
        "UpdateSourceArgs": "{{$FullBaseDir}}game",
        "SkipOnFailure": false
    },
    {
        "UpdateStageName": "Proton GE Download",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "Executable",
        "UpdateSourceData": "/bin/bash",
        "UpdateSourceArgs": "-c \"cd \\\"{{$FullRootDir}}\\\"; mkdir -p \\\"{{$FullRootDir}}.proton/compatdata\\\" \\\"{{$FullBaseDir}}.steam/steam\\\" \\\"{{$FullBaseDir}}.config/protonfixes\\\" || exit 1; ProtonVersion=\\\"{{SpecificReleaseVersion}}\\\"; ProtonVersion=\\\"${ProtonVersion// /}\\\"; if [[ -x \\\"{{$FullRootDir}}.proton/proton\\\" && -f \\\"{{$FullRootDir}}.proton/version\\\" ]]; then read _ InstalledVersion < \\\"{{$FullRootDir}}.proton/version\\\"; else InstalledVersion=\\\"\\\"; fi; if [[ -z \\\"$ProtonVersion\\\" ]]; then ProtonVersion=$(wget -qO- https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases/latest | jq -r .tag_name); fi; if [[ ! \\\"$ProtonVersion\\\" =~ ^GE-Proton[0-9]+-[0-9]+$ ]]; then echo \\\"Invalid Proton GE release version specified\\\" && exit 1; elif [[ -n \\\"$InstalledVersion\\\" && \\\"$InstalledVersion\\\" == \\\"$ProtonVersion\\\"* ]]; then echo \\\"Proton GE version $ProtonVersion already installed. Skipping\\\" && exit 0; else rm -f GE-Proton.tar.gz >/dev/null 2>&1; suffix=\\\"\\\"; ver=\\\"${ProtonVersion#GE-Proton}\\\"; major=\\\"${ver%%-*}\\\"; minor=\\\"${ver#*-}\\\"; if (( major > 11 || (major == 11 && minor >= 4) )); then suffix=\\\"-x86_64\\\"; fi; wget -qO GE-Proton.tar.gz https://github.com/GloriousEggroll/proton-ge-custom/releases/download/$ProtonVersion/${ProtonVersion}${suffix}.tar.gz || { echo \\\"Proton GE download failed from GitHub. Aborting\\\"; exit 1; }; rm -rf \\\"{{$FullRootDir}}.proton/compatdata/\\\"* >/dev/null 2>&1; tar -xzf GE-Proton.tar.gz -C \\\"{{$FullRootDir}}.proton\\\" --strip-components=1 && rm -f GE-Proton.tar.gz >/dev/null 2>&1 && echo \\\"Proton GE version $ProtonVersion installed\\\" && { STEAM_COMPAT_DATA_PATH=\\\"{{$FullRootDir}}.proton/compatdata\\\" STEAM_COMPAT_CLIENT_INSTALL_PATH=\\\"{{$FullBaseDir}}.steam/steam\\\" HOME=\\\"{{$FullBaseDir}}\\\" WINEDEBUG=-all \\\"{{$FullRootDir}}.proton/proton\\\" runinprefix cmd /c exit >/dev/null 2>&1; exit 0; }; fi\"",
        "SkipOnFailure": false
    },
    {
        "UpdateStageName": "Game Download and Install",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "Executable",
        "UpdateSourceData": "/bin/bash",
        "UpdateSourceArgs": "-c \"#!/bin/bash\n# Installs Farming Simulator 19 into the instance directory, for running under Proton.\n# The product key is posted to the GIANTS download portal to get the download link, then the disc image is\n# fetched and its payload unpacked.\n#\n# The game's Inno Setup installer is never executed. It requires administrator rights and would need a Wine\n# prefix to run in; innoextract reads its payload directly instead.\nset -uo pipefail\n\nKEY=\\\"\\\"\nDOWNLOAD_URL=\\\"\\\"\nGAME_DIR=\\\"\\\"\nWORK_DIR=\\\"\\\"\nwhile [[ $# -gt 0 ]]; do\n    case \\\"$1\\\" in\n        --key) KEY=\\\"$2\\\"; shift 2 ;;\n        --url) DOWNLOAD_URL=\\\"$2\\\"; shift 2 ;;\n        --gamedir) GAME_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --workdir) WORK_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        *) echo \\\"ERROR: unknown argument '$1'\\\"; exit 1 ;;\n    esac\ndone\n[[ -n \\\"$GAME_DIR\\\" && -n \\\"$WORK_DIR\\\" ]] || { echo \\\"ERROR: --gamedir and --workdir are required\\\"; exit 1; }\n\nPORTAL=\\\"https://eshop.giants-software.com/downloads.php\\\"\nUA=\\\"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\\\"\nINNOEXTRACT_URL=\\\"https://github.com/dscharrer/innoextract/releases/download/1.9/innoextract-1.9-linux.tar.xz\\\"\nINNOEXTRACT_SHA256=\\\"008efe5011476ccc4aae17c3e22038b5a1bc5c7aad2b9d4d869537bf3874d21f\\\"\n\n# dedicatedServer.exe appears early, so only a marker written at the end proves the install finished.\nINSTALLED_MARKER=\\\"$GAME_DIR/.amp-install-complete\\\"\nif [[ -f \\\"$INSTALLED_MARKER\\\" ]]; then\n    echo \\\"Farming Simulator 19 $(cat \\\"$INSTALLED_MARKER\\\" 2>/dev/null) already installed. Skipping\\\"\n    echo \\\"Delete the game folder inside this instance to force a reinstall\\\"\n    exit 0\nfi\nif [[ -f \\\"$GAME_DIR/dedicatedServer.exe\\\" ]]; then\n    echo \\\"A previous install did not finish; installing over it\\\"\nfi\n\nif [[ -z \\\"$DOWNLOAD_URL\\\" ]]; then\n    if [[ -z \\\"$KEY\\\" ]]; then\n        echo \\\"ERROR: No product key is set, so the download link cannot be looked up.\\\"\n        echo \\\"       Set the Product Key setting on this instance, then update it again.\\\"\n        exit 1\n    fi\n    echo \\\"Looking up the download link from the GIANTS download portal...\\\"\n    PAGE=$(curl -sS -A \\\"$UA\\\" --data-urlencode \\\"activationKey=$KEY\\\" --data-urlencode \\\"foobar=DOWNLOAD\\\" \\\"$PORTAL\\\") || {\n        echo \\\"ERROR: Could not reach the GIANTS download portal.\\\"; exit 1; }\n    mapfile -t LINKS < <(grep -oE 'https://cdn[0-9]*\\.giants-software\\.com/eshop/[^\\\"]+' <<<\\\"$PAGE\\\" | sort -u)\n    if [[ ${#LINKS[@]} -eq 0 ]]; then\n        echo \\\"ERROR: The download portal returned no downloads for that product key.\\\"\n        echo \\\"       Check the Product Key setting, or confirm the key at the portal in a browser.\\\"\n        exit 1\n    fi\n    DOWNLOAD_URL=$(printf '%s\\n' \\\"${LINKS[@]}\\\" | grep -E 'FarmingSimulator2019.*\\.img$' | head -1)\n    if [[ -z \\\"$DOWNLOAD_URL\\\" ]]; then\n        echo \\\"ERROR: No Farming Simulator 19 download was found for that product key.\\\"\n        exit 1\n    fi\n    echo \\\"Found ${DOWNLOAD_URL##*/}\\\"\nfi\n\nmkdir -p \\\"$GAME_DIR\\\" \\\"$WORK_DIR\\\" || exit 1\n\n# ~5GB download, unpacked to ~5GB more, then ~10GB of game files. Fail now rather than after the download.\nFREE_GB=$(( $(df -P -k \\\"$GAME_DIR\\\" | awk 'NR==2{print $4}') / 1024 / 1024 ))\nif [[ $FREE_GB -lt 17 ]]; then\n    echo \\\"ERROR: Only ${FREE_GB}GB is free on the instance drive, and installing Farming Simulator 19 needs about 17GB.\\\"\n    exit 1\nfi\n\nIMG=\\\"$WORK_DIR/fs19_download.img\\\"\nUNPACK=\\\"$WORK_DIR/fs19_unpack\\\"\nSTAGING=\\\"$WORK_DIR/fs19_staging\\\"\nTOOLS=\\\"$WORK_DIR/tools\\\"\nrm -rf \\\"$UNPACK\\\" \\\"$STAGING\\\"; rm -f \\\"$IMG\\\"\n\necho \\\"Downloading Farming Simulator 19. This is a multi-gigabyte download and will take a while\\\"\n# The CDN 403s any request that does not carry the portal as its referer.\n# Content-Length gives the total up front so progress can be reported; without it the download\n# still runs, just with no percentage.\nTOTAL=$(curl -fsSLI -A \\\"$UA\\\" -e \\\"$PORTAL\\\" \\\"$DOWNLOAD_URL\\\" 2>/dev/null \\\n        | tr -d '\\r' | awk 'BEGIN{IGNORECASE=1} /^content-length:/{n=$2} END{print n+0}')\ncurl -fsS -A \\\"$UA\\\" -e \\\"$PORTAL\\\" -o \\\"$IMG\\\" \\\"$DOWNLOAD_URL\\\" &\nCURL_PID=$!\nwhile kill -0 \\\"$CURL_PID\\\" 2>/dev/null; do\n    sleep 10\n    CUR=$(stat -c%s \\\"$IMG\\\" 2>/dev/null || echo 0)\n    (( TOTAL > 0 && CUR > 0 )) && awk -v c=\\\"$CUR\\\" -v t=\\\"$TOTAL\\\" \\\n        'BEGIN { printf \\\"Downloading: %.1f%% (%.2f GB of %.2f GB)\\n\\\", c*100/t, c/1073741824, t/1073741824 }'\ndone\nif ! wait \\\"$CURL_PID\\\"; then\n    echo \\\"ERROR: The download failed.\\\"\n    echo \\\"       Clear the Download Link setting so a fresh link is looked up from your key.\\\"\n    rm -f \\\"$IMG\\\"; exit 1\nfi\necho \\\"Downloaded $(awk -v b=\\\"$(stat -c%s \\\"$IMG\\\")\\\" 'BEGIN{printf \\\"%.2f\\\", b/1073741824}') GB\\\"\n\n# --- ISO9660 extraction, without mounting ---\n# Mounting needs root. Extents are contiguous, so this is a seek and a copy. Only the root dir is read.\nu8()  { echo \\\"${BYTES[$1]}\\\"; }\nu32() { echo $(( ${BYTES[$1]} | ${BYTES[$(($1+1))]}<<8 | ${BYTES[$(($1+2))]}<<16 | ${BYTES[$(($1+3))]}<<24 )); }\n\nextract_iso() {\n    local img=\\\"$1\\\" dest=\\\"$2\\\"\n    # Primary volume descriptor sits at sector 16 and holds the root directory record at offset 156.\n    local magic\n    magic=$(dd if=\\\"$img\\\" bs=1 skip=32769 count=5 2>/dev/null)\n    [[ \\\"$magic\\\" == \\\"CD001\\\" ]] || { echo \\\"ERROR: the download is not an ISO9660 disc image\\\"; return 1; }\n\n    mapfile -t BYTES < <(dd if=\\\"$img\\\" bs=2048 skip=16 count=1 2>/dev/null | od -An -tu1 -v | tr -s ' ' '\\n' | grep -v '^$')\n    local root_lba root_size\n    root_lba=$(u32 158); root_size=$(u32 166)\n\n    mapfile -t BYTES < <(dd if=\\\"$img\\\" bs=2048 skip=\\\"$root_lba\\\" count=$(( (root_size + 2047) / 2048 )) 2>/dev/null | od -An -tu1 -v | tr -s ' ' '\\n' | grep -v '^$')\n\n    mkdir -p \\\"$dest\\\"\n    local p=0 len lba size flags filen name\n    while (( p < root_size )); do\n        len=$(u8 $p)\n        if (( len == 0 )); then p=$(( (p / 2048 + 1) * 2048 )); continue; fi\n        lba=$(u32 $((p+2))); size=$(u32 $((p+10)))\n        flags=$(u8 $((p+25))); filen=$(u8 $((p+32)))\n        local first=${BYTES[$((p+33))]}\n\n        # \\\".\\\" and \\\"..\\\" are a single 0x00/0x01 byte; detect by value, as $() cannot carry a NUL through.\n        if (( filen == 1 && first <= 1 )); then p=$(( p + len )); continue; fi\n\n        name=\\\"\\\"\n        local i c\n        for (( i=0; i<filen; i++ )); do\n            c=${BYTES[$((p+33+i))]}\n            name+=$(printf \\\"\\\\$(printf '%03o' \\\"$c\\\")\\\")\n        done\n        p=$(( p + len ))\n\n        (( flags & 2 )) && continue\n        name=\\\"${name%%;*}\\\"; name=\\\"${name%.}\\\"\n        [[ -n \\\"$name\\\" ]] || continue\n\n        dd if=\\\"$img\\\" bs=2048 skip=\\\"$lba\\\" count=$(( (size + 2047) / 2048 )) 2>/dev/null | head -c \\\"$size\\\" > \\\"$dest/$name\\\"\n    done\n}\n\necho \\\"Reading the disc image\\\"\nextract_iso \\\"$IMG\\\" \\\"$UNPACK\\\" || { rm -f \\\"$IMG\\\"; exit 1; }\n\n# ISO9660 has no hyphen, so \\\"Setup-1.bin\\\" is stored as \\\"SETUP_1.BIN\\\". Link every spelling; links are free.\nshopt -s nullglob nocaseglob\nfor f in \\\"$UNPACK\\\"/*_*.bin; do\n    base=\\\"${f##*/}\\\"\n    for alt in \\\"$(sed -E 's/_([0-9]+)\\.bin$/-\\1.bin/I' <<<\\\"$base\\\")\\\" \\\"${base//_/-}\\\"; do\n        [[ \\\"$alt\\\" == \\\"$base\\\" || -e \\\"$UNPACK/$alt\\\" ]] && continue\n        ln \\\"$f\\\" \\\"$UNPACK/$alt\\\" 2>/dev/null || cp \\\"$f\\\" \\\"$UNPACK/$alt\\\"\n    done\ndone\n\n# Names come out of ISO9660 uppercased, so this lookup stays case-insensitive.\nSETUP=\\\"\\\"\nfor n in Setup.exe FarmingSimulator2019.exe; do\n    for cand in \\\"$UNPACK\\\"/$n; do [[ -f \\\"$cand\\\" ]] && SETUP=\\\"$cand\\\" && break 2; done\ndone\nif [[ -z \\\"$SETUP\\\" ]]; then\n    for cand in \\\"$UNPACK\\\"/*.exe; do [[ -f \\\"$cand\\\" ]] && SETUP=\\\"$cand\\\" && break; done\nfi\nshopt -u nullglob nocaseglob\n[[ -n \\\"$SETUP\\\" ]] || { echo \\\"ERROR: the installer was not found in the disc image\\\"; rm -rf \\\"$UNPACK\\\"; rm -f \\\"$IMG\\\"; exit 1; }\n\n# Release the image before unpacking, so it does not sit beside both the payload and the game files.\nrm -f \\\"$IMG\\\"\n\necho \\\"Fetching innoextract\\\"\nmkdir -p \\\"$TOOLS\\\"\nINNO=$(find \\\"$TOOLS\\\" -type f -name innoextract -perm -u+x 2>/dev/null | head -1)\nif [[ -z \\\"$INNO\\\" ]]; then\n    if ! curl -fsSL -o \\\"$TOOLS/ie.tar.xz\\\" \\\"$INNOEXTRACT_URL\\\"; then\n        echo \\\"ERROR: could not download innoextract\\\"; exit 1\n    fi\n    GOT=$(sha256sum \\\"$TOOLS/ie.tar.xz\\\" | cut -d' ' -f1)\n    if [[ \\\"$GOT\\\" != \\\"$INNOEXTRACT_SHA256\\\" ]]; then\n        echo \\\"ERROR: The innoextract download failed its checksum check.\\\"\n        rm -f \\\"$TOOLS/ie.tar.xz\\\"; exit 1\n    fi\n    tar -xJf \\\"$TOOLS/ie.tar.xz\\\" -C \\\"$TOOLS\\\" && rm -f \\\"$TOOLS/ie.tar.xz\\\"\n    # The archive ships a wrapper plus per-architecture binaries; the wrapper picks the right one.\n    INNO=$(find \\\"$TOOLS\\\" -maxdepth 2 -type f -name innoextract -perm -u+x | head -1)\nfi\n[[ -n \\\"$INNO\\\" ]] || { echo \\\"ERROR: innoextract was not found after downloading it\\\"; exit 1; }\n\necho \\\"Unpacking the game. This takes several minutes\\\"\nif ! \\\"$INNO\\\" --extract --output-dir \\\"$STAGING\\\" --color=off --progress=no \\\"$SETUP\\\"; then\n    echo \\\"ERROR: innoextract failed to unpack the installer\\\"\n    rm -rf \\\"$UNPACK\\\" \\\"$STAGING\\\"; exit 1\nfi\n\n# innoextract lays the payload out under the installer's {app} constant.\nif [[ ! -d \\\"$STAGING/app\\\" ]]; then\n    echo \\\"ERROR: the unpacked payload has no app directory\\\"\n    rm -rf \\\"$UNPACK\\\" \\\"$STAGING\\\"; exit 1\nfi\necho \\\"Moving the game into place\\\"\nshopt -s dotglob\nmv \\\"$STAGING/app\\\"/* \\\"$GAME_DIR\\\"/ || { echo \\\"ERROR: could not move the game into place\\\"; exit 1; }\nshopt -u dotglob\nrm -rf \\\"$UNPACK\\\" \\\"$STAGING\\\"\n\nif [[ ! -f \\\"$GAME_DIR/dedicatedServer.exe\\\" ]]; then\n    echo \\\"ERROR: The game was unpacked but no dedicated server was found in it.\\\"\n    exit 1\nfi\ncat \\\"$GAME_DIR/VERSION\\\" 2>/dev/null > \\\"$INSTALLED_MARKER\\\"\necho \\\"Farming Simulator 19 $(cat \\\"$GAME_DIR/VERSION\\\" 2>/dev/null) installed\\\"\n\" fs-install --key \"{{ActivationKey}}\" --url \"{{DownloadURL}}\" --gamedir \"{{$FullBaseDir}}game\" --workdir \"{{$FullBaseDir}}work\"",
        "SkipOnFailure": false,
        "ProcessToolOutput": true
    }
]
