From b585aec2609018c45fd3c546120c5d4dcd86fa40 Mon Sep 17 00:00:00 2001 From: Frank Harris Date: Fri, 12 Jun 2026 10:14:53 -0500 Subject: [PATCH] workshop fix 37 --- Panel/includes/lib_remote.php | 33 ++++++-- Panel/lang/English/modules/steam_workshop.php | 5 ++ Panel/modules/gamemanager/server_monitor.php | 8 ++ Panel/modules/steam_workshop/functions.php | 84 +++++++++++++++++-- Panel/modules/steam_workshop/main.php | 2 +- Panel/modules/steam_workshop/uninstall.php | 39 ++++++++- docs/features/WORKSHOP_SYSTEM.md | 2 + docs/modules/steam_workshop.md | 5 ++ 8 files changed, 162 insertions(+), 16 deletions(-) diff --git a/Panel/includes/lib_remote.php b/Panel/includes/lib_remote.php index 355a0195..a810138a 100644 --- a/Panel/includes/lib_remote.php +++ b/Panel/includes/lib_remote.php @@ -1214,9 +1214,9 @@ class OGPRemoteLibrary return -4; } - public function get_workshop_mods_info(&$data) + public function get_workshop_mods_info(&$data, $home_id = "") { - $args = $this->encryptParam("mods_info"); + $args = $this->encryptParam($home_id === "" ? "mods_info" : $home_id); $this->add_enc_chk($args); $request = xmlrpc_encode_request("get_workshop_mods_info", $args); $response = $this->sendRequest($request); @@ -1237,9 +1237,32 @@ class OGPRemoteLibrary $lines = explode('\n',$data_tmp); foreach ((array)$lines as $line) { - @list($string_name, $mod_title) = explode(':', base64_decode($line), 2); - if($string_name != "" and $mod_title != "") - $data["$string_name"] = $mod_title; + $decoded_line = base64_decode($line); + $fields = explode("\t", $decoded_line); + if(count($fields) >= 8) + { + $mod_string = $fields[0]; + if($mod_string != "") + { + $data["$mod_string"] = array( + 'mod_string' => $fields[0], + 'workshop_mod_id' => $fields[1], + 'title' => $fields[2], + 'installed_folder' => $fields[3], + 'installed_path' => $fields[4], + 'workshop_app_id' => $fields[5], + 'install_status' => $fields[6], + 'install_time' => $fields[7], + 'last_update_time' => isset($fields[8]) ? $fields[8] : $fields[7], + ); + } + } + else + { + @list($string_name, $mod_title) = explode(':', $decoded_line, 2); + if($string_name != "" and $mod_title != "") + $data["$string_name"] = $mod_title; + } } return $retval; } diff --git a/Panel/lang/English/modules/steam_workshop.php b/Panel/lang/English/modules/steam_workshop.php index 4a5762bd..b9d353e6 100644 --- a/Panel/lang/English/modules/steam_workshop.php +++ b/Panel/lang/English/modules/steam_workshop.php @@ -77,6 +77,11 @@ define('LANG_post_install', "Postinstall Script"); define('LANG_post_install_info', "The necessary commands in bash to move the mods to the mods folder. Valid replacements: %mods_full_path% (the full path to the Wokshop mods folder), %workshop_mod_id%, %first_file% (first file is the first file found in the mod folder downloaded by SteamCMD)"); define('LANG_install_mods', "Install Mods"); define('LANG_uninstall_mods', "Uninstall Mods"); +define('LANG_workshop_mod_line', "Workshop mod line"); +define('LANG_workshop_mod_title', "Mod title"); +define('LANG_installed_folder', "Installed folder"); +define('LANG_install_status', "Install status"); +define('LANG_install_time', "Install time"); define('LANG_failed_uninstalling_mod', "Failed uninstalling mod %s"); define('LANG_uninstall', "Uninstall Script"); define('LANG_uninstall_info', "This is the script called when a mod is uninstalled, Valid replacements: %mods_full_path% (the full path to the wokshop mods folder), %mod_string% (mod string is the name listed in the configuration file for this mod)."); diff --git a/Panel/modules/gamemanager/server_monitor.php b/Panel/modules/gamemanager/server_monitor.php index 8d35c02d..b0ff411f 100644 --- a/Panel/modules/gamemanager/server_monitor.php +++ b/Panel/modules/gamemanager/server_monitor.php @@ -28,6 +28,7 @@ require_once("modules/config_games/server_config_parser.php"); require_once("includes/refreshed.php"); require_once('includes/lib_remote.php'); +require_once("modules/steam_workshop/functions.php"); if (!defined('GSP_WEBSITE_DOCS_BASE_URL')) { define('GSP_WEBSITE_DOCS_BASE_URL', 'https://gameservers.world/docs/'); @@ -488,6 +489,13 @@ echo "is_screen_running(OGP_SCREEN_TYPE_UPDATE,$server_home['home_id']) === 1; + if($update_in_progress) + { + $workshop_log_txt = ""; + $workshop_log_state = $remote->get_log(OGP_SCREEN_TYPE_UPDATE, $server_home['home_id'], clean_path($server_home['home_path']), $workshop_log_txt, 25); + if($workshop_log_state == 1 && steam_workshop_log_is_terminal($workshop_log_txt)) + $update_in_progress = false; + } if($screen_running) { // Check if the screen running the server is running. diff --git a/Panel/modules/steam_workshop/functions.php b/Panel/modules/steam_workshop/functions.php index 1c13ed66..175d9dbd 100644 --- a/Panel/modules/steam_workshop/functions.php +++ b/Panel/modules/steam_workshop/functions.php @@ -564,8 +564,10 @@ function get_installed_mods($home_cfg, $remote, $xml) if(!steam_workshop_has_config_file_path($filepath)) { - $retval = $remote->get_workshop_mods_info($mod_info_array); - return $retval == "1" && !empty($mod_info_array) ? $mod_info_array : False; + $retval = $remote->get_workshop_mods_info($mod_info_array, $home_cfg['home_id']); + if($retval != "1") + $retval = $remote->get_workshop_mods_info($mod_info_array); + return $retval == "1" && !empty($mod_info_array) ? steam_workshop_normalize_installed_mod_records($mod_info_array, $home_cfg, $xml) : False; } if($remote->rfile_exists($full_filepath) === 0) @@ -580,7 +582,9 @@ function get_installed_mods($home_cfg, $remote, $xml) $current_mods_string = trim($matches[$mods_backreference_index]); if($current_mods_string != '') { - $retval = $remote->get_workshop_mods_info($mod_info_array); + $retval = $remote->get_workshop_mods_info($mod_info_array, $home_cfg['home_id']); + if($retval != "1") + $retval = $remote->get_workshop_mods_info($mod_info_array); $current = explode($string_separator, $current_mods_string); $installed_mods = array(); foreach($current as $c) @@ -588,10 +592,10 @@ function get_installed_mods($home_cfg, $remote, $xml) if($c != "") { $mod_string = trim($c); - if($retval == "1") - $installed_mods["$mod_string"] = isset($mod_info_array["$mod_string"])?$mod_info_array["$mod_string"]:$mod_string; + if($retval == "1" && isset($mod_info_array["$mod_string"])) + $installed_mods["$mod_string"] = is_array($mod_info_array["$mod_string"]) ? $mod_info_array["$mod_string"] : steam_workshop_installed_record_from_title($mod_string, $mod_info_array["$mod_string"], $home_cfg, $xml); else - $installed_mods["$mod_string"] = $mod_string; + $installed_mods["$mod_string"] = steam_workshop_installed_record_from_title($mod_string, $mod_string, $home_cfg, $xml); } } return $installed_mods; @@ -603,6 +607,74 @@ function get_installed_mods($home_cfg, $remote, $xml) return False; } +function steam_workshop_installed_record_from_title($mod_string, $title, $home_cfg, $xml) +{ + $mods_full_path = clean_path($home_cfg['home_path'].'/'.$xml->mods_path); + $installed_folder = $mod_string; + if(strpos($mod_string, '@') !== 0 && preg_match('/^\d+$/', $mod_string)) + $installed_folder = '@'.$mod_string; + return array( + 'mod_string' => (string)$mod_string, + 'workshop_mod_id' => preg_match('/^\d+$/', (string)$mod_string) ? (string)$mod_string : '', + 'title' => (string)$title, + 'installed_folder' => $installed_folder, + 'installed_path' => clean_path($mods_full_path.'/'.$installed_folder), + 'workshop_app_id' => (string)$xml->workshop_id, + 'install_status' => 'installed', + 'install_time' => '', + 'last_update_time' => '', + ); +} + +function steam_workshop_normalize_installed_mod_records($mods, $home_cfg, $xml) +{ + $records = array(); + foreach((array)$mods as $mod_string => $record) + { + if(is_array($record)) + { + if(!isset($record['mod_string']) || $record['mod_string'] === '') + $record['mod_string'] = $mod_string; + $records[$record['mod_string']] = $record; + } + else + { + $records[$mod_string] = steam_workshop_installed_record_from_title($mod_string, $record, $home_cfg, $xml); + } + } + return $records; +} + +function steam_workshop_build_mod_line($mods, $xml) +{ + $parts = array(); + foreach((array)$mods as $record) + { + if(is_array($record)) + { + $value = isset($record['installed_folder']) && $record['installed_folder'] !== '' ? $record['installed_folder'] : $record['mod_string']; + $parts[] = $value; + } + else + { + $parts[] = $record; + } + } + $separator = isset($xml->config->string_separator) && (string)$xml->config->string_separator !== '' ? stripcslashes($xml->config->string_separator) : ';'; + $mod_list = implode($separator, array_filter($parts)); + $format = isset($xml->config->variable) ? (string)$xml->config->variable : ''; + if($format !== '' && strpos($format, '{MOD_LIST}') !== false) + return str_replace('{MOD_LIST}', $mod_list, $format); + if($format !== '' && preg_match('/^-/', $format)) + return $format.$mod_list; + return $mod_list; +} + +function steam_workshop_log_is_terminal($log_txt) +{ + return preg_match('/GSP_WORKSHOP_INSTALL_(COMPLETE|FAILED)/', (string)$log_txt) === 1; +} + function remove_mod($home_cfg, $remote, $xml, $mod_string) { $config = $xml->config; diff --git a/Panel/modules/steam_workshop/main.php b/Panel/modules/steam_workshop/main.php index b717452a..e169eaf5 100644 --- a/Panel/modules/steam_workshop/main.php +++ b/Panel/modules/steam_workshop/main.php @@ -137,7 +137,7 @@ function exec_ogp_module() if(isset($_GET['show_log'])) { $update_active = $remote->get_log(OGP_SCREEN_TYPE_UPDATE,$home_id,clean_path($home_cfg['home_path']),$log_txt); - if ( $update_active == 1 ) + if ( $update_active == 1 && !steam_workshop_log_is_terminal($log_txt) ) { if(isset($_POST['sgc'])) { diff --git a/Panel/modules/steam_workshop/uninstall.php b/Panel/modules/steam_workshop/uninstall.php index f586ec4d..23395d08 100644 --- a/Panel/modules/steam_workshop/uninstall.php +++ b/Panel/modules/steam_workshop/uninstall.php @@ -145,13 +145,44 @@ function exec_ogp_module() if($mods and count($mods) > 0) { + $mod_line = steam_workshop_build_mod_line($mods, $xml); $ft = new FormTable(); $ft->start_form("?m=steam_workshop&p=uninstall&home_id-mod_id-ip-port=".$_GET['home_id-mod_id-ip-port'], "post", "autocomplete=\"off\""); $ft->start_table(); - echo ''; + echo ""; + echo ''; $ft->end_table(); $ft->add_button("submit", "uninstall", get_lang('uninstall_mods')); $ft->end_form(); diff --git a/docs/features/WORKSHOP_SYSTEM.md b/docs/features/WORKSHOP_SYSTEM.md index 59f72c36..069d83bd 100644 --- a/docs/features/WORKSHOP_SYSTEM.md +++ b/docs/features/WORKSHOP_SYSTEM.md @@ -49,6 +49,8 @@ Current rule: - post-install scripts still run - `WorkshopModsInfo` still records installed items - uninstall can rely on `WorkshopModsInfo` when no config file is managed +- installed-mod tracking is per home when the agent supports `get_workshop_mods_info(home_id)` +- Workshop installs write `/WorkshopInstallStatus.json` and terminal log markers so the Panel can clear stale Workshop update messages without hiding unrelated running updates This keeps advanced regex editing available without forcing it for simple Arma/DayZ-style `@mod` installs. diff --git a/docs/modules/steam_workshop.md b/docs/modules/steam_workshop.md index fab6e7b9..301ca410 100644 --- a/docs/modules/steam_workshop.md +++ b/docs/modules/steam_workshop.md @@ -57,6 +57,7 @@ Dedicated Steam Workshop support for game servers. - the main Workshop page `Back` link is rendered as a real panel button - uninstall remains in the dedicated `steam_workshop` module +- the uninstall page is also the installed-mods view and shows tracked title, Workshop ID, installed folder/path, status, install time, and a copyable mod line ## Legacy RPC Behavior @@ -65,6 +66,10 @@ Dedicated Steam Workshop support for game servers. - both agents skip the generated `cat` / regex / config-write block when `config_file_path` is blank - both agents still run custom post-install scripts and still write `WorkshopModsInfo` - uninstall falls back to `WorkshopModsInfo` when no config file is managed +- new agents write per-home installed mod records under `WorkshopModsInfo/home_/` +- each installed record includes mod string, Workshop item ID, title, installed folder/path, Workshop App ID, status, and timestamps +- agents also write `/WorkshopInstallStatus.json` with `running`, `completed`, or `failed` Workshop install state +- Workshop install logs end with `GSP_WORKSHOP_INSTALL_COMPLETE` or `GSP_WORKSHOP_INSTALL_FAILED`; the Panel uses these markers to avoid stale `Update in progress` displays when the generic update screen remains visible ## Security Concerns
'; - foreach($mods as $mod_id => $mod_name) - echo "
"; - echo '
".get_lang('workshop_mod_line')."
". + "
'; + echo ""; + foreach($mods as $mod_id => $mod_record) + { + if(is_array($mod_record)) + { + $title = isset($mod_record['title']) && $mod_record['title'] !== '' ? $mod_record['title'] : $mod_record['mod_string']; + $workshop_mod_id = isset($mod_record['workshop_mod_id']) ? $mod_record['workshop_mod_id'] : ''; + $installed_folder = isset($mod_record['installed_folder']) ? $mod_record['installed_folder'] : ''; + $installed_path = isset($mod_record['installed_path']) ? $mod_record['installed_path'] : ''; + $install_status = isset($mod_record['install_status']) ? $mod_record['install_status'] : ''; + $install_time = isset($mod_record['install_time']) ? $mod_record['install_time'] : ''; + } + else + { + $title = $mod_record; + $workshop_mod_id = preg_match('/^\d+$/', (string)$mod_id) ? $mod_id : ''; + $installed_folder = $mod_id; + $installed_path = ''; + $install_status = 'installed'; + $install_time = ''; + } + echo "". + "". + "". + "". + "". + "". + "". + ""; + } + echo '
".get_lang('workshop_mod_title')."".get_lang('workshop_id')."".get_lang('installed_folder')."".get_lang('install_status')."".get_lang('install_time')."
".steam_workshop_h($workshop_mod_id)."".steam_workshop_h($installed_folder)."".steam_workshop_h($install_status)."".steam_workshop_h($install_time)."