patch workshop
This commit is contained in:
parent
c8287596b5
commit
126c12b04e
4 changed files with 701 additions and 95 deletions
|
|
@ -1,133 +1,119 @@
|
|||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
MANIFEST="${1:-}"
|
||||
|
||||
if [ -z "$MANIFEST" ] || [ ! -f "$MANIFEST" ]; then
|
||||
echo "ERROR: Workshop manifest missing: $MANIFEST"
|
||||
exit 2
|
||||
echo "ERROR: Workshop manifest missing: $MANIFEST"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
MANIFEST_DIR="$(dirname "$MANIFEST")"
|
||||
LOG="$MANIFEST_DIR/workshop_install.log"
|
||||
touch "$LOG"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
|
||||
}
|
||||
|
||||
json_string() {
|
||||
local key="$1"
|
||||
perl -0777 -ne '
|
||||
my $key = shift @ARGV;
|
||||
if (/"\Q$key\E"\s*:\s*"([^"]*)"/s) {
|
||||
my $v = $1;
|
||||
$v =~ s/\\\//\//g;
|
||||
print $v;
|
||||
}
|
||||
' "$key" "$MANIFEST"
|
||||
json_value() {
|
||||
local key="$1"
|
||||
perl -0777 -ne '
|
||||
my $k = shift @ARGV;
|
||||
if (/"\Q$k\E"\s*:\s*"([^"]*)"/s) {
|
||||
my $v = $1;
|
||||
$v =~ s/\\\//\//g;
|
||||
print $v;
|
||||
exit;
|
||||
}
|
||||
if (/"\Q$k\E"\s*:\s*([0-9]+)/s) {
|
||||
print $1;
|
||||
exit;
|
||||
}
|
||||
' "$key" "$MANIFEST"
|
||||
}
|
||||
|
||||
json_items() {
|
||||
perl -0777 -ne '
|
||||
if (/"items"\s*:\s*\[(.*?)\]/s) {
|
||||
my $x = $1;
|
||||
while ($x =~ /"([0-9]{3,20})"/g) { print "$1\n"; }
|
||||
while ($x =~ /(?<![0-9])([0-9]{3,20})(?![0-9])/g) { print "$1\n"; }
|
||||
}
|
||||
' "$MANIFEST" | awk '!seen[$0]++'
|
||||
perl -0777 -ne '
|
||||
if (/"items"\s*:\s*\[(.*?)\]/s) {
|
||||
my $x = $1;
|
||||
while ($x =~ /"([0-9]{3,20})"/g) { print "$1\n"; }
|
||||
while ($x =~ /(?<![0-9])([0-9]{3,20})(?![0-9])/g) { print "$1\n"; }
|
||||
}
|
||||
' "$MANIFEST" | awk '!seen[$0]++'
|
||||
}
|
||||
|
||||
ACTION="$(json_string action)"
|
||||
APPID="$(json_string workshop_app_id)"
|
||||
SERVER_PATH="$(json_string server_path)"
|
||||
|
||||
ACTION="$(json_value action)"
|
||||
APPID="$(json_value workshop_app_id)"
|
||||
STEAM_APPID="$(json_value steam_app_id)"
|
||||
SERVER_PATH="$(json_value server_path)"
|
||||
[ -z "$ACTION" ] && ACTION="install"
|
||||
[ -z "$APPID" ] && APPID="$STEAM_APPID"
|
||||
[ -z "$SERVER_PATH" ] && SERVER_PATH="$(pwd)"
|
||||
|
||||
if [ -z "$APPID" ]; then
|
||||
log "ERROR: workshop_app_id missing from manifest."
|
||||
exit 3
|
||||
log "ERROR: workshop_app_id missing from manifest."
|
||||
echo "Manifest debug:"
|
||||
sed -n '1,80p' "$MANIFEST"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
ITEMS="$(json_items)"
|
||||
|
||||
if [ -z "$ITEMS" ]; then
|
||||
log "ERROR: no Workshop item IDs found in manifest."
|
||||
exit 4
|
||||
log "ERROR: no Workshop item IDs found in manifest."
|
||||
echo "Manifest debug:"
|
||||
sed -n '1,80p' "$MANIFEST"
|
||||
exit 4
|
||||
fi
|
||||
|
||||
find_steamcmd() {
|
||||
for c in "${STEAMCMD_PATH:-}" steamcmd steamcmd.sh steamcmd.exe \
|
||||
/OGP/steamcmd/steamcmd.exe /OGP/steamcmd/steamcmd.sh \
|
||||
"$SERVER_PATH/steamcmd/steamcmd.exe" "$SERVER_PATH/steamcmd/steamcmd.sh" \
|
||||
/home/gameserver/steamcmd/steamcmd.sh "$HOME/steamcmd/steamcmd.sh" "$HOME/steamcmd/steamcmd.exe"
|
||||
do
|
||||
[ -z "$c" ] && continue
|
||||
if command -v "$c" >/dev/null 2>&1; then command -v "$c"; return 0; fi
|
||||
if [ -f "$c" ]; then echo "$c"; return 0; fi
|
||||
done
|
||||
return 1
|
||||
for c in "${STEAMCMD_PATH:-}" steamcmd steamcmd.sh steamcmd.exe \
|
||||
/OGP/steamcmd/steamcmd.exe /OGP/steamcmd/steamcmd.sh \
|
||||
"$SERVER_PATH/steamcmd/steamcmd.exe" "$SERVER_PATH/steamcmd/steamcmd.sh" \
|
||||
/home/gameserver/steamcmd/steamcmd.sh "$HOME/steamcmd/steamcmd.sh" "$HOME/steamcmd/steamcmd.exe"
|
||||
do
|
||||
[ -z "$c" ] && continue
|
||||
if command -v "$c" >/dev/null 2>&1; then command -v "$c"; return 0; fi
|
||||
if [ -f "$c" ]; then echo "$c"; return 0; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
log "GSP Workshop job starting. action=$ACTION appid=$APPID server_path=$SERVER_PATH"
|
||||
|
||||
if [ "$ACTION" = "remove" ]; then
|
||||
for id in $ITEMS; do
|
||||
log "Removing Workshop item files for $id if present."
|
||||
rm -rf "$SERVER_PATH/@$id" "$SERVER_PATH/workshop/@$id" "$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
done
|
||||
log "Remove job complete."
|
||||
exit 0
|
||||
for id in $ITEMS; do
|
||||
log "Removing Workshop item files for $id if present."
|
||||
rm -rf "$SERVER_PATH/@$id" "$SERVER_PATH/workshop/@$id" "$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
done
|
||||
log "Remove job complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
STEAMCMD="$(find_steamcmd)" || {
|
||||
log "ERROR: steamcmd was not found. Install SteamCMD on the agent host or set STEAMCMD_PATH."
|
||||
exit 127
|
||||
log "ERROR: steamcmd was not found. Install SteamCMD on the agent host or set STEAMCMD_PATH."
|
||||
exit 127
|
||||
}
|
||||
|
||||
RUNSCRIPT="$MANIFEST_DIR/steamcmd_workshop_$$.txt"
|
||||
|
||||
{
|
||||
echo "@ShutdownOnFailedCommand 0"
|
||||
echo "@NoPromptForPassword 1"
|
||||
echo "login anonymous"
|
||||
echo "force_install_dir $SERVER_PATH"
|
||||
for id in $ITEMS; do
|
||||
echo "workshop_download_item $APPID $id validate"
|
||||
done
|
||||
echo "quit"
|
||||
echo "@ShutdownOnFailedCommand 0"
|
||||
echo "@NoPromptForPassword 1"
|
||||
echo "login anonymous"
|
||||
echo "force_install_dir $SERVER_PATH"
|
||||
for id in $ITEMS; do
|
||||
echo "workshop_download_item $APPID $id validate"
|
||||
done
|
||||
echo "quit"
|
||||
} > "$RUNSCRIPT"
|
||||
|
||||
log "Using SteamCMD: $STEAMCMD"
|
||||
log "Running SteamCMD runscript: $RUNSCRIPT"
|
||||
|
||||
"$STEAMCMD" +runscript "$RUNSCRIPT" 2>&1 | tee -a "$LOG"
|
||||
rc=${PIPESTATUS[0]}
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "ERROR: SteamCMD failed with exit $rc"
|
||||
exit "$rc"
|
||||
log "ERROR: SteamCMD failed with exit $rc"
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
for id in $ITEMS; do
|
||||
SRC="$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
DST="$SERVER_PATH/@$id"
|
||||
|
||||
if [ ! -d "$SRC" ]; then
|
||||
log "ERROR: downloaded Workshop source not found: $SRC"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
log "Installing Workshop item $id to $DST"
|
||||
rm -rf "$DST"
|
||||
cp -a "$SRC" "$DST"
|
||||
|
||||
if [ -d "$DST/keys" ]; then
|
||||
mkdir -p "$SERVER_PATH/keys"
|
||||
find "$DST/keys" -type f -iname '*.bikey' -exec cp -f {} "$SERVER_PATH/keys/" \;
|
||||
fi
|
||||
SRC="$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
DST="$SERVER_PATH/@$id"
|
||||
if [ ! -d "$SRC" ]; then
|
||||
log "ERROR: downloaded Workshop source not found: $SRC"
|
||||
exit 5
|
||||
fi
|
||||
log "Installing Workshop item $id to $DST"
|
||||
rm -rf "$DST"
|
||||
cp -a "$SRC" "$DST"
|
||||
if [ -d "$DST/keys" ]; then
|
||||
mkdir -p "$SERVER_PATH/keys"
|
||||
find "$DST/keys" -type f -iname '*.bikey' -exec cp -f {} "$SERVER_PATH/keys/" \;
|
||||
fi
|
||||
done
|
||||
|
||||
log "Workshop job complete."
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
MANIFEST="${1:-}"
|
||||
|
||||
if [ -z "$MANIFEST" ] || [ ! -f "$MANIFEST" ]; then
|
||||
echo "ERROR: Workshop manifest missing: $MANIFEST"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
MANIFEST_DIR="$(dirname "$MANIFEST")"
|
||||
LOG="$MANIFEST_DIR/workshop_install.log"
|
||||
touch "$LOG"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"
|
||||
}
|
||||
|
||||
json_string() {
|
||||
local key="$1"
|
||||
perl -0777 -ne '
|
||||
my $key = shift @ARGV;
|
||||
if (/"\Q$key\E"\s*:\s*"([^"]*)"/s) {
|
||||
my $v = $1;
|
||||
$v =~ s/\\\//\//g;
|
||||
print $v;
|
||||
}
|
||||
' "$key" "$MANIFEST"
|
||||
}
|
||||
|
||||
json_items() {
|
||||
perl -0777 -ne '
|
||||
if (/"items"\s*:\s*\[(.*?)\]/s) {
|
||||
my $x = $1;
|
||||
while ($x =~ /"([0-9]{3,20})"/g) { print "$1\n"; }
|
||||
while ($x =~ /(?<![0-9])([0-9]{3,20})(?![0-9])/g) { print "$1\n"; }
|
||||
}
|
||||
' "$MANIFEST" | awk '!seen[$0]++'
|
||||
}
|
||||
|
||||
ACTION="$(json_string action)"
|
||||
APPID="$(json_string workshop_app_id)"
|
||||
SERVER_PATH="$(json_string server_path)"
|
||||
|
||||
[ -z "$ACTION" ] && ACTION="install"
|
||||
[ -z "$SERVER_PATH" ] && SERVER_PATH="$(pwd)"
|
||||
|
||||
if [ -z "$APPID" ]; then
|
||||
log "ERROR: workshop_app_id missing from manifest."
|
||||
exit 3
|
||||
fi
|
||||
|
||||
ITEMS="$(json_items)"
|
||||
|
||||
if [ -z "$ITEMS" ]; then
|
||||
log "ERROR: no Workshop item IDs found in manifest."
|
||||
exit 4
|
||||
fi
|
||||
|
||||
find_steamcmd() {
|
||||
for c in "${STEAMCMD_PATH:-}" steamcmd steamcmd.sh steamcmd.exe \
|
||||
/OGP/steamcmd/steamcmd.exe /OGP/steamcmd/steamcmd.sh \
|
||||
"$SERVER_PATH/steamcmd/steamcmd.exe" "$SERVER_PATH/steamcmd/steamcmd.sh" \
|
||||
/home/gameserver/steamcmd/steamcmd.sh "$HOME/steamcmd/steamcmd.sh" "$HOME/steamcmd/steamcmd.exe"
|
||||
do
|
||||
[ -z "$c" ] && continue
|
||||
if command -v "$c" >/dev/null 2>&1; then command -v "$c"; return 0; fi
|
||||
if [ -f "$c" ]; then echo "$c"; return 0; fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
log "GSP Workshop job starting. action=$ACTION appid=$APPID server_path=$SERVER_PATH"
|
||||
|
||||
if [ "$ACTION" = "remove" ]; then
|
||||
for id in $ITEMS; do
|
||||
log "Removing Workshop item files for $id if present."
|
||||
rm -rf "$SERVER_PATH/@$id" "$SERVER_PATH/workshop/@$id" "$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
done
|
||||
log "Remove job complete."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
STEAMCMD="$(find_steamcmd)" || {
|
||||
log "ERROR: steamcmd was not found. Install SteamCMD on the agent host or set STEAMCMD_PATH."
|
||||
exit 127
|
||||
}
|
||||
|
||||
RUNSCRIPT="$MANIFEST_DIR/steamcmd_workshop_$$.txt"
|
||||
|
||||
{
|
||||
echo "@ShutdownOnFailedCommand 0"
|
||||
echo "@NoPromptForPassword 1"
|
||||
echo "login anonymous"
|
||||
echo "force_install_dir $SERVER_PATH"
|
||||
for id in $ITEMS; do
|
||||
echo "workshop_download_item $APPID $id validate"
|
||||
done
|
||||
echo "quit"
|
||||
} > "$RUNSCRIPT"
|
||||
|
||||
log "Using SteamCMD: $STEAMCMD"
|
||||
log "Running SteamCMD runscript: $RUNSCRIPT"
|
||||
|
||||
"$STEAMCMD" +runscript "$RUNSCRIPT" 2>&1 | tee -a "$LOG"
|
||||
rc=${PIPESTATUS[0]}
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
log "ERROR: SteamCMD failed with exit $rc"
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
for id in $ITEMS; do
|
||||
SRC="$SERVER_PATH/steamapps/workshop/content/$APPID/$id"
|
||||
DST="$SERVER_PATH/@$id"
|
||||
|
||||
if [ ! -d "$SRC" ]; then
|
||||
log "ERROR: downloaded Workshop source not found: $SRC"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
log "Installing Workshop item $id to $DST"
|
||||
rm -rf "$DST"
|
||||
cp -a "$SRC" "$DST"
|
||||
|
||||
if [ -d "$DST/keys" ]; then
|
||||
mkdir -p "$SERVER_PATH/keys"
|
||||
find "$DST/keys" -type f -iname '*.bikey' -exec cp -f {} "$SERVER_PATH/keys/" \;
|
||||
fi
|
||||
done
|
||||
|
||||
log "Workshop job complete."
|
||||
exit 0
|
||||
|
|
@ -167,6 +167,18 @@ function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml,
|
|||
}
|
||||
|
||||
$manifest_dir = dirname($manifest_path);
|
||||
$workshop_app_id = !empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml);
|
||||
$steam_app_id = !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : scm_extract_workshop_steam_app_id($server_xml);
|
||||
if ($workshop_app_id === '' && $steam_app_id !== '') {
|
||||
$workshop_app_id = $steam_app_id;
|
||||
}
|
||||
if ($steam_app_id === '' && $workshop_app_id !== '') {
|
||||
$steam_app_id = $workshop_app_id;
|
||||
}
|
||||
if ($workshop_app_id === '' || !preg_match('/^[0-9]+$/', $workshop_app_id)) {
|
||||
$error = 'Workshop App ID is missing from the game XML workshop_support block.';
|
||||
return false;
|
||||
}
|
||||
$manifest = array(
|
||||
'manifest_version' => 1,
|
||||
'action' => (string)$action,
|
||||
|
|
@ -174,8 +186,8 @@ function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml,
|
|||
'home_cfg_id' => (int)$home_info['home_cfg_id'],
|
||||
'game_path' => $home_path,
|
||||
'server_path' => $home_path,
|
||||
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
|
||||
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
|
||||
'workshop_app_id' => $workshop_app_id,
|
||||
'steam_app_id' => $steam_app_id,
|
||||
'items' => array_values($item_ids),
|
||||
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
|
||||
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,475 @@
|
|||
<?php
|
||||
/*
|
||||
*
|
||||
* GSP - Workshop Content actions (Phase 1)
|
||||
*
|
||||
*/
|
||||
|
||||
require_once("includes/lib_remote.php");
|
||||
require_once("modules/config_games/server_config_parser.php");
|
||||
require_once(dirname(__FILE__) . '/server_content_helpers.php');
|
||||
|
||||
function scm_workshop_log_action($db, $home_id, $user_id, $message)
|
||||
{
|
||||
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
|
||||
}
|
||||
|
||||
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
|
||||
{
|
||||
if (empty($item_ids)) {
|
||||
return true;
|
||||
}
|
||||
$escaped_ids = array();
|
||||
foreach ($item_ids as $item_id) {
|
||||
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
|
||||
}
|
||||
$set = array(
|
||||
"install_state='" . $db->realEscapeSingle($state) . "'",
|
||||
"updated_at=NOW()",
|
||||
);
|
||||
if ($mark_install) {
|
||||
$set[] = "last_installed_at=NOW()";
|
||||
}
|
||||
if ($mark_update) {
|
||||
$set[] = "last_updated_at=NOW()";
|
||||
}
|
||||
if ($error === null) {
|
||||
$set[] = "last_error=NULL";
|
||||
} else {
|
||||
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
|
||||
}
|
||||
|
||||
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
|
||||
SET ".implode(", ", $set)."
|
||||
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
|
||||
return (bool)$db->query($query);
|
||||
}
|
||||
|
||||
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
|
||||
{
|
||||
if (empty($item_ids)) {
|
||||
return array();
|
||||
}
|
||||
$escaped_ids = array();
|
||||
foreach ($item_ids as $item_id) {
|
||||
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
|
||||
}
|
||||
$rows = $db->resultQuery(
|
||||
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
|
||||
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
|
||||
);
|
||||
$allowed = array();
|
||||
if (is_array($rows)) {
|
||||
foreach ((array)$rows as $row) {
|
||||
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
|
||||
}
|
||||
}
|
||||
return array_values($allowed);
|
||||
}
|
||||
|
||||
function scm_workshop_get_content_template($db, $addon_id)
|
||||
{
|
||||
$addon_id = (int)$addon_id;
|
||||
if ($addon_id <= 0) {
|
||||
return array();
|
||||
}
|
||||
scm_ensure_phase2_schema($db);
|
||||
$rows = $db->resultQuery(
|
||||
"SELECT addon_id, name, content_version, description
|
||||
FROM `" . OGP_DB_PREFIX . "addons`
|
||||
WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
|
||||
LIMIT 1"
|
||||
);
|
||||
return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
|
||||
}
|
||||
|
||||
function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
|
||||
{
|
||||
$install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
|
||||
$copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
|
||||
$xml_install_path = scm_extract_workshop_install_path($server_xml);
|
||||
$keys_target_path = scm_workshop_keys_target_path($server_xml, $home_info);
|
||||
$item_details = array();
|
||||
$resolved_app_id = '';
|
||||
$steam_app_id = '';
|
||||
|
||||
foreach ($item_ids as $item_id) {
|
||||
$payload = array(
|
||||
'workshop_item_id' => (string)$item_id,
|
||||
'install_strategy' => $install_strategy,
|
||||
);
|
||||
$message = '';
|
||||
$runtime = scm_build_workshop_runtime_context($db, $home_info, $server_xml, $payload, $message);
|
||||
if ($runtime === false) {
|
||||
$runtime = array();
|
||||
}
|
||||
$item_app_id = isset($runtime['workshop_app_id']) ? (string)$runtime['workshop_app_id'] : '';
|
||||
if ($resolved_app_id === '' && $item_app_id !== '') {
|
||||
$resolved_app_id = $item_app_id;
|
||||
}
|
||||
if ($steam_app_id === '' && !empty($runtime['steam_app_id'])) {
|
||||
$steam_app_id = (string)$runtime['steam_app_id'];
|
||||
}
|
||||
$item_details[(string)$item_id] = array(
|
||||
'workshop_item_id' => (string)$item_id,
|
||||
'title' => '',
|
||||
'folder_name' => isset($runtime['folder_name']) && $runtime['folder_name'] !== '' ? (string)$runtime['folder_name'] : '@' . $item_id,
|
||||
'target_path_template' => isset($runtime['target_path_template']) ? (string)$runtime['target_path_template'] : scm_get_default_workshop_target_template($install_strategy),
|
||||
'target_path_resolved' => isset($runtime['target_path_resolved']) ? (string)$runtime['target_path_resolved'] : '',
|
||||
'install_strategy' => $install_strategy,
|
||||
'copy_keys' => $copy_keys ? 1 : 0,
|
||||
'keys_target_path' => $keys_target_path,
|
||||
);
|
||||
}
|
||||
|
||||
if ($resolved_app_id === '') {
|
||||
$resolved_app_id = scm_extract_workshop_app_id($server_xml);
|
||||
}
|
||||
if ($steam_app_id === '') {
|
||||
$steam_app_id = scm_extract_workshop_steam_app_id($server_xml);
|
||||
}
|
||||
|
||||
return array(
|
||||
'workshop_app_id' => $resolved_app_id,
|
||||
'steam_app_id' => $steam_app_id,
|
||||
'server_root' => rtrim((string)$home_info['home_path'], '/'),
|
||||
'install_strategy' => $install_strategy,
|
||||
'copy_keys' => $copy_keys ? 1 : 0,
|
||||
'target_path_template' => $xml_install_path !== '' ? $xml_install_path : scm_get_default_workshop_target_template($install_strategy),
|
||||
'keys_target_path' => $keys_target_path,
|
||||
'post_install_script' => scm_workshop_post_install_action($server_xml),
|
||||
'launch_param_additions' => isset($server_xml->workshop_support->startup_param_format) ? trim((string)$server_xml->workshop_support->startup_param_format) : '',
|
||||
'content_template_id' => isset($template['addon_id']) ? (int)$template['addon_id'] : 0,
|
||||
'content_template_name' => isset($template['name']) ? (string)$template['name'] : '',
|
||||
'item_details' => $item_details,
|
||||
);
|
||||
}
|
||||
|
||||
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array(), &$result_details = array())
|
||||
{
|
||||
$error = '';
|
||||
$result_details = array();
|
||||
if (empty($item_ids)) {
|
||||
$error = 'No Workshop IDs were selected for this action.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$manifest_path = scm_get_workshop_manifest_path($home_info);
|
||||
if ($manifest_path === false) {
|
||||
$error = 'Manifest path validation failed for this server home.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
|
||||
if (!scm_path_is_under_home($home_path, $manifest_path)) {
|
||||
$error = 'Manifest path is outside of the server home.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$manifest_dir = dirname($manifest_path);
|
||||
$manifest = array(
|
||||
'manifest_version' => 1,
|
||||
'action' => (string)$action,
|
||||
'home_id' => (int)$home_info['home_id'],
|
||||
'home_cfg_id' => (int)$home_info['home_cfg_id'],
|
||||
'game_path' => $home_path,
|
||||
'server_path' => $home_path,
|
||||
'workshop_app_id' => (!empty($extra_manifest['workshop_app_id']) ? (string)$extra_manifest['workshop_app_id'] : scm_extract_workshop_app_id($server_xml)),
|
||||
'steam_app_id' => !empty($extra_manifest['steam_app_id']) ? (string)$extra_manifest['steam_app_id'] : '',
|
||||
'items' => array_values($item_ids),
|
||||
'item_details' => !empty($extra_manifest['item_details']) && is_array($extra_manifest['item_details']) ? $extra_manifest['item_details'] : array(),
|
||||
'install_strategy' => !empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : '',
|
||||
'target_path' => !empty($extra_manifest['target_path_template']) ? (string)$extra_manifest['target_path_template'] : scm_get_default_workshop_target_template(!empty($extra_manifest['install_strategy']) ? (string)$extra_manifest['install_strategy'] : ''),
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
);
|
||||
if (!empty($extra_manifest)) {
|
||||
$manifest['extra'] = $extra_manifest;
|
||||
}
|
||||
$manifest_json = json_encode($manifest);
|
||||
if ($manifest_json === false) {
|
||||
$error = 'Failed to encode workshop manifest JSON.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$remote = new OGPRemoteLibrary(
|
||||
$home_info['agent_ip'],
|
||||
$home_info['agent_port'],
|
||||
$home_info['encryption_key'],
|
||||
$home_info['timeout']
|
||||
);
|
||||
|
||||
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
|
||||
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
|
||||
$error = 'Failed to write workshop manifest to remote server.';
|
||||
return false;
|
||||
}
|
||||
$script_path = scm_prepare_workshop_script_for_agent($remote, $home_info, $server_xml, $error);
|
||||
if ($script_path === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
|
||||
$output = $remote->exec($command);
|
||||
if (!is_string($output) || $output === '') {
|
||||
$error = 'Workshop script did not return an execution status.';
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
|
||||
$error = 'Workshop script exit marker not found in output.';
|
||||
return false;
|
||||
}
|
||||
$result_details = array(
|
||||
'manifest_path' => $manifest_path,
|
||||
'script_path' => $script_path,
|
||||
'log_path' => clean_path($manifest_dir . (scm_is_windows_home($home_info) ? '/workshop_install_windows.log' : '/workshop_install.log')),
|
||||
'output' => trim(preg_replace('/__GSP_WORKSHOP_EXIT:\d+/', '', $output)),
|
||||
);
|
||||
$exit_code = (int)$matches[1];
|
||||
if ($exit_code !== 0) {
|
||||
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error, $addon_id = 0, array $options = array())
|
||||
{
|
||||
$message = '';
|
||||
$is_error = true;
|
||||
if (!scm_ensure_workshop_schema($db)) {
|
||||
$message = 'Workshop schema migration failed.';
|
||||
return false;
|
||||
}
|
||||
scm_ensure_phase2_schema($db);
|
||||
|
||||
$home_id = (int)$home_info['home_id'];
|
||||
$user_id = (int)$user_id;
|
||||
$addon_id = (int)$addon_id;
|
||||
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
|
||||
if ($server_xml === false) {
|
||||
$message = 'Unable to read server configuration for workshop action.';
|
||||
return false;
|
||||
}
|
||||
if (!scm_workshop_is_supported($server_xml)) {
|
||||
$message = 'This game XML does not enable Steam Workshop support. Add a valid workshop_support block before installing Workshop items.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$template = scm_workshop_get_content_template($db, $addon_id);
|
||||
|
||||
if ($action === 'install_new') {
|
||||
$invalid = array();
|
||||
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
|
||||
if (!empty($invalid)) {
|
||||
$message = 'Invalid Workshop item entries. Use a numeric Workshop ID or Steam Workshop URL: ' . implode(', ', $invalid);
|
||||
return false;
|
||||
}
|
||||
if (empty($item_ids)) {
|
||||
$message = 'Enter at least one Steam Workshop ID or Workshop URL.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
|
||||
$resolved_app_id = isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '';
|
||||
if ($resolved_app_id === '') {
|
||||
$message = 'Workshop App ID is missing from the game XML workshop_support block.';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check whether the content_id column exists (added in db_version 6).
|
||||
$has_content_id_col = (bool)$db->resultQuery(
|
||||
"SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '" . $db->realEscapeSingle(OGP_DB_PREFIX . 'server_content_workshop') . "'
|
||||
AND COLUMN_NAME = 'content_id'"
|
||||
);
|
||||
|
||||
$next_order_rows = $db->resultQuery(
|
||||
"SELECT COALESCE(MAX(load_order), 0) AS max_order FROM `".OGP_DB_PREFIX."server_content_workshop`
|
||||
WHERE home_id=".$home_id
|
||||
);
|
||||
$next_order = (is_array($next_order_rows) && isset($next_order_rows[0]['max_order'])) ? (int)$next_order_rows[0]['max_order'] : 0;
|
||||
foreach ($item_ids as $item_id) {
|
||||
$item_detail = isset($manifest_context['item_details'][(string)$item_id]) ? $manifest_context['item_details'][(string)$item_id] : array();
|
||||
$install_path = isset($item_detail['target_path_resolved']) ? (string)$item_detail['target_path_resolved'] : '';
|
||||
$install_strategy = isset($item_detail['install_strategy']) ? (string)$item_detail['install_strategy'] : (string)$manifest_context['install_strategy'];
|
||||
$next_order++;
|
||||
$content_id_col = $has_content_id_col && $addon_id > 0 ? ", content_id" : '';
|
||||
$content_id_val = $has_content_id_col && $addon_id > 0 ? ", " . $addon_id : '';
|
||||
$content_id_upd = $has_content_id_col && $addon_id > 0 ? ", content_id=VALUES(content_id)" : '';
|
||||
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
|
||||
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_path, install_strategy, enabled, load_order, install_state, created_by, created_at, updated_at" . $content_id_col . ")
|
||||
VALUES (
|
||||
".$home_id.",
|
||||
".(int)$home_info['home_cfg_id'].",
|
||||
".(int)$home_info['remote_server_id'].",
|
||||
'".$db->realEscapeSingle($resolved_app_id)."',
|
||||
'".$db->realEscapeSingle($item_id)."',
|
||||
'".$db->realEscapeSingle($install_path)."',
|
||||
'".$db->realEscapeSingle($install_strategy)."',
|
||||
1,
|
||||
".$next_order.",
|
||||
'queued',
|
||||
".$user_id.",
|
||||
NOW(),
|
||||
NOW()
|
||||
" . $content_id_val . "
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
home_cfg_id=VALUES(home_cfg_id),
|
||||
remote_server_id=VALUES(remote_server_id),
|
||||
workshop_app_id=VALUES(workshop_app_id),
|
||||
install_path=VALUES(install_path),
|
||||
install_strategy=VALUES(install_strategy),
|
||||
enabled=1,
|
||||
install_state='queued',
|
||||
last_error=NULL,
|
||||
updated_at=NOW()" . $content_id_upd;
|
||||
$db->query($query);
|
||||
}
|
||||
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||
$error = '';
|
||||
$details = array();
|
||||
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error, $manifest_context, $details);
|
||||
if ($ok) {
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
|
||||
scm_workshop_record_catalog_items($db, $resolved_app_id, $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
|
||||
$is_error = false;
|
||||
$message = 'Workshop item(s) installed successfully. Manifest: '.scm_h(isset($details['manifest_path']) ? $details['manifest_path'] : '').' Log: '.scm_h(isset($details['log_path']) ? $details['log_path'] : '');
|
||||
return true;
|
||||
}
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
|
||||
$message = $error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'enable_selected' || $action === 'disable_selected' || $action === 'save_update_policy') {
|
||||
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
|
||||
if (empty($item_ids)) {
|
||||
$message = 'Select one or more saved Workshop IDs.';
|
||||
return false;
|
||||
}
|
||||
if ($action === 'save_update_policy') {
|
||||
$policy = isset($options['update_policy']) ? (string)$options['update_policy'] : 'manual';
|
||||
if (!scm_workshop_set_update_policy($db, $home_id, $item_ids, $policy)) {
|
||||
$message = 'Failed to save Workshop update policy.';
|
||||
return false;
|
||||
}
|
||||
$is_error = false;
|
||||
$message = 'Workshop update policy saved for selected item(s).';
|
||||
return true;
|
||||
}
|
||||
$enabled = ($action === 'enable_selected') ? 1 : 0;
|
||||
if (!scm_workshop_set_enabled($db, $home_id, $item_ids, $enabled)) {
|
||||
$message = 'Failed to update Workshop enabled state.';
|
||||
return false;
|
||||
}
|
||||
$is_error = false;
|
||||
$message = $enabled ? 'Selected Workshop item(s) enabled.' : 'Selected Workshop item(s) disabled.';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if ($action === 'remove_selected') {
|
||||
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
|
||||
if (empty($item_ids)) {
|
||||
$message = 'Select one or more saved Workshop IDs to remove.';
|
||||
return false;
|
||||
}
|
||||
$escaped_ids = array();
|
||||
foreach ($item_ids as $item_id) {
|
||||
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
|
||||
}
|
||||
$db->query(
|
||||
"DELETE FROM `" . OGP_DB_PREFIX . "server_content_workshop`
|
||||
WHERE home_id=" . (int)$home_id . "
|
||||
AND workshop_item_id IN (" . implode(",", $escaped_ids) . ")"
|
||||
);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, "remove_selected ids=" . implode(',', $item_ids) . " status=db_removed");
|
||||
$is_error = false;
|
||||
$message = 'Selected Workshop item(s) removed from this server list. Installed files, if any, can be cleaned up separately.';
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($action === 'update_selected' || $action === 'download_selected') {
|
||||
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
|
||||
if (empty($item_ids)) {
|
||||
$message = 'Select one or more saved Workshop IDs.';
|
||||
return false;
|
||||
}
|
||||
$target_action = ($action === 'remove_selected') ? 'remove' : (($action === 'download_selected') ? 'download_only' : 'update');
|
||||
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||
$error = '';
|
||||
$details = array();
|
||||
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error, $manifest_context, $details);
|
||||
if ($ok) {
|
||||
if ($target_action === 'remove') {
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
|
||||
} elseif ($target_action === 'download_only') {
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'downloaded', null, false, true);
|
||||
scm_workshop_set_update_policy($db, $home_id, $item_ids, 'install_on_restart');
|
||||
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
|
||||
} else {
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
|
||||
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
|
||||
}
|
||||
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
|
||||
$is_error = false;
|
||||
if ($target_action === 'remove') {
|
||||
$message = 'Selected Workshop item(s) removed.';
|
||||
} elseif ($target_action === 'download_only') {
|
||||
$message = 'Selected Workshop item(s) downloaded and marked for install on next restart.';
|
||||
} else {
|
||||
$message = 'Selected Workshop item(s) updated successfully.';
|
||||
}
|
||||
$message .= ' Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
|
||||
return true;
|
||||
}
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
|
||||
$message = $error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'update_all') {
|
||||
$rows = $db->resultQuery(
|
||||
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
|
||||
WHERE home_id=".$home_id." AND install_state<>'removed'"
|
||||
);
|
||||
$item_ids = array();
|
||||
if (is_array($rows)) {
|
||||
foreach ((array)$rows as $row) {
|
||||
$item_ids[] = (string)$row['workshop_item_id'];
|
||||
}
|
||||
}
|
||||
$item_ids = scm_parse_selected_workshop_ids($item_ids);
|
||||
if (empty($item_ids)) {
|
||||
$message = 'No Workshop IDs are currently saved for this server.';
|
||||
return false;
|
||||
}
|
||||
$manifest_context = scm_workshop_build_manifest_context($db, $home_info, $server_xml, $item_ids, $template);
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||
$error = '';
|
||||
$details = array();
|
||||
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error, $manifest_context, $details);
|
||||
if ($ok) {
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
|
||||
scm_workshop_record_catalog_items($db, isset($manifest_context['workshop_app_id']) ? (string)$manifest_context['workshop_app_id'] : '', $item_ids, $home_info, isset($manifest_context['item_details']) ? $manifest_context['item_details'] : array(), true);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=success");
|
||||
$is_error = false;
|
||||
$message = 'All saved Workshop item(s) updated successfully. Log: ' . scm_h(isset($details['log_path']) ? $details['log_path'] : '');
|
||||
return true;
|
||||
}
|
||||
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." addon_id=".$addon_id." status=failed error=".$error);
|
||||
$message = $error;
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = 'Invalid workshop action.';
|
||||
return false;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue