├── docs ├── preview-1.png ├── preview-2.png └── preview-3.png ├── providers └── .gitkeep ├── data └── .gitkeep ├── .gitmodules ├── src ├── CSP2.Desktop │ ├── Views │ │ ├── Pages │ │ │ ├── MapHistoryPage.xaml.cs │ │ │ ├── DownloadManagerPage.xaml.cs │ │ │ ├── SettingsPage.xaml.cs │ │ │ ├── DebugConsolePage.xaml.cs │ │ │ ├── PluginMarketPage.xaml.cs │ │ │ ├── ServerManagementPage.xaml.cs │ │ │ ├── MapHistoryPage.xaml │ │ │ ├── ServerInstallPage.xaml.cs │ │ │ └── LogConsolePage.xaml.cs │ │ ├── DownloadManagerWindow.xaml.cs │ │ ├── MapHistoryView.xaml.cs │ │ ├── Dialogs │ │ │ ├── ConfirmDialog.xaml.cs │ │ │ ├── RestartConfirmDialog.xaml.cs │ │ │ ├── ConfirmDialog.xaml │ │ │ ├── SimpleServerConfigDialog.xaml.cs │ │ │ ├── FrameworkInstallProgressDialog.xaml.cs │ │ │ ├── FrameworkInstallProgressDialog.xaml │ │ │ └── RestartConfirmDialog.xaml │ │ ├── ErrorDialog.xaml.cs │ │ └── ErrorDialog.xaml │ ├── GlobalUsings.cs │ ├── Properties │ │ ├── launchSettings.json │ │ ├── Settings.settings │ │ └── Settings.Designer.cs │ ├── Controls │ │ └── FireworksControl.xaml │ ├── Converters │ │ ├── BoolToStatusTextConverter.cs │ │ ├── BoolToInstallTextConverter.cs │ │ ├── InverseBoolConverter.cs │ │ ├── StringToVisibilityConverter.cs │ │ ├── CountToVisibilityConverter.cs │ │ ├── BoolToVisibilityConverter.cs │ │ ├── InverseBoolToVisibilityConverter.cs │ │ ├── EqualityToVisibilityConverter.cs │ │ ├── ScrollBarOpacityConverter.cs │ │ ├── PercentageWidthConverter.cs │ │ ├── StatusToTextConverter.cs │ │ ├── ProgressBarWidthConverter.cs │ │ ├── LogLevelToColorConverter.cs │ │ ├── StatusToColorConverter.cs │ │ └── LocalizationConverter.cs │ ├── app.manifest │ ├── App.xaml │ ├── CSP2.Desktop.csproj │ ├── Resources │ │ └── Styles │ │ │ ├── DarkTheme.xaml │ │ │ ├── LightTheme.xaml │ │ │ └── Colors.xaml │ ├── Models │ │ └── PluginViewModel.cs │ ├── Services │ │ ├── ApplicationRestartService.cs │ │ └── ThemeService.cs │ └── ViewModels │ │ └── DownloadManagerViewModel.cs ├── CSP2.Providers │ ├── CSP2.Providers.csproj │ └── Platforms │ │ └── Windows │ │ └── WindowsPlatformProvider.cs └── CSP2.Core │ ├── Models │ ├── ServerStatus.cs │ ├── RCONConfig.cs │ ├── ProviderMetadata.cs │ ├── InstalledPlugin.cs │ ├── ProgressInfo.cs │ ├── InstallResult.cs │ ├── FrameworkInfo.cs │ ├── MapHistoryEntry.cs │ ├── DownloadTask.cs │ ├── Server.cs │ └── ServerConfig.cs │ ├── CSP2.Core.csproj │ ├── Abstractions │ ├── ISteamWorkshopService.cs │ ├── IMapHistoryService.cs │ ├── ISteamCmdService.cs │ ├── IPlatformProvider.cs │ ├── IDownloadManager.cs │ ├── IRCONClient.cs │ ├── IPluginRepositoryService.cs │ ├── IFrameworkProvider.cs │ ├── IPluginManager.cs │ ├── IConfigurationService.cs │ └── IServerManager.cs │ ├── Services │ ├── ProviderRegistry.cs │ └── DownloadManager.cs │ ├── Utilities │ └── CommandHistory.cs │ └── Logging │ └── DebugLogger.cs ├── .gitattributes ├── LICENSE ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── translation.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── dotnet-desktop.yml ├── .gitignore ├── CSP2.sln ├── .gitmessage ├── README.zh-CN.md └── README.md /docs/preview-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yichen11818/CSP2/HEAD/docs/preview-1.png -------------------------------------------------------------------------------- /docs/preview-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yichen11818/CSP2/HEAD/docs/preview-2.png -------------------------------------------------------------------------------- /docs/preview-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yichen11818/CSP2/HEAD/docs/preview-3.png -------------------------------------------------------------------------------- /providers/.gitkeep: -------------------------------------------------------------------------------- 1 | # 此目录用于存放第三方Provider DLL 2 | # 用户可以将社区开发的Provider放在这里,程序启动时会自动加载 3 | 4 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- 1 | # 此目录用于存放运行时数据 2 | # 如:servers.json, settings.json, plugins-cache.json 3 | # 这些文件会在程序首次运行时自动创建 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "plugin-repository"] 2 | path = plugin-repository 3 | url = https://github.com/yichen11818/csp2-plugin-repository.git 4 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/MapHistoryPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace CSP2.Desktop.Views.Pages; 4 | 5 | public partial class MapHistoryPage : Page 6 | { 7 | public MapHistoryPage() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // 全局 Using 别名,解决 WPF 和 WinForms 命名空间冲突 2 | global using Application = System.Windows.Application; 3 | global using UserControl = System.Windows.Controls.UserControl; 4 | global using MessageBox = System.Windows.MessageBox; 5 | global using Color = System.Windows.Media.Color; 6 | 7 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CSP2 (Hot Reload)": { 4 | "commandName": "Project", 5 | "hotReloadEnabled": true, 6 | "hotReloadProfile": "default" 7 | }, 8 | "CSP2 (Debug)": { 9 | "commandName": "Project" 10 | } 11 | } 12 | } 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/DownloadManagerPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace CSP2.Desktop.Views.Pages; 4 | 5 | /// 6 | /// DownloadManagerPage.xaml 的交互逻辑 7 | /// 8 | public partial class DownloadManagerPage : UserControl 9 | { 10 | public DownloadManagerPage() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/CSP2.Providers/CSP2.Providers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 12 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/SettingsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using CSP2.Desktop.ViewModels; 3 | 4 | namespace CSP2.Desktop.Views.Pages; 5 | 6 | /// 7 | /// SettingsPage.xaml 的交互逻辑 8 | /// 9 | public partial class SettingsPage : UserControl 10 | { 11 | public SettingsPage(SettingsViewModel viewModel) 12 | { 13 | InitializeComponent(); 14 | DataContext = viewModel; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/DebugConsolePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using CSP2.Desktop.ViewModels; 3 | 4 | namespace CSP2.Desktop.Views.Pages; 5 | 6 | /// 7 | /// DebugConsolePage.xaml 的交互逻辑 8 | /// 9 | public partial class DebugConsolePage : UserControl 10 | { 11 | public DebugConsolePage(DebugConsoleViewModel viewModel) 12 | { 13 | InitializeComponent(); 14 | DataContext = viewModel; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/PluginMarketPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using CSP2.Desktop.ViewModels; 3 | 4 | namespace CSP2.Desktop.Views.Pages; 5 | 6 | /// 7 | /// PluginMarketPage.xaml 的交互逻辑 8 | /// 9 | public partial class PluginMarketPage : UserControl 10 | { 11 | public PluginMarketPage(PluginMarketViewModel viewModel) 12 | { 13 | InitializeComponent(); 14 | DataContext = viewModel; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | zh-CN 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/DownloadManagerWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace CSP2.Desktop.Views; 4 | 5 | /// 6 | /// DownloadManagerWindow.xaml 的交互逻辑 7 | /// 8 | public partial class DownloadManagerWindow : Window 9 | { 10 | public DownloadManagerWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | 15 | private void CloseButton_Click(object sender, RoutedEventArgs e) 16 | { 17 | this.Close(); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/ServerManagementPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using CSP2.Desktop.ViewModels; 3 | 4 | namespace CSP2.Desktop.Views.Pages; 5 | 6 | /// 7 | /// ServerManagementPage.xaml 的交互逻辑 8 | /// 9 | public partial class ServerManagementPage : UserControl 10 | { 11 | public ServerManagementPage(ServerManagementViewModel viewModel) 12 | { 13 | InitializeComponent(); 14 | DataContext = viewModel; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # C# files 5 | *.cs text diff=csharp 6 | 7 | # XAML files 8 | *.xaml text 9 | 10 | # JSON files 11 | *.json text 12 | 13 | # Markdown files 14 | *.md text 15 | 16 | # Windows script files 17 | *.bat text eol=crlf 18 | *.cmd text eol=crlf 19 | *.ps1 text eol=crlf 20 | 21 | # Shell script files 22 | *.sh text eol=lf 23 | 24 | # Binary files 25 | *.dll binary 26 | *.exe binary 27 | *.png binary 28 | *.jpg binary 29 | *.ico binary 30 | *.zip binary 31 | 32 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/ServerStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 服务器状态枚举 5 | /// 6 | public enum ServerStatus 7 | { 8 | /// 9 | /// 已停止 10 | /// 11 | Stopped, 12 | 13 | /// 14 | /// 启动中 15 | /// 16 | Starting, 17 | 18 | /// 19 | /// 运行中 20 | /// 21 | Running, 22 | 23 | /// 24 | /// 停止中 25 | /// 26 | Stopping, 27 | 28 | /// 29 | /// 崩溃 30 | /// 31 | Crashed 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Controls/FireworksControl.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/CSP2.Core/CSP2.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 12 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/BoolToStatusTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace CSP2.Desktop.Converters; 5 | 6 | /// 7 | /// 将布尔值转换为状态文本的转换器 8 | /// 9 | public class BoolToStatusTextConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is bool isInstalled) 14 | { 15 | return isInstalled ? "✅ 已安装" : "❌ 未安装"; 16 | } 17 | return "❓ 未知"; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/BoolToInstallTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace CSP2.Desktop.Converters; 5 | 6 | /// 7 | /// 将布尔值转换为安装按钮文本的转换器 8 | /// 9 | public class BoolToInstallTextConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is bool isInstalled) 14 | { 15 | return isInstalled ? "🔄 重新安装" : "📥 安装"; 16 | } 17 | return "📥 安装"; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/MapHistoryPage.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/InverseBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace CSP2.Desktop.Converters; 5 | 6 | /// 7 | /// 反转布尔值的转换器 8 | /// 9 | public class InverseBoolConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is bool boolValue) 14 | { 15 | return !boolValue; 16 | } 17 | return true; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | if (value is bool boolValue) 23 | { 24 | return !boolValue; 25 | } 26 | return false; 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/RCONConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// RCON 连接配置 5 | /// 用于远程连接服务器(可能与本地服务器不同) 6 | /// 7 | public class RCONConfig 8 | { 9 | /// 10 | /// 是否启用 RCON 连接 11 | /// 12 | public bool Enabled { get; set; } = false; 13 | 14 | /// 15 | /// RCON 服务器地址(默认 localhost) 16 | /// 17 | public string Host { get; set; } = "127.0.0.1"; 18 | 19 | /// 20 | /// RCON 端口(默认与服务器端口相同) 21 | /// 22 | public int Port { get; set; } = 27015; 23 | 24 | /// 25 | /// RCON 密码 26 | /// 27 | public string Password { get; set; } = string.Empty; 28 | 29 | /// 30 | /// 连接超时时间(毫秒) 31 | /// 32 | public int Timeout { get; set; } = 5000; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/StringToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 字符串到可见性的转换器(空字符串为Collapsed) 9 | /// 10 | public class StringToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is string str) 15 | { 16 | return string.IsNullOrWhiteSpace(str) ? Visibility.Collapsed : Visibility.Visible; 17 | } 18 | return Visibility.Collapsed; 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/CountToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 集合数量到可见性的转换器(用于显示空状态提示) 9 | /// 10 | public class CountToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is int count) 15 | { 16 | // 当数量为0时显示,否则隐藏 17 | return count == 0 ? Visibility.Visible : Visibility.Collapsed; 18 | } 19 | return Visibility.Visible; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/MapHistoryView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using CSP2.Desktop.ViewModels; 3 | 4 | namespace CSP2.Desktop.Views; 5 | 6 | public partial class MapHistoryView : UserControl 7 | { 8 | public MapHistoryView() 9 | { 10 | InitializeComponent(); 11 | Loaded += MapHistoryView_OnLoaded; 12 | } 13 | 14 | /// 15 | /// 当视图加载时,加载地图历史 16 | /// 17 | private void MapHistoryView_OnLoaded(object sender, System.Windows.RoutedEventArgs e) 18 | { 19 | if (DataContext is MapHistoryViewModel viewModel) 20 | { 21 | // 使用 Execute 而不是 ExecuteAsync 22 | if (viewModel.LoadHistoryCommand.CanExecute(null)) 23 | { 24 | viewModel.LoadHistoryCommand.Execute(null); 25 | } 26 | } 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/ProviderMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// Provider提供者元数据 5 | /// 6 | public class ProviderMetadata 7 | { 8 | /// 9 | /// 提供者唯一标识(小写) 10 | /// 11 | public required string Id { get; set; } 12 | 13 | /// 14 | /// 提供者显示名称 15 | /// 16 | public required string Name { get; set; } 17 | 18 | /// 19 | /// 版本号 20 | /// 21 | public required string Version { get; set; } 22 | 23 | /// 24 | /// 作者 25 | /// 26 | public required string Author { get; set; } 27 | 28 | /// 29 | /// 描述 30 | /// 31 | public string Description { get; set; } = string.Empty; 32 | 33 | /// 34 | /// 优先级(数字越大优先级越高) 35 | /// 36 | public int Priority { get; set; } = 0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 布尔值到可见性的转换器 9 | /// 10 | public class BoolToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is bool boolValue) 15 | { 16 | return boolValue ? Visibility.Visible : Visibility.Collapsed; 17 | } 18 | return Visibility.Collapsed; 19 | } 20 | 21 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 22 | { 23 | if (value is Visibility visibility) 24 | { 25 | return visibility == Visibility.Visible; 26 | } 27 | return false; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/ServerInstallPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Desktop.ViewModels; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | 5 | namespace CSP2.Desktop.Views.Pages; 6 | 7 | /// 8 | /// ServerInstallPage.xaml 的交互逻辑 9 | /// 10 | public partial class ServerInstallPage : UserControl 11 | { 12 | public ServerInstallPage() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | private void SteamCmdOption_Click(object sender, MouseButtonEventArgs e) 18 | { 19 | if (DataContext is ServerInstallPageViewModel vm) 20 | { 21 | vm.SelectSteamCmdModeCommand.Execute(null); 22 | } 23 | } 24 | 25 | private void ExistingOption_Click(object sender, MouseButtonEventArgs e) 26 | { 27 | if (DataContext is ServerInstallPageViewModel vm) 28 | { 29 | vm.SelectExistingModeCommand.Execute(null); 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/InverseBoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 反向布尔值到可见性的转换器 9 | /// True -> Collapsed, False -> Visible 10 | /// 11 | public class InverseBoolToVisibilityConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is bool boolValue) 16 | { 17 | return boolValue ? Visibility.Collapsed : Visibility.Visible; 18 | } 19 | return Visibility.Visible; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | if (value is Visibility visibility) 25 | { 26 | return visibility != Visibility.Visible; 27 | } 28 | return true; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/EqualityToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 相等性到可见性转换器 9 | /// 10 | public class EqualityToVisibilityConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value == null || parameter == null) 15 | return Visibility.Collapsed; 16 | 17 | var valueStr = value.ToString(); 18 | var paramStr = parameter.ToString(); 19 | 20 | return string.Equals(valueStr, paramStr, StringComparison.OrdinalIgnoreCase) 21 | ? Visibility.Visible 22 | : Visibility.Collapsed; 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/InstalledPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 已安装的插件 5 | /// 6 | public class InstalledPlugin 7 | { 8 | /// 9 | /// 插件ID 10 | /// 11 | public required string Id { get; set; } 12 | 13 | /// 14 | /// 插件名称 15 | /// 16 | public required string Name { get; set; } 17 | 18 | /// 19 | /// 已安装版本 20 | /// 21 | public required string Version { get; set; } 22 | 23 | /// 24 | /// 所属框架 25 | /// 26 | public required string Framework { get; set; } 27 | 28 | /// 29 | /// 是否启用 30 | /// 31 | public bool Enabled { get; set; } = true; 32 | 33 | /// 34 | /// 安装路径 35 | /// 36 | public required string InstallPath { get; set; } 37 | 38 | /// 39 | /// 安装时间 40 | /// 41 | public DateTime InstalledAt { get; set; } = DateTime.Now; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/ScrollBarOpacityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 滚动条透明度转换器 - 鼠标悬停时显示,否则半透明 9 | /// 10 | public class ScrollBarOpacityConverter : IMultiValueConverter 11 | { 12 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (values.Length < 2) 15 | return 0.5; 16 | 17 | // values[0]: ScrollBar的IsMouseOver 18 | // values[1]: Thumb的IsMouseOver 19 | 20 | bool scrollBarMouseOver = values[0] is bool sb && sb; 21 | bool thumbMouseOver = values[1] is bool tb && tb; 22 | 23 | // 如果任一为true,返回完全不透明 24 | return (scrollBarMouseOver || thumbMouseOver) ? 1.0 : 0.5; 25 | } 26 | 27 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/PercentageWidthConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 百分比宽度转换器 - 根据百分比计算实际宽度 9 | /// 10 | public class PercentageWidthConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is not double actualWidth) 15 | return 0.0; 16 | 17 | if (parameter is not string percentStr) 18 | return 0.0; 19 | 20 | if (!double.TryParse(percentStr, out double percent)) 21 | return 0.0; 22 | 23 | // 将百分比转换为 0-1 的小数 24 | var percentage = Math.Max(0, Math.Min(100, percent)) / 100.0; 25 | 26 | // 返回实际宽度 27 | return actualWidth * percentage; 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/StatusToTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using CSP2.Core.Models; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 服务器状态到文本的转换器 9 | /// 10 | public class StatusToTextConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is ServerStatus status) 15 | { 16 | return status switch 17 | { 18 | ServerStatus.Running => "运行中", 19 | ServerStatus.Stopped => "已停止", 20 | ServerStatus.Starting => "启动中", 21 | ServerStatus.Stopping => "停止中", 22 | ServerStatus.Crashed => "已崩溃", 23 | _ => "未知" 24 | }; 25 | } 26 | return "未知"; 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 CSP2 Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | PerMonitorV2 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/ISteamWorkshopService.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// Steam Workshop 服务接口 7 | /// 提供与 Steam Workshop API 交互的功能 8 | /// 9 | public interface ISteamWorkshopService 10 | { 11 | /// 12 | /// 从 Steam Workshop 获取地图信息 13 | /// 14 | /// Workshop ID 15 | /// 取消令牌 16 | /// 地图历史条目(包含地图信息) 17 | Task GetMapInfoAsync(string workshopId, CancellationToken cancellationToken = default); 18 | 19 | /// 20 | /// 下载地图预览图 21 | /// 22 | /// 预览图 URL 23 | /// Workshop ID (用于命名文件) 24 | /// 取消令牌 25 | /// 本地预览图路径 26 | Task DownloadPreviewImageAsync(string previewUrl, string workshopId, CancellationToken cancellationToken = default); 27 | 28 | /// 29 | /// 从 URL 解析 Workshop ID 30 | /// 31 | /// Steam Workshop URL 或纯 ID 32 | /// Workshop ID,解析失败返回 null 33 | string? ParseWorkshopId(string url); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/ProgressBarWidthConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 进度条宽度转换器 - 根据进度值计算实际宽度 9 | /// 10 | public class ProgressBarWidthConverter : IMultiValueConverter 11 | { 12 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (values.Length < 3) 15 | return 0.0; 16 | 17 | // values[0]: Grid的实际宽度 18 | // values[1]: 当前值 (Value) 19 | // values[2]: 最大值 (Maximum) 20 | 21 | if (values[0] is not double actualWidth || 22 | values[1] is not double currentValue || 23 | values[2] is not double maximum) 24 | { 25 | return 0.0; 26 | } 27 | 28 | if (maximum <= 0) 29 | return 0.0; 30 | 31 | // 计算进度百分比 32 | var percentage = Math.Max(0, Math.Min(1, currentValue / maximum)); 33 | 34 | // 返回实际宽度 35 | return actualWidth * percentage; 36 | } 37 | 38 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/ProgressInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 进度信息基类 5 | /// 6 | public class ProgressInfo 7 | { 8 | /// 9 | /// 进度百分比(0-100) 10 | /// 11 | public double Percentage { get; set; } 12 | 13 | /// 14 | /// 当前状态描述 15 | /// 16 | public string Message { get; set; } = string.Empty; 17 | } 18 | 19 | /// 20 | /// 下载进度信息 21 | /// 22 | public class DownloadProgress : ProgressInfo 23 | { 24 | /// 25 | /// 已下载字节数 26 | /// 27 | public long BytesDownloaded { get; set; } 28 | 29 | /// 30 | /// 总字节数 31 | /// 32 | public long TotalBytes { get; set; } 33 | 34 | /// 35 | /// 下载速度(字节/秒) 36 | /// 37 | public long BytesPerSecond { get; set; } 38 | } 39 | 40 | /// 41 | /// 安装进度信息 42 | /// 43 | public class InstallProgress : ProgressInfo 44 | { 45 | /// 46 | /// 当前步骤 47 | /// 48 | public string CurrentStep { get; set; } = string.Empty; 49 | 50 | /// 51 | /// 总步骤数 52 | /// 53 | public int TotalSteps { get; set; } 54 | 55 | /// 56 | /// 当前步骤索引(从1开始) 57 | /// 58 | public int CurrentStepIndex { get; set; } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/LogLevelToColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using System.Windows.Media; 4 | using CSP2.Desktop.ViewModels; 5 | 6 | namespace CSP2.Desktop.Converters; 7 | 8 | /// 9 | /// 日志级别到颜色的转换器 10 | /// 11 | public class LogLevelToColorConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is UILogLevel level) 16 | { 17 | return level switch 18 | { 19 | UILogLevel.Error => new SolidColorBrush(Color.FromRgb(239, 68, 68)), // 红色 20 | UILogLevel.Warning => new SolidColorBrush(Color.FromRgb(245, 158, 11)), // 黄色 21 | UILogLevel.Info => new SolidColorBrush(Color.FromRgb(59, 130, 246)), // 蓝色 22 | UILogLevel.Command => new SolidColorBrush(Color.FromRgb(16, 185, 129)), // 绿色 23 | UILogLevel.Debug => new SolidColorBrush(Color.FromRgb(156, 163, 175)), // 灰色 24 | _ => new SolidColorBrush(Color.FromRgb(226, 232, 240)) 25 | }; 26 | } 27 | return new SolidColorBrush(Color.FromRgb(226, 232, 240)); 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/StatusToColorConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using System.Windows.Media; 4 | using CSP2.Core.Models; 5 | 6 | namespace CSP2.Desktop.Converters; 7 | 8 | /// 9 | /// 服务器状态到颜色的转换器 10 | /// 11 | public class StatusToColorConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (value is ServerStatus status) 16 | { 17 | return status switch 18 | { 19 | ServerStatus.Running => new SolidColorBrush(Color.FromRgb(16, 185, 129)), // 绿色 20 | ServerStatus.Stopped => new SolidColorBrush(Color.FromRgb(107, 114, 128)), // 灰色 21 | ServerStatus.Starting => new SolidColorBrush(Color.FromRgb(59, 130, 246)), // 蓝色 22 | ServerStatus.Stopping => new SolidColorBrush(Color.FromRgb(245, 158, 11)), // 黄色 23 | ServerStatus.Crashed => new SolidColorBrush(Color.FromRgb(239, 68, 68)), // 红色 24 | _ => new SolidColorBrush(Color.FromRgb(107, 114, 128)) 25 | }; 26 | } 27 | return new SolidColorBrush(Color.FromRgb(107, 114, 128)); 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace CSP2.Desktop.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = new Settings(); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("zh-CN")] 29 | public string Language { 30 | get { 31 | return ((string)(this["Language"])); 32 | } 33 | set { 34 | this["Language"] = value; 35 | } 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IMapHistoryService.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 地图历史服务接口 7 | /// 8 | public interface IMapHistoryService 9 | { 10 | /// 11 | /// 获取所有地图历史记录 12 | /// 13 | Task> GetAllEntriesAsync(); 14 | 15 | /// 16 | /// 获取指定服务器的地图历史记录 17 | /// 18 | /// 服务器 ID 19 | Task> GetServerEntriesAsync(string serverId); 20 | 21 | /// 22 | /// 添加或更新地图历史记录 23 | /// 24 | /// 服务器 ID 25 | /// 地图历史条目 26 | Task AddOrUpdateEntryAsync(string serverId, MapHistoryEntry entry); 27 | 28 | /// 29 | /// 记录地图加载(自动创建或更新记录) 30 | /// 31 | /// 服务器 ID 32 | /// Workshop ID 33 | Task RecordMapLoadAsync(string serverId, string workshopId); 34 | 35 | /// 36 | /// 删除地图历史记录 37 | /// 38 | /// 服务器 ID 39 | /// Workshop ID 40 | Task DeleteEntryAsync(string serverId, string workshopId); 41 | 42 | /// 43 | /// 清空指定服务器的所有地图历史 44 | /// 45 | /// 服务器 ID 46 | Task ClearServerHistoryAsync(string serverId); 47 | 48 | /// 49 | /// 清空所有地图历史 50 | /// 51 | Task ClearAllHistoryAsync(); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Converters/LocalizationConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace CSP2.Desktop.Converters; 6 | 7 | /// 8 | /// 本地化转换器 - 用于XAML中的动态本地化 9 | /// 注意:推荐使用 LocalizationHelper 进行XAML绑定 10 | /// 11 | public class LocalizationConverter : IValueConverter 12 | { 13 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if (parameter is not string key) 16 | return value; 17 | 18 | try 19 | { 20 | // 使用 LocalizationHelper 访问 JSON 资源 21 | return Helpers.LocalizationHelper.Instance[key]; 22 | } 23 | catch 24 | { 25 | return $"[{key}]"; 26 | } 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | 35 | /// 36 | /// 本地化扩展 - 简化XAML中的使用 37 | /// 注意:推荐使用 LocalizationHelper 进行XAML绑定 38 | /// 39 | public class LocalizationExtension : System.Windows.Markup.MarkupExtension 40 | { 41 | public string Key { get; set; } 42 | 43 | public LocalizationExtension(string key) 44 | { 45 | Key = key; 46 | } 47 | 48 | public override object ProvideValue(IServiceProvider serviceProvider) 49 | { 50 | try 51 | { 52 | // 使用 LocalizationHelper 访问 JSON 资源 53 | return Helpers.LocalizationHelper.Instance[Key]; 54 | } 55 | catch 56 | { 57 | return $"[{Key}]"; 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report / 问题报告 3 | about: Create a report to help us improve / 创建报告帮助我们改进 4 | title: '[Bug] ' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## 🐛 Bug Description / 问题描述 10 | 11 | 12 | 13 | 14 | 15 | 16 | ## 📝 Steps to Reproduce / 复现步骤 17 | 18 | 19 | 20 | 21 | 1. Go to / 前往 '...' 22 | 2. Click on / 点击 '...' 23 | 3. Scroll down to / 滚动到 '...' 24 | 4. See error / 看到错误 25 | 26 | ## ✅ Expected Behavior / 期望行为 27 | 28 | 29 | 30 | 31 | 32 | 33 | ## ❌ Actual Behavior / 实际行为 34 | 35 | 36 | 37 | 38 | 39 | 40 | ## 📸 Screenshots / 截图 41 | 42 | 43 | 44 | 45 | 46 | 47 | ## 💻 Environment / 环境信息 48 | 49 | **Desktop / 桌面环境:** 50 | - OS: 51 | - CSP2 Version: 52 | - .NET Version: 53 | 54 | **Language / 界面语言:** 55 | - 56 | 57 | ## 📋 Logs / 日志 58 | 59 | 60 | 61 | 62 | ``` 63 | Paste logs here 64 | ``` 65 | 66 | ## 📚 Additional Context / 补充信息 67 | 68 | 69 | 70 | 71 | 72 | 73 | --- 74 | 75 | ## ✅ Checklist / 检查清单 76 | 77 | - [ ] I have searched for similar issues / 我已搜索类似的问题 78 | - [ ] I have included all relevant information / 我已包含所有相关信息 79 | - [ ] I have checked the logs / 我已查看日志 80 | 81 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/ISteamCmdService.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// SteamCMD服务接口 7 | /// 8 | public interface ISteamCmdService 9 | { 10 | /// 11 | /// 检查SteamCMD是否已安装 12 | /// 13 | /// 是否已安装 14 | Task IsSteamCmdInstalledAsync(); 15 | 16 | /// 17 | /// 下载并安装SteamCMD 18 | /// 19 | /// 安装路径 20 | /// 进度报告 21 | /// 是否成功 22 | Task InstallSteamCmdAsync(string installPath, IProgress? progress = null); 23 | 24 | /// 25 | /// 安装或更新CS2专用服务器 26 | /// 27 | /// 服务器安装路径 28 | /// 是否验证文件完整性 29 | /// 进度报告 30 | /// 是否成功 31 | Task InstallOrUpdateServerAsync(string serverPath, bool validate = false, 32 | IProgress? progress = null); 33 | 34 | /// 35 | /// 验证服务器文件完整性 36 | /// 37 | /// 服务器路径 38 | /// 进度报告 39 | /// 是否成功 40 | Task ValidateServerFilesAsync(string serverPath, IProgress? progress = null); 41 | 42 | /// 43 | /// 获取SteamCMD安装路径 44 | /// 45 | /// 安装路径 46 | string GetSteamCmdPath(); 47 | 48 | /// 49 | /// 卸载SteamCMD 50 | /// 51 | /// 是否成功 52 | Task UninstallSteamCmdAsync(); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/InstallResult.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 安装结果 5 | /// 6 | public class InstallResult 7 | { 8 | /// 9 | /// 是否成功 10 | /// 11 | public bool Success { get; set; } 12 | 13 | /// 14 | /// 消息 15 | /// 16 | public string Message { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 错误信息(如果失败) 20 | /// 21 | public string? ErrorMessage { get; set; } 22 | 23 | /// 24 | /// 异常信息(如果有) 25 | /// 26 | public Exception? Exception { get; set; } 27 | 28 | /// 29 | /// 已安装的文件列表 30 | /// 31 | public List InstalledFiles { get; set; } = new(); 32 | 33 | /// 34 | /// 已安装的依赖插件列表(插件ID -> 插件名称) 35 | /// 36 | public Dictionary InstalledDependencies { get; set; } = new(); 37 | 38 | /// 39 | /// 创建成功结果 40 | /// 41 | public static InstallResult CreateSuccess(string message, List? installedFiles = null) 42 | { 43 | return new InstallResult 44 | { 45 | Success = true, 46 | Message = message, 47 | InstalledFiles = installedFiles ?? new() 48 | }; 49 | } 50 | 51 | /// 52 | /// 创建失败结果 53 | /// 54 | public static InstallResult CreateFailure(string message, Exception? exception = null) 55 | { 56 | return new InstallResult 57 | { 58 | Success = false, 59 | Message = message, 60 | ErrorMessage = exception?.Message ?? message, // 如果没有异常,使用 message 61 | Exception = exception 62 | }; 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CSP2 .gitignore 2 | 3 | ## Visual Studio 4 | .vs/ 5 | *.user 6 | *.suo 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | ## Build Results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | [Aa][Rr][Mm]/ 18 | [Aa][Rr][Mm]64/ 19 | bld/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | [Ll]og/ 23 | [Ll]ogs/ 24 | 25 | ## .NET Core 26 | project.lock.json 27 | project.fragment.lock.json 28 | artifacts/ 29 | 30 | ## NuGet 31 | *.nupkg 32 | *.snupkg 33 | **/packages/* 34 | !**/packages/build/ 35 | *.nuget.props 36 | *.nuget.targets 37 | .nuget/ 38 | 39 | ## Test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | *.trx 43 | *.coverage 44 | *.coveragexml 45 | 46 | ## Rider 47 | .idea/ 48 | *.sln.iml 49 | 50 | ## VS Code 51 | .vscode/ 52 | !.vscode/settings.json 53 | !.vscode/tasks.json 54 | !.vscode/launch.json 55 | !.vscode/extensions.json 56 | 57 | ## User-specific files 58 | *.rsuser 59 | *.suo 60 | *.user 61 | *.userosscache 62 | *.sln.docstates 63 | 64 | ## Runtime Data (应用程序生成的数据) 65 | data/servers.json 66 | data/settings.json 67 | data/plugins-cache.json 68 | data/logs/ 69 | 70 | ## Temporary files 71 | *.tmp 72 | *.temp 73 | *.log 74 | 75 | ## OS Files 76 | .DS_Store 77 | Thumbs.db 78 | Desktop.ini 79 | 80 | ## Backup files 81 | *.bak 82 | *.backup 83 | *~ 84 | 85 | ## Third-party providers (用户安装的Provider DLL) 86 | providers/*.dll 87 | !providers/.gitkeep 88 | 89 | ## SteamCMD (如果集成) 90 | steamcmd/ 91 | 92 | ## CS2 Server Files (如果下载) 93 | servers/ 94 | 95 | 96 | *.bat 97 | 98 | CLAUDE.md 99 | 100 | ## Cursor IDE 101 | .cursor/ 102 | 103 | ## Plugin Repository (子项目) 104 | plugin-repository/node_modules/ 105 | plugin-repository/package-lock.json 106 | plugin-repository/manifest.json 107 | plugin-repository/*.log -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IPlatformProvider.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | using System.Diagnostics; 3 | 4 | namespace CSP2.Core.Abstractions; 5 | 6 | /// 7 | /// 平台提供者接口 - 抽象不同操作系统的差异 8 | /// 9 | public interface IPlatformProvider 10 | { 11 | /// 12 | /// 提供者元数据 13 | /// 14 | ProviderMetadata Metadata { get; } 15 | 16 | /// 17 | /// 当前平台是否支持 18 | /// 19 | bool IsSupported(); 20 | 21 | /// 22 | /// 启动服务器进程 23 | /// 24 | /// 服务器可执行文件路径 25 | /// 启动参数 26 | /// 工作目录 27 | /// 进程对象 28 | Task StartServerProcessAsync(string serverPath, string arguments, string workingDirectory); 29 | 30 | /// 31 | /// 停止服务器进程 32 | /// 33 | /// 进程对象 34 | /// 是否强制终止 35 | Task StopServerProcessAsync(Process process, bool force = false); 36 | 37 | /// 38 | /// 检查端口是否被占用 39 | /// 40 | /// 端口号 41 | /// 是否被占用 42 | Task IsPortInUseAsync(int port); 43 | 44 | /// 45 | /// 获取系统信息 46 | /// 47 | /// 系统信息字典 48 | Task> GetSystemInfoAsync(); 49 | 50 | /// 51 | /// 检查文件是否有执行权限(Linux特有) 52 | /// 53 | /// 文件路径 54 | /// 是否有执行权限 55 | Task HasExecutePermissionAsync(string filePath); 56 | 57 | /// 58 | /// 设置文件执行权限(Linux特有) 59 | /// 60 | /// 文件路径 61 | Task SetExecutePermissionAsync(string filePath); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request / 功能建议 3 | about: Suggest an idea for this project / 为此项目提出想法 4 | title: '[Feature] ' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## 💡 Feature Description / 功能描述 10 | 11 | 12 | 13 | 14 | 15 | 16 | ## 🎯 Problem / Use Case / 问题/使用场景 17 | 18 | 19 | 20 | 21 | **I'm always frustrated when / 我总是感到困扰当...** 22 | 23 | 24 | 25 | ## 💭 Proposed Solution / 建议方案 26 | 27 | 28 | 29 | 30 | 31 | 32 | ## 🔄 Alternatives / 替代方案 33 | 34 | 35 | 36 | 37 | 38 | 39 | ## 📸 Mockups / Screenshots / 模型图/截图 40 | 41 | 42 | 43 | 44 | 45 | 46 | ## 📊 Priority / 优先级 47 | 48 | 49 | 50 | 51 | - [ ] 🔴 Critical / 关键 52 | - [ ] 🟡 High / 高 53 | - [ ] 🟢 Medium / 中 54 | - [ ] ⚪ Low / 低 55 | 56 | ## 🎁 Willing to Contribute / 愿意贡献 57 | 58 | - [ ] I would like to implement this feature / 我愿意实现这个功能 59 | - [ ] I can help with design / 我可以帮助设计 60 | - [ ] I can help with testing / 我可以帮助测试 61 | - [ ] I can help with documentation / 我可以帮助编写文档 62 | 63 | ## 📚 Additional Context / 补充信息 64 | 65 | 66 | 67 | 68 | 69 | 70 | --- 71 | 72 | ## ✅ Checklist / 检查清单 73 | 74 | - [ ] I have searched for similar feature requests / 我已搜索类似的功能请求 75 | - [ ] I have checked the roadmap / 我已查看路线图 76 | - [ ] This feature aligns with the project goals / 此功能符合项目目标 77 | 78 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/FrameworkInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 插件框架信息 5 | /// 6 | public class FrameworkInfo 7 | { 8 | /// 9 | /// 框架唯一标识(小写) 10 | /// 11 | public required string Id { get; set; } 12 | 13 | /// 14 | /// 框架显示名称 15 | /// 16 | public required string Name { get; set; } 17 | 18 | /// 19 | /// 短名称 20 | /// 21 | public required string ShortName { get; set; } 22 | 23 | /// 24 | /// 描述 25 | /// 26 | public string Description { get; set; } = string.Empty; 27 | 28 | /// 29 | /// 依赖的其他框架ID列表 30 | /// 31 | public string[] Dependencies { get; set; } = Array.Empty(); 32 | 33 | /// 34 | /// 安装路径(相对于服务器根目录) 35 | /// 36 | public string InstallPath { get; set; } = string.Empty; 37 | 38 | /// 39 | /// 插件路径(相对于服务器根目录) 40 | /// 41 | public string PluginPath { get; set; } = string.Empty; 42 | 43 | /// 44 | /// 配置路径(相对于服务器根目录) 45 | /// 46 | public string ConfigPath { get; set; } = string.Empty; 47 | 48 | /// 49 | /// 支持的平台(windows, linux) 50 | /// 51 | public string[] SupportedPlatforms { get; set; } = Array.Empty(); 52 | 53 | /// 54 | /// 仓库地址 55 | /// 56 | public string RepositoryUrl { get; set; } = string.Empty; 57 | 58 | /// 59 | /// 文档地址 60 | /// 61 | public string DocumentationUrl { get; set; } = string.Empty; 62 | 63 | /// 64 | /// 已安装版本 65 | /// 66 | public string? InstalledVersion { get; set; } 67 | 68 | /// 69 | /// 最新版本 70 | /// 71 | public string? LatestVersion { get; set; } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Pages/LogConsolePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using System.Windows.Input; 3 | using CSP2.Desktop.ViewModels; 4 | 5 | namespace CSP2.Desktop.Views.Pages; 6 | 7 | /// 8 | /// LogConsolePage.xaml 的交互逻辑 9 | /// 10 | public partial class LogConsolePage : UserControl 11 | { 12 | private readonly LogConsoleViewModel _viewModel; 13 | 14 | public LogConsolePage(LogConsoleViewModel viewModel) 15 | { 16 | InitializeComponent(); 17 | DataContext = viewModel; 18 | _viewModel = viewModel; 19 | } 20 | 21 | /// 22 | /// 日志文本变化时自动滚动到底部 23 | /// 24 | private void LogTextBox_TextChanged(object sender, TextChangedEventArgs e) 25 | { 26 | if (_viewModel.AutoScroll && LogTextBox.Text.Length > 0) 27 | { 28 | LogTextBox.ScrollToEnd(); 29 | } 30 | } 31 | 32 | /// 33 | /// 命令输入框键盘事件处理(支持 ↑↓ 导航历史) 34 | /// 35 | private void CommandTextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) 36 | { 37 | if (e.Key == System.Windows.Input.Key.Up) 38 | { 39 | // ↑ 键:显示上一条命令 40 | _viewModel.NavigateHistoryOlder(); 41 | 42 | // 将光标移到末尾 43 | CommandTextBox.SelectionStart = CommandTextBox.Text.Length; 44 | CommandTextBox.SelectionLength = 0; 45 | 46 | e.Handled = true; // 阻止默认行为 47 | } 48 | else if (e.Key == System.Windows.Input.Key.Down) 49 | { 50 | // ↓ 键:显示下一条命令 51 | _viewModel.NavigateHistoryNewer(); 52 | 53 | // 将光标移到末尾 54 | CommandTextBox.SelectionStart = CommandTextBox.Text.Length; 55 | CommandTextBox.SelectionLength = 0; 56 | 57 | e.Handled = true; // 阻止默认行为 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IDownloadManager.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 下载管理器接口 7 | /// 8 | public interface IDownloadManager 9 | { 10 | /// 11 | /// 所有下载任务 12 | /// 13 | IReadOnlyList Tasks { get; } 14 | 15 | /// 16 | /// 活动下载任务数量 17 | /// 18 | int ActiveTaskCount { get; } 19 | 20 | /// 21 | /// 下载任务添加事件 22 | /// 23 | event EventHandler? TaskAdded; 24 | 25 | /// 26 | /// 下载任务更新事件 27 | /// 28 | event EventHandler? TaskUpdated; 29 | 30 | /// 31 | /// 下载任务完成事件 32 | /// 33 | event EventHandler? TaskCompleted; 34 | 35 | /// 36 | /// 下载任务失败事件 37 | /// 38 | event EventHandler? TaskFailed; 39 | 40 | /// 41 | /// 添加下载任务 42 | /// 43 | void AddTask(DownloadTask task); 44 | 45 | /// 46 | /// 开始下载任务 47 | /// 48 | Task StartTaskAsync(string taskId); 49 | 50 | /// 51 | /// 暂停下载任务 52 | /// 53 | Task PauseTaskAsync(string taskId); 54 | 55 | /// 56 | /// 取消下载任务 57 | /// 58 | Task CancelTaskAsync(string taskId); 59 | 60 | /// 61 | /// 移除下载任务 62 | /// 63 | void RemoveTask(string taskId); 64 | 65 | /// 66 | /// 清空已完成的任务 67 | /// 68 | void ClearCompletedTasks(); 69 | 70 | /// 71 | /// 更新任务进度 72 | /// 73 | void UpdateTaskProgress(string taskId, double progress, string? logMessage = null); 74 | 75 | /// 76 | /// 更新任务状态 77 | /// 78 | void UpdateTaskStatus(string taskId, DownloadTaskStatus status, string? errorMessage = null); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IRCONClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace CSP2.Core.Abstractions; 5 | 6 | /// 7 | /// RCON 客户端接口 8 | /// 用于通过 RCON 协议远程管理 CS2 服务器 9 | /// 10 | public interface IRCONClient : IDisposable 11 | { 12 | /// 13 | /// 是否已连接 14 | /// 15 | bool IsConnected { get; } 16 | 17 | /// 18 | /// 服务器地址 19 | /// 20 | string Host { get; } 21 | 22 | /// 23 | /// 服务器端口 24 | /// 25 | int Port { get; } 26 | 27 | /// 28 | /// 连接到 RCON 服务器 29 | /// 30 | /// 服务器地址 31 | /// RCON 端口(默认 27015) 32 | /// RCON 密码 33 | /// 连接超时时间(毫秒) 34 | /// 是否连接成功 35 | Task ConnectAsync(string host, int port, string password, int timeout = 5000); 36 | 37 | /// 38 | /// 断开连接 39 | /// 40 | Task DisconnectAsync(); 41 | 42 | /// 43 | /// 发送命令到服务器 44 | /// 45 | /// 要执行的命令 46 | /// 服务器响应 47 | Task SendCommandAsync(string command); 48 | 49 | /// 50 | /// RCON 连接状态改变事件 51 | /// 52 | event EventHandler? ConnectionChanged; 53 | 54 | /// 55 | /// RCON 错误事件 56 | /// 57 | event EventHandler? ErrorOccurred; 58 | } 59 | 60 | /// 61 | /// RCON 连接状态改变事件参数 62 | /// 63 | public class RCONConnectionChangedEventArgs : EventArgs 64 | { 65 | public bool IsConnected { get; set; } 66 | public string? Message { get; set; } 67 | } 68 | 69 | /// 70 | /// RCON 错误事件参数 71 | /// 72 | public class RCONErrorEventArgs : EventArgs 73 | { 74 | public Exception Exception { get; set; } = null!; 75 | public string Message { get; set; } = string.Empty; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /CSP2.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34316.72 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSP2.Core", "src\CSP2.Core\CSP2.Core.csproj", "{A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSP2.Providers", "src\CSP2.Providers\CSP2.Providers.csproj", "{B2C3D4E5-F6A7-4B5C-9D1E-2F3A4B5C6D7E}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSP2.Desktop", "src\CSP2.Desktop\CSP2.Desktop.csproj", "{C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B2C3D4E5-F6A7-4B5C-9D1E-2F3A4B5C6D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B2C3D4E5-F6A7-4B5C-9D1E-2F3A4B5C6D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B2C3D4E5-F6A7-4B5C-9D1E-2F3A4B5C6D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B2C3D4E5-F6A7-4B5C-9D1E-2F3A4B5C6D7E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | 36 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Dialogs/ConfirmDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | 5 | namespace CSP2.Desktop.Views.Dialogs; 6 | 7 | public partial class ConfirmDialog : Window 8 | { 9 | public ConfirmDialog(string title, string message, string subtitle = "此操作无法撤销", 10 | string confirmButtonText = "确认", string icon = "⚠️", bool isDangerous = true) 11 | { 12 | InitializeComponent(); 13 | 14 | TitleTextBlock.Text = title; 15 | MessageTextBlock.Text = message; 16 | SubtitleTextBlock.Text = subtitle; 17 | ConfirmButton.Content = confirmButtonText; 18 | IconTextBlock.Text = icon; 19 | 20 | // 根据类型调整图标背景颜色 21 | if (isDangerous) 22 | { 23 | var iconBorder = (Border)IconTextBlock.Parent; 24 | iconBorder.Background = (System.Windows.Media.Brush)FindResource("DangerLightBrush"); 25 | ConfirmButton.Style = (Style)FindResource("DangerButtonStyle"); 26 | } 27 | else 28 | { 29 | var iconBorder = (Border)IconTextBlock.Parent; 30 | iconBorder.Background = (System.Windows.Media.Brush)FindResource("InfoLightBrush"); 31 | ConfirmButton.Style = (Style)FindResource("PrimaryButtonStyle"); 32 | } 33 | } 34 | 35 | private void ConfirmButton_Click(object sender, RoutedEventArgs e) 36 | { 37 | DialogResult = true; 38 | Close(); 39 | } 40 | 41 | private void CancelButton_Click(object sender, RoutedEventArgs e) 42 | { 43 | DialogResult = false; 44 | Close(); 45 | } 46 | 47 | /// 48 | /// 显示确认对话框 49 | /// 50 | public static bool Show(Window owner, string title, string message, 51 | string subtitle = "此操作无法撤销", string confirmButtonText = "确认", 52 | string icon = "⚠️", bool isDangerous = true) 53 | { 54 | var dialog = new ConfirmDialog(title, message, subtitle, confirmButtonText, icon, isDangerous) 55 | { 56 | Owner = owner 57 | }; 58 | return dialog.ShowDialog() == true; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/MapHistoryEntry.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// Workshop 地图历史记录条目 5 | /// 6 | public class MapHistoryEntry 7 | { 8 | /// 9 | /// Workshop ID (Steam Workshop 文件 ID) 10 | /// 11 | public string WorkshopId { get; set; } = string.Empty; 12 | 13 | /// 14 | /// 地图名称 15 | /// 16 | public string MapName { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 地图作者 Steam ID 20 | /// 21 | public string AuthorId { get; set; } = string.Empty; 22 | 23 | /// 24 | /// 地图作者名称 25 | /// 26 | public string AuthorName { get; set; } = string.Empty; 27 | 28 | /// 29 | /// 地图描述 30 | /// 31 | public string Description { get; set; } = string.Empty; 32 | 33 | /// 34 | /// 首次加载时间 35 | /// 36 | public DateTime FirstLoadedAt { get; set; } 37 | 38 | /// 39 | /// 最后加载时间 40 | /// 41 | public DateTime LastLoadedAt { get; set; } 42 | 43 | /// 44 | /// 加载次数 45 | /// 46 | public int LoadCount { get; set; } 47 | 48 | /// 49 | /// 预览图本地路径 50 | /// 51 | public string PreviewImagePath { get; set; } = string.Empty; 52 | 53 | /// 54 | /// 预览图 URL (Steam CDN) 55 | /// 56 | public string PreviewImageUrl { get; set; } = string.Empty; 57 | 58 | /// 59 | /// 文件大小 (字节) 60 | /// 61 | public long FileSize { get; set; } 62 | 63 | /// 64 | /// 创建时间 (Unix timestamp) 65 | /// 66 | public long TimeCreated { get; set; } 67 | 68 | /// 69 | /// 更新时间 (Unix timestamp) 70 | /// 71 | public long TimeUpdated { get; set; } 72 | 73 | /// 74 | /// Workshop 页面 URL 75 | /// 76 | public string WorkshopUrl => $"https://steamcommunity.com/sharedfiles/filedetails/?id={WorkshopId}"; 77 | 78 | /// 79 | /// 是否已下载预览图 80 | /// 81 | public bool HasPreviewImage => !string.IsNullOrEmpty(PreviewImagePath) && File.Exists(PreviewImagePath); 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/CSP2.Desktop.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | true 7 | true 8 | 12 9 | enable 10 | enable 11 | app.manifest 12 | 13 | 14 | true 15 | true 16 | 17 | 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | True 40 | True 41 | Settings.settings 42 | 43 | 44 | 45 | 46 | 47 | SettingsSingleFileGenerator 48 | Settings.Designer.cs 49 | 50 | 51 | 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /.gitmessage: -------------------------------------------------------------------------------- 1 | # (): 2 | # │ │ │ 3 | # │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end. 4 | # │ │ 5 | # │ └─⫸ Commit Scope: i18n|core|ui|provider|docs|config|deps|ci 6 | # │ 7 | # └─⫸ Commit Type: feat|fix|docs|style|refactor|perf|test|chore 8 | 9 | # Example for i18n commits: 10 | # feat(i18n): add Japanese language support 11 | # fix(i18n): correct server status translations in zh-CN 12 | # chore(i18n): update all locale files with new keys 13 | 14 | # Remember to: 15 | # - Use English for commit messages 16 | # - Keep the subject line to 50 characters or less 17 | # - Separate subject from body with a blank line 18 | # - Wrap the body at 72 characters 19 | # - Use the body to explain what and why vs. how 20 | 21 | # Body (optional): 22 | # 23 | 24 | 25 | # Footer (optional - reference issues, breaking changes): 26 | # Fixes #123 27 | # Closes #456 28 | # BREAKING CHANGE: description of breaking change 29 | 30 | 31 | # ───────────────────────────────────────────────────────────────────── 32 | # Commit Types: 33 | # feat: A new feature 34 | # fix: A bug fix 35 | # docs: Documentation only changes 36 | # style: Changes that don't affect code meaning (formatting, etc) 37 | # refactor: Code change that neither fixes a bug nor adds a feature 38 | # perf: Performance improvements 39 | # test: Adding or correcting tests 40 | # chore: Changes to build process or auxiliary tools 41 | # 42 | # Common Scopes: 43 | # i18n: Internationalization (translations, locales) 44 | # core: Core library (CSP2.Core) 45 | # ui: User interface (CSP2.Desktop) 46 | # provider: Provider implementations (CSP2.Providers) 47 | # docs: Documentation files 48 | # config: Configuration files 49 | # deps: Dependencies updates 50 | # ci: CI/CD related changes 51 | # 52 | # i18n Specific Guidelines: 53 | # - When adding new language: feat(i18n): add [language] support 54 | # - When fixing translations: fix(i18n): correct [section] in [locale] 55 | # - When updating translations: chore(i18n): update [locale] translations 56 | # - Always update ALL language files when adding new keys 57 | # 58 | # For detailed guidelines, see: docs/i18n-commit-guide.md 59 | # ───────────────────────────────────────────────────────────────────── 60 | 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/translation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🌍 Translation / 翻译 3 | about: Report translation issues or request new language support / 报告翻译问题或请求新语言支持 4 | title: '[i18n] ' 5 | labels: i18n, translation 6 | assignees: '' 7 | --- 8 | 9 | ## 🌍 Translation Type / 翻译类型 10 | 11 | 12 | 13 | 14 | - [ ] 🐛 Translation error / 翻译错误 15 | - [ ] ✨ New language request / 新语言请求 16 | - [ ] 📝 Translation improvement / 翻译改进 17 | - [ ] 🔄 Translation update / 翻译更新 18 | 19 | --- 20 | 21 | ## 📋 Details / 详细信息 22 | 23 | ### Language / 语言 24 | 25 | 26 | 27 | ### Affected Area / 影响区域 28 | 29 | 30 | 31 | ### Current Translation / 当前翻译 32 | 33 | ``` 34 | Current: 35 | ``` 36 | 37 | ### Suggested Translation / 建议翻译 38 | 39 | ``` 40 | Suggested: 41 | ``` 42 | 43 | ### Context / 上下文 44 | 45 | 46 | 47 | 48 | ### Reason / 原因 49 | 50 | 51 | 52 | 53 | --- 54 | 55 | ## 🎯 For New Language Requests / 新增语言请求 56 | 57 | ### Language Information / 语言信息 58 | - **Language name:** 59 | - **Native name:** 60 | - **Language code:** 61 | - **Number of speakers:** 62 | 63 | ### Volunteer Information / 志愿者信息 64 | - [ ] I am a native speaker / 我是母语者 65 | - [ ] I can provide complete translation / 我可以提供完整翻译 66 | - [ ] I can review translations / 我可以审核翻译 67 | 68 | **Your GitHub username:** @ 69 | 70 | **Estimated completion time:** 71 | 72 | --- 73 | 74 | ## 📚 Additional Context / 补充信息 75 | 76 | 77 | 78 | 79 | 80 | --- 81 | 82 | ## ✅ Checklist / 检查清单 83 | 84 | - [ ] I have checked existing translations / 我已检查现有翻译 85 | - [ ] I have read the [i18n guidelines](../../docs/i18n-commit-guide.md) / 我已阅读国际化指南 86 | - [ ] I have searched for similar issues / 我已搜索类似的问题 87 | 88 | --- 89 | 90 | **Note:** For detailed guidelines, please refer to [i18n Commit Guidelines](../../docs/i18n-commit-guide.md) 91 | **注意:** 详细指南请参考 [i18n提交规范](../../docs/i18n-commit-guide.md) 92 | 93 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Dialogs/RestartConfirmDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace CSP2.Desktop.Views.Dialogs; 4 | 5 | /// 6 | /// 重启确认对话框 7 | /// 8 | public partial class RestartConfirmDialog : Window 9 | { 10 | /// 11 | /// 对话框结果 12 | /// 13 | public bool ShouldRestart { get; private set; } = false; 14 | 15 | /// 16 | /// 更改类型(用于显示不同的消息) 17 | /// 18 | public string ChangeType { get; set; } = ""; 19 | 20 | public RestartConfirmDialog() 21 | { 22 | InitializeComponent(); 23 | UpdateMessage(); 24 | } 25 | 26 | public RestartConfirmDialog(string changeType) : this() 27 | { 28 | ChangeType = changeType; 29 | UpdateMessage(); 30 | } 31 | 32 | /// 33 | /// 更新消息内容 34 | /// 35 | private void UpdateMessage() 36 | { 37 | if (MessageTextBlock == null) return; 38 | 39 | var message = ChangeType switch 40 | { 41 | "Language" => "语言设置已更改,需要重启应用程序以完全生效。", 42 | "Theme" => "主题设置已更改,需要重启应用程序以完全生效。", 43 | _ => "设置已更改,需要重启应用程序以完全生效。" 44 | }; 45 | 46 | MessageTextBlock.Text = message; 47 | } 48 | 49 | /// 50 | /// 重启按钮点击事件 51 | /// 52 | private void RestartButton_Click(object sender, RoutedEventArgs e) 53 | { 54 | ShouldRestart = true; 55 | DialogResult = true; 56 | Close(); 57 | } 58 | 59 | /// 60 | /// 取消按钮点击事件 61 | /// 62 | private void CancelButton_Click(object sender, RoutedEventArgs e) 63 | { 64 | ShouldRestart = false; 65 | DialogResult = false; 66 | Close(); 67 | } 68 | 69 | /// 70 | /// 关闭按钮点击事件 71 | /// 72 | private void CloseButton_Click(object sender, RoutedEventArgs e) 73 | { 74 | CancelButton_Click(sender, e); 75 | } 76 | 77 | /// 78 | /// 显示重启确认对话框 79 | /// 80 | /// 父窗口 81 | /// 更改类型 82 | /// 是否选择重启 83 | public static bool ShowDialog(Window? owner, string changeType = "") 84 | { 85 | var dialog = new RestartConfirmDialog(changeType) 86 | { 87 | Owner = owner 88 | }; 89 | 90 | var result = dialog.ShowDialog(); 91 | return result == true && dialog.ShouldRestart; 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/ErrorDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | 4 | namespace CSP2.Desktop.Views; 5 | 6 | /// 7 | /// 错误对话框 - 显示详细错误信息并允许用户复制 8 | /// 9 | public partial class ErrorDialog : Window 10 | { 11 | public ErrorDialog() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | /// 17 | /// 显示错误对话框 18 | /// 19 | /// 简短错误消息 20 | /// 异常对象(可选) 21 | /// 副标题(可选) 22 | public static void Show(string errorMessage, Exception? exception = null, string? subtitle = null) 23 | { 24 | var dialog = new ErrorDialog(); 25 | 26 | // 设置副标题 27 | if (!string.IsNullOrEmpty(subtitle)) 28 | { 29 | dialog.SubtitleText.Text = subtitle; 30 | } 31 | 32 | // 设置错误消息 33 | dialog.ErrorMessageText.Text = errorMessage; 34 | 35 | // 构建详细信息 36 | var detailText = $"=== CSP2 错误报告 ===\n\n"; 37 | detailText += $"时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}\n\n"; 38 | detailText += $"错误消息:\n{errorMessage}\n\n"; 39 | 40 | if (exception != null) 41 | { 42 | detailText += $"异常类型: {exception.GetType().FullName}\n\n"; 43 | detailText += $"异常详情:\n{exception.Message}\n\n"; 44 | detailText += $"堆栈跟踪:\n{exception.StackTrace}\n\n"; 45 | 46 | // 如果有内部异常 47 | if (exception.InnerException != null) 48 | { 49 | detailText += $"--- 内部异常 ---\n\n"; 50 | detailText += $"类型: {exception.InnerException.GetType().FullName}\n"; 51 | detailText += $"消息: {exception.InnerException.Message}\n"; 52 | detailText += $"堆栈: {exception.InnerException.StackTrace}\n\n"; 53 | } 54 | } 55 | 56 | detailText += $"=== 环境信息 ===\n"; 57 | detailText += $"操作系统: {Environment.OSVersion}\n"; 58 | detailText += $".NET 版本: {Environment.Version}\n"; 59 | detailText += $"工作目录: {Environment.CurrentDirectory}\n"; 60 | 61 | dialog.DetailTextBox.Text = detailText; 62 | 63 | // 显示对话框 64 | dialog.ShowDialog(); 65 | } 66 | 67 | private void CopyButton_Click(object sender, RoutedEventArgs e) 68 | { 69 | try 70 | { 71 | System.Windows.Clipboard.SetText(DetailTextBox.Text); 72 | MessageBox.Show("错误信息已复制到剪贴板!", "CSP2", MessageBoxButton.OK, MessageBoxImage.Information); 73 | } 74 | catch (Exception ex) 75 | { 76 | MessageBox.Show($"复制失败:{ex.Message}", "CSP2", MessageBoxButton.OK, MessageBoxImage.Warning); 77 | } 78 | } 79 | 80 | private void CloseButton_Click(object sender, RoutedEventArgs e) 81 | { 82 | Close(); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Resources/Styles/DarkTheme.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | #1e1e1e 8 | #2d2d2d 9 | #252525 10 | #3c3c3c 11 | #0f172a 12 | #0c1421 13 | 14 | 15 | #e5e7eb 16 | #9ca3af 17 | #6b7280 18 | #4b5563 19 | 20 | 21 | #374151 22 | #4b5563 23 | #1f2937 24 | 25 | 26 | #312e81 27 | 28 | 29 | #064e3b 30 | #78350f 31 | #7f1d1d 32 | #164e63 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IPluginRepositoryService.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 插件仓库服务接口 7 | /// 8 | public interface IPluginRepositoryService 9 | { 10 | /// 11 | /// 获取插件清单(带缓存) 12 | /// 13 | /// 是否强制刷新 14 | /// 插件清单 15 | Task GetManifestAsync(bool forceRefresh = false); 16 | 17 | /// 18 | /// 搜索插件 19 | /// 20 | /// 关键词 21 | /// 匹配的插件列表 22 | Task> SearchPluginsAsync(string keyword); 23 | 24 | /// 25 | /// 按分类过滤 26 | /// 27 | /// 分类名称 28 | /// 插件列表 29 | Task> GetPluginsByCategoryAsync(string category); 30 | 31 | /// 32 | /// 获取插件详情 33 | /// 34 | /// 插件ID 35 | /// 插件信息,不存在返回null 36 | Task GetPluginDetailsAsync(string pluginId); 37 | 38 | /// 39 | /// 检查插件更新 40 | /// 41 | /// 已安装的插件列表 42 | /// 插件ID到最新版本的映射 43 | Task> CheckUpdatesAsync(List installedPlugins); 44 | 45 | /// 46 | /// 刷新缓存 47 | /// 48 | /// 是否成功 49 | Task RefreshCacheAsync(); 50 | } 51 | 52 | /// 53 | /// 插件清单 54 | /// 55 | public class PluginManifest 56 | { 57 | /// 58 | /// 清单版本 59 | /// 60 | public string Version { get; set; } = "1.0"; 61 | 62 | /// 63 | /// 最后更新时间 64 | /// 65 | public DateTime LastUpdated { get; set; } = DateTime.Now; 66 | 67 | /// 68 | /// 插件列表 69 | /// 70 | public List Plugins { get; set; } = new(); 71 | 72 | /// 73 | /// 分类列表 74 | /// 75 | public List Categories { get; set; } = new(); 76 | } 77 | 78 | /// 79 | /// 分类信息 80 | /// 81 | public class CategoryInfo 82 | { 83 | /// 84 | /// 分类ID(小写,用于匹配) 85 | /// 86 | public string Id { get; set; } = string.Empty; 87 | 88 | /// 89 | /// 分类名称(英文) 90 | /// 91 | public string Name { get; set; } = string.Empty; 92 | 93 | /// 94 | /// 分类名称(中文) 95 | /// 96 | public string? NameZh { get; set; } 97 | 98 | /// 99 | /// 分类描述(英文) 100 | /// 101 | public string? Description { get; set; } 102 | 103 | /// 104 | /// 分类描述(中文) 105 | /// 106 | public string? DescriptionZh { get; set; } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Resources/Styles/LightTheme.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | #fafafa 8 | #ffffff 9 | #ffffff 10 | #f8fafc 11 | #1e293b 12 | #0f172a 13 | 14 | 15 | #1f2937 16 | #6b7280 17 | #9ca3af 18 | #d1d5db 19 | 20 | 21 | #e5e7eb 22 | #f3f4f6 23 | #d1d5db 24 | 25 | 26 | #e0e7ff 27 | 28 | 29 | #d1fae5 30 | #fef3c7 31 | #fee2e2 32 | #d1f5ff 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/DownloadTask.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 下载任务模型 5 | /// 6 | public class DownloadTask 7 | { 8 | /// 9 | /// 任务ID 10 | /// 11 | public string Id { get; set; } = Guid.NewGuid().ToString(); 12 | 13 | /// 14 | /// 任务名称 15 | /// 16 | public string Name { get; set; } = string.Empty; 17 | 18 | /// 19 | /// 任务描述 20 | /// 21 | public string Description { get; set; } = string.Empty; 22 | 23 | /// 24 | /// 任务类型 25 | /// 26 | public DownloadTaskType TaskType { get; set; } 27 | 28 | /// 29 | /// 下载进度 (0-100) 30 | /// 31 | public double Progress { get; set; } 32 | 33 | /// 34 | /// 下载速度 (字节/秒) 35 | /// 36 | public long Speed { get; set; } 37 | 38 | /// 39 | /// 总大小 (字节) 40 | /// 41 | public long TotalSize { get; set; } 42 | 43 | /// 44 | /// 已下载大小 (字节) 45 | /// 46 | public long DownloadedSize { get; set; } 47 | 48 | /// 49 | /// 任务状态 50 | /// 51 | public DownloadTaskStatus Status { get; set; } 52 | 53 | /// 54 | /// 创建时间 55 | /// 56 | public DateTime CreatedTime { get; set; } = DateTime.Now; 57 | 58 | /// 59 | /// 开始时间 60 | /// 61 | public DateTime? StartTime { get; set; } 62 | 63 | /// 64 | /// 完成时间 65 | /// 66 | public DateTime? CompletedTime { get; set; } 67 | 68 | /// 69 | /// 日志消息 70 | /// 71 | public List LogMessages { get; set; } = new(); 72 | 73 | /// 74 | /// 错误消息 75 | /// 76 | public string? ErrorMessage { get; set; } 77 | } 78 | 79 | /// 80 | /// 下载任务类型 81 | /// 82 | public enum DownloadTaskType 83 | { 84 | /// 85 | /// SteamCMD下载 86 | /// 87 | SteamCmd, 88 | 89 | /// 90 | /// 插件下载 91 | /// 92 | Plugin, 93 | 94 | /// 95 | /// 框架下载 96 | /// 97 | Framework, 98 | 99 | /// 100 | /// 更新下载 101 | /// 102 | Update, 103 | 104 | /// 105 | /// 其他 106 | /// 107 | Other 108 | } 109 | 110 | /// 111 | /// 下载任务状态 112 | /// 113 | public enum DownloadTaskStatus 114 | { 115 | /// 116 | /// 等待中 117 | /// 118 | Pending, 119 | 120 | /// 121 | /// 下载中 122 | /// 123 | Downloading, 124 | 125 | /// 126 | /// 暂停 127 | /// 128 | Paused, 129 | 130 | /// 131 | /// 完成 132 | /// 133 | Completed, 134 | 135 | /// 136 | /// 失败 137 | /// 138 | Failed, 139 | 140 | /// 141 | /// 取消 142 | /// 143 | Cancelled 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/CSP2.Core/Models/Server.cs: -------------------------------------------------------------------------------- 1 | namespace CSP2.Core.Models; 2 | 3 | /// 4 | /// 服务器实体 5 | /// 6 | public class Server 7 | { 8 | /// 9 | /// 唯一标识(GUID) 10 | /// 11 | public string Id { get; set; } = Guid.NewGuid().ToString(); 12 | 13 | /// 14 | /// 服务器名称 15 | /// 16 | public required string Name { get; set; } 17 | 18 | /// 19 | /// 服务器安装路径 20 | /// 21 | public required string InstallPath { get; set; } 22 | 23 | /// 24 | /// 当前状态 25 | /// 26 | public ServerStatus Status { get; set; } = ServerStatus.Stopped; 27 | 28 | /// 29 | /// 配置信息 30 | /// 31 | public ServerConfig Config { get; set; } = new(); 32 | 33 | /// 34 | /// RCON 连接配置 35 | /// 36 | public RCONConfig RCONConfig { get; set; } = new(); 37 | 38 | /// 39 | /// 已安装的框架列表 40 | /// 41 | public List Frameworks { get; set; } = new(); 42 | 43 | /// 44 | /// 创建时间 45 | /// 46 | public DateTime CreatedAt { get; set; } = DateTime.Now; 47 | 48 | /// 49 | /// 最后启动时间 50 | /// 51 | public DateTime? LastStartedAt { get; set; } 52 | 53 | /// 54 | /// 服务器安装来源 55 | /// 56 | public ServerInstallSource InstallSource { get; set; } = ServerInstallSource.Manual; 57 | 58 | /// 59 | /// 是否由CSP2管理文件(用于判断是否可以卸载) 60 | /// 61 | public bool IsManagedByCSP2 { get; set; } = false; 62 | 63 | /// 64 | /// 判断两个服务器对象是否相等(基于 Id) 65 | /// 66 | public override bool Equals(object? obj) 67 | { 68 | if (obj is Server other) 69 | { 70 | return Id == other.Id; 71 | } 72 | return false; 73 | } 74 | 75 | /// 76 | /// 获取哈希码(基于 Id) 77 | /// 78 | public override int GetHashCode() 79 | { 80 | return Id?.GetHashCode() ?? 0; 81 | } 82 | } 83 | 84 | /// 85 | /// 服务器安装来源 86 | /// 87 | public enum ServerInstallSource 88 | { 89 | /// 90 | /// 手动添加 91 | /// 92 | Manual, 93 | 94 | /// 95 | /// 通过SteamCMD下载 96 | /// 97 | SteamCmd, 98 | 99 | /// 100 | /// 使用现有的Steam安装 101 | /// 102 | ExistingSteam, 103 | 104 | /// 105 | /// 使用现有的本地安装 106 | /// 107 | ExistingLocal 108 | } 109 | 110 | /// 111 | /// 已安装的框架信息 112 | /// 113 | public class InstalledFramework 114 | { 115 | /// 116 | /// 框架ID 117 | /// 118 | public required string Id { get; set; } 119 | 120 | /// 121 | /// 已安装版本 122 | /// 123 | public required string Version { get; set; } 124 | 125 | /// 126 | /// 安装时间 127 | /// 128 | public DateTime InstalledAt { get; set; } = DateTime.Now; 129 | } 130 | 131 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Models/PluginViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | using CSP2.Core.Models; 3 | 4 | namespace CSP2.Desktop.Models; 5 | 6 | /// 7 | /// 插件视图模型 - 包装 PluginInfo 并添加UI状态 8 | /// 9 | public partial class PluginViewModel : ObservableObject 10 | { 11 | /// 12 | /// 插件信息 13 | /// 14 | public PluginInfo PluginInfo { get; } 15 | 16 | /// 17 | /// 是否已安装 18 | /// 19 | [ObservableProperty] 20 | private bool _isInstalled; 21 | 22 | /// 23 | /// 已安装的版本(null表示未安装) 24 | /// 25 | [ObservableProperty] 26 | private string? _installedVersion; 27 | 28 | /// 29 | /// 是否有更新 30 | /// 31 | [ObservableProperty] 32 | private bool _hasUpdate; 33 | 34 | /// 35 | /// 是否正在安装 36 | /// 37 | [ObservableProperty] 38 | private bool _isInstalling; 39 | 40 | public PluginViewModel(PluginInfo pluginInfo) 41 | { 42 | PluginInfo = pluginInfo; 43 | } 44 | 45 | // 转发 PluginInfo 的常用属性 46 | public string Id => PluginInfo.Id; 47 | public string Name => PluginInfo.Name; 48 | public string? Slug => PluginInfo.Slug; 49 | public AuthorInfo? Author => PluginInfo.Author; 50 | public string Description => PluginInfo.Description; 51 | public string? DescriptionZh => PluginInfo.DescriptionZh; 52 | public string Framework => PluginInfo.Framework; 53 | public string? FrameworkVersion => PluginInfo.FrameworkVersion; 54 | public string[] Dependencies => PluginInfo.Dependencies; 55 | public string Category => PluginInfo.Category; 56 | public string[] Tags => PluginInfo.Tags; 57 | public string Version => PluginInfo.Version; 58 | public string? Changelog => PluginInfo.Changelog; 59 | public DownloadInfo? Download => PluginInfo.Download; 60 | public string DownloadUrl => PluginInfo.DownloadUrl; 61 | public long DownloadSize => PluginInfo.DownloadSize; 62 | public RepositoryInfo? Repository => PluginInfo.Repository; 63 | public InstallationInfo? Installation => PluginInfo.Installation; 64 | public ConfigurationInfo? Configuration => PluginInfo.Configuration; 65 | public LinksInfo? Links => PluginInfo.Links; 66 | public MediaInfo? Media => PluginInfo.Media; 67 | public bool Verified => PluginInfo.Verified; 68 | public bool Featured => PluginInfo.Featured; 69 | public bool OfficialSupport => PluginInfo.OfficialSupport; 70 | public DownloadsInfo? Downloads => PluginInfo.Downloads; 71 | public RatingInfo? Rating => PluginInfo.Rating; 72 | public CompatibilityInfo? Compatibility => PluginInfo.Compatibility; 73 | public MetadataInfo? Metadata => PluginInfo.Metadata; 74 | 75 | /// 76 | /// 安装状态文本 77 | /// 78 | public string InstallStatusText 79 | { 80 | get 81 | { 82 | if (IsInstalling) 83 | return "安装中..."; 84 | if (HasUpdate) 85 | return $"已安装 (v{InstalledVersion}) - 有更新"; 86 | if (IsInstalled) 87 | return $"已安装 (v{InstalledVersion})"; 88 | return "未安装"; 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IFrameworkProvider.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 框架提供者接口 - 抽象不同插件框架的差异 7 | /// 8 | public interface IFrameworkProvider 9 | { 10 | /// 11 | /// 提供者元数据 12 | /// 13 | ProviderMetadata Metadata { get; } 14 | 15 | /// 16 | /// 框架信息 17 | /// 18 | FrameworkInfo FrameworkInfo { get; } 19 | 20 | /// 21 | /// 检查框架是否已安装 22 | /// 23 | /// 服务器根目录 24 | /// 是否已安装 25 | Task IsInstalledAsync(string serverPath); 26 | 27 | /// 28 | /// 获取已安装的框架版本 29 | /// 30 | /// 服务器根目录 31 | /// 版本号,未安装返回null 32 | Task GetInstalledVersionAsync(string serverPath); 33 | 34 | /// 35 | /// 安装框架 36 | /// 37 | /// 服务器根目录 38 | /// 版本号,null表示安装最新版 39 | /// 进度报告 40 | /// 安装结果 41 | Task InstallAsync(string serverPath, string? version = null, 42 | IProgress? progress = null); 43 | 44 | /// 45 | /// 更新框架 46 | /// 47 | /// 服务器根目录 48 | /// 进度报告 49 | /// 安装结果 50 | Task UpdateAsync(string serverPath, IProgress? progress = null); 51 | 52 | /// 53 | /// 卸载框架 54 | /// 55 | /// 服务器根目录 56 | /// 是否成功 57 | Task UninstallAsync(string serverPath); 58 | 59 | /// 60 | /// 扫描已安装的插件 61 | /// 62 | /// 服务器根目录 63 | /// 已安装的插件列表 64 | Task> ScanInstalledPluginsAsync(string serverPath); 65 | 66 | /// 67 | /// 安装插件 68 | /// 69 | /// 服务器根目录 70 | /// 插件信息 71 | /// 进度报告 72 | /// 安装结果 73 | Task InstallPluginAsync(string serverPath, PluginInfo pluginInfo, 74 | IProgress? progress = null); 75 | 76 | /// 77 | /// 卸载插件 78 | /// 79 | /// 服务器根目录 80 | /// 已安装的插件 81 | /// 是否成功 82 | Task UninstallPluginAsync(string serverPath, InstalledPlugin plugin); 83 | 84 | /// 85 | /// 启用或禁用插件 86 | /// 87 | /// 服务器根目录 88 | /// 已安装的插件 89 | /// 是否启用 90 | /// 是否成功 91 | Task SetPluginEnabledAsync(string serverPath, InstalledPlugin plugin, bool enabled); 92 | 93 | /// 94 | /// 检查框架是否有更新 95 | /// 96 | /// 当前版本 97 | /// 最新版本,null表示无更新 98 | Task CheckUpdateAsync(string currentVersion); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 📋 Description / 描述 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## 🔖 Type of Change / 更改类型 9 | 10 | 11 | 12 | 13 | - [ ] 🐛 Bug fix / 问题修复 14 | - [ ] ✨ New feature / 新功能 15 | - [ ] 🌍 i18n / Translation / 国际化/翻译 16 | - [ ] 📝 Documentation / 文档 17 | - [ ] 🎨 UI/UX improvement / 界面/体验改进 18 | - [ ] ♻️ Refactoring / 代码重构 19 | - [ ] ⚡ Performance improvement / 性能优化 20 | - [ ] ✅ Tests / 测试 21 | - [ ] 🔧 Configuration / 配置 22 | - [ ] 📦 Dependencies update / 依赖更新 23 | 24 | ## 🌍 i18n Checklist / 国际化检查清单 25 | 26 | 27 | 28 | 29 | **For Translation PRs / 翻译PR适用:** 30 | 31 | - [ ] All language files are updated / 所有语言文件已更新 (`en.json`, `zh-CN.json`, etc.) 32 | - [ ] JSON syntax is valid (no trailing commas) / JSON语法正确(无尾随逗号) 33 | - [ ] Keys follow the project naming convention / 键名遵循项目命名规范 34 | - [ ] Proper use of placeholders (`{0}`, `{1}`) / 正确使用占位符 35 | - [ ] No hardcoded strings in code / 代码中无硬编码字符串 36 | - [ ] Tested with both short and long translations / 已测试短/长翻译文本 37 | - [ ] Language code follows standard format (e.g., `zh-CN`, `ja-JP`) / 语言代码遵循标准格式 38 | 39 | **For New Language Addition / 新增语言适用:** 40 | 41 | - [ ] Created `[locale].json` with complete translations / 创建了完整的语言文件 42 | - [ ] Added language option to Settings page / 在设置页面添加了语言选项 43 | - [ ] Updated `LocalizationHelper` if needed / 如需要,已更新LocalizationHelper 44 | - [ ] Updated README.md with new language / 在README中添加了新语言说明 45 | - [ ] Native speaker review requested / 已请母语者审核 46 | 47 | ## 🔍 Changes Made / 具体更改 48 | 49 | 50 | 51 | 52 | - 53 | - 54 | - 55 | 56 | ## 🧪 Testing / 测试 57 | 58 | 59 | 60 | 61 | - [ ] Tested locally / 本地测试通过 62 | - [ ] UI tested with different languages (if applicable) / UI在不同语言下测试(如适用) 63 | - [ ] No linter errors / 无语法检查错误 64 | - [ ] All existing tests pass / 所有现有测试通过 65 | 66 | **Test Environment / 测试环境:** 67 | - OS: 68 | - .NET Version: 69 | 70 | ## 📸 Screenshots / 截图 71 | 72 | 73 | 74 | 75 | 76 | 77 | ## 📚 Related Issues / 相关Issue 78 | 79 | 80 | 81 | 82 | Fixes # 83 | Closes # 84 | Related to # 85 | 86 | ## ✅ Checklist / 检查清单 87 | 88 | 89 | 90 | 91 | - [ ] My code follows the project's code style / 我的代码遵循项目代码规范 92 | - [ ] I have performed a self-review / 我已进行自我审查 93 | - [ ] I have commented my code, particularly in hard-to-understand areas / 我已为代码添加注释,特别是难以理解的部分 94 | - [ ] I have made corresponding changes to the documentation / 我已更新相应的文档 95 | - [ ] My changes generate no new warnings / 我的更改没有产生新的警告 96 | - [ ] I have read the [Contributing Guidelines](docs/i18n-commit-guide.md) / 我已阅读贡献指南 97 | 98 | ## 💬 Additional Notes / 补充说明 99 | 100 | 101 | 102 | 103 | 104 | 105 | --- 106 | 107 | **For i18n contributors:** Please refer to [i18n Commit Guidelines](../docs/i18n-commit-guide.md) for detailed standards. 108 | **i18n贡献者请参考:** [i18n提交规范](../docs/i18n-commit-guide.md) 了解详细标准。 109 | 110 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IPluginManager.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 插件管理器接口 7 | /// 8 | public interface IPluginManager 9 | { 10 | /// 11 | /// 获取可用插件列表(从仓库) 12 | /// 13 | /// 是否强制刷新缓存 14 | /// 插件列表 15 | Task> GetAvailablePluginsAsync(bool forceRefresh = false); 16 | 17 | /// 18 | /// 获取指定服务器已安装的插件 19 | /// 20 | /// 服务器ID 21 | /// 已安装的插件列表 22 | Task> GetInstalledPluginsAsync(string serverId); 23 | 24 | /// 25 | /// 安装插件 26 | /// 27 | /// 服务器ID 28 | /// 插件ID 29 | /// 进度报告 30 | /// 安装结果 31 | Task InstallPluginAsync(string serverId, string pluginId, 32 | IProgress? progress = null); 33 | 34 | /// 35 | /// 更新插件 36 | /// 37 | /// 服务器ID 38 | /// 插件ID 39 | /// 进度报告 40 | /// 安装结果 41 | Task UpdatePluginAsync(string serverId, string pluginId, 42 | IProgress? progress = null); 43 | 44 | /// 45 | /// 卸载插件 46 | /// 47 | /// 服务器ID 48 | /// 插件ID 49 | /// 是否成功 50 | Task UninstallPluginAsync(string serverId, string pluginId); 51 | 52 | /// 53 | /// 启用或禁用插件 54 | /// 55 | /// 服务器ID 56 | /// 插件ID 57 | /// 是否启用 58 | /// 是否成功 59 | Task SetPluginEnabledAsync(string serverId, string pluginId, bool enabled); 60 | 61 | /// 62 | /// 检查插件更新 63 | /// 64 | /// 服务器ID 65 | /// 有更新的插件列表 66 | Task> CheckUpdatesAsync(string serverId); 67 | 68 | /// 69 | /// 搜索插件 70 | /// 71 | /// 关键词 72 | /// 匹配的插件列表 73 | Task> SearchPluginsAsync(string keyword); 74 | 75 | /// 76 | /// 按分类获取插件 77 | /// 78 | /// 分类名称 79 | /// 插件列表 80 | Task> GetPluginsByCategoryAsync(string category); 81 | } 82 | 83 | /// 84 | /// 插件更新信息 85 | /// 86 | public class PluginUpdateInfo 87 | { 88 | /// 89 | /// 插件ID 90 | /// 91 | public required string PluginId { get; set; } 92 | 93 | /// 94 | /// 插件名称 95 | /// 96 | public required string PluginName { get; set; } 97 | 98 | /// 99 | /// 当前版本 100 | /// 101 | public required string CurrentVersion { get; set; } 102 | 103 | /// 104 | /// 最新版本 105 | /// 106 | public required string LatestVersion { get; set; } 107 | 108 | /// 109 | /// 更新说明 110 | /// 111 | public string? ReleaseNotes { get; set; } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /src/CSP2.Core/Services/ProviderRegistry.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Abstractions; 2 | using CSP2.Core.Models; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace CSP2.Core.Services; 6 | 7 | /// 8 | /// Provider注册中心 9 | /// 10 | public class ProviderRegistry 11 | { 12 | private readonly List _platformProviders = new(); 13 | private readonly List _frameworkProviders = new(); 14 | private readonly ILogger _logger; 15 | 16 | public ProviderRegistry(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | /// 22 | /// 注册平台提供者 23 | /// 24 | public void RegisterPlatformProvider(IPlatformProvider provider) 25 | { 26 | if (_platformProviders.Any(p => p.Metadata.Id == provider.Metadata.Id)) 27 | { 28 | _logger.LogWarning("平台Provider {Id} 已注册,将被覆盖", provider.Metadata.Id); 29 | _platformProviders.RemoveAll(p => p.Metadata.Id == provider.Metadata.Id); 30 | } 31 | 32 | _platformProviders.Add(provider); 33 | _logger.LogInformation("已注册平台Provider: {Name} v{Version}", 34 | provider.Metadata.Name, provider.Metadata.Version); 35 | } 36 | 37 | /// 38 | /// 注册框架提供者 39 | /// 40 | public void RegisterFrameworkProvider(IFrameworkProvider provider) 41 | { 42 | if (_frameworkProviders.Any(p => p.Metadata.Id == provider.Metadata.Id)) 43 | { 44 | _logger.LogWarning("框架Provider {Id} 已注册,将被覆盖", provider.Metadata.Id); 45 | _frameworkProviders.RemoveAll(p => p.Metadata.Id == provider.Metadata.Id); 46 | } 47 | 48 | _frameworkProviders.Add(provider); 49 | _logger.LogInformation("已注册框架Provider: {Name} v{Version}", 50 | provider.Metadata.Name, provider.Metadata.Version); 51 | } 52 | 53 | /// 54 | /// 自动选择最佳平台Provider 55 | /// 56 | public IPlatformProvider GetBestPlatformProvider() 57 | { 58 | var supportedProviders = _platformProviders 59 | .Where(p => p.IsSupported()) 60 | .OrderByDescending(p => p.Metadata.Priority) 61 | .ToList(); 62 | 63 | if (supportedProviders.Count == 0) 64 | { 65 | throw new InvalidOperationException("No provider found for the current platform"); 66 | } 67 | 68 | var selected = supportedProviders.First(); 69 | _logger.LogInformation("选择平台Provider: {Name}", selected.Metadata.Name); 70 | return selected; 71 | } 72 | 73 | /// 74 | /// 获取指定框架Provider 75 | /// 76 | public IFrameworkProvider? GetFrameworkProvider(string frameworkId) 77 | { 78 | return _frameworkProviders.FirstOrDefault(p => p.Metadata.Id == frameworkId); 79 | } 80 | 81 | /// 82 | /// 列出所有可用框架 83 | /// 84 | public List GetAvailableFrameworks() 85 | { 86 | return _frameworkProviders 87 | .Select(p => p.FrameworkInfo) 88 | .OrderBy(f => f.Name) 89 | .ToList(); 90 | } 91 | 92 | /// 93 | /// 获取所有已注册的平台Provider 94 | /// 95 | public List GetAllPlatformProviders() 96 | { 97 | return _platformProviders.ToList(); 98 | } 99 | 100 | /// 101 | /// 获取所有已注册的框架Provider 102 | /// 103 | public List GetAllFrameworkProviders() 104 | { 105 | return _frameworkProviders.ToList(); 106 | } 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-desktop.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow will build, test, sign and package a WPF or Windows Forms desktop application 7 | # built on .NET Core. 8 | # To learn how to migrate your existing application to .NET Core, 9 | # refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework 10 | # 11 | # To configure this workflow: 12 | # 13 | # 1. Configure environment variables 14 | # GitHub sets default environment variables for every workflow run. 15 | # Replace the variables relative to your project in the "env" section below. 16 | # 17 | # 2. Signing 18 | # Generate a signing certificate in the Windows Application 19 | # Packaging Project or add an existing signing certificate to the project. 20 | # Next, use PowerShell to encode the .pfx file using Base64 encoding 21 | # by running the following Powershell script to generate the output string: 22 | # 23 | # $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte 24 | # [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' 25 | # 26 | # Open the output file, SigningCertificate_Encoded.txt, and copy the 27 | # string inside. Then, add the string to the repo as a GitHub secret 28 | # and name it "Base64_Encoded_Pfx." 29 | # For more information on how to configure your signing certificate for 30 | # this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing 31 | # 32 | # Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". 33 | # See "Build the Windows Application Packaging project" below to see how the secret is used. 34 | # 35 | # For more information on GitHub Actions, refer to https://github.com/features/actions 36 | # For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, 37 | # refer to https://github.com/microsoft/github-actions-for-desktop-apps 38 | 39 | name: .NET Core Desktop 40 | 41 | on: 42 | push: 43 | branches: [ "main" ] 44 | pull_request: 45 | branches: [ "main" ] 46 | 47 | jobs: 48 | 49 | build: 50 | 51 | strategy: 52 | matrix: 53 | configuration: [Debug, Release] 54 | 55 | runs-on: windows-latest # For a list of available runner types, refer to 56 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 57 | 58 | env: 59 | Solution_Name: CSP2.sln # The main solution file 60 | 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@v4 64 | with: 65 | fetch-depth: 0 66 | 67 | # Install the .NET Core workload 68 | - name: Install .NET Core 69 | uses: actions/setup-dotnet@v4 70 | with: 71 | dotnet-version: 8.0.x 72 | 73 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild 74 | - name: Setup MSBuild.exe 75 | uses: microsoft/setup-msbuild@v2 76 | 77 | # Execute all unit tests in the solution 78 | - name: Execute unit tests 79 | run: dotnet test 80 | 81 | # Restore the application to populate the obj folder with RuntimeIdentifiers 82 | - name: Restore the application 83 | run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration 84 | env: 85 | Configuration: ${{ matrix.configuration }} 86 | 87 | # Build the solution 88 | - name: Build the solution 89 | run: msbuild $env:Solution_Name /p:Configuration=$env:Configuration 90 | env: 91 | Configuration: ${{ matrix.configuration }} 92 | -------------------------------------------------------------------------------- /src/CSP2.Core/Abstractions/IConfigurationService.cs: -------------------------------------------------------------------------------- 1 | using CSP2.Core.Models; 2 | 3 | namespace CSP2.Core.Abstractions; 4 | 5 | /// 6 | /// 配置服务接口 7 | /// 8 | public interface IConfigurationService 9 | { 10 | /// 11 | /// 加载所有服务器配置 12 | /// 13 | /// 服务器列表 14 | Task> LoadServersAsync(); 15 | 16 | /// 17 | /// 保存所有服务器配置 18 | /// 19 | /// 服务器列表 20 | /// 是否成功 21 | Task SaveServersAsync(List servers); 22 | 23 | /// 24 | /// 加载应用设置 25 | /// 26 | /// 应用设置 27 | Task LoadAppSettingsAsync(); 28 | 29 | /// 30 | /// 保存应用设置 31 | /// 32 | /// 应用设置 33 | /// 是否成功 34 | Task SaveAppSettingsAsync(AppSettings settings); 35 | 36 | /// 37 | /// 获取数据目录路径 38 | /// 39 | /// 数据目录路径 40 | string GetDataDirectory(); 41 | 42 | /// 43 | /// 同步加载应用设置(用于初始化场景) 44 | /// 45 | /// 应用设置 46 | AppSettings LoadSettings(); 47 | } 48 | 49 | /// 50 | /// 应用设置 51 | /// 52 | public class AppSettings 53 | { 54 | /// 55 | /// 版本号 56 | /// 57 | public string Version { get; set; } = "1.0"; 58 | 59 | /// 60 | /// UI设置 61 | /// 62 | public UiSettings Ui { get; set; } = new(); 63 | 64 | /// 65 | /// SteamCMD设置 66 | /// 67 | public SteamCmdSettings SteamCmd { get; set; } = new(); 68 | 69 | /// 70 | /// 插件仓库设置 71 | /// 72 | public RepositorySettings Repository { get; set; } = new(); 73 | } 74 | 75 | /// 76 | /// UI设置 77 | /// 78 | public class UiSettings 79 | { 80 | /// 81 | /// 主题(dark, light) 82 | /// 83 | public string Theme { get; set; } = "dark"; 84 | 85 | /// 86 | /// 语言(zh-CN, en-US) 87 | /// 88 | public string Language { get; set; } = "zh-CN"; 89 | 90 | /// 91 | /// 是否自动检查更新 92 | /// 93 | public bool AutoCheckUpdates { get; set; } = true; 94 | 95 | /// 96 | /// 窗口宽度 97 | /// 98 | public double WindowWidth { get; set; } = 1200; 99 | 100 | /// 101 | /// 窗口高度 102 | /// 103 | public double WindowHeight { get; set; } = 800; 104 | 105 | /// 106 | /// 关闭时最小化到托盘(而非退出程序) 107 | /// 108 | public bool MinimizeToTray { get; set; } = true; 109 | 110 | /// 111 | /// 是否已询问过用户首次关闭行为 112 | /// 113 | public bool MinimizeToTrayAsked { get; set; } = false; 114 | 115 | /// 116 | /// 是否首次运行(用于显示欢迎动画) 117 | /// 118 | public bool IsFirstRun { get; set; } = true; 119 | } 120 | 121 | /// 122 | /// SteamCMD设置 123 | /// 124 | public class SteamCmdSettings 125 | { 126 | /// 127 | /// 安装路径 128 | /// 129 | public string InstallPath { get; set; } = string.Empty; 130 | 131 | /// 132 | /// 是否自动下载 133 | /// 134 | public bool AutoDownload { get; set; } = true; 135 | } 136 | 137 | /// 138 | /// 仓库设置 139 | /// 140 | public class RepositorySettings 141 | { 142 | /// 143 | /// 主仓库URL(优先使用) 144 | /// 留空则使用默认官方源 145 | /// 146 | public string Url { get; set; } = string.Empty; 147 | 148 | /// 149 | /// 镜像源URL列表(作为降级备选) 150 | /// 151 | public string[] MirrorUrls { get; set; } = Array.Empty(); 152 | 153 | /// 154 | /// 缓存过期时间(秒) 155 | /// 156 | public int CacheExpiration { get; set; } = 3600; 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/CSP2.Providers/Platforms/Windows/WindowsPlatformProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net.NetworkInformation; 3 | using System.Runtime.InteropServices; 4 | using CSP2.Core.Abstractions; 5 | using CSP2.Core.Models; 6 | 7 | namespace CSP2.Providers.Platforms.Windows; 8 | 9 | /// 10 | /// Windows平台提供者实现 11 | /// 12 | public class WindowsPlatformProvider : IPlatformProvider 13 | { 14 | public ProviderMetadata Metadata => new() 15 | { 16 | Id = "windows", 17 | Name = "Windows", 18 | Version = "1.0.0", 19 | Author = "CSP2 Team", 20 | Description = "Windows平台支持", 21 | Priority = 100 22 | }; 23 | 24 | public bool IsSupported() 25 | { 26 | return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 27 | } 28 | 29 | public async Task StartServerProcessAsync(string serverPath, string arguments, string workingDirectory) 30 | { 31 | var startInfo = new ProcessStartInfo 32 | { 33 | FileName = serverPath, 34 | Arguments = arguments, 35 | WorkingDirectory = workingDirectory, 36 | UseShellExecute = false, 37 | CreateNoWindow = false, // 允许显示服务器控制台窗口 38 | RedirectStandardOutput = true, 39 | RedirectStandardError = true, 40 | RedirectStandardInput = false // 不重定向stdin,避免与CS2控制台冲突 41 | }; 42 | 43 | var process = new Process { StartInfo = startInfo }; 44 | 45 | if (!process.Start()) 46 | { 47 | throw new InvalidOperationException("无法启动服务器进程"); 48 | } 49 | 50 | await Task.CompletedTask; 51 | return process; 52 | } 53 | 54 | public async Task StopServerProcessAsync(Process process, bool force = false) 55 | { 56 | if (process.HasExited) 57 | { 58 | return; 59 | } 60 | 61 | // CS2服务器不支持通过stdin发送quit命令(会导致控制台错误) 62 | // 直接终止进程是最可靠的方式 63 | try 64 | { 65 | if (!force) 66 | { 67 | // 给进程一点时间保存状态 68 | await Task.Delay(500); 69 | } 70 | 71 | if (!process.HasExited) 72 | { 73 | process.Kill(entireProcessTree: true); 74 | } 75 | } 76 | catch (Exception) 77 | { 78 | // 进程可能已经退出,忽略异常 79 | } 80 | } 81 | 82 | public Task IsPortInUseAsync(int port) 83 | { 84 | var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); 85 | var listeners = ipGlobalProperties.GetActiveTcpListeners(); 86 | var connections = ipGlobalProperties.GetActiveTcpConnections(); 87 | 88 | bool inUse = listeners.Any(x => x.Port == port) || 89 | connections.Any(x => x.LocalEndPoint.Port == port); 90 | 91 | return Task.FromResult(inUse); 92 | } 93 | 94 | public Task> GetSystemInfoAsync() 95 | { 96 | var info = new Dictionary 97 | { 98 | ["OS"] = "Windows", 99 | ["Version"] = Environment.OSVersion.VersionString, 100 | ["Architecture"] = RuntimeInformation.OSArchitecture.ToString(), 101 | ["ProcessorCount"] = Environment.ProcessorCount.ToString(), 102 | ["MachineName"] = Environment.MachineName, 103 | ["UserName"] = Environment.UserName, 104 | ["Is64BitOS"] = Environment.Is64BitOperatingSystem.ToString(), 105 | [".NET Version"] = RuntimeInformation.FrameworkDescription 106 | }; 107 | 108 | return Task.FromResult(info); 109 | } 110 | 111 | public Task HasExecutePermissionAsync(string filePath) 112 | { 113 | // Windows不需要特殊的执行权限 114 | return Task.FromResult(File.Exists(filePath)); 115 | } 116 | 117 | public Task SetExecutePermissionAsync(string filePath) 118 | { 119 | // Windows不需要设置执行权限 120 | return Task.CompletedTask; 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /src/CSP2.Desktop/Views/Dialogs/ConfirmDialog.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 41 | 42 | 43 | 46 | 51 | 56 | 57 | 58 | 59 | 60 | 65 | 66 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 89 | 96 | 97 | 101 | 102 | 107 | 111 | 112 | 113 | 114 | 115 | 116 | 119 | 123 |