[
    {
        "UpdateStageName": "Configure Dedicated Server",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "Executable",
        "UpdateSourceData": "/bin/bash",
        "UpdateSourceArgs": "-c \"#!/bin/bash\n# Prepares the dedicated server before AMP starts it: writes dedicatedServer.xml from AMP settings and\n# keeps the game's profile directory inside the instance.\n#\n# The game hardcodes its profile to \\\"Documents/My Games/FarmingSimulator2019\\\" under the user profile. Running\n# under Proton with HOME pointed at the instance is what makes that per-instance instead of machine-wide.\nset -uo pipefail\n\nGAME_DIR=\\\"\\\"; LOG_DIR=\\\"\\\"; PROFILE_DIR=\\\"\\\"; PROTON=\\\"\\\"; COMPAT_DIR=\\\"\\\"; STEAM_DIR=\\\"\\\"; HOME_DIR=\\\"\\\"\nWEB_PORT=\\\"8080\\\"; TLS_PORT=\\\"8443\\\"; TLS_ON=\\\"false\\\"; ADMIN_USER=\\\"admin\\\"; ADMIN_PASS=\\\"\\\"; GAME_PORT=\\\"\\\"\nwhile [[ $# -gt 0 ]]; do\n    case \\\"$1\\\" in\n        --gamedir) GAME_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --logdir) LOG_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --profiledir) PROFILE_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --compatdata) COMPAT_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --steamdir) STEAM_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --home) HOME_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --proton) PROTON=\\\"$2\\\"; shift 2 ;;\n        --gameport) GAME_PORT=\\\"$2\\\"; shift 2 ;;\n        --webport) WEB_PORT=\\\"$2\\\"; shift 2 ;;\n        --tlsport) TLS_PORT=\\\"$2\\\"; shift 2 ;;\n        --tls) TLS_ON=\\\"$2\\\"; shift 2 ;;\n        --user) ADMIN_USER=\\\"$2\\\"; shift 2 ;;\n        --pass) ADMIN_PASS=\\\"$2\\\"; shift 2 ;;\n        *) echo \\\"ERROR: unknown argument '$1'\\\"; exit 1 ;;\n    esac\ndone\n[[ -n \\\"$GAME_DIR\\\" && -n \\\"$LOG_DIR\\\" ]] || { echo \\\"ERROR: --gamedir and --logdir are required\\\"; exit 1; }\n\n# Wine keeps Documents as a real directory inside the prefix rather than linking it to $HOME, so the prefix\n# is where the game actually writes. It lives under the instance, which is what keeps savegames per-instance;\n# falling back to $HOME would put every instance on the host into one shared profile.\n#\n# App.EnvironmentVariables applies to the game process, not to pre-start stages, so the path is passed in\n# as an argument and only falls back to the environment when the script is run by hand.\n[[ -n \\\"$COMPAT_DIR\\\" ]] || COMPAT_DIR=\\\"${STEAM_COMPAT_DATA_PATH:-}\\\"\nif [[ -z \\\"$COMPAT_DIR\\\" ]]; then\n    echo \\\"ERROR: No Proton prefix path was given (--compatdata).\\\"\n    exit 1\nfi\n# Proton reads these from the environment, so export them for the prefix creation below. HOME is set to the\n# same instance path the game runs with: Proton writes protonfixes state into it, and the container's own\n# /home/amp is discarded when the container is recreated.\nexport STEAM_COMPAT_DATA_PATH=\\\"$COMPAT_DIR\\\"\n[[ -n \\\"$STEAM_DIR\\\" ]] && export STEAM_COMPAT_CLIENT_INSTALL_PATH=\\\"$STEAM_DIR\\\"\n[[ -n \\\"$HOME_DIR\\\" ]] && { mkdir -p \\\"$HOME_DIR\\\" 2>/dev/null; export HOME=\\\"$HOME_DIR\\\"; }\nmkdir -p \\\"$COMPAT_DIR\\\" \\\"${STEAM_DIR:-$COMPAT_DIR}\\\" 2>/dev/null\n\nPFX_USER=\\\"$COMPAT_DIR/pfx/drive_c/users/steamuser\\\"\nif [[ ! -d \\\"$PFX_USER\\\" && -n \\\"$PROTON\\\" && -x \\\"$PROTON\\\" ]]; then\n    echo \\\"Creating the Proton prefix...\\\"\n    \\\"$PROTON\\\" runinprefix cmd /c exit >/dev/null 2>&1\nfi\nDOCUMENTS=\\\"$(readlink -f \\\"$PFX_USER/Documents\\\" 2>/dev/null)\\\"\nif [[ -z \\\"$DOCUMENTS\\\" ]]; then\n    echo \\\"ERROR: The Proton prefix at $COMPAT_DIR could not be created or read.\\\"\n    exit 1\nfi\n\n# The game's profile path inside the prefix is nine directories below the instance, which is deeper than\n# AMP's backup traversal reaches. Keep the real directory shallow and point the prefix at it, so savegames,\n# mods and the server database sit two or three levels down and get backed up.\nPROFILE=\\\"$DOCUMENTS/My Games/FarmingSimulator2019\\\"\nif [[ -n \\\"$PROFILE_DIR\\\" ]]; then\n    mkdir -p \\\"$PROFILE_DIR\\\" || { echo \\\"ERROR: could not create $PROFILE_DIR\\\"; exit 1; }\n    if [[ -L \\\"$PROFILE\\\" ]]; then\n        if [[ \\\"$(readlink -f \\\"$PROFILE\\\")\\\" != \\\"$(readlink -f \\\"$PROFILE_DIR\\\")\\\" ]]; then rm -f \\\"$PROFILE\\\"; fi\n    elif [[ -d \\\"$PROFILE\\\" ]]; then\n        # An earlier start may have written savegames here before the link existed; keep them.\n        cp -a \\\"$PROFILE/.\\\" \\\"$PROFILE_DIR/\\\" 2>/dev/null\n        rm -rf \\\"$PROFILE\\\"\n    fi\n    if [[ ! -e \\\"$PROFILE\\\" ]]; then\n        mkdir -p \\\"$(dirname \\\"$PROFILE\\\")\\\"\n        ln -s \\\"$PROFILE_DIR\\\" \\\"$PROFILE\\\" && echo \\\"Linked the game profile to $PROFILE_DIR\\\"\n    fi\n    PROFILE=\\\"$PROFILE_DIR\\\"\nfi\n\nGAME_LOGS=\\\"$PROFILE/dedicated_server/logs\\\"\nmkdir -p \\\"$GAME_LOGS\\\" || { echo \\\"ERROR: could not create $GAME_LOGS\\\"; exit 1; }\n[[ -f \\\"$GAME_LOGS/server.log\\\" ]] || : > \\\"$GAME_LOGS/server.log\\\"\n\n# AMP tails a path inside the instance, so point that at the profile's log directory.\nif [[ -L \\\"$LOG_DIR\\\" ]]; then\n    [[ \\\"$(readlink -f \\\"$LOG_DIR\\\")\\\" == \\\"$(readlink -f \\\"$GAME_LOGS\\\")\\\" ]] || { rm -f \\\"$LOG_DIR\\\"; }\nelif [[ -d \\\"$LOG_DIR\\\" ]]; then\n    rmdir \\\"$LOG_DIR\\\" 2>/dev/null || rm -rf \\\"$LOG_DIR\\\"\nfi\nif [[ ! -e \\\"$LOG_DIR\\\" ]]; then\n    ln -s \\\"$GAME_LOGS\\\" \\\"$LOG_DIR\\\" && echo \\\"Linked instance log directory to $GAME_LOGS\\\"\nfi\n\nXML=\\\"$GAME_DIR/dedicatedServer.xml\\\"\nif [[ ! -f \\\"$XML\\\" ]]; then\n    echo \\\"ERROR: Could not find $XML\\\"\n    echo \\\"       Update this instance to install the game before starting it.\\\"\n    exit 1\nfi\n\n[[ \\\"$TLS_ON\\\" == \\\"true\\\" ]] || TLS_ON=\\\"false\\\"\n# Anything heading into a sed replacement has to have the delimiter and backreference characters escaped.\nesc() { printf '%s' \\\"$1\\\" | sed -e 's/[|&\\\\]/\\\\&/g'; }\nU=$(esc \\\"$ADMIN_USER\\\"); P=$(esc \\\"$ADMIN_PASS\\\")\n\nsed -E -i \\\n    -e \\\"s|(<webserver[^>]*port=\\\\\\\")[^\\\\\\\"]*\\\\\\\"|\\1${WEB_PORT}\\\\\\\"|\\\" \\\n    -e \\\"s|(<tls[^>]*port=\\\\\\\")[^\\\\\\\"]*\\\\\\\"|\\1${TLS_PORT}\\\\\\\"|\\\" \\\n    -e \\\"s|(<tls[^>]*active=\\\\\\\")[^\\\\\\\"]*\\\\\\\"|\\1${TLS_ON}\\\\\\\"|\\\" \\\n    -e \\\"s|<username>[^<]*</username>|<username>${U}</username>|\\\" \\\n    \\\"$XML\\\" || { echo \\\"ERROR: could not rewrite $XML\\\"; exit 1; }\n\nif [[ -n \\\"$ADMIN_PASS\\\" ]]; then\n    # The server rewrites <password> as <passphrase> on first run, so write the tag it settles on.\n    sed -E -i \\\n        -e \\\"s|<password>[^<]*</password>|<passphrase>${P}</passphrase>|\\\" \\\n        -e \\\"s|<passphrase>[^<]*</passphrase>|<passphrase>${P}</passphrase>|\\\" \\\n        \\\"$XML\\\"\nfi\n\n# The game port lives in the web interface's saved settings, not dedicatedServer.xml.\n# Absent until the web interface saves once; the start form carries the port until then.\nSERVER_CONFIG=\\\"$PROFILE/dedicated_server/dedicatedServerConfig.xml\\\"\nif [[ -n \\\"$GAME_PORT\\\" && -f \\\"$SERVER_CONFIG\\\" ]]; then\n    if sed -E -i \\\"s|<port>[^<]*</port>|<port>${GAME_PORT}</port>|\\\" \\\"$SERVER_CONFIG\\\"; then\n        echo \\\"Set the game server port to ${GAME_PORT}\\\"\n    else\n        echo \\\"WARNING: could not set the game server port in $SERVER_CONFIG\\\"\n    fi\nfi\n\necho \\\"Configured dedicated server on port ${WEB_PORT}\\\"\n\" fs-prepare --gamedir \"{{$FullBaseDir}}game\" --logdir \"{{$FullBaseDir}}logs\" --profiledir \"{{$FullBaseDir}}profile\" --compatdata \"{{$FullRootDir}}.proton/compatdata\" --steamdir \"{{$FullBaseDir}}.steam/steam\" --home \"{{$FullBaseDir}}\" --proton \"{{$FullRootDir}}.proton/proton\" --webport {{$WebInterfacePort}} --gameport {{$GamePort}} --tlsport {{$WebInterfaceTLSPort}} --tls \"{{EnableTLS}}\" --user \"{{AdminUsername}}\" --pass \"{{AdminPassword}}\"",
        "SkipOnFailure": false
    },
    {
        "UpdateStageName": "Activate Farming Simulator 19",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "Executable",
        "UpdateSourceData": "/bin/bash",
        "UpdateSourceArgs": "-c \"#!/bin/bash\n# Activates Farming Simulator 19 by driving the launcher's product key dialog under Proton.\n#\n# The game offers no command line, config file or offline path for the key, so the dialog is the only route.\n# Under Wine the dialog is a real X window, so it is driven with xdotool on a headless X server rather than\n# with SendMessage. Activation is a single file, so this exits immediately once that file exists.\nset -uo pipefail\n\nKEY=\\\"\\\"; GAME_DIR=\\\"\\\"; PROTON=\\\"\\\"; COMPAT_DIR=\\\"\\\"; STEAM_DIR=\\\"\\\"; HOME_DIR=\\\"\\\"; DIALOG_TIMEOUT=90; ACTIVATION_TIMEOUT=120\nwhile [[ $# -gt 0 ]]; do\n    case \\\"$1\\\" in\n        --key) KEY=\\\"$2\\\"; shift 2 ;;\n        --gamedir) GAME_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --compatdata) COMPAT_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --steamdir) STEAM_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --home) HOME_DIR=\\\"${2%/}\\\"; shift 2 ;;\n        --proton) PROTON=\\\"$2\\\"; shift 2 ;;\n        *) echo \\\"ERROR: unknown argument '$1'\\\"; exit 1 ;;\n    esac\ndone\n[[ -n \\\"$GAME_DIR\\\" ]] || { echo \\\"ERROR: --gamedir is required\\\"; exit 1; }\n\n# Activation lives in the prefix, so it must be the prefix the server runs in; $HOME is shared between\n# instances. App.EnvironmentVariables skips pre-start stages, so the path arrives as an argument.\n[[ -n \\\"$COMPAT_DIR\\\" ]] || COMPAT_DIR=\\\"${STEAM_COMPAT_DATA_PATH:-}\\\"\nif [[ -z \\\"$COMPAT_DIR\\\" ]]; then\n    echo \\\"ERROR: No Proton prefix path was given (--compatdata).\\\"\n    exit 1\nfi\n# Proton reads these from the environment when the launcher is run below, and needs a writable HOME.\nexport STEAM_COMPAT_DATA_PATH=\\\"$COMPAT_DIR\\\"\n[[ -n \\\"$STEAM_DIR\\\" ]] && export STEAM_COMPAT_CLIENT_INSTALL_PATH=\\\"$STEAM_DIR\\\"\n[[ -n \\\"$HOME_DIR\\\" ]] && { mkdir -p \\\"$HOME_DIR\\\" 2>/dev/null; export HOME=\\\"$HOME_DIR\\\"; }\n\nPROFILE=\\\"$(readlink -f \\\"$COMPAT_DIR/pfx/drive_c/users/steamuser/Documents\\\" 2>/dev/null)\\\"\nif [[ -z \\\"$PROFILE\\\" ]]; then\n    echo \\\"ERROR: The Proton prefix at $COMPAT_DIR does not exist yet.\\\"\n    exit 1\nfi\nPROFILE=\\\"$PROFILE/My Games/FarmingSimulator2019\\\"\nACTIVATION=\\\"$PROFILE/AHC_63805.dat\\\"\n\nif [[ -f \\\"$ACTIVATION\\\" ]]; then\n    echo \\\"Farming Simulator 19 is already activated for this instance\\\"\n    exit 0\nfi\n\nKEY=\\\"${KEY// /}\\\"\nif [[ -z \\\"$KEY\\\" ]]; then\n    echo \\\"ERROR: No product key is set.\\\"\n    echo \\\"       Set the Product Key setting on this instance, then start it again.\\\"\n    exit 1\nfi\n\nLAUNCHER=\\\"$GAME_DIR/FarmingSimulator2019.exe\\\"\n[[ -f \\\"$LAUNCHER\\\" ]] || { echo \\\"ERROR: Could not find $LAUNCHER\\\"; echo \\\"       Update this instance to install the game before starting it.\\\"; exit 1; }\n[[ -n \\\"$PROTON\\\" && -x \\\"$PROTON\\\" ]] || { echo \\\"ERROR: Proton was not found at '$PROTON'\\\"; exit 1; }\n\nfor tool in xdotool Xvfb; do\n    command -v \\\"$tool\\\" >/dev/null 2>&1 || {\n        echo \\\"ERROR: $tool is not installed, so the product key cannot be entered.\\\"\n        exit 1\n    }\ndone\n\nmkdir -p \\\"$PROFILE\\\"\n\n# A private X server: the dialog needs somewhere to draw, but nothing needs to see it.\n# Containers get their own /tmp, but instances running directly on a host share it, so claim a free\n# display number rather than picking one at random and colliding with another instance.\nDISPLAY_NUM=\\\"\\\"\nfor n in $(seq 90 120); do\n    [[ -e \\\"/tmp/.X${n}-lock\\\" || -e \\\"/tmp/.X11-unix/X${n}\\\" ]] && continue\n    DISPLAY_NUM=\\\"$n\\\"; break\ndone\n[[ -n \\\"$DISPLAY_NUM\\\" ]] || { echo \\\"ERROR: no free X display number could be found.\\\"; exit 1; }\nXvfb \\\":$DISPLAY_NUM\\\" -screen 0 1024x768x24 >/dev/null 2>&1 &\nXVFB_PID=$!\nexport DISPLAY=\\\":$DISPLAY_NUM\\\"\nsleep 2\n\nkill_game() {\n    local pid comm\n    for pid in $(pgrep -f \\\"$1\\\" 2>/dev/null); do\n        [[ \\\"$pid\\\" == \\\"$$\\\" ]] && continue\n        comm=$(cat \\\"/proc/$pid/comm\\\" 2>/dev/null) || continue\n        case \\\"$comm\\\" in bash|sh|dash) continue ;; esac\n        kill \\\"$pid\\\" >/dev/null 2>&1\n    done\n}\ncleanup() {\n    kill_game 'FarmingSimulator2019Game.exe'\n    kill_game 'FarmingSimulator2019.exe'\n    [[ -n \\\"${XVFB_PID:-}\\\" ]] && kill \\\"$XVFB_PID\\\" >/dev/null 2>&1\n    return 0\n}\ntrap cleanup EXIT\n\necho \\\"Activating Farming Simulator 19...\\\"\n\\\"$PROTON\\\" runinprefix \\\"$LAUNCHER\\\" >/dev/null 2>&1 &\n\n# The dialog is titled \\\"Product Key for Farming Simulator 19\\\"; match loosely in case it is localised.\nWID=\\\"\\\"\ndeadline=$(( SECONDS + DIALOG_TIMEOUT ))\nwhile (( SECONDS < deadline )); do\n    sleep 2\n    # Without --onlyvisible this also matches unmapped Wine windows, which cannot be typed into.\n    WID=$(xdotool search --onlyvisible --name 'Product Key|FarmingSimulator2019' 2>/dev/null | head -1)\n    [[ -n \\\"$WID\\\" ]] && break\ndone\n\nif [[ -z \\\"$WID\\\" ]]; then\n    [[ -f \\\"$ACTIVATION\\\" ]] && { echo \\\"Farming Simulator 19 is already activated for this instance\\\"; exit 0; }\n    echo \\\"ERROR: The product key dialog did not appear.\\\"\n    exit 1\nfi\n\n# Snapshot the current windows: \\\"any window that is not the dialog\\\" also matches the launcher's own.\nWINDOWS_BEFORE=\\\" $(xdotool search --onlyvisible --name '.*' 2>/dev/null | tr '\\n' ' ') \\\"\n\n# The key field holds focus and Enter is the default button. windowactivate needs a window manager and\n# a bare Xvfb has none, so focus is set directly; keystrokes go via XTEST because Wine ignores XSendEvent.\nxdotool windowraise \\\"$WID\\\" 2>/dev/null\nxdotool windowfocus \\\"$WID\\\" 2>/dev/null\nsleep 1\nxdotool type --clearmodifiers --delay 40 \\\"$KEY\\\"\nsleep 1\nxdotool key --clearmodifiers Return\n\n# A window the launcher did not already have open is its reply, and proof the key reached the field.\nRESULT_WID=\\\"\\\"\ndeadline=$(( SECONDS + ACTIVATION_TIMEOUT ))\nwhile (( SECONDS < deadline )); do\n    sleep 3\n    [[ -f \\\"$ACTIVATION\\\" ]] && break\n    if [[ -z \\\"$RESULT_WID\\\" ]]; then\n        for w in $(xdotool search --onlyvisible --name '.*' 2>/dev/null); do\n            [[ \\\"$WINDOWS_BEFORE\\\" == *\\\" $w \\\"* ]] && continue\n            name=$(xdotool getwindowname \\\"$w\\\" 2>/dev/null)\n            [[ -n \\\"$name\\\" ]] || continue\n            RESULT_WID=\\\"$w\\\"\n            break\n        done\n    fi\ndone\n\nif [[ -f \\\"$ACTIVATION\\\" ]]; then\n    echo \\\"Activation successful\\\"\n    exit 0\nfi\n\nif [[ -n \\\"$RESULT_WID\\\" ]]; then\n    # A reply window means the server answered, so this is a licensing refusal, not a UI failure.\n    echo \\\"ERROR: Activation failed. The key is invalid, or its activation limit has been reached.\\\"\n    echo \\\"       Every instance activates separately. Contact GIANTS support about the key.\\\"\nelse\n    echo \\\"ERROR: The product key could not be entered, so activation was not attempted.\\\"\nfi\nexit 1\n\" fs-activate --key \"{{ActivationKey}}\" --gamedir \"{{$FullBaseDir}}game\" --compatdata \"{{$FullRootDir}}.proton/compatdata\" --steamdir \"{{$FullBaseDir}}.steam/steam\" --home \"{{$FullBaseDir}}\" --proton \"{{$FullRootDir}}.proton/proton\"",
        "SkipOnFailure": false
    },
    {
        "UpdateStageName": "Start Game Server",
        "UpdateSourcePlatform": "Linux",
        "UpdateSource": "Executable",
        "UpdateSourceData": "/bin/bash",
        "UpdateSourceArgs": "-c \"#!/bin/bash\n# Starts the game server by submitting the dedicated server web interface's start form.\n# The server only spawns the game when that form is posted - there is no autostart setting.\n# Run from PreStartStages with RunInBackground so it outlives the stage while waiting.\n# Any setting left blank keeps whatever the web interface already has saved.\nset -uo pipefail\n\nHOST=\\\"\\\"; PORT=\\\"\\\"; USERNAME=\\\"admin\\\"; PASSWORD=\\\"\\\"; GAME_PORT=\\\"\\\"; SAVEGAME=\\\"\\\"; TIMEOUT=300\nSERVER_NAME=\\\"\\\"; SERVER_PASS=\\\"\\\"; GAME_ADMIN_PASS=\\\"\\\"; MAX_PLAYERS=\\\"\\\"; LANGUAGE=\\\"\\\"\nAUTOSAVE=\\\"\\\"; STATS=\\\"\\\"; PAUSE_EMPTY=\\\"\\\"; CROSSPLAY=\\\"\\\"\nwhile [[ $# -gt 0 ]]; do\n    case \\\"$1\\\" in\n        --host) HOST=\\\"$2\\\"; shift 2 ;;\n        --port) PORT=\\\"$2\\\"; shift 2 ;;\n        --user) USERNAME=\\\"$2\\\"; shift 2 ;;\n        --pass) PASSWORD=\\\"$2\\\"; shift 2 ;;\n        --gameport) GAME_PORT=\\\"$2\\\"; shift 2 ;;\n        --savegame) SAVEGAME=\\\"$2\\\"; shift 2 ;;\n        --timeout) TIMEOUT=\\\"$2\\\"; shift 2 ;;\n        --name) SERVER_NAME=\\\"$2\\\"; shift 2 ;;\n        --password) SERVER_PASS=\\\"$2\\\"; shift 2 ;;\n        --adminpass) GAME_ADMIN_PASS=\\\"$2\\\"; shift 2 ;;\n        --maxplayers) MAX_PLAYERS=\\\"$2\\\"; shift 2 ;;\n        --language) LANGUAGE=\\\"$2\\\"; shift 2 ;;\n        --autosave) AUTOSAVE=\\\"$2\\\"; shift 2 ;;\n        --statsinterval) STATS=\\\"$2\\\"; shift 2 ;;\n        --pauseempty) PAUSE_EMPTY=\\\"$2\\\"; shift 2 ;;\n        --crossplay) CROSSPLAY=\\\"$2\\\"; shift 2 ;;\n        *) echo \\\"ERROR: unknown argument '$1'\\\"; exit 1 ;;\n    esac\ndone\n[[ -n \\\"$PORT\\\" ]] || { echo \\\"ERROR: --port is required\\\"; exit 1; }\n\nJAR=\\\"$(mktemp)\\\"\ntrap 'rm -f \\\"$JAR\\\"' EXIT\nUNCHECKED=$'\\002'\n\n# Reads the form into \\\"name<TAB>value\\\" lines. Fields differ between titles, so nothing is hardcoded.\n# An unchecked box is reported as \\002 so a setting can still switch it on.\nparse_form() {\n    sed -e 's/></>\\n</g' \\\n    | awk '\n        function attr(s, a,   r) {\n            if (match(s, a \\\"[[:space:]]*=[[:space:]]*\\\\\\\"\\\")) {\n                r = substr(s, RSTART + RLENGTH)\n                return substr(r, 1, index(r, \\\"\\\\\\\"\\\") - 1)\n            }\n            return \\\"\\001\\\"\n        }\n        /^<input/ {\n            t = attr($0, \\\"type\\\"); n = attr($0, \\\"name\\\")\n            if (n == \\\"\\001\\\" || t == \\\"submit\\\" || t == \\\"button\\\" || t == \\\"reset\\\") next\n            v = attr($0, \\\"value\\\"); if (v == \\\"\\001\\\") v = \\\"\\\"\n            if (t == \\\"checkbox\\\") { print n \\\"\\t\\\" ($0 ~ /checked/ ? (v == \\\"\\\" ? \\\"on\\\" : v) : \\\"\\002\\\"); next }\n            print n \\\"\\t\\\" v\n            next\n        }\n        /^<select/ { sel = attr($0, \\\"name\\\"); chosen = \\\"\\001\\\"; first = \\\"\\001\\\"; next }\n        /^<option/ && sel != \\\"\\\" {\n            v = attr($0, \\\"value\\\")\n            if (v == \\\"\\001\\\") { v = $0; sub(/^<option[^>]*>/, \\\"\\\", v) }\n            if (first == \\\"\\001\\\") first = v\n            if ($0 ~ /selected/ && chosen == \\\"\\001\\\") chosen = v\n            next\n        }\n        /^<\\/select>/ && sel != \\\"\\\" {\n            print sel \\\"\\t\\\" (chosen != \\\"\\001\\\" ? chosen : (first != \\\"\\001\\\" ? first : \\\"\\\"))\n            sel = \\\"\\\"; next\n        }\n    '\n}\n\n# The server binds one interface address, not the wildcard, so the loopback may not answer.\ncandidate_hosts() {\n    if [[ -n \\\"$HOST\\\" ]]; then printf '%s\\n' \\\"$HOST\\\"; return; fi\n    printf '127.0.0.1\\n'\n    hostname -I 2>/dev/null | tr ' \\t' '\\n\\n'\n    ip -4 -o addr show scope global 2>/dev/null | awk '{ print $4 }' | cut -d/ -f1\n}\nmapfile -t CANDIDATES < <(candidate_hosts | grep -E '^[0-9]+(\\.[0-9]+){3}$' | awk '!seen[$0]++')\n[[ ${#CANDIDATES[@]} -gt 0 ]] || { echo \\\"ERROR: no local address could be found to reach the web interface on.\\\"; exit 1; }\n\necho \\\"Waiting for the dedicated server web interface on port ${PORT}...\\\"\nFOUND=\\\"\\\"\ndeadline=$(( SECONDS + TIMEOUT ))\nwhile (( SECONDS < deadline )); do\n    for h in \\\"${CANDIDATES[@]}\\\"; do\n        if curl -fsS -m 5 -o /dev/null \\\"http://${h}:${PORT}/index.html?lang=en\\\" 2>/dev/null; then FOUND=\\\"$h\\\"; break 2; fi\n    done\n    sleep 3\ndone\nif [[ -z \\\"$FOUND\\\" ]]; then\n    echo \\\"ERROR: the web interface did not come up within ${TIMEOUT}s, so the game server was not started.\\\"\n    echo \\\"       Tried: ${CANDIDATES[*]}\\\"\n    exit 1\nfi\nBASE=\\\"http://${FOUND}:${PORT}/index.html?lang=en\\\"\necho \\\"Web interface answered on ${FOUND}:${PORT}\\\"\n\nif ! curl -fsS -m 15 -c \\\"$JAR\\\" -o /dev/null -X POST \\\"$BASE\\\" \\\n        --data-urlencode \\\"username=$USERNAME\\\" \\\n        --data-urlencode \\\"password=$PASSWORD\\\" \\\n        --data-urlencode \\\"login=Login\\\"; then\n    echo \\\"ERROR: could not reach the web interface to log in.\\\"\n    exit 1\nfi\n\nPAGE=\\\"$(curl -fsS -m 15 -b \\\"$JAR\\\" \\\"$BASE\\\" 2>/dev/null)\\\"\nif [[ -z \\\"$PAGE\\\" ]]; then\n    echo \\\"ERROR: the web interface returned nothing after logging in.\\\"\n    exit 1\nfi\n# No form means the login was rejected.\nif ! grep -q 'name=\\\"start_server\\\"\\|name=\\\"stop_server\\\"' <<<\\\"$PAGE\\\"; then\n    echo \\\"ERROR: the web interface rejected the admin username and password, so the game server was not started.\\\"\n    echo \\\"       These come from the Admin Username and Admin Password settings on this instance.\\\"\n    exit 1\nfi\nif grep -q 'name=\\\"stop_server\\\"' <<<\\\"$PAGE\\\"; then\n    echo \\\"The game server is already running\\\"\n    exit 0\nfi\n\n# Resend the form as served, overriding only the fields that have an AMP setting filled in.\nARGS=()\nAPPLIED=()\nwhile IFS=$'\\t' read -r name value; do\n    [[ -n \\\"$name\\\" ]] || continue\n    case \\\"$name\\\" in\n        server_port)         [[ -n \\\"$GAME_PORT\\\" ]] && value=\\\"$GAME_PORT\\\" ;;\n        savegame)            [[ -n \\\"$SAVEGAME\\\" ]] && value=\\\"$SAVEGAME\\\" ;;\n        game_name)           [[ -n \\\"$SERVER_NAME\\\" ]] && { value=\\\"$SERVER_NAME\\\"; APPLIED+=(name); } ;;\n        game_password)       [[ -n \\\"$SERVER_PASS\\\" ]] && { value=\\\"$SERVER_PASS\\\"; APPLIED+=(password); } ;;\n        admin_password)      [[ -n \\\"$GAME_ADMIN_PASS\\\" ]] && { value=\\\"$GAME_ADMIN_PASS\\\"; APPLIED+=(adminpass); } ;;\n        max_player)          [[ -n \\\"$MAX_PLAYERS\\\" ]] && { value=\\\"$MAX_PLAYERS\\\"; APPLIED+=(maxplayers); } ;;\n        mp_language)         [[ -n \\\"$LANGUAGE\\\" ]] && { value=\\\"$LANGUAGE\\\"; APPLIED+=(language); } ;;\n        auto_save_interval)  [[ -n \\\"$AUTOSAVE\\\" ]] && { value=\\\"$AUTOSAVE\\\"; APPLIED+=(autosave); } ;;\n        stats_interval)      [[ -n \\\"$STATS\\\" ]] && { value=\\\"$STATS\\\"; APPLIED+=(stats); } ;;\n        pause_game_if_empty) [[ -n \\\"$PAUSE_EMPTY\\\" ]] && { value=\\\"$PAUSE_EMPTY\\\"; APPLIED+=(pause); } ;;\n        crossplay_allowed)\n            case \\\"$CROSSPLAY\\\" in\n                true)  value=\\\"on\\\"; APPLIED+=(crossplay) ;;\n                false) APPLIED+=(crossplay); continue ;;\n            esac ;;\n    esac\n    # An unchecked box submits nothing unless a setting switched it on.\n    [[ \\\"$value\\\" == \\\"$UNCHECKED\\\" ]] && continue\n    ARGS+=(--data-urlencode \\\"$name=$value\\\")\ndone < <(parse_form <<<\\\"$PAGE\\\")\n\nif [[ ${#ARGS[@]} -eq 0 ]]; then\n    echo \\\"ERROR: no settings could be read from the start form, so the game server was not started.\\\"\n    exit 1\nfi\nARGS+=(--data-urlencode \\\"start_server=Start\\\")\n[[ ${#APPLIED[@]} -gt 0 ]] && echo \\\"Applied AMP settings: ${APPLIED[*]}\\\"\n\necho \\\"Starting the game server\\\"\nif ! curl -fsS -m 60 -b \\\"$JAR\\\" -c \\\"$JAR\\\" -o /dev/null -X POST \\\"$BASE\\\" \\\"${ARGS[@]}\\\"; then\n    echo \\\"ERROR: the start request failed.\\\"\n    exit 1\nfi\n\n# The Stop button replacing Start means the game server is up.\ndeadline=$(( SECONDS + 120 ))\nwhile (( SECONDS < deadline )); do\n    sleep 3\n    if curl -fsS -m 15 -b \\\"$JAR\\\" \\\"$BASE\\\" 2>/dev/null | grep -q 'name=\\\"stop_server\\\"'; then\n        echo \\\"Game server started\\\"\n        exit 0\n    fi\ndone\necho \\\"ERROR: the game server did not report as started. Check the web interface on port ${PORT}.\\\"\nexit 1\n\" fs-autostart --port {{$WebInterfacePort}} --user \"{{AdminUsername}}\" --pass \"{{AdminPassword}}\" --gameport {{$GamePort}} --savegame \"{{AutoStartSavegame}}\" --name \"{{ServerName}}\" --password \"{{ServerPassword}}\" --adminpass \"{{GameAdminPassword}}\" --maxplayers \"{{MaxPlayers}}\" --language \"{{ServerLanguage}}\" --autosave \"{{AutoSaveInterval}}\" --statsinterval \"{{StatsInterval}}\" --pauseempty \"{{PauseWhenEmpty}}\"",
        "UpdateSourceConditionSetting": "AutoStartGameServer",
        "UpdateSourceConditionValue": "true",
        "RunInBackground": true,
        "SkipOnFailure": true
    }
]
