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 '';
- foreach($mods as $mod_id => $mod_name)
- echo "$mod_name ";
- echo '
';
+ echo "".get_lang('workshop_mod_line')." ".
+ " ";
+ 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