From be0033b8cac465140a4d2a834721ef27ef9dd090 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Fri, 1 May 2026 00:00:15 -0400 Subject: [PATCH 1/8] added security check to make sure a user don't add a computer commands in any of the textbox in the install server window and version check will run every 20min. --- .editorconfig | 2 +- Database/GameDatabase.cs | 25 ++--- Database/WarningDatabase.cs | 25 ++--- Design/GridHelper.cs | 24 ++--- Design/GridStyler.cs | 25 ++--- Design/UIStyleHelper.cs | 25 ++--- FileFolderHandler/FileHandler.cs | 25 ++--- FileFolderHandler/FolderHandler.cs | 25 ++--- Help/DefaultArgumentsDisplay.cs | 24 ++--- LICENSE.md | 29 ++++-- MainGUI.cs | 120 ++++++++++++++---------- MonitoringHandler/ResourceMonitor.cs | 25 ++--- MonitoringHandler/ResourceMonitorGUI.cs | 25 ++--- README.md | 10 -- ServerHandler/ConfigHandler.cs | 25 ++--- ServerHandler/GameFix.cs | 25 ++--- ServerHandler/GameServer.cs | 25 ++--- ServerHandler/ScheduleSettingsGUI.cs | 25 ++--- ServerHandler/ServerConfig.cs | 25 ++--- ServerHandler/ServerSettingsGUI.cs | 33 ++++--- ServerHandler/Servers.cs | 48 +++++++--- SteamCMDHandler/ServerInstaller.cs | 25 ++--- SteamCMDHandler/SteamCMD.cs | 25 ++--- SynixEngine/Actions.cs | 25 ++--- SynixEngine/BackupManager.cs | 25 ++--- SynixEngine/Core.cs | 25 ++--- SynixEngine/PortChecker.cs | 25 ++--- SynixEngine/Validator.cs | 47 ++++++++++ SynixEngine/Watchdog.cs | 25 ++--- 29 files changed, 482 insertions(+), 355 deletions(-) diff --git a/.editorconfig b/.editorconfig index 0522679..890528e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,7 +30,7 @@ dotnet_search_reference_assemblies = true # Organize usings dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = false -file_header_template = Copyright (c) 2026 ubidzz. All Rights Reserved.\n\nThis file is part of Synix Control Panel.\n\nThis code is provided for transparent viewing and personal use only.\nUnauthorized distribution, public modification, or commercial\nuse of this source code or the compiled executable is strictly\nprohibited. Please refer to the LICENSE file in the root\ndirectory for full terms. +file_header_template = ============================================================================\nPROJECT: Synix Game Server Control Panel\nAUTHOR: Jason Turner (ubidzz)\nCOPYRIGHT: © 2026 All Rights Reserved.\n\nLEGAL NOTICE:\nThis source code is proprietary and confidential.\n1. Permission is granted for PERSONAL, NON-COMMERCIAL use only.\n2. You may modify this code for your own use, but you may NOT redistribute,\n rebrand, or sell this code or derivative works without written consent.\n3. The "Synix" brand and logic remain the property of Jason Turner.\n============================================================================ # this. and Me. preferences dotnet_style_qualification_for_event = false diff --git a/Database/GameDatabase.cs b/Database/GameDatabase.cs index 5ebe74f..3c408ad 100644 --- a/Database/GameDatabase.cs +++ b/Database/GameDatabase.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.ServerHandler; namespace Synix_Control_Panel.Database @@ -3136,4 +3137,4 @@ public class PostInstallStep public string FileContent { get; init; } = ""; } } -} \ No newline at end of file +} diff --git a/Database/WarningDatabase.cs b/Database/WarningDatabase.cs index 77c3482..98a42ca 100644 --- a/Database/WarningDatabase.cs +++ b/Database/WarningDatabase.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.ServerHandler; namespace Synix_Control_Panel.Database @@ -99,4 +100,4 @@ private void btnNo_Click(object sender, EventArgs e) this.Close(); } } -} \ No newline at end of file +} diff --git a/Design/GridHelper.cs b/Design/GridHelper.cs index 8c81711..f16d15a 100644 --- a/Design/GridHelper.cs +++ b/Design/GridHelper.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ namespace Synix_Control_Panel.Design { public static class GridHelper @@ -44,3 +45,4 @@ public static void RefreshWithPersistence(DataGridView dgv, object dataSource) } } } + diff --git a/Design/GridStyler.cs b/Design/GridStyler.cs index b460938..3a3fd5b 100644 --- a/Design/GridStyler.cs +++ b/Design/GridStyler.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Windows.Forms.DataVisualization.Charting; using static Synix_Control_Panel.SynixEngine.Core; @@ -198,4 +199,4 @@ public static void SetStatusColor(DataGridView dgv, DataGridViewCellFormattingEv } } } -} \ No newline at end of file +} diff --git a/Design/UIStyleHelper.cs b/Design/UIStyleHelper.cs index fb712fd..3e96505 100644 --- a/Design/UIStyleHelper.cs +++ b/Design/UIStyleHelper.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.ComponentModel; using System.Drawing.Drawing2D; @@ -186,4 +187,4 @@ public static void StyleWarningLabel(Label lbl, string alignment = "MiddleCenter lbl.Invalidate(); } } -} \ No newline at end of file +} diff --git a/FileFolderHandler/FileHandler.cs b/FileFolderHandler/FileHandler.cs index 022c7ca..0ca833a 100644 --- a/FileFolderHandler/FileHandler.cs +++ b/FileFolderHandler/FileHandler.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.FileFolderHandler; // Points to your CreateFolders utility using System.Text.Json; @@ -126,4 +127,4 @@ public static bool Copy(string sourceFilePath, string targetFolderPath, string t } } } -} \ No newline at end of file +} diff --git a/FileFolderHandler/FolderHandler.cs b/FileFolderHandler/FolderHandler.cs index 22a12c2..181bf39 100644 --- a/FileFolderHandler/FolderHandler.cs +++ b/FileFolderHandler/FolderHandler.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ namespace Synix_Control_Panel.FileFolderHandler { public static class FolderHandler @@ -76,4 +77,4 @@ public static bool Rename(GameServer oldServer, GameServer newServer) } } } -} \ No newline at end of file +} diff --git a/Help/DefaultArgumentsDisplay.cs b/Help/DefaultArgumentsDisplay.cs index d382daa..5ffb9a7 100644 --- a/Help/DefaultArgumentsDisplay.cs +++ b/Help/DefaultArgumentsDisplay.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ namespace Synix_Control_Panel.Help { public partial class DefaultArgumentsDisplay : Form @@ -25,3 +26,4 @@ private void btnClose_Click(object sender, EventArgs e) } } } + diff --git a/LICENSE.md b/LICENSE.md index e0ab3fe..4431f19 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,10 +1,23 @@ -## 📜 License & Legal Information -*Copyright © 2026 ubidzz. All Rights Reserved.* +# SYNIX CONTROL PANEL - LIMITED PROPRIETARY LICENSE +**Copyright (c) 2026 Jason Turner. All Rights Reserved.** -The **Synix Control Panel** is a proprietary software project. -* **Permitted Use:** Free for personal, non-commercial use. -* **Source Code:** Provided for transparent viewing and educational purposes only. -* **Strict Restrictions:** Redistribution, public modification, or commercial resale of the code or compiled binaries is strictly prohibited. +### 1. GRANT OF LICENSE +Permission is hereby granted to any individual to download, view, and modify the source code of the **Synix Control Panel** for **PERSONAL, NON-COMMERCIAL USE ONLY**. -**This code is provided for transparent viewing and personal use only.** -Unauthorized distribution, public modification, or commercial use of this source code or the compiled executable is strictly prohibited. Please refer to the LICENSE file in the root directory for full terms. \ No newline at end of file +### 2. MODIFICATIONS +You are permitted to modify the source code to suit your personal requirements. However, you are **STRICTLY PROHIBITED** from publishing, sharing, or distributing these modifications (forks) to the public without express written consent from the original author. + +### 3. PROHIBITIONS & RESTRICTIONS +* **NO COMMERCIAL USE:** You may not sell, lease, rent, or profit from this software, its source code, or any compiled binaries. +* **NO REBRANDING:** You may not remove the "Synix" name, logos, or author credits. You may not represent this software as your own creation. +* **NO REDISTRIBUTION:** You may not host this software or its source code on other repositories, websites, or file-sharing platforms for public download. +* **NO DECOMPILATION:** You may not attempt to reverse engineer or decompile the official binaries for the purpose of removing licensing checks or branding. + +### 4. OWNERSHIP & TRADEMARK +"Synix" and its associated code are the intellectual property of Jason Turner. This license does not transfer ownership. Any unauthorized use of the Synix brand for commercial purposes is a violation of trademark and copyright law. + +### 5. TERMINATION & LEGAL ACTION +Any violation of these terms automatically terminates your right to use or possess this software. The author reserves the right to take legal action, including DMCA takedown notices and civil litigation, against any party found to be in violation of this license. + +### 6. DISCLAIMER +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY. \ No newline at end of file diff --git a/MainGUI.cs b/MainGUI.cs index e89fafe..a286db2 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Design; using Synix_Control_Panel.ServerHandler; using Synix_Control_Panel.SteamCMDHandler; @@ -32,6 +33,8 @@ public partial class MainGUI : Form private const int maxGraphPoints = 60; private static Font boldFont = new Font("Segoe UI", 9, FontStyle.Bold); private static Font regularFont = new Font("Segoe UI", 9, FontStyle.Regular); + private bool isPrivacyLoading = false; + private System.Windows.Forms.Timer? versionTimer; public MainGUI() { @@ -45,6 +48,7 @@ public MainGUI() GridStyler.ApplyTransparentTheme(dataGridView1); Instance = this; _ = LoadNetworkInfo(); + InitializeVersionCheckTimer(); _ = VersionCheck(); } @@ -194,7 +198,7 @@ await Task.Run(() => Design.GridStyler.HeartbeatChart(chartHeartbeat, systemTotalRamGb); Design.GridStyler.DashboardLabels(lblTotalCpu, lblTotalRam); - // 🎯 THE GRAPH FIX: Shove a dummy point in and FORCE the heavy graphics engine to draw instantly + // 🎯 THE GRAPH FIX: Shove a dummy point in and FORCE the heavy graphics engine to draw instantly chartHeartbeat.Series["TotalCPU"].Points.AddXY(chartTickCounter, 0); chartHeartbeat.Series["TotalRAM"].Points.AddXY(chartTickCounter, 0); chartHeartbeat.Update(); // This is the magic line that prevents the UI freeze @@ -205,14 +209,14 @@ await Task.Run(() => isDownloadActive = true; await Task.Delay(100); - AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); + AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); AppendLog("Checking SteamCMD dependencies..."); await Task.Run(() => SteamCMD.EnsureSteamCMD(msg => AppendLog(msg))); isDownloadActive = false; AppendLog("Initialization complete."); - AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); + AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); } public void UpdateGrid() @@ -250,10 +254,10 @@ private async void btnAddServer_Click(object sender, EventArgs e) // UI-specific check if (isInitializing) return; isDownloadActive = true; - AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); + AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); await Core.Instance.AddServerAndReport(); isDownloadActive = false; - AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); + AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); } private void btnEdit_Click(object sender, EventArgs e) @@ -280,10 +284,10 @@ private async void btnUpdate_Click(object sender, EventArgs e) if (dataGridView1.CurrentRow?.DataBoundItem is GameServer selectedServer) { isDownloadActive = true; - AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); + AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); await Core.Instance.UpdateServerAndReport(selectedServer); isDownloadActive = false; - AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); + AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); } else { @@ -299,10 +303,10 @@ private async void btnFileValidation_Click(object sender, EventArgs e) if (dataGridView1.CurrentRow?.DataBoundItem is GameServer selectedServer) { isDownloadActive = true; - AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); + AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); await Core.Instance.ValidationServerAndReport(selectedServer); isDownloadActive = false; - AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); + AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); } else { @@ -333,13 +337,13 @@ private async void btnBackup_Click(object sender, EventArgs e) // 1. SELECTION CHECKS if (dataGridView1.CurrentRow == null) { - AppendLog("[🚨 ERROR] No row is currently selected!", Color.Red); + AppendLog("[🚨 ERROR] No row is currently selected!", Color.Red); return; } if (!(dataGridView1.CurrentRow.DataBoundItem is GameServer selectedServer)) { - AppendLog("[🚨 ERROR] Invalid GameServer object!", Color.Red); + AppendLog("[🚨 ERROR] Invalid GameServer object!", Color.Red); return; } @@ -352,15 +356,15 @@ private async void btnBackup_Click(object sender, EventArgs e) // 2. STATUS CHECK: Ensure the server is Stopped before zipping if (selectedServer.Status != StatusManager.GetStatus(ServerState.Stopped)) { - AppendLog($"[🚨 ERROR] {selectedServer.ServerName} must be Stopped to perform a backup.", Color.Orange); + AppendLog($"[🚨 ERROR] {selectedServer.ServerName} must be Stopped to perform a backup.", Color.Orange); return; } selectedServer.Status = Core.StatusManager.GetStatus(Core.ServerState.BackingUp); isDownloadActive = true; - AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); - AppendLog($"[💾 BACKUP] Starting backup compression for {selectedServer.ServerName}...", Color.Cyan); + AppendLog($"[🔒 WARNING] Synix close window button is now Disabled!", Color.Orange, true); + AppendLog($"[💾 BACKUP] Starting backup compression for {selectedServer.ServerName}...", Color.Cyan); await Task.Run(() => { @@ -369,24 +373,24 @@ await Task.Run(() => isDownloadActive = false; selectedServer.Status = Core.StatusManager.GetStatus(Core.ServerState.Stopped); - AppendLog($"[💾 BACKUP] Finished backing up {selectedServer.ServerName}.", Color.LimeGreen); - AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); + AppendLog($"[💾 BACKUP] Finished backing up {selectedServer.ServerName}.", Color.LimeGreen); + AppendLog($"[🔓 WARNING] Synix close window button is now Enabled!", Color.Orange, true); UpdateGrid(); } - // 🎯 1. Change 'void' to 'async void' (standard for event handlers) + // 🎯 1. Change 'void' to 'async void' (standard for event handlers) private async void btnStart_Click(object sender, EventArgs e) { // 1. SELECTION CHECKS if (dataGridView1.CurrentRow == null) { - AppendLog("[🚨 ERROR] No row is currently selected!", Color.Red); + AppendLog("[🚨 ERROR] No row is currently selected!", Color.Red); return; } if (!(dataGridView1.CurrentRow.DataBoundItem is GameServer selectedServer)) { - AppendLog("[🚨 ERROR] Invalid GameServer object!", Color.Red); + AppendLog("[🚨 ERROR] Invalid GameServer object!", Color.Red); return; } @@ -397,13 +401,13 @@ private async void btnStart_Click(object sender, EventArgs e) return; } - // 🎯 THE RESOURCE SAFEGUARD: Check CPU and RAM before we do anything else + // 🎯 THE RESOURCE SAFEGUARD: Check CPU and RAM before we do anything else // This respects your 5GB Windows buffer logic if (!Core.Instance.PassResourceGuard(out string guardMsg)) { AppendLog(guardMsg, Color.Red, true); // Bold red for critical resource warnings MessageBox.Show(guardMsg, "System Resource Exhaustion", MessageBoxButtons.OK, MessageBoxIcon.Warning); - return; // 🛑 Launch aborted + return; // 🛑 Launch aborted } if (!Core.Instance.ValidateIntegrityAndReport(selectedServer)) return; @@ -416,7 +420,7 @@ private async void btnStart_Click(object sender, EventArgs e) UpdateGrid(); } - // 🚀 4. EXECUTION: The actual launch process + // 🚀 4. EXECUTION: The actual launch process await Servers.Start(selectedServer, msg => { this.Invoke((MethodInvoker)delegate @@ -432,7 +436,7 @@ private void btnStop_Click(object sender, EventArgs e) { if (dataGridView1.CurrentRow?.DataBoundItem is GameServer selectedServer) { - // 🛡️ 1. THE SPAM LOCK + // 🛡️ 1. THE SPAM LOCK if (!Core.Instance.PassStopSpamLock(selectedServer, out string lockMsg)) { // If it's already stopping or dead, print the orange warning and block the click! @@ -440,7 +444,7 @@ private void btnStop_Click(object sender, EventArgs e) return; } - // 🚀 2. SENDS COMMAND: MainGUI -> Engine -> Servers.cs + // 🚀 2. SENDS COMMAND: MainGUI -> Engine -> Servers.cs // If it passes the bouncer, hand it off to the Engine to do the graceful shutdown. Core.Instance.StopServerAndReport(selectedServer); } @@ -489,7 +493,7 @@ private void btnOpenBackup_Click(object sender, EventArgs e) string rootBackupPath = @"C:\Synix\BackupGames"; - // 🎯 These lines will no longer have CS0103 errors: + // 🎯 These lines will no longer have CS0103 errors: string cleanGame = BackupManager.GetSafeName(selectedServer.Game); string cleanServer = BackupManager.GetSafeName(selectedServer.ServerName); @@ -502,7 +506,7 @@ private void btnOpenBackup_Click(object sender, EventArgs e) } else { - AppendLog($"[📜 INFO] Creating directory: {fullPath}", Color.Yellow); + AppendLog($"[📜 INFO] Creating directory: {fullPath}", Color.Yellow); try { Directory.CreateDirectory(fullPath); @@ -517,7 +521,7 @@ private async void btnPublicConnection_Click(object sender, EventArgs e) var selectedServer = GetSelectedServer(); if (selectedServer == null) return; - Core.Instance.Log($"[📡 NETWORK] Testing WAN Connectivity for {selectedServer.ServerName}...", Color.White); + Core.Instance.Log($"[📡 NETWORK] Testing WAN Connectivity for {selectedServer.ServerName}...", Color.White); try { @@ -529,16 +533,16 @@ private async void btnPublicConnection_Click(object sender, EventArgs e) if (isResponding) { - Core.Instance.Log($"[🛰️ ONLINE] {selectedServer.ServerName} is visible at {publicIp}:{selectedServer.QueryPort}!", Color.Green); + Core.Instance.Log($"[🛰️ ONLINE] {selectedServer.ServerName} is visible at {publicIp}:{selectedServer.QueryPort}!", Color.Green); } else { - Core.Instance.Log($"[🔒 BLOCK] {selectedServer.ServerName} is running but HIDDEN. Check Router/Firewall for UDP {selectedServer.QueryPort} or try setting a different query port.", Color.Red); + Core.Instance.Log($"[🔒 BLOCK] {selectedServer.ServerName} is running but HIDDEN. Check Router/Firewall for UDP {selectedServer.QueryPort} or try setting a different query port.", Color.Red); } } catch (Exception ex) { - Core.Instance.Log($"[🚨 ERROR] Could not retrieve Public IP: {ex.Message}", Color.Yellow); + Core.Instance.Log($"[🚨 ERROR] Could not retrieve Public IP: {ex.Message}", Color.Yellow); } } @@ -547,7 +551,7 @@ private async void btnLocalConnection_Click(object sender, EventArgs e) var selectedServer = GetSelectedServer(); if (selectedServer == null) return; - Core.Instance.Log($"[📡 NETWORK] Testing LAN Connectivity for {selectedServer.ServerName}...", Color.White); + Core.Instance.Log($"[📡 NETWORK] Testing LAN Connectivity for {selectedServer.ServerName}...", Color.White); try { @@ -559,16 +563,16 @@ private async void btnLocalConnection_Click(object sender, EventArgs e) if (isResponding) { - Core.Instance.Log($"[🛰️ ONLINE] {selectedServer.ServerName} is visible at {localIp}:{selectedServer.QueryPort}!", Color.Green); + Core.Instance.Log($"[🛰️ ONLINE] {selectedServer.ServerName} is visible at {localIp}:{selectedServer.QueryPort}!", Color.Green); } else { - Core.Instance.Log($"[🔒 BLOCK] {selectedServer.ServerName} is running but HIDDEN. Check Router/Firewall for UDP {selectedServer.QueryPort} or try setting a different query port.", Color.Red); + Core.Instance.Log($"[🔒 BLOCK] {selectedServer.ServerName} is running but HIDDEN. Check Router/Firewall for UDP {selectedServer.QueryPort} or try setting a different query port.", Color.Red); } } catch (Exception ex) { - Core.Instance.Log($"[🚨 ERROR] Could not retrieve Public IP: {ex.Message}", Color.Yellow); + Core.Instance.Log($"[🚨 ERROR] Could not retrieve Public IP: {ex.Message}", Color.Yellow); } } @@ -611,6 +615,22 @@ private void btnHelp_Click(object sender, EventArgs e) } } + private void InitializeVersionCheckTimer() + { + versionTimer = new System.Windows.Forms.Timer(); + + // 20 minutes * 60 seconds * 1000 milliseconds + versionTimer.Interval = 20 * 60 * 1000; + + versionTimer.Tick += async (sender, e) => + { + // This fires every 20 minutes in the background + await VersionCheck(); + }; + + versionTimer.Start(); + } + private async Task VersionCheck() { string currentVersion = "Unknown"; @@ -660,7 +680,7 @@ private async Task VersionCheck() // 3. COMPARE THE TWO STRINGS if (latestVersion == currentVersion) { - lblUpdateStatus.Text = " ✔ You are running the latest version " + currentVersion; + lblUpdateStatus.Text = " ✔ You are running the latest version " + currentVersion; lblUpdateStatus.ForeColor = Color.Black; lblUpdateStatus.TextAlign = ContentAlignment.MiddleRight; lblUpdateStatus.BackColor = Color.Green; @@ -668,7 +688,7 @@ private async Task VersionCheck() else { // If it's still failing, this will tell us WHY by showing what it thinks currentVersion is - lblUpdateStatus.Text = " ⚠️ A newer Synix " + latestVersion + " version is available! Running Version: " + currentVersion + ""; + lblUpdateStatus.Text = " ⚠️ A newer Synix " + latestVersion + " version is available! Running Version: " + currentVersion + ""; lblUpdateStatus.ForeColor = Color.White; lblUpdateStatus.TextAlign = ContentAlignment.MiddleRight; lblUpdateStatus.BackColor = Color.Red; @@ -680,7 +700,7 @@ private async Task VersionCheck() } catch { - lblUpdateStatus.Text = "[🚨 ERROR] Could not check for updates."; + lblUpdateStatus.Text = "[🚨 ERROR] Could not check for updates."; lblUpdateStatus.ForeColor = Color.Black; lblUpdateStatus.TextAlign = ContentAlignment.MiddleRight; lblUpdateStatus.BackColor = Color.Red; @@ -705,8 +725,8 @@ private void btnDownloadUpdate_Click(object sender, EventArgs e) catch (Exception ex) { // If something goes wrong, log it so you can see it in your video - AppendLog($"[🚨 ERROR] Could not open browser: {ex.Message}", Color.Red); + AppendLog($"[🚨 ERROR] Could not open browser: {ex.Message}", Color.Red); } } } -} \ No newline at end of file +} diff --git a/MonitoringHandler/ResourceMonitor.cs b/MonitoringHandler/ResourceMonitor.cs index 626d8a8..d1fc506 100644 --- a/MonitoringHandler/ResourceMonitor.cs +++ b/MonitoringHandler/ResourceMonitor.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.SynixEngine; using System.Diagnostics; using System.Management; @@ -193,4 +194,4 @@ public static double GetGlobalCpuUsage() } } } -} \ No newline at end of file +} diff --git a/MonitoringHandler/ResourceMonitorGUI.cs b/MonitoringHandler/ResourceMonitorGUI.cs index 04b0428..3de996e 100644 --- a/MonitoringHandler/ResourceMonitorGUI.cs +++ b/MonitoringHandler/ResourceMonitorGUI.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Reflection; namespace Synix_Control_Panel @@ -208,4 +209,4 @@ private void ResourceMonitorGUI_FormClosed(object sender, FormClosedEventArgs e) GC.WaitForPendingFinalizers(); } } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 19ecaa8..cb275ec 100644 --- a/README.md +++ b/README.md @@ -109,16 +109,6 @@ Synix solves the "Hidden Server" mystery with a proprietary two-tier diagnostic --- -## 📜 License & Legal Information -*Copyright © 2026 ubidzz. All Rights Reserved.* - -The **Synix Control Panel** is a proprietary software project. -* **Permitted Use:** Free for personal, non-commercial use. -* **Source Code:** Provided for transparent viewing and educational purposes only. -* **Strict Restrictions:** Redistribution, public modification, or commercial resale of the code or compiled binaries is strictly prohibited. - ---- - UI image image diff --git a/ServerHandler/ConfigHandler.cs b/ServerHandler/ConfigHandler.cs index 25b8b57..3342752 100644 --- a/ServerHandler/ConfigHandler.cs +++ b/ServerHandler/ConfigHandler.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -363,4 +364,4 @@ private static void SaveXML(string path, List data) } } } -} \ No newline at end of file +} diff --git a/ServerHandler/GameFix.cs b/ServerHandler/GameFix.cs index 185ad6e..f458eaa 100644 --- a/ServerHandler/GameFix.cs +++ b/ServerHandler/GameFix.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.SynixEngine; namespace Synix_Control_Panel.ServerHandler @@ -436,4 +437,4 @@ private static bool CopySteamDLLs(string installPath, string BinariesDir) return filesCopied; } } -} \ No newline at end of file +} diff --git a/ServerHandler/GameServer.cs b/ServerHandler/GameServer.cs index b610b02..c185733 100644 --- a/ServerHandler/GameServer.cs +++ b/ServerHandler/GameServer.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.ServerHandler; using System.Diagnostics; @@ -105,4 +106,4 @@ public string UptimeDisplay return $"{duration.Hours:D2}:{duration.Minutes:D2}:{duration.Seconds:D2}"; } } -} \ No newline at end of file +} diff --git a/ServerHandler/ScheduleSettingsGUI.cs b/ServerHandler/ScheduleSettingsGUI.cs index 408b460..2330dfe 100644 --- a/ServerHandler/ScheduleSettingsGUI.cs +++ b/ServerHandler/ScheduleSettingsGUI.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ namespace Synix_Control_Panel.ServerHandler { public partial class ScheduleSettingsGUI : Form @@ -66,4 +67,4 @@ private void btnCancel_Click(object sender, EventArgs e) this.Close(); } } -} \ No newline at end of file +} diff --git a/ServerHandler/ServerConfig.cs b/ServerHandler/ServerConfig.cs index 8a785c7..0093a77 100644 --- a/ServerHandler/ServerConfig.cs +++ b/ServerHandler/ServerConfig.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ namespace Synix_Control_Panel.ServerHandler { public partial class ServerConfig : Form @@ -102,4 +103,4 @@ private void btnSave_Click(object sender, EventArgs e) this.Close(); } } -} \ No newline at end of file +} diff --git a/ServerHandler/ServerSettingsGUI.cs b/ServerHandler/ServerSettingsGUI.cs index a9eb758..ae2640b 100644 --- a/ServerHandler/ServerSettingsGUI.cs +++ b/ServerHandler/ServerSettingsGUI.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.Help; using Synix_Control_Panel.ServerHandler; @@ -323,6 +324,14 @@ private void btnSave_Click(object sender, EventArgs e) if (!Core.Instance.ValidatePortsAndReport(_existingServer, gPort, qPort, rPort, chkEnableRcon.Checked, aPort ?? 0, numAppPort.Enabled, selectedGame)) return; string newPath = txtInstallPath.Text.Trim(); NewServer = new GameServer { Game = selectedGame, ServerName = newName, Port = gPort, QueryPort = qPort, RconPort = rPort, AppPort = aPort, Password = txtPassword.Text, AdminPassword = txtAdminPassword.Text, MaxPlayers = (int)numMaxPlayers.Value, WorldName = cmbWorldName.Text, GameMode = cmbCompetitive.Text, WorldSeed = txtWorldSeed.Text.Trim(), ExtraArgs = txtExtraArgs.Text, IsDefaultPath = chkDefaultPath.Checked, UpdateOnStart = chkUpdateOnStart.Checked, EnableRcon = chkEnableRcon.Checked, RconPassword = txtRconPassword.Text, InstallPath = newPath, IsScheduledRestartEnabled = chkEnableSchedule.Checked, RestartTime = _selectedTime, RestartDays = (bool[])_selectedDays.Clone(), IsDiscordAlertEnabled = chkEnableDiscord.Checked, DiscordWebhook = txtDiscordWebhook.Text.Trim(), Status = _existingServer?.Status ?? StatusManager.GetStatus(ServerState.Stopped), BackupOnStart = chkBackupOnStart.Checked }; + + if (!IsGameServerConfigSafe(NewServer)) + { + MessageBox.Show("Security Alert: One of your inputs contains illegal characters.", + "Input Blocked", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + try { if (_isEditMode && _existingServer != null) @@ -375,4 +384,4 @@ private void cmbGame_SelectedIndexChanged(object sender, EventArgs e) private void PopulateMaps(GameInfo gameData, string selectedMap) { cmbWorldName.Items.Clear(); if (gameData.Maps == null) return; foreach (var map in gameData.Maps) cmbWorldName.Items.Add(map); cmbWorldName.Text = selectedMap; } private void PopulateGameModes(GameInfo gameData, string selectedMode) { cmbCompetitive.Items.Clear(); if (gameData.GameModes == null) return; foreach (var mode in gameData.GameModes) cmbCompetitive.Items.Add(mode); if (cmbCompetitive.Items.Contains(selectedMode)) cmbCompetitive.SelectedItem = selectedMode; else if (cmbCompetitive.Items.Count > 0) cmbCompetitive.SelectedIndex = 0; } } -} \ No newline at end of file +} diff --git a/ServerHandler/Servers.cs b/ServerHandler/Servers.cs index 5ed2885..7347d1f 100644 --- a/ServerHandler/Servers.cs +++ b/ServerHandler/Servers.cs @@ -1,16 +1,18 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.SynixEngine; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Runtime.InteropServices; using static Synix_Control_Panel.SynixEngine.Core; @@ -169,6 +171,28 @@ public static async Task Start(GameServer server, Action logCallback, St args = args.Replace("{mode}", translatedMode); } + // 🛡️ SECURITY GUARD: Validate ExtraArgs before appending + if (!string.IsNullOrWhiteSpace(server.ExtraArgs)) + { + if (!IsGameServerConfigSafe(server.ExtraArgs)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected in the extra arguments. Aborting startup.", Color.Red); + server.Status = StatusManager.GetStatus(ServerState.Stopped); + return; + } + + args = $"{args} \"{server.ExtraArgs.Trim()}\""; + } + + args = args.Replace(" ", " ").Trim(); + + if (!IsStringSafe(args)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected. Aborting startup.", Color.Red); + server.Status = StatusManager.GetStatus(ServerState.Stopped); + return; + } + // 🚀 7. CONFIGURE PROCESS ProcessStartInfo psi = new() { @@ -282,4 +306,4 @@ private static void FinalizeStoppedState(GameServer server) server.RunningProcess = null; } } -} \ No newline at end of file +} diff --git a/SteamCMDHandler/ServerInstaller.cs b/SteamCMDHandler/ServerInstaller.cs index 8614dc5..150a61c 100644 --- a/SteamCMDHandler/ServerInstaller.cs +++ b/SteamCMDHandler/ServerInstaller.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Diagnostics; namespace Synix_Control_Panel.SteamCMDHandler @@ -111,4 +112,4 @@ public static string GetSteamError(int code) }; } } -} \ No newline at end of file +} diff --git a/SteamCMDHandler/SteamCMD.cs b/SteamCMDHandler/SteamCMD.cs index 8e3fea3..058b59d 100644 --- a/SteamCMDHandler/SteamCMD.cs +++ b/SteamCMDHandler/SteamCMD.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Diagnostics; using System.IO.Compression; @@ -90,4 +91,4 @@ public static async Task EnsureSteamCMD(Action logCallback) } } } -} \ No newline at end of file +} diff --git a/SynixEngine/Actions.cs b/SynixEngine/Actions.cs index 2481587..fc9ff16 100644 --- a/SynixEngine/Actions.cs +++ b/SynixEngine/Actions.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.FileFolderHandler; using Synix_Control_Panel.ServerHandler; @@ -595,4 +596,4 @@ await Servers.Start(server, msg => UpdateGridStatus(); } } -} \ No newline at end of file +} diff --git a/SynixEngine/BackupManager.cs b/SynixEngine/BackupManager.cs index 7468f3a..f772bdb 100644 --- a/SynixEngine/BackupManager.cs +++ b/SynixEngine/BackupManager.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.IO.Compression; namespace Synix_Control_Panel.SynixEngine @@ -75,4 +76,4 @@ public static string GetSafeName(string name) return name.Replace(" ", "_"); } } -} \ No newline at end of file +} diff --git a/SynixEngine/Core.cs b/SynixEngine/Core.cs index 9abf1ab..e036f88 100644 --- a/SynixEngine/Core.cs +++ b/SynixEngine/Core.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ // 🎯 THE FIX: You must include this so the Core knows what a "GameServer" is using System.Diagnostics; using System.Text; @@ -225,4 +226,4 @@ public static bool IsSystemSafeToStart() public bool IsBasicInfoValid(string name, string game) => !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(game); public bool IsServerSetupValid(string name, string game) => !string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(game); } -} \ No newline at end of file +} diff --git a/SynixEngine/PortChecker.cs b/SynixEngine/PortChecker.cs index ec018af..eb003e7 100644 --- a/SynixEngine/PortChecker.cs +++ b/SynixEngine/PortChecker.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -88,4 +89,4 @@ public bool IsPortInUseLocally(int port) catch { return false; } } } -} \ No newline at end of file +} diff --git a/SynixEngine/Validator.cs b/SynixEngine/Validator.cs index 62cd16b..6ca5bc4 100644 --- a/SynixEngine/Validator.cs +++ b/SynixEngine/Validator.cs @@ -8,11 +8,15 @@ // prohibited. Please refer to the LICENSE file in the root // directory for full terms. using Synix_Control_Panel.Database; +using System.Reflection; +using System.Text.RegularExpressions; namespace Synix_Control_Panel.SynixEngine { public partial class Core { + private static readonly Regex SafeRegex = new Regex(@"^[a-zA-Z0-9\s\-+:\""\\/._=?,]*$", RegexOptions.Compiled); + public bool CanServerStart(GameServer server, out string errorMessage) { var dbEntry = GameDatabase.GetGame(server.Game); @@ -255,5 +259,48 @@ public bool PassResourceGuard(out string message) return true; } + // 1. The dedicated string checker (Used by Start sequence) + public static bool IsStringSafe(string input) + { + // If it's empty, it's safe (no injection possible) + if (string.IsNullOrWhiteSpace(input)) return true; + + // Block directory traversal climbing + if (input.Contains("..")) return false; + + // Run the regex (Make sure SafeRegex includes = ? , if you need them for games like ARK/Rust) + return SafeRegex.IsMatch(input); + } + + // 2. The object checker (Used by Save button) + public static bool IsGameServerConfigSafe(object obj) + { + if (obj == null) return false; + + // SAFETY CATCH: If someone accidentally passes a direct string into the object checker, + // route it to the string checker instead of using Reflection. + if (obj is string directString) + { + return IsStringSafe(directString); + } + + PropertyInfo[] properties = obj.GetType().GetProperties(); + + foreach (var prop in properties) + { + if (prop.PropertyType == typeof(string)) + { + string value = (string)prop.GetValue(obj); + + // Pass the extracted string to our dedicated checker + if (!IsStringSafe(value)) + { + Core.Instance.Log($"[🚨 SECURITY] Illegal characters found in property: {prop.Name}"); + return false; + } + } + } + return true; + } } } \ No newline at end of file diff --git a/SynixEngine/Watchdog.cs b/SynixEngine/Watchdog.cs index 3abd1f4..c1e1a66 100644 --- a/SynixEngine/Watchdog.cs +++ b/SynixEngine/Watchdog.cs @@ -1,14 +1,15 @@ -/* - * Copyright (c) 2026 ubidzz. All Rights Reserved. - * - * This file is part of Synix Control Panel. - * - * This code is provided for transparent viewing and personal use only. - * Unauthorized distribution, public modification, or commercial - * use of this source code or the compiled executable is strictly - * prohibited. Please refer to the LICENSE file in the root - * directory for full terms. - */ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ using Synix_Control_Panel.Database; using System.Diagnostics; @@ -244,4 +245,4 @@ private long GetBytesPerSecond() } } } -} \ No newline at end of file +} From f9c53899cc30695da1c54769304ae8b4fd485367 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Mon, 25 May 2026 10:25:48 -0400 Subject: [PATCH 2/8] updated the style look and fixed a app id in the db --- Database/GameDatabase.cs | 2 +- Design/GridStyler.cs | 79 +++++++++++++++- MainGUI.Designer.cs | 156 ++++++++++++++++---------------- MainGUI.cs | 1 + MainGUI.resx | 4 +- Properties/Settings.Designer.cs | 2 +- ServerHandler/GameServer.cs | 4 +- ServerHandler/Servers.cs | 57 ++++++------ SynixEngine/Actions.cs | 4 +- SynixEngine/Status.cs | 2 +- 10 files changed, 196 insertions(+), 115 deletions(-) diff --git a/Database/GameDatabase.cs b/Database/GameDatabase.cs index 7d714b3..8566a41 100644 --- a/Database/GameDatabase.cs +++ b/Database/GameDatabase.cs @@ -281,7 +281,7 @@ public static class GameDatabase }, new() { Game = "Abiotic Factor", - AppID = "2816220", + AppID = "2857200", ExeName = @"AbioticFactor\Binaries\Win64\AbioticFactorServer-Win64-Shipping.exe", RequiredArgs = "{map}?Listen -log -MaxPlayers={MaxPlayers} -Port={port} -QueryPort={query} -ServerPassword=\"{pass}\" -SteamAppId={steamAppID}", RelativeConfigPath = @"AbioticFactor\Saved\Config\WindowsServer\GameUserSettings.ini", diff --git a/Design/GridStyler.cs b/Design/GridStyler.cs index ab23612..6ef5623 100644 --- a/Design/GridStyler.cs +++ b/Design/GridStyler.cs @@ -12,6 +12,7 @@ // ============================================================================ using System.Windows.Forms.DataVisualization.Charting; using static Synix_Control_Panel.SynixEngine.Core; +using System.Drawing.Drawing2D; namespace Synix_Control_Panel.Design { @@ -39,7 +40,8 @@ public static void DarkTheme(DataGridView dgv) if (dgv.Columns.Contains("colGame")) dgv.Columns["colGame"].DataPropertyName = "Game"; if (dgv.Columns.Contains("colPort")) dgv.Columns["colPort"].DataPropertyName = "Port"; if (dgv.Columns.Contains("colStatus")) dgv.Columns["colStatus"].DataPropertyName = "Status"; - dgv.Columns["PlayerCountDisplay"].DefaultCellStyle.ForeColor = Color.Cyan; + //if (dgv.Columns.Contains("colPlayerCount")) dgv.Columns["colPlayerCount"].DataPropertyName = "PlayerCount"; + //if (dgv.Columns.Contains("colUptime")) dgv.Columns["colUptime"].DataPropertyName = "Uptime"; // Header Style (Kills the blue Game column) dgv.EnableHeadersVisualStyles = false; @@ -57,8 +59,42 @@ public static void DarkTheme(DataGridView dgv) } } + public static void ApplyRoundedCorners(DataGridView dgv, int radius) + { + // Apply the rounded corners immediately + UpdateGridRegion(dgv, radius); + + // Ensure the rounded corners recalculate if the form is resized + dgv.Resize += (s, e) => UpdateGridRegion(dgv, radius); + } + + private static void UpdateGridRegion(DataGridView dgv, int radius) + { + if (dgv == null || dgv.Width == 0 || dgv.Height == 0) return; + + int diameter = radius * 2; + GraphicsPath path = new GraphicsPath(); + + path.StartFigure(); + // Top Left Corner + path.AddArc(new Rectangle(0, 0, diameter, diameter), 180, 90); + // Top Right Corner + path.AddArc(new Rectangle(dgv.Width - diameter, 0, diameter, diameter), 270, 90); + // Bottom Right Corner + path.AddArc(new Rectangle(dgv.Width - diameter, dgv.Height - diameter, diameter, diameter), 0, 90); + // Bottom Left Corner + path.AddArc(new Rectangle(0, dgv.Height - diameter, diameter, diameter), 90, 90); + path.CloseFigure(); + + // Apply the new region and dispose of the old one to prevent memory leaks + Region oldRegion = dgv.Region; + dgv.Region = new Region(path); + oldRegion?.Dispose(); + } + public static void ApplyTransparentTheme(DataGridView dgv) { + dgv.RowHeadersVisible = false; dgv.BackgroundColor = BackgroundBlack; dgv.BorderStyle = BorderStyle.None; @@ -70,6 +106,47 @@ public static void ApplyTransparentTheme(DataGridView dgv) dgv.GridColor = Color.FromArgb(45, 45, 45); dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + + // Add this line to trigger the custom border drawing + dgv.RowPostPaint -= Dgv_PaintGlowingSelection; // Prevent multiple subscriptions + dgv.RowPostPaint += Dgv_PaintGlowingSelection; + } + + private static void Dgv_PaintGlowingSelection(object sender, DataGridViewRowPostPaintEventArgs e) + { + DataGridView dgv = sender as DataGridView; + if (dgv == null) return; + + if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected) + { + // 1. Get the exact width of the data columns, skipping the row header + int startX = dgv.RowHeadersVisible ? dgv.RowHeadersWidth : 0; + int width = dgv.Columns.GetColumnsWidth(DataGridViewElementStates.Visible) - dgv.HorizontalScrollingOffset; + + // 2. Define the bounds. We inset by 2 pixels so the thickest pen doesn't get clipped. + Rectangle bounds = new Rectangle(startX + 2, e.RowBounds.Y + 2, width - 5, e.RowBounds.Height - 5); + + Color neonColor = Color.DarkCyan; // The color of the glow + + // LAYER 1: The wide, faint blur (Width 5) + using (Pen outerGlow = new Pen(Color.FromArgb(40, neonColor), 5)) + { + e.Graphics.DrawRectangle(outerGlow, bounds); + } + + // LAYER 2: The tighter, brighter blur (Width 3) + using (Pen innerGlow = new Pen(Color.FromArgb(100, neonColor), 3)) + { + e.Graphics.DrawRectangle(innerGlow, bounds); + } + + // LAYER 3: The intense hot core (Width 1) + // Using White makes it look like actual glowing gas, but you can change this back to Lime if you prefer. + using (Pen corePen = new Pen(Color.White, 1)) + { + e.Graphics.DrawRectangle(corePen, bounds); + } + } } public static void PaintTransparentRows(DataGridView dgv, DataGridViewCellPaintingEventArgs e) diff --git a/MainGUI.Designer.cs b/MainGUI.Designer.cs index 8672cc0..257ea97 100644 --- a/MainGUI.Designer.cs +++ b/MainGUI.Designer.cs @@ -34,13 +34,6 @@ private void InitializeComponent() System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainGUI)); dataGridView1 = new DataGridView(); - colGame = new DataGridViewTextBoxColumn(); - colName = new DataGridViewTextBoxColumn(); - colPort = new DataGridViewTextBoxColumn(); - colQueryPort = new DataGridViewTextBoxColumn(); - PlayerCountDisplay = new DataGridViewTextBoxColumn(); - UptimeDisplay = new DataGridViewTextBoxColumn(); - colStatus = new DataGridViewTextBoxColumn(); rtbLog = new RichTextBox(); btnStart = new Button(); btnStop = new Button(); @@ -59,8 +52,8 @@ private void InitializeComponent() toolStripSeparator5 = new ToolStripSeparator(); updateServerToolStripMenuItem = new ToolStripMenuItem(); fileValidationToolStripMenuItem = new ToolStripMenuItem(); - backupServerToolStripMenuItem = new ToolStripMenuItem(); btnExportBatch = new ToolStripMenuItem(); + backupServerToolStripMenuItem = new ToolStripMenuItem(); toolStripSeparator3 = new ToolStripSeparator(); connectionTestToolStripMenuItem = new ToolStripMenuItem(); connectionLocalTestToolStripMenuItem = new ToolStripMenuItem(); @@ -76,6 +69,13 @@ private void InitializeComponent() lblUpdateStatus = new Label(); btnDownloadUpdate = new Button(); chkPrivacyMode = new CheckBox(); + colGame = new DataGridViewTextBoxColumn(); + colName = new DataGridViewTextBoxColumn(); + colPort = new DataGridViewTextBoxColumn(); + colQueryPort = new DataGridViewTextBoxColumn(); + colPlayerCount = new DataGridViewTextBoxColumn(); + colUptime = new DataGridViewTextBoxColumn(); + colStatus = new DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit(); ((System.ComponentModel.ISupportInitialize)logo).BeginInit(); ((System.ComponentModel.ISupportInitialize)chartHeartbeat).BeginInit(); @@ -88,7 +88,7 @@ private void InitializeComponent() dataGridView1.AllowUserToDeleteRows = false; dataGridView1.BorderStyle = BorderStyle.None; dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dataGridView1.Columns.AddRange(new DataGridViewColumn[] { colGame, colName, colPort, colQueryPort, PlayerCountDisplay, UptimeDisplay, colStatus }); + dataGridView1.Columns.AddRange(new DataGridViewColumn[] { colGame, colName, colPort, colQueryPort, colPlayerCount, colUptime, colStatus }); dataGridView1.Location = new Point(12, 140); dataGridView1.Name = "dataGridView1"; dataGridView1.ReadOnly = true; @@ -98,61 +98,6 @@ private void InitializeComponent() dataGridView1.CellFormatting += dataGridView1_CellFormatting; dataGridView1.CellPainting += dataGridView1_CellPainting; // - // colGame - // - colGame.DataPropertyName = "Game"; - colGame.HeaderText = "Game"; - colGame.Name = "colGame"; - colGame.ReadOnly = true; - colGame.Width = 200; - // - // colName - // - colName.DataPropertyName = "ServerName"; - colName.HeaderText = "Server Name"; - colName.Name = "colName"; - colName.ReadOnly = true; - colName.Width = 220; - // - // colPort - // - colPort.DataPropertyName = "Port"; - colPort.HeaderText = "Port"; - colPort.Name = "colPort"; - colPort.ReadOnly = true; - colPort.Width = 80; - // - // colQueryPort - // - colQueryPort.DataPropertyName = "QueryPort"; - colQueryPort.HeaderText = "Query Port"; - colQueryPort.Name = "colQueryPort"; - colQueryPort.ReadOnly = true; - colQueryPort.Width = 80; - // - // PlayerCountDisplay - // - PlayerCountDisplay.DataPropertyName = "PlayerCountDisplay"; - PlayerCountDisplay.HeaderText = "Players"; - PlayerCountDisplay.Name = "PlayerCountDisplay"; - PlayerCountDisplay.ReadOnly = true; - PlayerCountDisplay.Width = 70; - // - // UptimeDisplay - // - UptimeDisplay.DataPropertyName = "UptimeDisplay"; - UptimeDisplay.HeaderText = "UPTIME"; - UptimeDisplay.Name = "UptimeDisplay"; - UptimeDisplay.ReadOnly = true; - // - // colStatus - // - colStatus.DataPropertyName = "Status"; - colStatus.HeaderText = "Status"; - colStatus.Name = "colStatus"; - colStatus.ReadOnly = true; - colStatus.Width = 90; - // // rtbLog // rtbLog.BackColor = SystemColors.ActiveCaptionText; @@ -246,12 +191,12 @@ private void InitializeComponent() // contextMenuStrip.Items.AddRange(new ToolStripItem[] { btnHelp, openServerConfig, installServer, toolStripSeparator1 }); contextMenuStrip.Name = "contextMenuStrip"; - contextMenuStrip.Size = new Size(181, 98); + contextMenuStrip.Size = new Size(152, 76); // // btnHelp // btnHelp.Name = "btnHelp"; - btnHelp.Size = new Size(180, 22); + btnHelp.Size = new Size(151, 22); btnHelp.Text = "Help"; btnHelp.Click += btnHelp_Click; // @@ -259,7 +204,7 @@ private void InitializeComponent() // openServerConfig.DropDownItems.AddRange(new ToolStripItem[] { openServerFolderToolStripMenuItem, backupToolStripMenuItem, toolStripSeparator2, editServerToolStripMenuItem, openServerConfigFileToolStripMenuItem, toolStripSeparator5, updateServerToolStripMenuItem, fileValidationToolStripMenuItem, btnExportBatch, backupServerToolStripMenuItem, toolStripSeparator3, connectionTestToolStripMenuItem, connectionLocalTestToolStripMenuItem, toolStripSeparator4, deleteServerToolStripMenuItem }); openServerConfig.Name = "openServerConfig"; - openServerConfig.Size = new Size(180, 22); + openServerConfig.Size = new Size(151, 22); openServerConfig.Text = "Server Options"; // // openServerFolderToolStripMenuItem @@ -314,13 +259,6 @@ private void InitializeComponent() fileValidationToolStripMenuItem.Text = "Game Validation"; fileValidationToolStripMenuItem.Click += btnFileValidation_Click; // - // backupServerToolStripMenuItem - // - backupServerToolStripMenuItem.Name = "backupServerToolStripMenuItem"; - backupServerToolStripMenuItem.Size = new Size(196, 22); - backupServerToolStripMenuItem.Text = "Backup Server"; - backupServerToolStripMenuItem.Click += btnBackup_Click; - // // btnExportBatch // btnExportBatch.Name = "btnExportBatch"; @@ -328,6 +266,13 @@ private void InitializeComponent() btnExportBatch.Text = "Export to Batch File"; btnExportBatch.Click += btnExportBatch_Click; // + // backupServerToolStripMenuItem + // + backupServerToolStripMenuItem.Name = "backupServerToolStripMenuItem"; + backupServerToolStripMenuItem.Size = new Size(196, 22); + backupServerToolStripMenuItem.Text = "Backup Server"; + backupServerToolStripMenuItem.Click += btnBackup_Click; + // // toolStripSeparator3 // toolStripSeparator3.Name = "toolStripSeparator3"; @@ -362,14 +307,14 @@ private void InitializeComponent() // installServer // installServer.Name = "installServer"; - installServer.Size = new Size(180, 22); + installServer.Size = new Size(151, 22); installServer.Text = "Install Server"; installServer.Click += btnAddServer_Click; // // toolStripSeparator1 // toolStripSeparator1.Name = "toolStripSeparator1"; - toolStripSeparator1.Size = new Size(177, 6); + toolStripSeparator1.Size = new Size(148, 6); // // btnServerActions // @@ -465,6 +410,61 @@ private void InitializeComponent() chkPrivacyMode.UseVisualStyleBackColor = true; chkPrivacyMode.CheckedChanged += chkPrivacyMode_CheckedChanged; // + // colGame + // + colGame.DataPropertyName = "Game"; + colGame.HeaderText = "Game"; + colGame.Name = "colGame"; + colGame.ReadOnly = true; + colGame.Width = 200; + // + // colName + // + colName.DataPropertyName = "ServerName"; + colName.HeaderText = "Server Name"; + colName.Name = "colName"; + colName.ReadOnly = true; + colName.Width = 260; + // + // colPort + // + colPort.DataPropertyName = "Port"; + colPort.HeaderText = "Port"; + colPort.Name = "colPort"; + colPort.ReadOnly = true; + colPort.Width = 80; + // + // colQueryPort + // + colQueryPort.DataPropertyName = "QueryPort"; + colQueryPort.HeaderText = "Query Port"; + colQueryPort.Name = "colQueryPort"; + colQueryPort.ReadOnly = true; + colQueryPort.Width = 80; + // + // colPlayerCount + // + colPlayerCount.DataPropertyName = "PlayerCount"; + colPlayerCount.HeaderText = "Players"; + colPlayerCount.Name = "colPlayerCount"; + colPlayerCount.ReadOnly = true; + colPlayerCount.Width = 70; + // + // colUptime + // + colUptime.DataPropertyName = "Uptime"; + colUptime.HeaderText = "UPTIME"; + colUptime.Name = "colUptime"; + colUptime.ReadOnly = true; + // + // colStatus + // + colStatus.DataPropertyName = "Status"; + colStatus.HeaderText = "Status"; + colStatus.Name = "colStatus"; + colStatus.ReadOnly = true; + colStatus.Width = 90; + // // MainGUI // AutoScaleDimensions = new SizeF(7F, 15F); @@ -547,8 +547,8 @@ private void InitializeComponent() private DataGridViewTextBoxColumn colName; private DataGridViewTextBoxColumn colPort; private DataGridViewTextBoxColumn colQueryPort; - private DataGridViewTextBoxColumn PlayerCountDisplay; - private DataGridViewTextBoxColumn UptimeDisplay; + private DataGridViewTextBoxColumn colPlayerCount; + private DataGridViewTextBoxColumn colUptime; private DataGridViewTextBoxColumn colStatus; } } diff --git a/MainGUI.cs b/MainGUI.cs index b367fc4..1cac0c4 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -45,6 +45,7 @@ public MainGUI() FileHandler.LoadServers(); _ = Core.Instance; GridStyler.DarkTheme(dataGridView1); + GridStyler.ApplyRoundedCorners(dataGridView1, 10); UIStyleHelper.InitializeToggles(this); dataGridView1.DataSource = serverList; typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); diff --git a/MainGUI.resx b/MainGUI.resx index 8dce2c9..e26e0f3 100644 --- a/MainGUI.resx +++ b/MainGUI.resx @@ -129,10 +129,10 @@ True - + True - + True diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index cef0111..e9c309c 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Synix_Control_Panel.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/ServerHandler/GameServer.cs b/ServerHandler/GameServer.cs index c185733..0688cf2 100644 --- a/ServerHandler/GameServer.cs +++ b/ServerHandler/GameServer.cs @@ -80,7 +80,7 @@ public class GameServer : GameInfo public bool IsFirstBoot { get; set; } = true; public string WorldSeed { get; set; } = "12345"; [JsonIgnore] - public string PlayerCountDisplay => $"{CurrentPlayers} / {MaxPlayers}"; + public string PlayerCount => $"{CurrentPlayers} / {MaxPlayers}"; public int? AppPort { get; set; } = 10777; public bool UpdateOnStart { get; set; } = false; public bool BackupOnStart { get; set; } = false; @@ -91,7 +91,7 @@ public class GameServer : GameInfo public bool IsProbing { get; set; } = false; [JsonIgnore] - public string UptimeDisplay + public string Uptime { get { diff --git a/ServerHandler/Servers.cs b/ServerHandler/Servers.cs index 8df1d05..e3dd19d 100644 --- a/ServerHandler/Servers.cs +++ b/ServerHandler/Servers.cs @@ -85,39 +85,38 @@ public static async Task Start(GameServer server, Action logCallb string targetId = dbEntry.AppID; string invokedId = targetId; - string appidPath = ""; + // 1. FAST CHECK: Look in the two exact places it belongs first (0ms operation) + string rootAppIdPath = Path.Combine(server.InstallPath, "steam_appid.txt"); + string binAppIdPath = Path.Combine(binDir, "steam_appid.txt"); + string appidPath = rootAppIdPath; // Default fallback - try + if (File.Exists(rootAppIdPath)) { - // This creates a "scanner" that looks through every single subfolder - var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions - { - // Keep looking through every subfolder - RecurseSubdirectories = true, - - // If it hits a folder it can't open (locked/protected), skip it and keep going - IgnoreInaccessible = true, - - // Use the maximum possible depth (effectively unlimited) - MaxRecursionDepth = int.MaxValue, - - // Skip things like symlinks to avoid getting stuck in a loop - AttributesToSkip = FileAttributes.ReparsePoint - }); - - // Find the first one that exists - appidPath = scanner.FirstOrDefault(); + appidPath = rootAppIdPath; } - catch + else if (File.Exists(binAppIdPath)) { - // If something goes catastrophic, fallback to the root - appidPath = Path.Combine(server.InstallPath, "steam_appid.txt"); + appidPath = binAppIdPath; } - - // If it's still empty, it truly isn't in that install folder - if (string.IsNullOrEmpty(appidPath)) + else { - appidPath = Path.Combine(server.InstallPath, "steam_appid.txt"); + // 2. LAST RESORT: Only run the heavy recursive scan if it's completely missing + try + { + var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + MaxRecursionDepth = 5, // Limit to 5 folders deep so it doesn't hang on 200k files + AttributesToSkip = FileAttributes.ReparsePoint + }); + + appidPath = scanner.FirstOrDefault() ?? rootAppIdPath; + } + catch + { + appidPath = rootAppIdPath; + } } // 🎯 THE INVOKE: Pull the ID from the file for {steamAppID} @@ -126,6 +125,10 @@ public static async Task Start(GameServer server, Action logCallb try { string fileContent = File.ReadAllText(appidPath).Trim(); + + // Safety: If SteamCMD adds weird invisible characters or newlines, strictly grab the first line + fileContent = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? ""; + if (!string.IsNullOrWhiteSpace(fileContent)) { invokedId = fileContent; diff --git a/SynixEngine/Actions.cs b/SynixEngine/Actions.cs index eb5ea12..8f7804d 100644 --- a/SynixEngine/Actions.cs +++ b/SynixEngine/Actions.cs @@ -412,14 +412,14 @@ public async Task ExecuteStartSequence(GameServer server, string status = "") Core.Instance.UpdateGridStatus(); } - if (stopServer) + if (stopServer && server.Status != StatusManager.GetStatus(ServerState.Stopping)) { Log($"[SYNIX] Stoping the {server.ServerName} server.", Color.Cyan, true); await StopServerAndReport(server); } - await Task.Delay(4000); + await Task.Delay(1000); if (server.Status == StatusManager.GetStatus(ServerState.Stopped)) { diff --git a/SynixEngine/Status.cs b/SynixEngine/Status.cs index 5088a59..bf99ad1 100644 --- a/SynixEngine/Status.cs +++ b/SynixEngine/Status.cs @@ -175,7 +175,7 @@ public async Task UpdatePlayerCount(GameServer server) { if (server.Status != StatusManager.GetStatus(ServerState.Running)) return; - // 🎯 Use your dynamic LAN IP and Loopback + // 🎯 Use dynamic LAN IP and Loopback string localIp = await Core.Instance.GetLocalIP(); var targets = new List { "127.0.0.1", localIp }.Where(x => !string.IsNullOrEmpty(x)).Distinct(); From e829e12f170b1940c7d1531fa8b5039d9830ddb2 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Tue, 26 May 2026 16:27:09 -0400 Subject: [PATCH 3/8] updatinging the style --- Design/GridStyler.cs | 208 +++++++++++++++++++++++- Images/discord.png | Bin 0 -> 6829 bytes Images/github.png | Bin 0 -> 8528 bytes MainGUI.Designer.cs | 246 ++++++++++++++++++----------- MainGUI.cs | 79 ++++++++++ Properties/Resources.Designer.cs | 20 +++ Properties/Resources.resx | 6 + ServerHandler/Servers.cs | 261 ++++++++++++++++--------------- SynixEngine/version.txt | 2 +- 9 files changed, 598 insertions(+), 224 deletions(-) create mode 100644 Images/discord.png create mode 100644 Images/github.png diff --git a/Design/GridStyler.cs b/Design/GridStyler.cs index 6ef5623..2152508 100644 --- a/Design/GridStyler.cs +++ b/Design/GridStyler.cs @@ -40,8 +40,8 @@ public static void DarkTheme(DataGridView dgv) if (dgv.Columns.Contains("colGame")) dgv.Columns["colGame"].DataPropertyName = "Game"; if (dgv.Columns.Contains("colPort")) dgv.Columns["colPort"].DataPropertyName = "Port"; if (dgv.Columns.Contains("colStatus")) dgv.Columns["colStatus"].DataPropertyName = "Status"; - //if (dgv.Columns.Contains("colPlayerCount")) dgv.Columns["colPlayerCount"].DataPropertyName = "PlayerCount"; - //if (dgv.Columns.Contains("colUptime")) dgv.Columns["colUptime"].DataPropertyName = "Uptime"; + if (dgv.Columns.Contains("colPlayerCount")) dgv.Columns["colPlayerCount"].DataPropertyName = "PlayerCount"; + if (dgv.Columns.Contains("colUptime")) dgv.Columns["colUptime"].DataPropertyName = "Uptime"; // Header Style (Kills the blue Game column) dgv.EnableHeadersVisualStyles = false; @@ -59,6 +59,210 @@ public static void DarkTheme(DataGridView dgv) } } + public static void StyleMinimizeButton(Button btn) + { + // Strip the default UI + btn.FlatStyle = FlatStyle.Flat; + btn.FlatAppearance.BorderSize = 0; + btn.BackColor = Color.Transparent; + btn.FlatAppearance.MouseOverBackColor = Color.Transparent; + btn.FlatAppearance.MouseDownBackColor = Color.Transparent; + + btn.Text = ""; + btn.TabStop = false; + + // Override the Paint event for the smooth pill shape + btn.Paint += (s, e) => + { + Button b = (Button)s; + e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + + Point mousePos = b.PointToClient(System.Windows.Forms.Cursor.Position); + bool isHovering = b.ClientRectangle.Contains(mousePos); + bool isPressed = isHovering && (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left; + + Color bgColor = Color.WhiteSmoke; + Color fgColor = Color.Black; + + // UPDATED: Much darker, highly visible gray hover colors + if (isPressed) + { + bgColor = Color.FromArgb(160, 160, 160); // Darker gray for click + } + else if (isHovering) + { + bgColor = Color.FromArgb(200, 200, 200); // Noticeable gray for hover + } + + // Draw the smooth background curve + using (var path = GetRoundedPath(b.ClientRectangle, 6)) + using (var brush = new SolidBrush(bgColor)) + { + e.Graphics.FillPath(brush, path); + } + + // Draw a perfect, crisp minimize line + int lineWidth = 12; + int lineThickness = 2; + + // Calculate exact center + int xPos = (b.Width / 2) - (lineWidth / 2); + int yPos = (b.Height / 2) - (lineThickness / 2) + 2; + + using (SolidBrush lineBrush = new SolidBrush(fgColor)) + { + e.Graphics.FillRectangle(lineBrush, xPos, yPos, lineWidth, lineThickness); + } + }; + + // Force instant redraws on mouse interaction + btn.MouseEnter += (s, e) => btn.Invalidate(); + btn.MouseLeave += (s, e) => btn.Invalidate(); + btn.MouseDown += (s, e) => btn.Invalidate(); + btn.MouseUp += (s, e) => btn.Invalidate(); + } + + public static void StyleIconButton(Button btn, Image icon, Color hoverColor) + { + // 1. Strip the default UI + btn.FlatStyle = FlatStyle.Flat; + btn.FlatAppearance.BorderSize = 0; + btn.BackColor = Color.Transparent; + btn.FlatAppearance.MouseOverBackColor = Color.Transparent; + btn.FlatAppearance.MouseDownBackColor = Color.Transparent; + + btn.Text = ""; + btn.TabStop = false; + + // 2. Override the Paint event for the smooth pill shape and image + btn.Paint += (s, e) => + { + Button b = (Button)s; + e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + + // This makes sure the PNG scales down smoothly without looking pixelated + e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + + Point mousePos = b.PointToClient(System.Windows.Forms.Cursor.Position); + bool isHovering = b.ClientRectangle.Contains(mousePos); + bool isPressed = isHovering && (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left; + + Color bgColor = Color.WhiteSmoke; // Default background to match your other buttons + + // Apply custom hover/click colors + if (isPressed) + { + bgColor = Color.DarkGray; + } + else if (isHovering) + { + bgColor = hoverColor; + } + + // Draw the smooth background curve + using (var path = GetRoundedPath(b.ClientRectangle, 6)) + using (var brush = new SolidBrush(bgColor)) + { + e.Graphics.FillPath(brush, path); + } + + // 3. Draw the Icon perfectly centered + if (icon != null) + { + // Calculate a size that fits comfortably inside the pill with a 4px padding + int iconSize = Math.Min(b.Width, b.Height) - 8; + int x = (b.Width - iconSize) / 2; + int y = (b.Height - iconSize) / 2; + + e.Graphics.DrawImage(icon, new Rectangle(x, y, iconSize, iconSize)); + } + }; + + // 4. Force instant redraws on mouse interaction + btn.MouseEnter += (s, e) => btn.Invalidate(); + btn.MouseLeave += (s, e) => btn.Invalidate(); + btn.MouseDown += (s, e) => btn.Invalidate(); + btn.MouseUp += (s, e) => btn.Invalidate(); + } + + public static void StyleCloseButton(Button btn) + { + // 1. Strip the default UI and make it perfectly transparent + btn.FlatStyle = FlatStyle.Flat; + btn.FlatAppearance.BorderSize = 0; + btn.BackColor = Color.Transparent; + btn.FlatAppearance.MouseOverBackColor = Color.Transparent; + btn.FlatAppearance.MouseDownBackColor = Color.Transparent; + + // Clear the standard text because we will draw it manually to layer it correctly + btn.Text = ""; + btn.TabStop = false; + + // 2. Override the Paint event to draw a high-quality smooth shape + btn.Paint += (s, e) => + { + Button b = (Button)s; + + // Turn on high-quality edge smoothing + e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + + // Determine if the mouse is hovering or actively clicking + Point mousePos = b.PointToClient(System.Windows.Forms.Cursor.Position); + bool isHovering = b.ClientRectangle.Contains(mousePos); + bool isPressed = isHovering && (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left; + + Color bgColor = Color.WhiteSmoke; + Color fgColor = Color.Black; + + if (isPressed) + { + bgColor = Color.FromArgb(178, 11, 22); // Dark Red (Click) + fgColor = Color.White; + } + else if (isHovering) + { + bgColor = Color.FromArgb(232, 17, 35); // Bright Red (Hover) + fgColor = Color.White; + } + + // Draw the smooth background + using (var path = GetRoundedPath(b.ClientRectangle, 6)) + using (var brush = new SolidBrush(bgColor)) + { + e.Graphics.FillPath(brush, path); + } + + // Draw the text exactly in the center + TextRenderer.DrawText( + e.Graphics, + "✕", + new Font("Segoe UI", 10, FontStyle.Bold), + b.ClientRectangle, + fgColor, + TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter + ); + }; + + // 3. Force the button to redraw itself instantly when the mouse interacts with it + btn.MouseEnter += (s, e) => btn.Invalidate(); + btn.MouseLeave += (s, e) => btn.Invalidate(); + btn.MouseDown += (s, e) => btn.Invalidate(); + btn.MouseUp += (s, e) => btn.Invalidate(); + } + + private static System.Drawing.Drawing2D.GraphicsPath GetRoundedPath(Rectangle rect, int radius) + { + System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath(); + int d = radius * 2; + path.StartFigure(); + path.AddArc(rect.X, rect.Y, d, d, 180, 90); + path.AddArc(rect.Width - d - 1, rect.Y, d, d, 270, 90); + path.AddArc(rect.Width - d - 1, rect.Height - d - 1, d, d, 0, 90); + path.AddArc(rect.X, rect.Height - d - 1, d, d, 90, 90); + path.CloseFigure(); + return path; + } + public static void ApplyRoundedCorners(DataGridView dgv, int radius) { // Apply the rounded corners immediately diff --git a/Images/discord.png b/Images/discord.png new file mode 100644 index 0000000000000000000000000000000000000000..d27a3053466d9c1d65a2b73ce541b6744a86ec43 GIT binary patch literal 6829 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

>!$Af@P@nHq4Ta@ zcZ~InXpML)FRtw{9#1AJoQ7Y>o&J5Oqy1d0XSc>=zK(3Ktw6H==eq{yV&zf*zoEC& zXskHa?4u3+ja;ix67sM%TJ~S6^Ffb_D2N%R6=j z3|0L$yx7#F#4XiLJ~NO1i|OUdwYp~A+}wRTZcYoYC6{^nlUBxm@DkoxYyraN^Tkl{ z?PRz6@z3`k-p$tJ9`1YvP6$(|1yzW?dSWnH3^* zMKm&tT7REvn3u?gdaONpnu%EQHm%$X$BgWJs*8<{^q6-Ta@o0f-Z0(tP_DhKDLvp< zb1hwpo$P%D9{8NVF1Ob8T6n>(>Jh7g|E2V$h~+S|N#ek=M4o);REGM^=?@kC5fLE0 zNlAN(O87OK(AELT@x*kg39m-kDwD`c2NPlM(*w18);+sOd7Z@uP9Rc53G{ zsbsnl_ec-s{6_+ZH~P_ca2OH9IX$*wGv(2GtXmV0Ses{^lw*7+d#0a;#?pd^6PHS5 zcM5KXOgjU|Ig>=K9NrZ8?6LY&)&QpUqPJMPm(T*tqFWx_nmgO+G~0>G4W&lNz5@d@ z?80rY!hVpGQv>av?z<-Om1L{px!~>8r2nL*h;VQEKltt|vNyxM)0)2VL*|YjPv8WX z=pDAxR`(OVHs4|Y@zH~j(w$Tmm20&(oy)h_IJY+If|Y@@Lw6k8D;RI0i8Y^NZ2mU* zjhgVZTh^iww%t$RyozPriXs-~Wt%CIJHwWEk9|Rj2 zMH^crU=f|&Q<>vaGCIR+=<2OEmX|H&ZMy;dvDB#ANC}N?Ao=8!DJh= zg8Y$+2zYDIp@fyfS|usgs(Ik)>zd0q2P^$KV@T|JtEjv2eHTFuEL&g7a4MDXW#!7v z+%TggpYC87L$8H7U%_>y`ZGs0ryw53xH|QLiAMqxwIp>>RH;{pi{!@aHLd#f5WbrE zaPQvzp|GfY`GXkl>v3lqA<#rjXuXNc=wU_Dzd=()DtxB$`GbpJC+{X%#?40M9`Y7f zZ;TwT(ARSUx5)kf3X$XL&Q8mwdCGV(bHn!9i2(`lo!M@`Cz4z#YHL$U1u>e10QVS} zkKS5Z;xy`;evvZjm*mwkiI+w~F~!1ae3qqjzs5%*<)iA`V3`bQninfj0TV#y zjdfbjm5?7~qfUagK189xm&g{ryGpwww>(DH1LwuCms(%`NcuO{t;HWDa4NJXL38FW zN5QPut<^S_?WRR~cO>YlB7Xd*-=_G(lWvET3_F)yCA7)Dj9YNRgcIatKM5(-Sva${ zLd~6Z%?gZHw^KXrH=KqI*ie_tqA@v=(amY88-3pj z$7IOAE!r%LfRZbmFOlDg-ulf+87cn6S0DXW9NW2C+Kkuly6IpciV>P}q=P^}SUk_k z09U3JwjH*!PU*qAAoQsVqps=*$&u!+v-VoE-^gaxkUTYexAf@oV(<|ld7esy{DaW3 zj7tIL^B4cOSnCfmqtlJV1+gV~frL~LVe<0M{yDpXgda2j#|TCOA%6EW-?ZUoRvkCHNQa{RR-SXhYLxl3Ea$8=C6HoatiAej?;bwxERhy02x|BNGIb~n~jGX)8l#X zPC0eUdvty3m>Lc^xlnxoZaYXPb)*1%)ga0{&@+oDc z)HrNg>v|unRkf^pv`M|qzuAjSni44&l1JC-KUf9n=SIPDk^qFtrB(vG5C~pyXZfPM!34Hp1pvR>=|z(f65=Iy5`Ky%Fi4*S{$INP g`tE-|C}#n5(jz6G^3Is?|DOOViW&+Pa;Aa*2S`Bwc>n+a literal 0 HcmV?d00001 diff --git a/Images/github.png b/Images/github.png new file mode 100644 index 0000000000000000000000000000000000000000..89a55a1a986b793219af85d3ffe568bc2e8a4e53 GIT binary patch literal 8528 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 + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap discord_icon { + get { + object obj = ResourceManager.GetObject("discord_icon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + ///

+ /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap github_icon { + get { + object obj = ResourceManager.GetObject("github_icon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 25cd251..f38c0ae 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -121,6 +121,12 @@ ..\Images\background.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\discord.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\github.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Images\logo.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/ServerHandler/Servers.cs b/ServerHandler/Servers.cs index e3dd19d..d9eb80b 100644 --- a/ServerHandler/Servers.cs +++ b/ServerHandler/Servers.cs @@ -12,10 +12,10 @@ // ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.SynixEngine; +using static Synix_Control_Panel.SynixEngine.Core; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Runtime.InteropServices; -using static Synix_Control_Panel.SynixEngine.Core; namespace Synix_Control_Panel.ServerHandler { @@ -40,9 +40,12 @@ public static class Servers public static async Task Start(GameServer server, Action logCallback, StartContext context = StartContext.Manual) { - if (!IsSystemSafeToStart()) return; try { + // 1. HARDWARE CHECKS (Backgrounded to prevent WMI/PerfCounter UI Freezes) + bool isSystemSafe = await Task.Run(() => IsSystemSafeToStart()); + if (!isSystemSafe) return; + if (!Core.Instance.PassResourceGuard(out string guardMsg)) { logCallback?.Invoke(guardMsg, Color.Orange); @@ -50,7 +53,6 @@ public static async Task Start(GameServer server, Action logCallb return; } - // 1. PRE-FLIGHT (Backup & Update) if (server.BackupOnStart && context != StartContext.CrashRecovery) { await Task.Run(() => Core.Instance.ExecuteBackup(server, context)); @@ -61,166 +63,167 @@ public static async Task Start(GameServer server, Action logCallb await Task.Run(() => Core.Instance.UpdateServerAndReport(server, "UPDATE", true)); } - // 2. TEMPLATE VALIDATION + // Safely update the DataGridView UI state on the main thread server.Status = StatusManager.GetStatus(ServerState.Starting); - var dbEntry = GameDatabase.GetGame(server.Game); - if (dbEntry == null) - { - logCallback?.Invoke("[🚨 ERROR] Game template not found.", Color.Red); - return; - } + MainGUI.Instance?.Invoke((Action)(() => MainGUI.Instance.UpdateGrid())); - // 3. PATH SETUP - string fullExePath = Path.Combine(server.InstallPath, dbEntry.ExeName); - string binDir = Path.GetDirectoryName(fullExePath) ?? ""; + ProcessStartInfo? psi = null; + string finalArgs = ""; - if (!File.Exists(fullExePath)) + // 2. HEAVY DISK & STRING PROCESSING (Backgrounded to prevent lag) + await Task.Run(() => { - logCallback?.Invoke($"[🚨 ERROR] Executable missing: {fullExePath}", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; - } - - // 4. DYNAMIC IDENTITY & SEARCH - string targetId = dbEntry.AppID; - string invokedId = targetId; + var dbEntry = GameDatabase.GetGame(server.Game); + if (dbEntry == null) + { + logCallback?.Invoke("[🚨 ERROR] Game template not found.", Color.Red); + return; + } - // 1. FAST CHECK: Look in the two exact places it belongs first (0ms operation) - string rootAppIdPath = Path.Combine(server.InstallPath, "steam_appid.txt"); - string binAppIdPath = Path.Combine(binDir, "steam_appid.txt"); - string appidPath = rootAppIdPath; // Default fallback + string fullExePath = Path.Combine(server.InstallPath, dbEntry.ExeName); + string binDir = Path.GetDirectoryName(fullExePath) ?? ""; - if (File.Exists(rootAppIdPath)) - { - appidPath = rootAppIdPath; - } - else if (File.Exists(binAppIdPath)) - { - appidPath = binAppIdPath; - } - else - { - // 2. LAST RESORT: Only run the heavy recursive scan if it's completely missing - try + if (!File.Exists(fullExePath)) { - var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions - { - RecurseSubdirectories = true, - IgnoreInaccessible = true, - MaxRecursionDepth = 5, // Limit to 5 folders deep so it doesn't hang on 200k files - AttributesToSkip = FileAttributes.ReparsePoint - }); - - appidPath = scanner.FirstOrDefault() ?? rootAppIdPath; + logCallback?.Invoke($"[🚨 ERROR] Executable missing: {fullExePath}", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; } - catch + + string targetId = dbEntry.AppID; + string invokedId = targetId; + + string rootAppIdPath = Path.Combine(server.InstallPath, "steam_appid.txt"); + string binAppIdPath = Path.Combine(binDir, "steam_appid.txt"); + string appidPath = rootAppIdPath; + + if (File.Exists(rootAppIdPath)) { appidPath = rootAppIdPath; } - } + else if (File.Exists(binAppIdPath)) + { + appidPath = binAppIdPath; + } + else + { + try + { + var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + MaxRecursionDepth = 5, + AttributesToSkip = FileAttributes.ReparsePoint + }); + + appidPath = scanner.FirstOrDefault() ?? rootAppIdPath; + } + catch + { + appidPath = rootAppIdPath; + } + } - // 🎯 THE INVOKE: Pull the ID from the file for {steamAppID} - if (File.Exists(appidPath)) - { - try + if (File.Exists(appidPath)) { - string fileContent = File.ReadAllText(appidPath).Trim(); + try + { + string fileContent = File.ReadAllText(appidPath).Trim(); - // Safety: If SteamCMD adds weird invisible characters or newlines, strictly grab the first line - fileContent = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? ""; + fileContent = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? ""; - if (!string.IsNullOrWhiteSpace(fileContent)) - { - invokedId = fileContent; + if (!string.IsNullOrWhiteSpace(fileContent)) + { + invokedId = fileContent; + } } + catch (Exception ex) { logCallback?.Invoke($"[⚠️ WARNING] File Read Error: {ex.Message}", Color.OrangeRed); } } - catch (Exception ex) { logCallback?.Invoke($"[⚠️ WARNING] File Read Error: {ex.Message}", Color.OrangeRed); } - } - // 🛠️ 6. ARGUMENT REPLACEMENT - string cleanIdentity = Core.Instance.GetSafeName(server.ServerName); - - string args = dbEntry.RequiredArgs - .Replace("{app_port}", server.AppPort?.ToString() ?? "0") - .Replace("{seed}", string.IsNullOrWhiteSpace(server.WorldSeed) ? "12345" : server.WorldSeed) - .Replace("{map}", server.WorldName) - .Replace("{steamAppID}", invokedId) - .Replace("{appid}", targetId) - .Replace("{port}", server.Port.ToString()) - .Replace("{query}", server.QueryPort.ToString()) - .Replace("{MaxPlayers}", server.MaxPlayers.ToString()) - .Replace("{pass}", server.Password ?? "") - .Replace("{adminpass}", server.AdminPassword ?? "") - .Replace("{ServerName}", server.ServerName) - .Replace("{InstallPath}", server.InstallPath) - .Replace("{Identity}", cleanIdentity); - - // 🎯 RCON LOGIC RESTORED - if (args.Contains("{rcon}")) - { - string formattedRcon = server.EnableRcon && !string.IsNullOrWhiteSpace(dbEntry.RconSyntax) - ? dbEntry.RconSyntax.Replace("{rcon_port}", server.RconPort.ToString()).Replace("{rcon_pass}", server.RconPassword ?? "") - : ""; - args = args.Replace("{rcon}", formattedRcon); - } + string cleanIdentity = Core.Instance.GetSafeName(server.ServerName); + + string args = dbEntry.RequiredArgs + .Replace("{app_port}", server.AppPort?.ToString() ?? "0") + .Replace("{seed}", string.IsNullOrWhiteSpace(server.WorldSeed) ? "12345" : server.WorldSeed) + .Replace("{map}", server.WorldName) + .Replace("{steamAppID}", invokedId) + .Replace("{appid}", targetId) + .Replace("{port}", server.Port.ToString()) + .Replace("{query}", server.QueryPort.ToString()) + .Replace("{MaxPlayers}", server.MaxPlayers.ToString()) + .Replace("{pass}", server.Password ?? "") + .Replace("{adminpass}", server.AdminPassword ?? "") + .Replace("{ServerName}", server.ServerName) + .Replace("{InstallPath}", server.InstallPath) + .Replace("{Identity}", cleanIdentity); + + if (args.Contains("{rcon}")) + { + string formattedRcon = server.EnableRcon && !string.IsNullOrWhiteSpace(dbEntry.RconSyntax) + ? dbEntry.RconSyntax.Replace("{rcon_port}", server.RconPort.ToString()).Replace("{rcon_pass}", server.RconPassword ?? "") + : ""; + args = args.Replace("{rcon}", formattedRcon); + } - // 🎯 GAME MODE TRANSLATION RESTORED - if (args.Contains("{mode}") && !string.IsNullOrWhiteSpace(server.GameMode)) - { - string translatedMode = (server.GameMode == "PVE" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) - ? "True" : (server.GameMode == "PVP" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) - ? "False" : server.GameMode; - args = args.Replace("{mode}", translatedMode); - } + if (args.Contains("{mode}") && !string.IsNullOrWhiteSpace(server.GameMode)) + { + string translatedMode = (server.GameMode == "PVE" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) + ? "True" : (server.GameMode == "PVP" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) + ? "False" : server.GameMode; + args = args.Replace("{mode}", translatedMode); + } - // 🛡️ SECURITY GUARD: Validate ExtraArgs before appending - if (!string.IsNullOrWhiteSpace(server.ExtraArgs)) - { - if (!IsGameServerConfigSafe(server.ExtraArgs)) + if (!string.IsNullOrWhiteSpace(server.ExtraArgs)) { - logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected in the extra arguments. Aborting startup.", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; + if (!IsGameServerConfigSafe(server.ExtraArgs)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected in the extra arguments. Aborting startup.", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; + } + + args = $"{args} \"{server.ExtraArgs.Trim()}\""; } - args = $"{args} \"{server.ExtraArgs.Trim()}\""; - } + args = args.Replace(" ", " ").Trim(); - args = args.Replace(" ", " ").Trim(); + if (!IsStringSafe(args)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected. Aborting startup.", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; + } - if (!IsStringSafe(args)) - { - logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected. Aborting startup.", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; - } + // Package the final validated strings into process parameters + finalArgs = args; + psi = new ProcessStartInfo + { + FileName = fullExePath, + Arguments = finalArgs, + WorkingDirectory = binDir, + UseShellExecute = false, + CreateNoWindow = false + }; - // 🚀 7. CONFIGURE PROCESS - ProcessStartInfo psi = new() - { - FileName = fullExePath, - Arguments = args, - WorkingDirectory = binDir, - UseShellExecute = false, - CreateNoWindow = false - }; + psi.EnvironmentVariables["SteamAppId"] = invokedId; + psi.EnvironmentVariables["SteamGameId"] = invokedId; + }); - // 🎯 MEMORY INJECTION - psi.EnvironmentVariables["SteamAppId"] = invokedId; - psi.EnvironmentVariables["SteamGameId"] = invokedId; + // If the background task failed early (missing exe, bad string), safely stop execution + if (psi == null) return; - logCallback?.Invoke($"[ARGUMENT] {args}", Color.Cyan); + // 3. LAUNCH PROCESS (Back on the UI thread, instantaneous) + logCallback?.Invoke($"[ARGUMENT] {finalArgs}", Color.Cyan); - // 🚀 8. EXECUTION & MONITORING Process? proc = Process.Start(psi); if (proc != null) { server.RunningProcess = proc; server.PID = proc.Id; - if (server.StartTime == null) server.StartTime = DateTime.Now; + server.StartTime = DateTime.Now; - // 🎯 DISCORD ALERT: Server Online (Clean alert) _ = Core.Instance.SendDiscordAlert(server, "SERVER STARTING", $"{server.ServerName} process has been initiated.", Color.Cyan); proc.EnableRaisingEvents = true; @@ -228,7 +231,6 @@ public static async Task Start(GameServer server, Action logCallb { if (server.Status == StatusManager.GetStatus(ServerState.Running)) { - // Watchdog handles the single Discord crash notification await Core.Instance.ExecuteStartSequence(server, "WATCHDOG"); } else @@ -256,7 +258,6 @@ public static async Task Stop(GameServer server, Action logCallba return; } - // 🎯 DISCORD ALERT: Manual Shutdown if (isManual) { _ = Core.Instance.SendDiscordAlert(server, "MANUAL SHUTDOWN", @@ -277,7 +278,7 @@ public static async Task Stop(GameServer server, Action logCallba if (cleanExit) { - logCallback?.Invoke($"[STOP] {server.ServerName} saved and closed cleanly.", Color.Lime); + logCallback?.Invoke($"[SYNIX] {server.ServerName} saved and closed cleanly.", Color.Lime); FinalizeStoppedState(server); return; } diff --git a/SynixEngine/version.txt b/SynixEngine/version.txt index d0f65cd..835ea23 100644 --- a/SynixEngine/version.txt +++ b/SynixEngine/version.txt @@ -1 +1 @@ -1.0.16 \ No newline at end of file +1.0.17 \ No newline at end of file From cbb7edde38c6dee0aab43fb8133124b593261bdf Mon Sep 17 00:00:00 2001 From: Jason <6037701+ubidzz@users.noreply.github.com> Date: Tue, 26 May 2026 16:41:34 -0400 Subject: [PATCH 4/8] Update README.md update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a3ef076..b374b1d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ [![VirusTotal](https://img.shields.io/badge/VirusTotal-1%2F71%20Clean-yellowgreen?style=for-the-badge&logo=virustotal)](https://www.virustotal.com/gui/file/c3a62c98e52bacccb57bc4e9b342feef20d2be49de4f91bfca164f7e6487d0b8?nocache=1) [![WinGet Status](https://img.shields.io/winget/v/ubidzz.Synix?style=for-the-badge&color=blue&label=WINGET%20INSTALL)](https://github.com/microsoft/winget-pkgs/tree/master/manifests/u/ubidzz/Synix) [![Donate with PayPal](https://img.shields.io/badge/PAYPAL-DONATE-0079C1?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.com/donate/?hosted_button_id=FAHU6EH6BX9J8) +![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/ubidzz/Synix-Control-Panel/total?style=for-the-badge&logo=github) **Synix Control Panel** is an elite, engine-driven management suite designed to provide a centralized "Brain" for game server hosting. By moving beyond simple batch scripts, Synix automates deployment, process health, networking diagnostics, and hardware stewardship within a **Zero-Admin (No UAC)** environment. From f8a11d6f338cd1a9fa5a7d857d16a39ba7274a3e Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 01:10:12 -0400 Subject: [PATCH 5/8] fixed the watchdog and maintenance stop and start server bug. Added game icon in the server DataGridView. --- Design/GridStyler.cs | 1 + FileFolderHandler/FileHandler.cs | 13 +++ MainGUI.Designer.cs | 137 +++++++++++++++-------------- MainGUI.cs | 31 +++++++ MainGUI.resx | 3 + ServerHandler/GameServer.cs | 2 + ServerHandler/ServerSettingsGUI.cs | 16 ++++ SynixEngine/Actions.cs | 114 ++++++++++++++---------- SynixEngine/IconManager.cs | 74 ++++++++++++++++ 9 files changed, 279 insertions(+), 112 deletions(-) create mode 100644 SynixEngine/IconManager.cs diff --git a/Design/GridStyler.cs b/Design/GridStyler.cs index 2152508..0e26c7b 100644 --- a/Design/GridStyler.cs +++ b/Design/GridStyler.cs @@ -36,6 +36,7 @@ public static void DarkTheme(DataGridView dgv) dgv.AutoGenerateColumns = false; // Map Columns to Class Properties + if (dgv.Columns.Contains("colIcon")) dgv.Columns["colIcon"].DataPropertyName = ""; if (dgv.Columns.Contains("colName")) dgv.Columns["colName"].DataPropertyName = "ServerName"; if (dgv.Columns.Contains("colGame")) dgv.Columns["colGame"].DataPropertyName = "Game"; if (dgv.Columns.Contains("colPort")) dgv.Columns["colPort"].DataPropertyName = "Port"; diff --git a/FileFolderHandler/FileHandler.cs b/FileFolderHandler/FileHandler.cs index c479831..cc45aab 100644 --- a/FileFolderHandler/FileHandler.cs +++ b/FileFolderHandler/FileHandler.cs @@ -56,6 +56,7 @@ public static void LoadServers() MainGUI.serverList.Clear(); foreach (var server in loadedServers) { + // 1. Grab the hardcoded data from the switch statement in your screenshot var masterData = GameDatabase.GetGame(server.Game); if (masterData != null) { @@ -63,6 +64,18 @@ public static void LoadServers() server.ExeName = masterData.ExeName; server.RequiredArgs = masterData.RequiredArgs; server.Maps = masterData.Maps.ToList(); + + // 2. Smash the JSON path and Hardcoded ExeName together + string fullExePath = Path.Combine(server.InstallPath, server.ExeName); + + // 3. Extract the icon + string iconPath = Synix_Control_Panel.SynixEngine.Core.GetLocalServerIcon(server.AppID, fullExePath); + + // 4. Attach it permanently to the object + if (File.Exists(iconPath)) + { + server.DisplayIcon = System.Drawing.Image.FromFile(iconPath); + } } MainGUI.serverList.Add(server); } diff --git a/MainGUI.Designer.cs b/MainGUI.Designer.cs index a6ba285..3e9075d 100644 --- a/MainGUI.Designer.cs +++ b/MainGUI.Designer.cs @@ -34,13 +34,6 @@ private void InitializeComponent() System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainGUI)); dataGridView1 = new DataGridView(); - colGame = new DataGridViewTextBoxColumn(); - colName = new DataGridViewTextBoxColumn(); - colPort = new DataGridViewTextBoxColumn(); - colQueryPort = new DataGridViewTextBoxColumn(); - colPlayerCount = new DataGridViewTextBoxColumn(); - colUptime = new DataGridViewTextBoxColumn(); - colStatus = new DataGridViewTextBoxColumn(); rtbLog = new RichTextBox(); btnStart = new Button(); btnStop = new Button(); @@ -80,6 +73,14 @@ private void InitializeComponent() btnMinimize = new Button(); btnDiscord = new Button(); btnGithub = new Button(); + DisplayIcon = new DataGridViewTextBoxColumn(); + colGame = new DataGridViewTextBoxColumn(); + colName = new DataGridViewTextBoxColumn(); + colPort = new DataGridViewTextBoxColumn(); + colQueryPort = new DataGridViewTextBoxColumn(); + colPlayerCount = new DataGridViewTextBoxColumn(); + colUptime = new DataGridViewTextBoxColumn(); + colStatus = new DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit(); ((System.ComponentModel.ISupportInitialize)logo).BeginInit(); ((System.ComponentModel.ISupportInitialize)chartHeartbeat).BeginInit(); @@ -92,7 +93,7 @@ private void InitializeComponent() dataGridView1.AllowUserToDeleteRows = false; dataGridView1.BorderStyle = BorderStyle.None; dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dataGridView1.Columns.AddRange(new DataGridViewColumn[] { colGame, colName, colPort, colQueryPort, colPlayerCount, colUptime, colStatus }); + dataGridView1.Columns.AddRange(new DataGridViewColumn[] { DisplayIcon, colGame, colName, colPort, colQueryPort, colPlayerCount, colUptime, colStatus }); dataGridView1.Location = new Point(12, 171); dataGridView1.MultiSelect = false; dataGridView1.Name = "dataGridView1"; @@ -103,62 +104,6 @@ private void InitializeComponent() dataGridView1.CellFormatting += dataGridView1_CellFormatting; dataGridView1.CellPainting += dataGridView1_CellPainting; // - // colGame - // - colGame.DataPropertyName = "Game"; - colGame.HeaderText = "Game"; - colGame.Name = "colGame"; - colGame.ReadOnly = true; - colGame.Width = 200; - // - // colName - // - colName.DataPropertyName = "ServerName"; - colName.HeaderText = "Server Name"; - colName.Name = "colName"; - colName.ReadOnly = true; - colName.Width = 280; - // - // colPort - // - colPort.DataPropertyName = "Port"; - colPort.HeaderText = "Port"; - colPort.Name = "colPort"; - colPort.ReadOnly = true; - colPort.Width = 80; - // - // colQueryPort - // - colQueryPort.DataPropertyName = "QueryPort"; - colQueryPort.HeaderText = "Query Port"; - colQueryPort.Name = "colQueryPort"; - colQueryPort.ReadOnly = true; - colQueryPort.Width = 80; - // - // colPlayerCount - // - colPlayerCount.DataPropertyName = "PlayerCount"; - colPlayerCount.HeaderText = "Players"; - colPlayerCount.Name = "colPlayerCount"; - colPlayerCount.ReadOnly = true; - colPlayerCount.Width = 70; - // - // colUptime - // - colUptime.DataPropertyName = "Uptime"; - colUptime.HeaderText = "UPTIME"; - colUptime.Name = "colUptime"; - colUptime.ReadOnly = true; - colUptime.Width = 80; - // - // colStatus - // - colStatus.DataPropertyName = "Status"; - colStatus.HeaderText = "Status"; - colStatus.Name = "colStatus"; - colStatus.ReadOnly = true; - colStatus.Width = 90; - // // rtbLog // rtbLog.BackColor = SystemColors.ActiveCaptionText; @@ -519,6 +464,69 @@ private void InitializeComponent() btnGithub.UseVisualStyleBackColor = true; btnGithub.Click += btnGithub_Click; // + // DisplayIcon + // + DisplayIcon.HeaderText = ""; + DisplayIcon.Name = "DisplayIcon"; + DisplayIcon.ReadOnly = true; + DisplayIcon.Width = 15; + // + // colGame + // + colGame.DataPropertyName = "Game"; + colGame.HeaderText = "Game"; + colGame.Name = "colGame"; + colGame.ReadOnly = true; + colGame.Width = 175; + // + // colName + // + colName.DataPropertyName = "ServerName"; + colName.HeaderText = "Server Name"; + colName.Name = "colName"; + colName.ReadOnly = true; + colName.Width = 255; + // + // colPort + // + colPort.DataPropertyName = "Port"; + colPort.HeaderText = "Port"; + colPort.Name = "colPort"; + colPort.ReadOnly = true; + colPort.Width = 80; + // + // colQueryPort + // + colQueryPort.DataPropertyName = "QueryPort"; + colQueryPort.HeaderText = "Query Port"; + colQueryPort.Name = "colQueryPort"; + colQueryPort.ReadOnly = true; + colQueryPort.Width = 80; + // + // colPlayerCount + // + colPlayerCount.DataPropertyName = "PlayerCount"; + colPlayerCount.HeaderText = "Players"; + colPlayerCount.Name = "colPlayerCount"; + colPlayerCount.ReadOnly = true; + colPlayerCount.Width = 70; + // + // colUptime + // + colUptime.DataPropertyName = "Uptime"; + colUptime.HeaderText = "UPTIME"; + colUptime.Name = "colUptime"; + colUptime.ReadOnly = true; + colUptime.Width = 80; + // + // colStatus + // + colStatus.DataPropertyName = "Status"; + colStatus.HeaderText = "Status"; + colStatus.Name = "colStatus"; + colStatus.ReadOnly = true; + colStatus.Width = 90; + // // MainGUI // AutoScaleDimensions = new SizeF(7F, 17F); @@ -607,6 +615,7 @@ private void InitializeComponent() private Button btnMinimize; private Button btnDiscord; private Button btnGithub; + private DataGridViewTextBoxColumn DisplayIcon; private DataGridViewTextBoxColumn colGame; private DataGridViewTextBoxColumn colName; private DataGridViewTextBoxColumn colPort; diff --git a/MainGUI.cs b/MainGUI.cs index 1b04aa1..bdc4def 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -38,6 +38,7 @@ public partial class MainGUI : Form private static Font regularFont = new Font("Segoe UI", 9, FontStyle.Regular); private bool isPrivacyLoading = false; private System.Windows.Forms.Timer? versionTimer; + public static Dictionary ServerIconsCache = new Dictionary(); public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2; @@ -57,7 +58,30 @@ public MainGUI() GridStyler.DarkTheme(dataGridView1); GridStyler.ApplyRoundedCorners(dataGridView1, 10); UIStyleHelper.InitializeToggles(this); + dataGridView1.DataSource = serverList; + dataGridView1.DataError += dataGridView1_DataError; + if (!dataGridView1.Columns.Contains("IconCol")) + { + DataGridViewImageColumn iconCol = new DataGridViewImageColumn(); + iconCol.Name = "IconCol"; + iconCol.HeaderText = ""; + iconCol.DataPropertyName = "DisplayIcon"; + iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; + iconCol.Width = 35; + + iconCol.DefaultCellStyle.Padding = new Padding(4); + + dataGridView1.Columns.Insert(0, iconCol); + + dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; + dataGridView1.RowTemplate.Height = 35; + foreach (DataGridViewRow row in dataGridView1.Rows) + { + row.Height = 35; + } + } + typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); GridStyler.ApplyTransparentTheme(dataGridView1); GridStyler.StyleCloseButton(btnClose); @@ -75,6 +99,13 @@ public MainGUI() _ = VersionCheck(); } + private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) + { + // If the grid throws a fit because the image is temporarily missing during an edit, + // this tells it to ignore the error and not draw the ugly Red X. + e.ThrowException = false; + } + private void tmrResourceUpdates_Tick(object sender, EventArgs e) { CheckRunningStatus(); diff --git a/MainGUI.resx b/MainGUI.resx index e26e0f3..e46bbc9 100644 --- a/MainGUI.resx +++ b/MainGUI.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + True + True diff --git a/ServerHandler/GameServer.cs b/ServerHandler/GameServer.cs index 0688cf2..8a93d2b 100644 --- a/ServerHandler/GameServer.cs +++ b/ServerHandler/GameServer.cs @@ -21,6 +21,8 @@ public class GameInfo { public string Game { get; init; } = string.Empty; [JsonIgnore] + public System.Drawing.Image DisplayIcon { get; set; } + [JsonIgnore] public bool HasAnnouncedOnline { get; set; } = false; [JsonIgnore] public bool NeedsConfigWarning { get; internal set; } diff --git a/ServerHandler/ServerSettingsGUI.cs b/ServerHandler/ServerSettingsGUI.cs index 2e6d569..e6991d5 100644 --- a/ServerHandler/ServerSettingsGUI.cs +++ b/ServerHandler/ServerSettingsGUI.cs @@ -356,6 +356,22 @@ private void btnSave_Click(object sender, EventArgs e) } } else MainGUI.serverList.Add(NewServer); + + var masterData = GameDatabase.GetGame(NewServer.Game); + if (masterData != null) + { + NewServer.AppID = masterData.AppID; + NewServer.ExeName = masterData.ExeName; + + string fullExePath = System.IO.Path.Combine(NewServer.InstallPath, NewServer.ExeName); + string iconPath = Synix_Control_Panel.SynixEngine.Core.GetLocalServerIcon(NewServer.AppID, fullExePath); + + if (System.IO.File.Exists(iconPath)) + { + NewServer.DisplayIcon = System.Drawing.Image.FromFile(iconPath); + } + } + FileHandler.SaveServers(); this.DialogResult = DialogResult.OK; this.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message); } diff --git a/SynixEngine/Actions.cs b/SynixEngine/Actions.cs index 8f7804d..331638f 100644 --- a/SynixEngine/Actions.cs +++ b/SynixEngine/Actions.cs @@ -21,6 +21,8 @@ namespace Synix_Control_Panel.SynixEngine { public partial class Core { + private static readonly HashSet _activeSequences = new HashSet(); + public async Task StopServerAndReport(GameServer server, bool isManual = true) { server.Status = StatusManager.GetStatus(ServerState.Stopping); @@ -372,70 +374,86 @@ public void EditServerAndReport(GameServer server) public async Task ExecuteStartSequence(GameServer server, string status = "") { - bool stopServer = false; - StartContext currentContext = StartContext.Manual; - - if (!PassResourceGuard(out string guardMsg)) + lock (_activeSequences) { - Log(guardMsg, System.Drawing.Color.Red, true); - MessageBox.Show(guardMsg, "System Resource Exhaustion", - System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); - return; + if (_activeSequences.Contains(server.ServerName)) + { + return; + } + _activeSequences.Add(server.ServerName); } - if (!ValidateIntegrityAndReport(server)) return; - if (ShouldBlockForConfig(server)) return; - - if (status == "RESTART") - { - Log($"[SYNIX] Starting restart sequence for {server.ServerName}...", Color.Cyan); - stopServer = true; - } - else if (status == "MAINTENANCE") - { - Log($"[🛠 MAINTENANCE] Scheduled restart sequence for {server.ServerName}.", Color.Cyan, true); - stopServer = true; - currentContext = StartContext.Scheduled; - } - else if (status == "WATCHDOG") + try { - server.Status = StatusManager.GetStatus(ServerState.Crashed); - string reason = !server.RunningProcess?.Responding ?? false ? "FREEZE" : "CRASH/CLOSE"; - Log($"[🛡️ WATCHDOG] {reason} detected on {server.ServerName}. Initializing recovery...", Color.Orange); + bool stopServer = false; + StartContext currentContext = StartContext.Manual; - _ = SendDiscordAlert(server, "🚨 CRASH DETECTED", - $"{server.ServerName} has terminated. Synix is attempting an automatic restart.", - Color.Red); + if (!PassResourceGuard(out string guardMsg)) + { + Log(guardMsg, System.Drawing.Color.Red, true); + MessageBox.Show(guardMsg, "System Resource Exhaustion", + System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); + return; + } - stopServer = true; - currentContext = StartContext.CrashRecovery; - Core.Instance.UpdateGridStatus(); - } + if (!ValidateIntegrityAndReport(server)) return; + if (ShouldBlockForConfig(server)) return; - if (stopServer && server.Status != StatusManager.GetStatus(ServerState.Stopping)) - { - Log($"[SYNIX] Stoping the {server.ServerName} server.", Color.Cyan, true); + if (status == "RESTART") + { + Log($"[SYNIX] Starting restart sequence for {server.ServerName}...", Color.Cyan); + stopServer = true; + } + else if (status == "MAINTENANCE") + { + Log($"[🛠 MAINTENANCE] Scheduled restart sequence for {server.ServerName}.", Color.Cyan, true); + stopServer = true; + currentContext = StartContext.Scheduled; + } + else if (status == "WATCHDOG") + { + server.Status = StatusManager.GetStatus(ServerState.Crashed); + string reason = !server.RunningProcess?.Responding ?? false ? "FREEZE" : "CRASH/CLOSE"; + Log($"[🛡️ WATCHDOG] {reason} detected on {server.ServerName}. Initializing recovery...", Color.Orange); - await StopServerAndReport(server); - } + _ = SendDiscordAlert(server, "🚨 CRASH DETECTED", + $"{server.ServerName} has terminated. Synix is attempting an automatic restart.", + Color.Red); - await Task.Delay(1000); + stopServer = true; + currentContext = StartContext.CrashRecovery; + Core.Instance.UpdateGridStatus(); + } - if (server.Status == StatusManager.GetStatus(ServerState.Stopped)) - { - Log($"[SYNIX] Starting the {server.ServerName} server.", Color.Cyan, true); - if (!PassSpamLock(server, out string lockMsg, "Start")) { Log(lockMsg, System.Drawing.Color.Orange); return; } + if (stopServer && server.PID != null) + { + Log($"[SYNIX] Stoping the {server.ServerName} server.", Color.Cyan, true); + await StopServerAndReport(server); + } - await Servers.Start(server, (msg, Color) => MainGUI.Instance?.Invoke((Action)(() => Log(msg, Color))), currentContext); + if (server.Status == StatusManager.GetStatus(ServerState.Stopped)) + { + Log($"[SYNIX] Starting the {server.ServerName} server.", Color.Cyan, true); + if (!PassSpamLock(server, out string lockMsg, "Start")) { Log(lockMsg, System.Drawing.Color.Orange); return; } + + await Servers.Start(server, (msg, Color) => MainGUI.Instance?.Invoke((Action)(() => Log(msg, Color))), currentContext); + } + else + { + if (server.Status != StatusManager.GetStatus(ServerState.Starting)) + { + Log($"[🚨 CRITICAL] Restart failed: {server.ServerName} is still stuck!", Color.Red); + } + } + stopServer = false; } - else + finally { - if (server.Status != StatusManager.GetStatus(ServerState.Starting)) + lock (_activeSequences) { - Log($"[🚨 CRITICAL] Restart failed: {server.ServerName} is still stuck!", Color.Red); + _activeSequences.Remove(server.ServerName); } } - stopServer = false; } public void RunUniversalHealthCheck() diff --git a/SynixEngine/IconManager.cs b/SynixEngine/IconManager.cs new file mode 100644 index 0000000..45f34b5 --- /dev/null +++ b/SynixEngine/IconManager.cs @@ -0,0 +1,74 @@ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ +using Synix_Control_Panel.FileFolderHandler; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; + +namespace Synix_Control_Panel.SynixEngine +{ + public partial class Core + { + private static Dictionary _iconPathCache = new Dictionary(); + private const string SynixRoot = @"C:\Synix\SynixData"; + + public static string GetLocalServerIcon(string Appid, string serverPath) + { + // 1. Check in-memory session cache first + if (_iconPathCache.TryGetValue(Appid, out string memoryPath)) + { + return memoryPath; + } + + // 2. Setup the output path in C:\Synix\GameIcons + string iconFolder = Path.Combine(SynixRoot, "GameIcons"); + FolderHandler.Create(iconFolder); + string localIconPath = Path.Combine(iconFolder, $"{Appid}.png"); + + // 3. If already extracted in a past session, return it + if (File.Exists(localIconPath)) + { + _iconPathCache[Appid] = localIconPath; + return localIconPath; + } + + // 4. Extract directly using the full path to the executable + if (File.Exists(serverPath)) + { + try + { + using (Icon extractedIcon = Icon.ExtractAssociatedIcon(serverPath)) + { + if (extractedIcon != null) + { + using (Bitmap bitmap = extractedIcon.ToBitmap()) + { + bitmap.Save(localIconPath, System.Drawing.Imaging.ImageFormat.Png); + _iconPathCache[Appid] = localIconPath; + return localIconPath; + } + } + } + } + catch + { + // Fall through if file is locked, in use, or lacks permissions + } + } + + // 5. Hard Fallback if file doesn't exist or extraction fails + return Path.Combine(SynixRoot, "GameIcons", "default_server.png"); + } + } +} \ No newline at end of file From 1e20e4ee98f4d4c34ae60cb16ff6671e924a7ebc Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 13:16:06 -0400 Subject: [PATCH 6/8] Fixed the server maintenance that triggers on what the user set and was missing to check days. --- MainGUI.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/MainGUI.cs b/MainGUI.cs index bdc4def..249e464 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -53,10 +53,6 @@ public MainGUI() InitializeComponent(); Instance = this; FileHandler.LoadServers(); - _ = Core.Instance; - - GridStyler.DarkTheme(dataGridView1); - GridStyler.ApplyRoundedCorners(dataGridView1, 10); UIStyleHelper.InitializeToggles(this); dataGridView1.DataSource = serverList; @@ -69,11 +65,9 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(4); dataGridView1.Columns.Insert(0, iconCol); - dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; dataGridView1.RowTemplate.Height = 35; foreach (DataGridViewRow row in dataGridView1.Rows) @@ -82,6 +76,8 @@ public MainGUI() } } + GridStyler.DarkTheme(dataGridView1); + GridStyler.ApplyRoundedCorners(dataGridView1, 10); typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); GridStyler.ApplyTransparentTheme(dataGridView1); GridStyler.StyleCloseButton(btnClose); @@ -89,20 +85,18 @@ public MainGUI() GridStyler.StyleIconButton(btnDiscord, Properties.Resources.discord_icon, Color.FromArgb(88, 101, 242)); GridStyler.StyleIconButton(btnGithub, Properties.Resources.github_icon, Color.FromArgb(200, 200, 200)); - Instance = this; chkPrivacyMode.Text = "Privacy Mode"; chkPrivacyMode.Checked = Properties.Settings.Default.PrivacyMode; this.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, this.Width, this.Height, 15, 15)); isPrivacyLoading = chkPrivacyMode.Checked; _ = LoadNetworkInfo(); - InitializeVersionCheckTimer(); + _ = Core.Instance; _ = VersionCheck(); + InitializeVersionCheckTimer(); } private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) { - // If the grid throws a fit because the image is temporarily missing during an edit, - // this tells it to ignore the error and not draw the ugly Red X. e.ThrowException = false; } @@ -141,9 +135,14 @@ private void tmrResourceUpdates_Tick(object sender, EventArgs e) if (needsTimeCheck) { string currentExactTime = DateTime.Now.ToString("HH:mm:ss"); + int currentDayIndex = (int)DateTime.Now.DayOfWeek; + foreach (var server in serverList) { - if (server.IsScheduledRestartEnabled && currentExactTime == (server.RestartTime + ":00")) + if (server.IsScheduledRestartEnabled && + server.RestartDays != null && + server.RestartDays[currentDayIndex] && + currentExactTime == (server.RestartTime + ":00")) { _ = Core.Instance.ExecuteStartSequence(server, "MAINTENANCE"); } From 39013980431c55ed4eebc5ff7499fb01fbe8ad22 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 14:08:03 -0400 Subject: [PATCH 7/8] disabling dataGridView1 AutoGenerateColumns so that it is not adding everything into it from the json file. --- MainGUI.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MainGUI.cs b/MainGUI.cs index 249e464..59655c8 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -54,9 +54,9 @@ public MainGUI() Instance = this; FileHandler.LoadServers(); UIStyleHelper.InitializeToggles(this); - + dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = serverList; - dataGridView1.DataError += dataGridView1_DataError; + //dataGridView1.DataError += dataGridView1_DataError; if (!dataGridView1.Columns.Contains("IconCol")) { DataGridViewImageColumn iconCol = new DataGridViewImageColumn(); @@ -65,7 +65,7 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(4); + iconCol.DefaultCellStyle.Padding = new Padding(5); dataGridView1.Columns.Insert(0, iconCol); dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; From 62fca8d6932082bf14fa09ac872f7d02618156a4 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 14:15:48 -0400 Subject: [PATCH 8/8] update --- MainGUI.Designer.cs | 4 ++-- MainGUI.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MainGUI.Designer.cs b/MainGUI.Designer.cs index 3e9075d..ca05429 100644 --- a/MainGUI.Designer.cs +++ b/MainGUI.Designer.cs @@ -469,7 +469,7 @@ private void InitializeComponent() DisplayIcon.HeaderText = ""; DisplayIcon.Name = "DisplayIcon"; DisplayIcon.ReadOnly = true; - DisplayIcon.Width = 15; + DisplayIcon.Width = 5; // // colGame // @@ -485,7 +485,7 @@ private void InitializeComponent() colName.HeaderText = "Server Name"; colName.Name = "colName"; colName.ReadOnly = true; - colName.Width = 255; + colName.Width = 265; // // colPort // diff --git a/MainGUI.cs b/MainGUI.cs index 59655c8..7ad30dc 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -65,7 +65,7 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(5); + iconCol.DefaultCellStyle.Padding = new Padding(6); dataGridView1.Columns.Insert(0, iconCol); dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;