From ebb7c52084aeaca9fd0e7d63752b77be454b27fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:47:39 +0000 Subject: [PATCH 01/10] Initial plan From d8a7f6803474a1d4084b3b149923435c95a39c95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:51:24 +0000 Subject: [PATCH 02/10] Fix RCON player data field mapping issue Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index 7cb5373..acbad25 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -171,12 +171,33 @@ public function testConnection() { /** * Get list of online players - * @return array Array of players + * @return array Array of players with normalized field names */ public function getPlayers() { try { $this->connect(); - return $this->rcon->getPlayersArray(); + $rawPlayers = $this->rcon->getPlayersArray(); + + // Normalize field names to match expected structure + // RCON library returns: id, name, GUID, ping, ip + // Code expects: num, name, guid, ping, time + $normalizedPlayers = []; + foreach ($rawPlayers as $player) { + $name = isset($player['name']) && trim($player['name']) !== '' + ? $player['name'] + : 'Unknown'; + + $normalizedPlayers[] = [ + 'num' => $player['id'] ?? null, + 'name' => $name, + 'guid' => $player['GUID'] ?? $player['guid'] ?? null, + 'ping' => $player['ping'] ?? 'N/A', + 'time' => 'N/A', // RCON doesn't provide playtime + 'ip' => $player['ip'] ?? null + ]; + } + + return $normalizedPlayers; } catch (Exception $e) { throw new Exception("Failed to get player list: " . $e->getMessage()); } From b27708c667e32df7e05939486d3ff23823fdea82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:52:13 +0000 Subject: [PATCH 03/10] Fix kickPlayer and banPlayer to use correct RCON field names Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index acbad25..254e3de 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -217,8 +217,10 @@ public function kickPlayer($identifier, $reason = '') { if (preg_match('/^\d{' . self::STEAM_ID64_LENGTH . '}$/', $identifier)) { $players = $this->rcon->getPlayersArray(); foreach ($players as $player) { - if (isset($player['guid']) && $player['guid'] === $identifier) { - $identifier = $player['num']; + // RCON library uses 'GUID' (uppercase), not 'guid' + if (isset($player['GUID']) && $player['GUID'] === $identifier) { + // RCON library uses 'id', not 'num' + $identifier = $player['id']; break; } } @@ -255,10 +257,12 @@ public function banPlayer($identifier, $reason = '', $duration = 0) { $guid = null; foreach ($players as $player) { - if ($player['num'] == $identifier || + // RCON library uses 'id', not 'num' + if ($player['id'] == $identifier || stripos($player['name'], $identifier) !== false) { - $guid = isset($player['guid']) ? $player['guid'] : null; - $identifier = $player['num']; + // RCON library uses 'GUID' (uppercase), not 'guid' + $guid = isset($player['GUID']) ? $player['GUID'] : null; + $identifier = $player['id']; break; } } From 1dbadbaa0eac901fa1ca73e7482f5fcf0a47cd02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:53:02 +0000 Subject: [PATCH 04/10] Address code review feedback: improve error handling and remove unnecessary fallbacks Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index 254e3de..4b6b7fd 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -183,14 +183,21 @@ public function getPlayers() { // Code expects: num, name, guid, ping, time $normalizedPlayers = []; foreach ($rawPlayers as $player) { + // Handle missing or empty player name $name = isset($player['name']) && trim($player['name']) !== '' ? $player['name'] : 'Unknown'; + + // Validate that required fields exist + if (!isset($player['id']) || !isset($player['GUID'])) { + error_log("RCON: Incomplete player data received - missing id or GUID"); + continue; // Skip this player if critical data is missing + } $normalizedPlayers[] = [ - 'num' => $player['id'] ?? null, + 'num' => $player['id'], 'name' => $name, - 'guid' => $player['GUID'] ?? $player['guid'] ?? null, + 'guid' => $player['GUID'], 'ping' => $player['ping'] ?? 'N/A', 'time' => 'N/A', // RCON doesn't provide playtime 'ip' => $player['ip'] ?? null From c12900dd5622564fea80538536b06f65e7ef6719 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:54:25 +0000 Subject: [PATCH 05/10] Improve code readability and error logging Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index 4b6b7fd..af11ee9 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -183,16 +183,15 @@ public function getPlayers() { // Code expects: num, name, guid, ping, time $normalizedPlayers = []; foreach ($rawPlayers as $player) { - // Handle missing or empty player name - $name = isset($player['name']) && trim($player['name']) !== '' - ? $player['name'] - : 'Unknown'; - // Validate that required fields exist if (!isset($player['id']) || !isset($player['GUID'])) { - error_log("RCON: Incomplete player data received - missing id or GUID"); + $playerName = $player['name'] ?? 'unknown'; + error_log("RCON: Incomplete player data received - missing id or GUID for player: {$playerName}"); continue; // Skip this player if critical data is missing } + + // Handle missing or empty player name + $name = (!empty($player['name']) && trim($player['name']) !== '') ? $player['name'] : 'Unknown'; $normalizedPlayers[] = [ 'num' => $player['id'], From f9002ac6658250bed728c5e87b564a0faa8869c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:01:04 +0000 Subject: [PATCH 06/10] Revert incorrect field mapping and use actual RCON library structure Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 47 +++++++++++++---------------------------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index af11ee9..1672163 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -171,39 +171,22 @@ public function testConnection() { /** * Get list of online players - * @return array Array of players with normalized field names + * @return array Array of players with enhanced fields */ public function getPlayers() { try { $this->connect(); - $rawPlayers = $this->rcon->getPlayersArray(); + $players = $this->rcon->getPlayersArray(); - // Normalize field names to match expected structure - // RCON library returns: id, name, GUID, ping, ip - // Code expects: num, name, guid, ping, time - $normalizedPlayers = []; - foreach ($rawPlayers as $player) { - // Validate that required fields exist - if (!isset($player['id']) || !isset($player['GUID'])) { - $playerName = $player['name'] ?? 'unknown'; - error_log("RCON: Incomplete player data received - missing id or GUID for player: {$playerName}"); - continue; // Skip this player if critical data is missing + // Add missing 'time' field that RCON doesn't provide + // RCON library already returns: num, name, id, ip, ipport, ping, guid + foreach ($players as &$player) { + if (!isset($player['time'])) { + $player['time'] = 'N/A'; // RCON doesn't track session time } - - // Handle missing or empty player name - $name = (!empty($player['name']) && trim($player['name']) !== '') ? $player['name'] : 'Unknown'; - - $normalizedPlayers[] = [ - 'num' => $player['id'], - 'name' => $name, - 'guid' => $player['GUID'], - 'ping' => $player['ping'] ?? 'N/A', - 'time' => 'N/A', // RCON doesn't provide playtime - 'ip' => $player['ip'] ?? null - ]; } - return $normalizedPlayers; + return $players; } catch (Exception $e) { throw new Exception("Failed to get player list: " . $e->getMessage()); } @@ -223,10 +206,8 @@ public function kickPlayer($identifier, $reason = '') { if (preg_match('/^\d{' . self::STEAM_ID64_LENGTH . '}$/', $identifier)) { $players = $this->rcon->getPlayersArray(); foreach ($players as $player) { - // RCON library uses 'GUID' (uppercase), not 'guid' - if (isset($player['GUID']) && $player['GUID'] === $identifier) { - // RCON library uses 'id', not 'num' - $identifier = $player['id']; + if (isset($player['guid']) && $player['guid'] === $identifier) { + $identifier = $player['num']; break; } } @@ -263,12 +244,10 @@ public function banPlayer($identifier, $reason = '', $duration = 0) { $guid = null; foreach ($players as $player) { - // RCON library uses 'id', not 'num' - if ($player['id'] == $identifier || + if ($player['num'] == $identifier || stripos($player['name'], $identifier) !== false) { - // RCON library uses 'GUID' (uppercase), not 'guid' - $guid = isset($player['GUID']) ? $player['GUID'] : null; - $identifier = $player['id']; + $guid = isset($player['guid']) ? $player['guid'] : null; + $identifier = $player['num']; break; } } From f9d0e66d5831740015ade174b6da5552282a4f4a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:02:40 +0000 Subject: [PATCH 07/10] Add unset after foreach with reference to prevent bugs Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/rcon_manager.php b/rcon_manager.php index 1672163..ebd3d2a 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -185,6 +185,7 @@ public function getPlayers() { $player['time'] = 'N/A'; // RCON doesn't track session time } } + unset($player); // Break the reference to avoid unexpected behavior return $players; } catch (Exception $e) { From e2f1567f15a74c0213cc8ed105cfb3695f667c48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:09:13 +0000 Subject: [PATCH 08/10] Add comprehensive RCON debug logging for troubleshooting Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_debug_log.php | 246 +++++++++++++++++++++++++++++++++++++++++++++ rcon_manager.php | 104 +++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 rcon_debug_log.php diff --git a/rcon_debug_log.php b/rcon_debug_log.php new file mode 100644 index 0000000..595e872 --- /dev/null +++ b/rcon_debug_log.php @@ -0,0 +1,246 @@ + + + + + + + RCON Debug Log - 420th Delta + + + + + + +
+
+

RCON Debug Log

+

View detailed RCON communication logs for debugging

+
+ + +
+ +
+ + +
+
+ Log File: + +
+
+ Status: + +
+
+ Size: + +
+
+ +
+ +
+ + +
+
+ +
+

Log Content

+ 0): ?> +
+ +
+

No log entries yet

+

Visit the Active Players page to generate RCON traffic

+
+ +
+
+ + + + diff --git a/rcon_manager.php b/rcon_manager.php index ebd3d2a..96d4546 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -13,6 +13,9 @@ class RconManager { // Steam ID64 is always 17 digits const STEAM_ID64_LENGTH = 17; + // RCON debug log file + const LOG_FILE = __DIR__ . '/rcon_debug.log'; + private $db; private $rcon; private $enabled; @@ -28,6 +31,25 @@ public function __construct() { $this->loadSettings(); } + /** + * Log RCON debug information + * @param string $message Log message + * @param mixed $data Optional data to log + */ + private function logDebug($message, $data = null) { + $timestamp = date('Y-m-d H:i:s'); + $logEntry = "[{$timestamp}] {$message}"; + + if ($data !== null) { + $logEntry .= "\n" . print_r($data, true); + } + + $logEntry .= "\n" . str_repeat('-', 80) . "\n"; + + // Append to log file + file_put_contents(self::LOG_FILE, $logEntry, FILE_APPEND); + } + /** * Load RCON settings from database */ @@ -127,17 +149,31 @@ public function getSettings() { */ private function connect() { if (!$this->libraryAvailable) { + $this->logDebug("RCON library not available"); throw new Exception("RCON library not installed. Run 'composer install' to enable RCON features."); } if (!$this->isEnabled()) { + $this->logDebug("RCON not enabled or not configured"); throw new Exception("RCON is not enabled or not configured"); } if (!$this->rcon) { try { + $this->logDebug("Attempting RCON connection", [ + 'host' => $this->host, + 'port' => $this->port, + 'password_set' => !empty($this->password) + ]); + $this->rcon = new \Nizarii\ARC($this->host, $this->password, $this->port); + + $this->logDebug("RCON connection established successfully"); } catch (Exception $e) { + $this->logDebug("RCON connection failed", [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); throw new Exception("Failed to connect to RCON server: " . $e->getMessage()); } } @@ -151,17 +187,27 @@ private function connect() { */ public function testConnection() { try { + $this->logDebug("testConnection() called"); + $this->connect(); // Try to get player list as connection test $players = $this->rcon->getPlayersArray(); + $this->logDebug("testConnection() successful", [ + 'player_count' => count($players) + ]); + return [ 'success' => true, 'message' => 'Connected successfully to RCON server', 'player_count' => count($players) ]; } catch (Exception $e) { + $this->logDebug("testConnection() failed", [ + 'error' => $e->getMessage() + ]); + return [ 'success' => false, 'message' => $e->getMessage() @@ -175,9 +221,18 @@ public function testConnection() { */ public function getPlayers() { try { + $this->logDebug("getPlayers() called"); + $this->connect(); + + $this->logDebug("Calling RCON getPlayersArray()"); $players = $this->rcon->getPlayersArray(); + $this->logDebug("Raw RCON response from getPlayersArray()", [ + 'player_count' => count($players), + 'raw_data' => $players + ]); + // Add missing 'time' field that RCON doesn't provide // RCON library already returns: num, name, id, ip, ipport, ping, guid foreach ($players as &$player) { @@ -187,8 +242,17 @@ public function getPlayers() { } unset($player); // Break the reference to avoid unexpected behavior + $this->logDebug("Processed player data", [ + 'player_count' => count($players), + 'processed_data' => $players + ]); + return $players; } catch (Exception $e) { + $this->logDebug("getPlayers() failed", [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); throw new Exception("Failed to get player list: " . $e->getMessage()); } } @@ -322,4 +386,44 @@ public function executeCommand($command) { throw new Exception("Failed to execute command: " . $e->getMessage()); } } + + /** + * Get the path to the RCON debug log file + * @return string Log file path + */ + public static function getLogFilePath() { + return self::LOG_FILE; + } + + /** + * Clear the RCON debug log file + * @return bool Success status + */ + public static function clearLog() { + if (file_exists(self::LOG_FILE)) { + return unlink(self::LOG_FILE); + } + return true; + } + + /** + * Get the contents of the RCON debug log file + * @param int $lines Number of lines to retrieve from the end (0 for all) + * @return string Log contents + */ + public static function getLog($lines = 0) { + if (!file_exists(self::LOG_FILE)) { + return "Log file does not exist yet."; + } + + $content = file_get_contents(self::LOG_FILE); + + if ($lines > 0) { + $logLines = explode("\n", $content); + $logLines = array_slice($logLines, -$lines); + return implode("\n", $logLines); + } + + return $content; + } } From 8bbd7daeaafcb6a8bc2537702ba8069b295adb9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:22:11 +0000 Subject: [PATCH 09/10] Fix RCON data parsing - transform numerical array to associative array Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index 96d4546..3434434 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -226,21 +226,46 @@ public function getPlayers() { $this->connect(); $this->logDebug("Calling RCON getPlayersArray()"); - $players = $this->rcon->getPlayersArray(); + $rawPlayers = $this->rcon->getPlayersArray(); $this->logDebug("Raw RCON response from getPlayersArray()", [ - 'player_count' => count($players), - 'raw_data' => $players + 'player_count' => count($rawPlayers), + 'raw_data' => $rawPlayers ]); - // Add missing 'time' field that RCON doesn't provide - // RCON library already returns: num, name, id, ip, ipport, ping, guid - foreach ($players as &$player) { - if (!isset($player['time'])) { - $player['time'] = 'N/A'; // RCON doesn't track session time + // Transform RCON data to expected format + // RCON returns numerically indexed arrays: + // [0] => player number + // [1] => IP:Port + // [2] => ping + // [3] => guid (BE GUID) + // [4] => player name + $players = []; + foreach ($rawPlayers as $rawPlayer) { + // Skip if not a valid player array + if (!is_array($rawPlayer) || count($rawPlayer) < 5) { + $this->logDebug("Skipping invalid player data", ['data' => $rawPlayer]); + continue; } + + // Extract IP and port from IP:Port format + $ipPort = explode(':', $rawPlayer[1]); + $ip = $ipPort[0] ?? ''; + $port = $ipPort[1] ?? ''; + + // Create associative array with expected field names + $player = [ + 'num' => $rawPlayer[0], + 'ip' => $ip, + 'ipport' => $port, + 'ping' => $rawPlayer[2], + 'guid' => $rawPlayer[3], + 'name' => $rawPlayer[4], + 'time' => 'N/A' // RCON doesn't track session time + ]; + + $players[] = $player; } - unset($player); // Break the reference to avoid unexpected behavior $this->logDebug("Processed player data", [ 'player_count' => count($players), From 0ae09e7acc24adb6bc4c9f5942835a480b4b3001 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 01:24:21 +0000 Subject: [PATCH 10/10] Add type safety to IP:Port string parsing Co-authored-by: Stage4000 <46226385+Stage4000@users.noreply.github.com> --- rcon_manager.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rcon_manager.php b/rcon_manager.php index 3434434..95319b4 100644 --- a/rcon_manager.php +++ b/rcon_manager.php @@ -249,7 +249,9 @@ public function getPlayers() { } // Extract IP and port from IP:Port format - $ipPort = explode(':', $rawPlayer[1]); + // Ensure we have a string before splitting + $ipPortString = is_string($rawPlayer[1]) ? $rawPlayer[1] : ''; + $ipPort = explode(':', $ipPortString); $ip = $ipPort[0] ?? ''; $port = $ipPort[1] ?? ''; @@ -257,7 +259,7 @@ public function getPlayers() { $player = [ 'num' => $rawPlayer[0], 'ip' => $ip, - 'ipport' => $port, + 'ipport' => $port, // Note: This is just the port number, not IP:Port 'ping' => $rawPlayer[2], 'guid' => $rawPlayer[3], 'name' => $rawPlayer[4],