resultQuery( "SELECT * FROM " . sw_table('steam_workshop_game_profiles') . " ORDER BY `game_name` ASC, `config_name` ASC" ); } /** * Return a single profile row by primary key. * * @param OGPDatabase $db * @param int $id * @return array|false */ function sw_get_profile_by_id($db, $id) { $id = (int)$id; $rows = $db->resultQuery( "SELECT * FROM " . sw_table('steam_workshop_game_profiles') . " WHERE `id` = $id LIMIT 1" ); return ($rows && isset($rows[0])) ? $rows[0] : false; } /** * Return a single profile row by config_name (= game_key from XML). * * @param OGPDatabase $db * @param string $config_name * @return array|false */ function sw_get_profile_by_config_name($db, $config_name) { $safe = $db->realEscapeSingle($config_name); $rows = $db->resultQuery( "SELECT * FROM " . sw_table('steam_workshop_game_profiles') . " WHERE `config_name` = '$safe' LIMIT 1" ); return ($rows && isset($rows[0])) ? $rows[0] : false; } /** * Return the Workshop profile that applies to a server home. * Resolves: home_id → config_homes.game_key → workshop profile. * * @param OGPDatabase $db * @param int $home_id * @return array|false profile row or false when none found / not enabled */ function sw_get_profile_for_home($db, $home_id) { $home_id = (int)$home_id; $rows = $db->resultQuery( "SELECT p.* FROM " . sw_table('steam_workshop_game_profiles') . " p JOIN " . sw_table('config_homes') . " c ON c.`game_key` = p.`config_name` JOIN " . sw_table('server_homes') . " s ON s.`home_cfg_id` = c.`home_cfg_id` WHERE s.`home_id` = $home_id AND p.`enabled` = 1 LIMIT 1" ); return ($rows && isset($rows[0])) ? $rows[0] : false; } // ── Mod helpers ─────────────────────────────────────────────────────────── /** * Return all mods for a server, sorted by sort_order ASC. * * @param OGPDatabase $db * @param int $home_id * @return array|false */ function sw_get_server_mods($db, $home_id) { $home_id = (int)$home_id; return $db->resultQuery( "SELECT * FROM " . sw_table('steam_workshop_server_mods') . " WHERE `home_id` = $home_id ORDER BY `sort_order` ASC, `id` ASC" ); } /** * Return a single mod row by primary key. * * @param OGPDatabase $db * @param int $id * @return array|false */ function sw_get_mod_by_id($db, $id) { $id = (int)$id; $rows = $db->resultQuery( "SELECT * FROM " . sw_table('steam_workshop_server_mods') . " WHERE `id` = $id LIMIT 1" ); return ($rows && isset($rows[0])) ? $rows[0] : false; } // ── Server / ownership helpers ──────────────────────────────────────────── /** * Return server_homes row joined with config_homes and remote_servers * for the given home_id, or false when not found. * * @param OGPDatabase $db * @param int $home_id * @return array|false */ function sw_get_home_info($db, $home_id) { $home_id = (int)$home_id; $rows = $db->resultQuery( "SELECT s.*, c.`game_key`, c.`game_name`, r.`agent_ip`, r.`agent_port` FROM " . sw_table('server_homes') . " s JOIN " . sw_table('config_homes') . " c ON c.`home_cfg_id` = s.`home_cfg_id` JOIN " . sw_table('remote_servers') . " r ON r.`remote_server_id` = s.`remote_server_id` WHERE s.`home_id` = $home_id LIMIT 1" ); return ($rows && isset($rows[0])) ? $rows[0] : false; } /** * Verify that the current session user is allowed to manage this home. * Admins always pass. Regular users/subusers must have an entry in * user_homes (or be the user_id_main). * * @param OGPDatabase $db * @param int $user_id * @param int $home_id * @return bool */ function sw_user_owns_home($db, $user_id, $home_id) { if (!isset($_SESSION['users_group'])) { return false; } if ($_SESSION['users_group'] === 'admin') { return true; } $user_id = (int)$user_id; $home_id = (int)$home_id; // Direct owner $rows = $db->resultQuery( "SELECT 1 FROM " . sw_table('server_homes') . " WHERE `home_id` = $home_id AND `user_id_main` = $user_id LIMIT 1" ); if ($rows) { return true; } // Assigned via user_homes $rows = $db->resultQuery( "SELECT 1 FROM " . sw_table('user_homes') . " WHERE `home_id` = $home_id AND `user_id` = $user_id LIMIT 1" ); if ($rows) { return true; } // Assigned via group $rows = $db->resultQuery( "SELECT 1 FROM " . sw_table('user_group_homes') . " ugh JOIN " . sw_table('user_groups') . " ug ON ug.`group_id` = ugh.`group_id` WHERE ugh.`home_id` = $home_id AND ug.`user_id` = $user_id LIMIT 1" ); return (bool)$rows; } // ── Game-config helpers ─────────────────────────────────────────────────── /** * Return an array of all game configs from the XML files. * Each element is a SimpleXMLElement (game_config root node). * * @return SimpleXMLElement[] */ function sw_get_all_game_configs() { if (!defined('SERVER_CONFIG_LOCATION')) { // server_config_parser.php defines this; load it if not already done. if (file_exists(__DIR__ . '/../../config_games/server_config_parser.php')) { require_once __DIR__ . '/../../config_games/server_config_parser.php'; } else { return array(); } } $configs = array(); foreach (glob(SERVER_CONFIG_LOCATION . '*.xml') as $file) { $xml = read_server_config($file); if ($xml !== false) { $configs[] = $xml; } } return $configs; } /** * Ensure every game config has a matching row in steam_workshop_game_profiles. * Only creates rows that are missing; never overwrites existing data. * * @param OGPDatabase $db * @return int number of new rows inserted */ function sw_sync_profiles($db) { $configs = sw_get_all_game_configs(); $created = 0; foreach ($configs as $xml) { $config_name = (string)$xml->game_key; $game_name = (string)$xml->game_name; if (empty($config_name)) { continue; } $existing = sw_get_profile_by_config_name($db, $config_name); if ($existing) { continue; // already have a profile for this game config } $safe_config = $db->realEscapeSingle($config_name); $safe_name = $db->realEscapeSingle($game_name); $ok = $db->query("INSERT IGNORE INTO " . sw_table('steam_workshop_game_profiles') . " (`config_name`, `game_name`, `enabled`) VALUES ('$safe_config', '$safe_name', 0)"); if ($ok) { $created++; } } return $created; } // ── Template / launch-param helpers ───────────────────────────────────── /** * Replace {PLACEHOLDER} tokens in $template with values from $vars. * Unknown tokens are left intact so admins can spot missing values. * * @param string $template * @param array $vars associative: 'PLACEHOLDER' => 'value' * @return string */ function sw_apply_template($template, array $vars) { $search = array(); $replace = array(); foreach ($vars as $key => $value) { $search[] = '{' . $key . '}'; $replace[] = (string)$value; } return str_replace($search, $replace, $template); } /** * Build the -mod= and -serverMod= launch parameter strings from an ordered * list of enabled mods and the game profile. * * Returns an associative array: * 'mod' => '-mod=@Mod1;@Mod2' (client mods) * 'servermod' => '-serverMod=@ServerOnly' (server-side mods) * 'combined' => '-mod=... -serverMod=...' (ready-to-paste) * * @param array $mods rows from steam_workshop_server_mods (must be pre-filtered * for enabled = 1 and sorted by sort_order) * @param array $profile row from steam_workshop_game_profiles * @return array */ function sw_generate_launch_params(array $mods, array $profile) { $mod_param = trim($profile['mod_launch_param_template'] ?? '-mod='); $servermod_param = trim($profile['servermod_launch_param_template'] ?? '-serverMod='); $client_folders = array(); $server_folders = array(); foreach ($mods as $mod) { if (empty($mod['enabled'])) { continue; } $folder = !empty($mod['folder_name']) ? $mod['folder_name'] : ('@' . $mod['workshop_id']); if ($mod['mod_type'] === 'server') { $server_folders[] = $folder; } else { $client_folders[] = $folder; } } $mod_str = $client_folders ? ($mod_param . implode(';', $client_folders)) : ''; $servermod_str = $server_folders ? ($servermod_param . implode(';', $server_folders)) : ''; $combined = trim($mod_str . ' ' . $servermod_str); return array( 'mod' => $mod_str, 'servermod' => $servermod_str, 'combined' => $combined, ); } // ── Server behavior settings helpers ───────────────────────────────────── /** * Return the workshop behavior settings row for a home, or an array of * safe defaults when no row exists yet. * * @param OGPDatabase $db * @param int $home_id * @return array */ function sw_get_server_settings($db, $home_id) { $home_id = (int)$home_id; $rows = $db->resultQuery( "SELECT * FROM " . sw_table('steam_workshop_server_settings') . " WHERE `home_id` = $home_id LIMIT 1" ); if ($rows && isset($rows[0]) && is_array($rows[0])) { $settings = $rows[0]; // Runtime normalization is kept as a fallback for legacy/manual rows that // were not updated via module migrations. $legacyUpdateMap = array( 'scheduled' => 'manual', ); $legacyRestartMap = array( 'if_empty' => 'if_stopped', 'next_restart' => 'if_stopped', 'immediate' => 'none', ); $legacyScheduleMap = array( 'hourly' => 'daily', ); if (isset($legacyUpdateMap[$settings['update_mode'] ?? ''])) { $settings['update_mode'] = $legacyUpdateMap[$settings['update_mode']]; } if (isset($legacyRestartMap[$settings['restart_behavior'] ?? ''])) { $settings['restart_behavior'] = $legacyRestartMap[$settings['restart_behavior']]; } if (isset($legacyScheduleMap[$settings['schedule_interval'] ?? ''])) { $settings['schedule_interval'] = $legacyScheduleMap[$settings['schedule_interval']]; } $validUpdateModes = array('manual', 'on_restart', 'before_start'); $validRestartBehaviors = array('none', 'if_stopped'); $validIntervals = array('disabled', 'daily', 'weekly'); if (!in_array($settings['update_mode'] ?? '', $validUpdateModes, true)) { $settings['update_mode'] = 'manual'; } if (!in_array($settings['restart_behavior'] ?? '', $validRestartBehaviors, true)) { $settings['restart_behavior'] = 'none'; } if (!in_array($settings['schedule_interval'] ?? '', $validIntervals, true)) { $settings['schedule_interval'] = 'disabled'; } return $settings; } // Safe defaults – manual only, no automatic restarts, schedule disabled return array( 'home_id' => $home_id, 'update_mode' => 'manual', 'restart_behavior' => 'none', 'schedule_interval' => 'disabled', ); } /** * Upsert the workshop behavior settings for a server home. * * @param OGPDatabase $db * @param int $home_id * @param array $data keys: update_mode, restart_behavior, schedule_interval * @return bool */ function sw_save_server_settings($db, $home_id, array $data) { $home_id = (int)$home_id; $valid_update_modes = array('manual', 'on_restart', 'before_start'); $valid_restart_behaviors = array('none', 'if_stopped'); $valid_intervals = array('disabled', 'daily', 'weekly'); $update_mode = in_array($data['update_mode'] ?? '', $valid_update_modes, true) ? $data['update_mode'] : 'manual'; $restart_behavior = in_array($data['restart_behavior'] ?? '', $valid_restart_behaviors, true) ? $data['restart_behavior'] : 'none'; $schedule_interval = in_array($data['schedule_interval'] ?? '', $valid_intervals, true) ? $data['schedule_interval'] : 'disabled'; $safe_um = $db->realEscapeSingle($update_mode); $safe_rb = $db->realEscapeSingle($restart_behavior); $safe_si = $db->realEscapeSingle($schedule_interval); return (bool)$db->query( "INSERT INTO " . sw_table('steam_workshop_server_settings') . " (`home_id`, `update_mode`, `restart_behavior`, `hot_load`, `warning_minutes`, `schedule_interval`, `created_at`, `updated_at`) VALUES ($home_id, '$safe_um', '$safe_rb', 'disabled', 0, '$safe_si', NOW(), NOW()) ON DUPLICATE KEY UPDATE `update_mode` = '$safe_um', `restart_behavior` = '$safe_rb', `hot_load` = 'disabled', `warning_minutes` = 0, `schedule_interval` = '$safe_si', `updated_at` = NOW()" ); } // ── Output helpers ──────────────────────────────────────────────────────── /** * Render a short inline success banner. * * @param string $msg * @return void */ function sw_success($msg) { echo '