Skip to content
Snippets Groups Projects
_v2-entry-point 11.2 KiB
Newer Older
#!/usr/bin/env bash

set -eu
me="$(readlink -f "$0")"
here="${me%/*}"
me="${me##*/}"

# This is a prototype and will probably not survive in its current form.
# Don't rely on it.

deploy=
is_main=yes
suite=scout
verbose=
log_to_file=
log_dir=${STEAM_LINUX_RUNTIME_LOG_DIR-"${here}/var"}
keep_logs=
use_timestamp=
if [ "${STEAM_LINUX_RUNTIME_VERBOSE-}" = 1 ]; then
    # Propagate it to pressure-vessel too
    export PRESSURE_VESSEL_VERBOSE=1
if [ "${STEAM_LINUX_RUNTIME_LOG-}" = 1 ]; then
    log_to_file=yes
    use_timestamp=yes
fi

if [ "${STEAM_LINUX_RUNTIME_KEEP_LOGS-}" = 1 ]; then
    keep_logs=yes
fi

    if [ -n "$use_timestamp" ]; then
        timestamp=$(LD_PRELOAD= date +'%H:%M:%S.%6N')
        printf '%s\n' "${timestamp}: ${me}[$$]: $*" >&2
    else
        printf '%s\n' "${me}[$$]: $*" >&2
    fi
info () {
    if [ -n "${log_to_file}${verbose}" ]; then
        log "$@"
    fi
}

verbose () {
    if [ -n "$verbose" ]; then
        log "$@"
    fi
}

usage () {
    local code="$1"
    shift

    if [ "$code" -ne 0 ]; then
        exec >&2
    fi

    echo "Usage:"
    echo "$me [OPTIONS] COMMAND [ARGS...]"
    echo
    echo "Run a command in a container that is shared with other"
    echo "invocations of the same Steam game."
    echo
    echo "Required arguments"
    echo "COMMAND [ARGS...] Run this."
    echo
    echo "Options"
    echo "--deploy=DIR      Deploy to this directory [default=SUITE]."
    echo "--keep-logs       Do not remove the older logs of this same app id."
    echo "--log-to-file     Log to a file instead of the default stderr."
    echo "--suite=SUITE     Run in this runtime [default=scout]."
    echo "--use-timestamp   Prepend the timestamp to the log entries [default with --log-to-file]."
    echo "--verb=%verb%     Mode to operate in [default=waitforexitandrun]."
    echo "--verbose         Be more verbose."

    exit "${code}"
}

getopt_temp="help"
getopt_temp="${getopt_temp},deploy:"
getopt_temp="${getopt_temp},log-to-file:"
getopt_temp="${getopt_temp},keep-logs:"
getopt_temp="${getopt_temp},suite:"
getopt_temp="${getopt_temp},use-timestamp:"
getopt_temp="${getopt_temp},verb:"
getopt_temp="${getopt_temp},verbose"

getopt_temp="$(getopt -o '' --long "$getopt_temp" -n "$me" -- "$@")"
eval "set -- $getopt_temp"
unset getopt_temp

while [ "$#" -gt 0 ]; do
    case "$1" in
        (--help)
            usage 0
            # not reached
            ;;

        (--deploy)
            deploy="$2"
            shift 2
            ;;

        (--log-to-file)
            log_to_file=yes
            use_timestamp=yes
            shift
            ;;

        (--keep-logs)
            keep_logs=yes
            shift
            ;;

        (--suite)
            suite="$2"
            shift 2
            ;;

        (--verb)
            case "$2" in
                (waitforexitandrun)
                    is_main=yes
                    ;;
                (*)
                    is_main=
                    ;;
            esac
            shift 2
            ;;

        (--use-timestamp)
            use_timestamp=yes
            shift
            ;;

            # Propagate it to pressure-vessel too
            export PRESSURE_VESSEL_VERBOSE=1
            shift
            ;;

        (--)
            shift
            break
            ;;

        (-*)
            log "Unknown option: $1"
            usage 125   # EX_USAGE from sysexits.h
            # not reached
            ;;

        (*)
            break
            ;;
    esac
done

if [ -z "$deploy" ]; then
    deploy="$suite"
fi

if [ -n "$log_to_file" ]; then
    app=
    log_filename=
    if [[ -z "${STEAM_COMPAT_APP_ID-}" && -z "${SteamAppId-}" ]]; then
        app="non-steam-game"
    else
        app="app${STEAM_COMPAT_APP_ID-${SteamAppId}}"
    fi

    if [ -z "${STEAM_COMPAT_SESSION_ID-}" ]; then
        log_filename="slr-${app}-t$(date +'%Y%m%dT%H%M%S').log"
    else
        log_filename="slr-${app}-s${STEAM_COMPAT_SESSION_ID}.log"
    fi

    mkdir -p "${log_dir}"

    # Remove older logs of the same app
    if [ -z "${keep_logs}" ]; then
        find "${log_dir}" -maxdepth 1 -name "slr-${app}-*.log" '!' -name "${log_filename}" -type f -delete
    fi

    ln -fns "${log_filename}" "${log_dir}/slr-latest.log" || :

    exec >> "${log_dir}/${log_filename}" 2>&1

    export PRESSURE_VESSEL_LOG_WITH_TIMESTAMP=1
fi

info "argv: $(printf '%q ' "$@")"
if [ "$#" -eq 0 ] || [ "x$1" = x-- ]; then
    log "Error: A command to run is required"
    usage 125
fi

# Steam App ID of this app or game
verbose "STEAM_COMPAT_APP_ID=${STEAM_COMPAT_APP_ID-}"

# Session ID used for coordination between processes involving the same game.
# If unset/empty, only execute a single command.
verbose "STEAM_COMPAT_SESSION_ID=${STEAM_COMPAT_SESSION_ID-}"
verbose "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR-}"
if [ -z "${STEAM_COMPAT_SESSION_ID-}" ]; then
    info "Non-session mode, behaving like the main program"
elif [ -n "$is_main" ]; then
    info "Main program in session mode"
    # Use the relaunch mode by default
    case "${PRESSURE_VESSEL_RELAUNCH_CONTAINER-1}" in
        (1)
            relaunch=yes
            ;;
        (0)
            ;;
        (*)
            echo "\$PRESSURE_VESSEL_RELAUNCH_CONTAINER should be either 0 or 1" >&2
            ;;
    esac
else
    verbose "Setup program in session mode"
# At this point $LD_LIBRARY_PATH might
# 1. be equal to $STEAM_RUNTIME_LIBRARY_PATH (e.g. a game without
#    any special launch options).
# 2. contain all the entries of $STEAM_RUNTIME_LIBRARY_PATH, plus eventually
#    any additional paths from the game launch options (e.g. a game launched
#    with "LD_LIBRARY_PATH=/my_game_path:${LD_LIBRARY_PATH} %command%")
# 3. contain just new entries from the game launch options (e.g. a game
#    launched with "LD_LIBRARY_PATH=/my_game_path %command%")
# 4. being unset or empty (e.g. a game launched with
#    "LD_LIBRARY_PATH="" %command%")
#
# We extract all the entries from $LD_LIBRARY_PATH that are not under the
# $STEAM_RUNTIME paths. In this way we should end up with a list of paths
# that are from the system $LD_LIBRARY_PATH, the system "ldconfig" and the
# manually set $LD_LIBRARY_PATH paths from the game launch options.
case "${STEAM_RUNTIME-}" in
    (/*)
        oldIFS="$IFS"
        IFS=:
        paths=

        for path in ${LD_LIBRARY_PATH-}; do
            if [ "${path}" == "${STEAM_RUNTIME}" ]; then
                # path is exactly the ${STEAM_RUNTIME}; ignore
                continue
            elif [ "${path#${STEAM_RUNTIME}/}" != "${path}" ]; then
                # path is ${STEAM_RUNTIME}/...; ignore
                continue
            else
                # keep it (note that we discard the extra leading ":" later)
                paths="${paths}:${path}"
            fi
        done

        IFS="$oldIFS"
        export PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="${paths#:}"
        ;;
    (*)
        # use LD_LIBRARY_PATH as-is
        export PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}"
        ;;
esac

unset LD_LIBRARY_PATH
unset STEAM_RUNTIME

pressure_vessel="${PRESSURE_VESSEL_PREFIX:-"${here}/pressure-vessel"}"

# Arguments for pressure-vessel-wrap, currently none
    # Run as a single command
    exec "$here/run-in-steamrt" \
        --arch=amd64,i386 \
        --deploy \
        --runtime=com.valvesoftware.SteamRuntime.Platform \
        --suite="${suite}" \
        "${deploy}" \
        -- \
        ${container_args[0]+"${container_args[@]}"} \
}

if [ -z "${STEAM_COMPAT_SESSION_ID-}" ]; then
    verbose "Non-session mode, just running the container"
    exec_container "$@"
# Session mode. Either PRESSURE_VESSEL_SOCKET_DIR or XDG_RUNTIME_DIR are required.
if [[ -v PRESSURE_VESSEL_SOCKET_DIR ]]; then
    if ! [ -O "$PRESSURE_VESSEL_SOCKET_DIR" ]; then
        log "PRESSURE_VESSEL_SOCKET_DIR is owned by someone else"
        exit 125
    fi
    rendezvous="${PRESSURE_VESSEL_SOCKET_DIR}/SteamLinuxRuntime.${STEAM_COMPAT_SESSION_ID:?}"
else
    if ! [ -O "$XDG_RUNTIME_DIR" ]; then
        log "XDG_RUNTIME_DIR is owned by someone else"
        exit 125
    fi
    rendezvous="${XDG_RUNTIME_DIR}/SteamLinuxRuntime.${STEAM_COMPAT_SESSION_ID:?}"
if mkdir -m700 "$rendezvous" 2>/dev/null; then
    :
elif [ -d "$rendezvous" ] && [ -O "$rendezvous" ]; then
    :
else
    log "Unable to create or adopt $rendezvous"
    exit 125
fi

terminate_container () {
    if ! [ -S "$rendezvous/socket" ]; then
        # nothing to do
        return
    fi

    "$pressure_vessel/bin/pressure-vessel-adverb" \
        --create \
        --wait \
        --write \
        --lock-file "${rendezvous}/.ref" \
        -- \
    "$pressure_vessel/bin/pressure-vessel-launch" \
        --socket="$rendezvous/socket" \
        --terminate
}

    info "Terminating and re-launching container for main game"
    terminate_container || :
    exec_container "$@"
    # not reached
fi

if [ -n "$is_main" ]; then
# Launch the pressure-vessel-launcher(1). This is used to run the
# setup commands (--verb=run), if any. If not requested via
# PRESSURE_VESSEL_RELAUNCH_CONTAINER=1, it is also used to run the
# actual game.
#
# We have to launch pressure-vessel-launcher(1) *without* setting
# SteamAppId, SteamAppUser, etc., otherwise Steam will think the game
# is already running and refuse to run it again. (Tested with SteamAppId,
# just guessing about the others)
"$pressure_vessel/bin/pressure-vessel-adverb" \
    --create \
    --wait \
    --write \
    --lock-file "${rendezvous}/.ref" \
    -- \
env \
    -u LD_PRELOAD \
    -u FONTCONFIG_PATH \
    -u SteamLauncherUI \
    -u SteamAppId \
    -u SteamAppUser \
    -u SteamClientLaunch \
    -u SteamEnv \
    -u SteamGameId \
    -u SteamOverlayGameId \
"${here}/_start-container-in-background" \
    --session="$rendezvous" \
    --suite="$suite" \
    -- \
    ${container_args[0]+"${container_args[@]}"} \
    if [ "${SteamAppId-}" != "${STEAM_COMPAT_APP_ID-}" ]; then
        log "Mismatch: SteamAppId=${SteamAppId-}, STEAM_COMPAT_APP_ID=${STEAM_COMPAT_APP_ID-}"
        exit 125
    fi

    launch_args=( \
        ${launch_args[0]+"${launch_args[@]}"} \
        --pass-env-matching '*' \
        --terminate \
    )

    # For the main game we wait for all processes to terminate before
    # shutting down the container.
    set -- \
        "$pressure_vessel/bin/pressure-vessel-adverb" \
        ${PRESSURE_VESSEL_SHELL+--shell="$PRESSURE_VESSEL_SHELL"} \
        ${PRESSURE_VESSEL_TERMINAL+--terminal="$PRESSURE_VESSEL_TERMINAL"} \
        ${launch_args[0]+"${launch_args[@]}"} \
        --unset-env LD_PRELOAD \
    )
fi

exec "$pressure_vessel/bin/pressure-vessel-launch" \
    --directory="$(pwd)" \
    --socket="$rendezvous/socket" \
    ${launch_args[0]+"${launch_args[@]}"} \
    -- \
    "$@"
exit 125
# not reached

# vim:set sw=4 sts=4 et: