[
  {
    "path": ".gitattributes",
    "content": "###############################################################################\n# Set default behavior to automatically normalize line endings.\n###############################################################################\n* text=auto\n\n###############################################################################\n# Set default behavior for command prompt diff.\n#\n# This is need for earlier builds of msysgit that does not have it on by\n# default for csharp files.\n# Note: This is only used by command line\n###############################################################################\n#*.cs     diff=csharp\n\n###############################################################################\n# Set the merge driver for project and solution files\n#\n# Merging from the command prompt will add diff markers to the files if there\n# are conflicts (Merging from VS is not affected by the settings below, in VS\n# the diff markers are never inserted). Diff markers may cause the following \n# file extensions to fail to load in VS. An alternative would be to treat\n# these files as binary and thus will always conflict and require user\n# intervention with every merge. To do so, just uncomment the entries below\n###############################################################################\n#*.sln       merge=binary\n#*.csproj    merge=binary\n#*.vbproj    merge=binary\n#*.vcxproj   merge=binary\n#*.vcproj    merge=binary\n#*.dbproj    merge=binary\n#*.fsproj    merge=binary\n#*.lsproj    merge=binary\n#*.wixproj   merge=binary\n#*.modelproj merge=binary\n#*.sqlproj   merge=binary\n#*.wwaproj   merge=binary\n\n###############################################################################\n# behavior for image files\n#\n# image files are treated as binary by default.\n###############################################################################\n#*.jpg   binary\n#*.png   binary\n#*.gif   binary\n\n###############################################################################\n# diff behavior for common document formats\n# \n# Convert binary document formats to text before diffing them. This feature\n# is only available from the command line. Turn it on by uncommenting the \n# entries below.\n###############################################################################\n#*.doc   diff=astextplain\n#*.DOC   diff=astextplain\n#*.docx  diff=astextplain\n#*.DOCX  diff=astextplain\n#*.dot   diff=astextplain\n#*.DOT   diff=astextplain\n#*.pdf   diff=astextplain\n#*.PDF   diff=astextplain\n#*.rtf   diff=astextplain\n#*.RTF   diff=astextplain\n"
  },
  {
    "path": ".github/workflows/dotnet-desktop.yml",
    "content": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\n# This workflow will build, test, sign and package a WPF or Windows Forms desktop application\n# built on .NET Core.\n# To learn how to migrate your existing application to .NET Core,\n# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework\n#\n# To configure this workflow:\n#\n# 1. Configure environment variables\n# GitHub sets default environment variables for every workflow run.\n# Replace the variables relative to your project in the \"env\" section below.\n#\n# 2. Signing\n# Generate a signing certificate in the Windows Application\n# Packaging Project or add an existing signing certificate to the project.\n# Next, use PowerShell to encode the .pfx file using Base64 encoding\n# by running the following Powershell script to generate the output string:\n#\n# $pfx_cert = Get-Content '.\\SigningCertificate.pfx' -Encoding Byte\n# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'\n#\n# Open the output file, SigningCertificate_Encoded.txt, and copy the\n# string inside. Then, add the string to the repo as a GitHub secret\n# and name it \"Base64_Encoded_Pfx.\"\n# For more information on how to configure your signing certificate for\n# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing\n#\n# Finally, add the signing certificate password to the repo as a secret and name it \"Pfx_Key\".\n# See \"Build the Windows Application Packaging project\" below to see how the secret is used.\n#\n# For more information on GitHub Actions, refer to https://github.com/features/actions\n# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,\n# refer to https://github.com/microsoft/github-actions-for-desktop-apps\n\nname: .NET Core Desktop\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n\n  build:\n\n    strategy:\n      matrix:\n        configuration: [Debug, Release]\n\n    runs-on: windows-latest  # For a list of available runner types, refer to\n                             # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v3\n      with:\n        fetch-depth: 0\n        \n    - name: Install .NET Core\n      uses: actions/setup-dotnet@v3\n      with:\n        dotnet-version: 7.0.x\n\n    - name: Restore dependencies\n      run: dotnet restore\n      \n    - name: Build\n      run: dotnet build --no-restore\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# NectarRcon Config\nconfig.json\nservers.json\npasswords.json\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Oo]ut/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) [year] [fullname]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "NectarRCON/App.xaml",
    "content": "﻿<Application\n    x:Class=\"NectarRCON.App\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:local=\"clr-namespace:NectarRCON\"\n    xmlns:sys=\"clr-namespace:System;assembly=netstandard\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    Exit=\"OnExit\"\n    Startup=\"OnStartup\">\n    <Application.Resources>\n        <ResourceDictionary>\n            <ResourceDictionary.MergedDictionaries>\n                <ui:ThemesDictionary Theme=\"Dark\" />\n                <ui:ControlsDictionary />\n                <ResourceDictionary Source=\"pack://application:,,,/NectarRCON;component/Resources/Languages/zh_cn.xaml\" />\n                <ResourceDictionary>\n                    <sys:String x:Key=\"app.version\">1.0.0-beta5</sys:String>\n                </ResourceDictionary>\n            </ResourceDictionary.MergedDictionaries>\n        </ResourceDictionary>\n    </Application.Resources>\n</Application>\n"
  },
  {
    "path": "NectarRCON/App.xaml.cs",
    "content": "﻿using Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Hosting;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Rcon;\nusing NectarRCON.Services;\nusing NectarRCON.ViewModels;\nusing NectarRCON.Windows;\nusing System;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Windows;\nusing Microsoft.Extensions.Logging;\nusing NectarRCON.Dp;\nusing Serilog;\nusing Wpf.Ui.Mvvm.Contracts;\nusing Wpf.Ui.Mvvm.Services;\n\nnamespace NectarRCON;\n\n/// <summary>\n/// Interaction logic for App.xaml\n/// </summary>\npublic partial class App\n{\n    private static readonly ILoggerFactory LoggerFactory = new LoggerFactory();\n    private static string LogFileName = $\"logs/program/log{DateTime.Now:yyyyMMddhhmm}.log\";\n\n    private static readonly IHost Host = Microsoft.Extensions.Hosting.Host\n        .CreateDefaultBuilder()\n        .ConfigureLogging(builder =>\n        {\n            Log.Logger = new LoggerConfiguration()\n                .WriteTo.File(LogFileName,\n                    rollingInterval: RollingInterval.Infinite, flushToDiskInterval: TimeSpan.FromSeconds(1))\n                .CreateLogger();\n\n            builder.AddSerilog();\n        })\n        .ConfigureServices((context, services) =>\n        {\n            services.AddSingleton(LoggerFactory);\n            services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));\n\n            services.AddHostedService<ApplicationHostService>();\n\n            services.AddSingleton<ILanguageService, LanguageService>();\n            services.AddSingleton<IConfigService, ConfigService>();\n            services.AddSingleton<ILogService, LogService>();\n            services.AddSingleton<IHistoryService, HistoryService>();\n            services.AddSingleton<IServerPasswordService, ServerPasswordService>();\n\n            services.AddSingleton<IThemeService, ThemeService>();\n            services.AddSingleton<INavigationService, NavigationService>();\n            services.AddSingleton<ISnackbarService, SnackbarService>();\n            services.AddSingleton<IServerInformationService, ServerInformationService>();\n\n            services.AddSingleton<IConnectingDialogService, ConnectingDialogService>();\n\n            // Rcon Connections\n            services.AddSingleton<IRconConnectionInfoService, RconConnectionInfoService>();\n            services.AddSingleton<IRconConnection, RconSingleConnection>();\n            services.AddSingleton<IRconConnection, RconMultiConnection>();\n\n            services.AddSingleton<IMessageBoxService, MessageBoxService>();\n\n            services.AddSingleton<IGroupService, GroupService>();\n\n\n            services.AddScoped<INavigationWindow, MainWindow>();\n            services.AddScoped<MainWindowViewModel>();\n\n            services.AddTransient<AddServerWindow>();\n            services.AddTransient<AddServerWindowViewModel>();\n        }).Build();\n\n    public static T GetService<T>()\n        where T : class\n    {\n        return (Host.Services.GetService(typeof(T)) as T)!;\n    }\n\n    public static T GetService<T>(Type type)\n        where T : class\n    {\n        return Host.Services.GetServices<T>().FirstOrDefault(t => t.GetType() == type)!;\n    }\n\n    private async void OnStartup(object sender, StartupEventArgs e)\n    {\n        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);\n\n        foreach (var rconEncoding in Enum.GetValues<Dp.RconEncoding>())\n        {\n            rconEncoding.GetEncoding();\n        }\n\n        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;\n        await Host.StartAsync();\n    }\n\n    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)\n    {\n        var exception = e.ExceptionObject as Exception;\n        Log.Error(\"未经处理的异常: {0}\", exception);\n\n        MessageBox.Show(\n            exception +\n            $\"\\n\\n程序遇到异常,即将退出!\\n建议前往Github提交Issue\\n请前往日志查看详细信息!\\nCheck log: {Path.Combine(Environment.CurrentDirectory, LogFileName).Replace(\"\\\\\", \"/\")}\",\n            \"程序崩溃\", MessageBoxButton.OK, MessageBoxImage.Error);\n        Environment.Exit(1);\n    }\n\n    private async void OnExit(object sender, ExitEventArgs e)\n    {\n        await Host.StopAsync();\n        Host.Dispose();\n    }\n}"
  },
  {
    "path": "NectarRCON/AssemblyInfo.cs",
    "content": "using System.Windows;\n\n[assembly: ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located\n                                     //(used if a resource is not found in the page,\n                                     // or application resource dictionaries)\n    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located\n                                              //(used if a resource is not found in the page,\n                                              // app, or any theme specific resource dictionaries)\n)]\n"
  },
  {
    "path": "NectarRCON/Converters/BoolToVisibilityConverter.cs",
    "content": "﻿using System;\nusing System.Windows.Data;\nusing System.Windows;\nusing System.Globalization;\n\nnamespace NectarRCON.Converters\n{\n    public class BoolToVisibilityConverter : IValueConverter\n    {\n        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if(value is bool boolValue)\n            {\n                return boolValue ? Visibility.Visible : Visibility.Hidden;\n            }\n            return null;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Converters/IntToVisibilityConverter.cs",
    "content": "﻿using System;\nusing System.Windows.Data;\nusing System.Windows;\nusing System.Globalization;\n\nnamespace NectarRCON.Converters\n{\n    public class IntToVisibilityConverter : IValueConverter\n    {\n        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            if(value is int intValue)\n            {\n                if(parameter is string boolStringValue)\n                {\n                    bool boolValue = System.Convert.ToBoolean(boolStringValue);\n                    // 如果参数为False\n                    // <= 0 ? Visible : Hidden\n                    // 否则反过来\n                    return boolValue \n                        ? (intValue <= 0 ? Visibility.Hidden : Visibility.Visible) \n                        : (intValue <= 0 ? Visibility.Visible : Visibility.Hidden);\n                }\n                return intValue <= 0 ? Visibility.Visible : Visibility.Hidden;\n            }\n            return null;\n        }\n\n        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Dp/DpFile.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text.Json;\n\nnamespace NectarRCON.Dp;\n\n/// <summary>\n/// 数据持久化文件\n/// </summary>\npublic abstract class DpFile\n{\n    /// <summary>\n    /// 文件名\n    /// </summary>\n    protected abstract string Name { get; }\n    \n    /// <summary>\n    /// 文件路径\n    /// </summary>\n    protected virtual string BasePath => string.Empty;\n\n    /// <summary>\n    /// 实例映射\n    /// </summary>\n    private static readonly Dictionary<Type, DpFile> InstanceMapping = [];\n    \n    /// <summary>\n    /// 保存数据\n    /// </summary>\n    public void Save()\n    {\n        var json = JsonSerializer.Serialize((object)this);\n        var filePath = Path.Combine(AppContext.BaseDirectory,\"dp\", BasePath, Name);\n        Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);\n        File.WriteAllText(filePath, json);\n    }\n\n    /// <summary>\n    /// 加载数据\n    /// </summary>\n    /// <param name=\"name\">文件名</param>\n    /// <param name=\"basePath\">文件路径</param>\n    /// <typeparam name=\"T\">类型</typeparam>\n    /// <returns>实例</returns>\n    private static T? Load<T>(string name, string? basePath = null)\n        where T : DpFile\n    {\n        var filePath = Path.Combine(AppContext.BaseDirectory, \"dp\", basePath ?? string.Empty, name);\n        if (!File.Exists(filePath)) return null;\n        var json = File.ReadAllText(filePath);\n        return JsonSerializer.Deserialize<T>(json);\n    }\n    \n    /// <summary>\n    /// 以单例模式加载数据\n    /// </summary>\n    /// <typeparam name=\"T\">类型</typeparam>\n    /// <returns>实例</returns>\n    public static T LoadSingleton<T>()\n        where T:DpFile\n    {\n        // 先从_instanceMapping拿数据\n        if (InstanceMapping.TryGetValue(typeof(T), out var cachedInstance))\n        {\n            return (T)cachedInstance;\n        }\n        \n        // 如果缓存没有 则使用找到此DPFile的无参构造函数 使用反射实例化后存放到_instanceMapping\n        var instance = Activator.CreateInstance<T>();\n        // 从instance中获取Name 随后load\n        InstanceMapping[typeof(T)] = Load<T>(instance.Name, instance.BasePath) ?? instance;\n        return (T)InstanceMapping[typeof(T)];\n    }\n}"
  },
  {
    "path": "NectarRCON/Dp/RconSettingsDp.cs",
    "content": "﻿using System.Text;\nusing System.Text.Json.Serialization;\n\nnamespace NectarRCON.Dp;\n\npublic enum RconEncoding\n{\n    Utf8 = 0,\n    Utf16 = 1,\n    Utf32 = 2,\n    Gb2312 = 3,\n    Gbk = 4,\n    Gb18030 = 5,\n    Ascii = 6,\n    Big5 = 7,\n    HzGb2312 = 8,\n}\n\npublic static class RconEncodingExtensions\n{\n    public static Encoding GetEncoding(this RconEncoding encoding)\n        => encoding switch\n        {\n            RconEncoding.Utf8 => Encoding.UTF8,\n            RconEncoding.Utf16 => Encoding.GetEncoding(\"UTF-16\"),\n            RconEncoding.Utf32 => Encoding.UTF32,\n            RconEncoding.Gb2312 => Encoding.GetEncoding(\"gb2312\"),\n            RconEncoding.Gbk => Encoding.GetEncoding(\"gbk\"),\n            RconEncoding.Gb18030 => Encoding.GetEncoding(\"gb18030\"),\n            RconEncoding.Ascii => Encoding.ASCII,\n            RconEncoding.Big5 => Encoding.GetEncoding(\"big5\"),\n            RconEncoding.HzGb2312 => Encoding.GetEncoding(\"hz-gb-2312\"),\n            _ => Encoding.UTF8,\n        };\n}\n\npublic class RconSettingsDp : DpFile\n{\n    protected override string Name => \"rcon_settings.json\";\n\n    /// <summary>\n    /// 连接时掉线自动尝试重连\n    /// </summary>\n    [JsonPropertyName(\"auto_reconnect\")]\n    public bool AutoReconnect { get; set; } = true;\n\n    /// <summary>\n    /// 掉线后不关闭连接窗口\n    /// </summary>\n    /// <returns></returns>\n    [JsonPropertyName(\"is_keep_connection_window_open\")]\n    public bool IsKeepConnectionWindowOpen { get; set; }\n\n    /// <summary>\n    /// 文本编码\n    /// </summary>\n    [JsonPropertyName(\"encoding\")]\n    public RconEncoding Encoding { get; set; } = RconEncoding.Utf8;\n}"
  },
  {
    "path": "NectarRCON/Entity/ClearLogValueMessage.cs",
    "content": "﻿namespace NectarRCON.Models;\npublic partial class ClearLogValueMessage\n{\n}\n"
  },
  {
    "path": "NectarRCON/Entity/Config.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace NectarRCON.Models;\npublic partial class Config\n{\n    [JsonPropertyName(\"language\")]\n    public string LanguageName { get; set; } = string.Empty;\n    [JsonPropertyName(\"theme\")]\n    public ETheme Theme { get; set; }\n    [JsonPropertyName(\"command_list\")]\n    public int CommandLimit;\n}"
  },
  {
    "path": "NectarRCON/Entity/ETheme.cs",
    "content": "﻿namespace NectarRCON.Models;\npublic enum ETheme : short\n{\n    System = 0,\n    Dark = 1,\n    Light = 2\n}"
  },
  {
    "path": "NectarRCON/Entity/Group.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Text.Json.Serialization;\n\nnamespace NectarRCON.Models\n{\n    public class Group\n    {\n        [JsonPropertyName(\"id\")]\n        public required string Id { get; set; }\n        [JsonPropertyName(\"name\")]\n        public required string Name { get; set; }\n        [JsonPropertyName(\"server_list\")]\n        public required List<string> Servers { get; set; }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Entity/MainPageLoadValueMessage.cs",
    "content": "﻿namespace NectarRCON.Models;\npublic partial class MainPageLoadValueMessage\n{\n    public required bool IsLoaded { get; init; }\n}"
  },
  {
    "path": "NectarRCON/Entity/ServerInformation.cs",
    "content": "﻿using System.Text.Json.Serialization;\nnamespace NectarRCON.Models;\npublic class ServerInformation\n{\n    [JsonPropertyName(\"name\")]\n    public string Name { get; set; } = string.Empty;\n    [JsonPropertyName(\"address\")]\n    public string Address { get; set; } = string.Empty;\n    [JsonPropertyName(\"adapter\")]\n    public string Adapter { get; set; } = string.Empty; // 适配类名\n    [JsonPropertyName(\"port\")]\n    public ushort Port { get; set; } = 0;\n    [JsonIgnore]\n    public string DisplayAddress\n    {\n        get => $\"{Address}:{Port}\";\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Entity/ServerPassword.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace NectarRCON.Models;\npublic class ServerPassword\n{\n    [JsonPropertyName(\"password\")]\n    public string Password { get; set; } = string.Empty;\n    [JsonPropertyName(\"is_empty\")]\n    public bool IsEmpty { get; set; }\n    [JsonPropertyName(\"name\")]\n    public string ServerName { get; set; } = string.Empty;\n}"
  },
  {
    "path": "NectarRCON/Interfaces/IConfigService.cs",
    "content": "﻿using NectarRCON.Models;\n\nnamespace NectarRCON.Interfaces;\npublic interface IConfigService\n{\n    void Save();\n    Config GetConfig();\n}"
  },
  {
    "path": "NectarRCON/Interfaces/IConnectingDialogService.cs",
    "content": "﻿using System.Windows.Controls;\n\nnamespace NectarRCON.Interfaces;\npublic interface IConnectingDialogService\n{\n    void SetDialog(Grid grid);\n    void Show();\n    void Close();\n}"
  },
  {
    "path": "NectarRCON/Interfaces/IGroupService.cs",
    "content": "﻿using NectarRCON.Models;\nusing System.Collections.Generic;\n\nnamespace NectarRCON.Interfaces;\n\npublic interface IGroupService\n{\n    /// <summary>\n    /// 获取所有组\n    /// </summary>\n    IReadOnlyList<Group> GetGroups();\n\n    /// <summary>\n    /// 通过组名字获取组\n    /// </summary>\n    /// <param name=\"name\">组名</param>\n    Group? FindGroup(string name);\n\n    /// <summary>\n    /// 通过组Id获取组\n    /// </summary>\n    /// <param name=\"groupId\">组Id</param>\n    Group? GetGroup(string groupId);\n\n    /// <summary>\n    /// 删除组\n    /// </summary>\n    /// <param name=\"groupId\">组Id</param>\n    void Delete(string groupId);\n\n    /// <summary>\n    /// 添加一个组\n    /// </summary>\n    string Add(Group group);\n}\n"
  },
  {
    "path": "NectarRCON/Interfaces/IHistoryService.cs",
    "content": "﻿namespace NectarRCON.Interfaces;\n\npublic class HistoryNode\n{ \n    public string? Cmd { get; set; }\n\n    public HistoryNode? Prev { get; set; }\n\n    public HistoryNode? Next { get; set; }\n}\n\npublic interface IHistoryService\n{\n    /// <summary>\n    /// 获取前一条命令\n    /// </summary>\n    /// <param name=\"current\"></param>\n    /// <returns></returns>\n    HistoryNode? Prev(HistoryNode? current);\n\n    /// <summary>\n    /// 获取后一条命令\n    /// </summary>\n    /// <param name=\"current\"></param>\n    /// <returns></returns>\n    HistoryNode? Next(HistoryNode? current);\n\n    /// <summary>\n    /// 输入命令\n    /// </summary>\n    /// <param name=\"cmd\"></param>\n    HistoryNode? InputCmd(string cmd);\n}\n"
  },
  {
    "path": "NectarRCON/Interfaces/ILanguageService.cs",
    "content": "﻿using System.Collections.Generic;\nusing System.Windows;\n\nnamespace NectarRCON.Interfaces;\npublic interface ILanguageService\n{\n    public void Refresh();\n    public ResourceDictionary GetSelectedLanguage();\n    public Dictionary<string, ResourceDictionary> GetLanguages();\n    public void SelectLanguage();\n    public void SelectLanguage(string languageName, bool name);\n    public string GetKey(string key);\n}"
  },
  {
    "path": "NectarRCON/Interfaces/ILogService.cs",
    "content": "﻿using NectarRCON.Models;\n\nnamespace NectarRCON.Interfaces;\npublic interface ILogService\n{\n    string GetText();\n    string Log(string message);\n    void Clear();\n    void SetServer(ServerInformation server);\n\n    void SetGroup(string groupId);\n}"
  },
  {
    "path": "NectarRCON/Interfaces/IMessageBoxService.cs",
    "content": "﻿using System;\nusing System.Windows;\n\nnamespace NectarRCON.Interfaces\n{\n    public interface IMessageBoxService\n    {\n        void Show(string message);\n        void Show(string message, string title);\n        MessageBoxResult Show(string message, string title, MessageBoxButton button);\n        void Show(string message, string title, MessageBoxImage image);\n        MessageBoxResult Show(string message, string title, MessageBoxButton button, MessageBoxImage image);\n        void Show(Exception exception, string? information);\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Interfaces/IRconConnectionInfoService.cs",
    "content": "﻿using NectarRCON.Models;\nusing System.Collections.Generic;\n\nnamespace NectarRCON.Interfaces;\npublic interface IRconConnectionInfoService\n{\n    /// <summary>\n    /// 清除所有连接信息\n    /// </summary>\n    void Clear();\n\n    /// <summary>\n    /// 添加一个服务器\n    /// </summary>\n    void AddInformation(string serverName);\n\n    /// <summary>\n    /// 获取最后一个被添加的服务器\n    /// </summary>\n    ServerInformation? GetLastInformation();\n\n    /// <summary>\n    /// 获取所有的信息\n    /// </summary>\n    IReadOnlyList<ServerInformation> GetInformation();\n\n    /// <summary>\n    /// 是否拥有多个服务器信息\n    /// </summary>\n    public bool HasMultipleInformation\n        => GetInformation().Count > 1;\n}\n"
  },
  {
    "path": "NectarRCON/Interfaces/IServerInformationService.cs",
    "content": "﻿using NectarRCON.Models;\nusing System.Collections.Generic;\n\nnamespace NectarRCON.Interfaces;\npublic interface IServerInformationService\n{\n    ServerInformation? GetServer(string name);\n    List<ServerInformation> GetServers();\n    void AddServer(ServerInformation server);\n    void Save();\n    void RemoveServer(string name);\n    void Update(string name, ServerInformation newInfo);\n    bool ServerIsExist(string name);\n}"
  },
  {
    "path": "NectarRCON/Interfaces/IServerPasswordService.cs",
    "content": "﻿using NectarRCON.Models;\n\nnamespace NectarRCON.Interfaces;\npublic interface IServerPasswordService\n{\n    void Save();\n    bool IsExist(ServerInformation server);\n    void Set(ServerInformation server, string? password, bool? isEmpty);\n    void Remove(ServerInformation server);\n    ServerPassword? Get(ServerInformation server);\n    ServerInformation GetSelect();\n    void Select(ServerInformation server);\n}\n"
  },
  {
    "path": "NectarRCON/Models/GroupModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Rcon;\nusing NectarRCON.Services;\nusing NectarRCON.ViewModels;\nusing NectarRCON.Windows;\nusing System;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Net.Sockets;\nusing System.Security.Authentication;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Xml.Linq;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.Models;\npublic partial class GroupModel:ObservableObject\n{\n    private readonly IGroupService _groupService;\n    private readonly ILanguageService _languageService;\n    private readonly IRconConnectionInfoService _connectionInfoService;\n    private IRconConnection _rconConnection;\n    private readonly INavigationService _navigationService;\n    private readonly IMessageBoxService _messageService;\n    private readonly IConnectingDialogService _connectingDialogService;\n    private readonly ILogService _logService;\n\n    [ObservableProperty]\n    private string _id;\n\n    [ObservableProperty]\n    private GroupPageViewModel _baseModel;\n\n    [ObservableProperty]\n    private string _name;\n\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(ConnectCommand))]\n    private ObservableCollection<GroupServerItemModel> _servers = new();\n\n\n#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    public GroupModel(string name, GroupPageViewModel baseModel)\n#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    {\n        _groupService = App.GetService<IGroupService>();\n        _languageService = App.GetService<ILanguageService>();\n        _connectionInfoService = App.GetService<IRconConnectionInfoService>();\n        _rconConnection = App.GetService<IRconConnection>(typeof(RconMultiConnection));\n        _navigationService = App.GetService<INavigationService>();\n        _messageService = App.GetService<IMessageBoxService>();\n        _connectingDialogService = App.GetService<IConnectingDialogService>();\n        _logService = App.GetService<ILogService>();\n\n        _baseModel = baseModel;\n        Name = name;\n        Id = _groupService.FindGroup(name)?.Id ?? throw new InvalidOperationException();\n        Refresh();\n    }\n\n    private void Refresh()\n    {\n        Group? group = _groupService.GetGroup(_id);\n        if (group != null)\n        {\n            Servers.Clear();\n            Name = group.Name;\n            foreach (var server in group.Servers)\n            {\n                Servers.Add(new(server, this));\n            }\n        }\n    }\n\n    [RelayCommand]\n    public void ItemRemove(string name)\n    {\n        Servers.Remove(Servers.Where(s => s.Name == name).First());\n        Save();\n        ConnectCommand.NotifyCanExecuteChanged();\n    }\n\n    private bool ConnectCommandCanExecute()\n        => Servers.Count >= 1;\n\n    [RelayCommand(CanExecute = nameof(ConnectCommandCanExecute))]\n    public void Connect()\n    {\n        _logService.SetGroup(_id);\n        _connectionInfoService.Clear();\n        _connectingDialogService.Show();\n\n        foreach (var server in Servers)\n        {\n            _connectionInfoService.AddInformation(server.Name);\n        }\n\n        if (_connectionInfoService.HasMultipleInformation)\n        {\n            _rconConnection = App.GetService<IRconConnection>(typeof(RconMultiConnection));\n        }\n        else\n        {\n            _rconConnection = App.GetService<IRconConnection>(typeof(RconSingleConnection));\n        }\n        _navigationService.Navigate(0);\n    }\n\n    [RelayCommand]\n    public void Add()\n    {\n        JoinGroupWindow window = new();\n        foreach (var server in Servers)\n        {\n            window.AddBlackList(server.Name);\n        }\n        window.ShowDialog();\n        window.Close();\n        if (window.SelectedServer != null)\n        {\n            Servers.Add(new(window.SelectedServer, this));\n            Save();\n        }\n        ConnectCommand.NotifyCanExecuteChanged();\n    }\n\n    private void Save()\n    {\n        Group group = _groupService.GetGroup(_id)!;\n        group.Servers.Clear();\n        foreach (var server in Servers)\n        {\n            group.Servers.Add(server.Name);\n        }\n        _groupService.Delete(group.Id);\n        _groupService.Add(group);\n        Refresh();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Models/GroupServerItemModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\n\nnamespace NectarRCON.Models;\npublic partial class GroupServerItemModel : ObservableObject\n{\n    [ObservableProperty]\n    private string _name;\n    [ObservableProperty]\n    private GroupModel _baseModel;\n\n#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    public GroupServerItemModel(string name, GroupModel baseModel)\n#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    {\n        Name = name;\n        BaseModel = baseModel;\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Models/ServerModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Rcon;\nusing NectarRCON.Services;\nusing NectarRCON.ViewModels;\nusing NectarRCON.Windows;\nusing System.Net.Sockets;\nusing System.Security.Authentication;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.Models\n{\n    public partial class ServerModel : ObservableObject\n    {\n        private readonly IServerPasswordService _passwordService;\n\n        private readonly IServerInformationService _informationService;\n        private readonly IConnectingDialogService _connectingDialogService;\n        private readonly IRconConnectionInfoService _connectionInfoService;\n        private readonly INavigationService _navigationService;\n        private readonly IServerPasswordService _serverPasswordService;\n        private readonly ILogService _logService;\n\n        private readonly ILanguageService _languageService;\n        private readonly ServerInformation _information;\n\n        private readonly ServersPageViewModel _viewModel;\n\n        private readonly IRconConnection _rconConnection;\n\n        [ObservableProperty]\n        private string _name;\n\n        [ObservableProperty]\n        private string _address;\n\n#pragma warning disable CS8618 // CS8618 你说的对,但是我已经初始化了\n        public ServerModel(ServerInformation information, ServersPageViewModel viewModel)\n#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n        {\n            _passwordService = App.GetService<IServerPasswordService>();\n            _informationService = App.GetService<IServerInformationService>();\n            _languageService = App.GetService<ILanguageService>();\n            _connectingDialogService = App.GetService<IConnectingDialogService>();\n            _rconConnection = App.GetService<IRconConnection>(typeof(RconSingleConnection));\n            _connectionInfoService = App.GetService<IRconConnectionInfoService>();\n            _navigationService = App.GetService<INavigationService>();\n            _serverPasswordService = App.GetService<IServerPasswordService>();\n            _logService = App.GetService<ILogService>();\n            _information = information;\n            _viewModel = viewModel;\n\n            Name = information.Name;\n            Address = $\"{information.Address}:{information.Port}\";\n        }\n\n        private void EditPass()\n        {\n            _passwordService.Select(_information);\n            EditPasswordWindow window = new EditPasswordWindow();\n            window.ShowDialog();\n        }\n\n        [RelayCommand]\n        public void EditPassword()\n        {\n            EditPass();\n        }\n\n        [RelayCommand]\n        public void Edit()\n        {\n            _passwordService.Select(_information);\n            EditServerWindow window = new EditServerWindow();\n            window.ShowDialog();\n            _viewModel.Refresh();\n        }\n\n        [RelayCommand]\n        public void Delete()\n        {\n            var result = MessageBox.Show(_languageService.GetKey(\"ui.server_page.confirm\"), _languageService.GetKey(\"ui.server_page.menu.delete\"), MessageBoxButton.YesNo, MessageBoxImage.Question);\n            if (result == MessageBoxResult.Yes)\n            {\n                _informationService.RemoveServer(Name);\n                _informationService.Save();\n                _passwordService.Remove(_information);\n                _passwordService.Save();\n                _viewModel.Refresh();\n            }\n        }\n\n        [RelayCommand]\n        public void Connect()\n        {\n            _logService.SetServer(_information);\n            if (_rconConnection.IsConnecting())\n                return;\n\n            if (_rconConnection.IsConnected())\n            {\n                _rconConnection.Close();\n            }\n            var server = _passwordService.Get(_information);\n            if (server == null)\n            {\n                EditPass();\n            }\n            else if (server.Password == null && !server.IsEmpty)\n            {\n                EditPass();\n            }\n            _connectionInfoService.Clear();\n            _connectionInfoService.AddInformation(Name);\n            _serverPasswordService.Select(_information);\n            _navigationService.Navigate(0);\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/NectarRCON.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>WinExe</OutputType>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <Nullable>enable</Nullable>\n    <UseWPF>true</UseWPF>\n    <ApplicationIcon>Resources\\Icon.ico</ApplicationIcon>\n    <ApplicationManifest>app.manifest</ApplicationManifest>\n    <Version>1.0.0-beta5</Version>\n    <LangVersion>12</LangVersion>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <None Remove=\"Resources\\head.png\" />\n    <None Remove=\"Resources\\Icon.png\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Content Include=\"Resources\\Icon.ico\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"CommunityToolkit.Mvvm\" Version=\"8.0.0\" />\n    <PackageReference Include=\"Microsoft.Extensions.Hosting\" Version=\"7.0.0\" />\n    <PackageReference Include=\"Microsoft.Xaml.Behaviors.Wpf\" Version=\"1.1.39\" />\n    <PackageReference Include=\"Serilog\" Version=\"3.1.2-dev-02097\" />\n    <PackageReference Include=\"Serilog.Extensions.Logging\" Version=\"8.0.1-dev-10370\" />\n    <PackageReference Include=\"Serilog.Sinks.File\" Version=\"5.0.1-dev-00972\" />\n    <PackageReference Include=\"System.Text.Encoding.CodePages\" Version=\"8.0.0\" />\n    <PackageReference Include=\"WPF-UI\" Version=\"2.0.3\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NectarRCON.Core\\NectarRCON.Core.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON.Export\\NectarRCON.Export.csproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Resource Include=\"Resources\\head.png\" />\n    <Resource Include=\"Resources\\Icon.png\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Page Update=\"Resources\\Languages\\en_us.xaml\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </Page>\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON/Rcon/IRconConnection.cs",
    "content": "﻿using NectarRCON.Models;\n\nnamespace NectarRCON.Rcon;\npublic delegate void MessageEvent(ServerInformation information, string message);\npublic delegate void RconEvent(ServerInformation information);\npublic interface IRconConnection\n{\n    event MessageEvent OnMessage;\n    event RconEvent OnClosed;\n    event RconEvent OnConnected;\n    event RconEvent OnConnecting;\n    void Connect();\n    void Send(string command);\n    void Close();\n    bool IsConnected();\n    bool IsConnecting();\n}"
  },
  {
    "path": "NectarRCON/Rcon/RconMultiConnection.cs",
    "content": "﻿using NectarRCON.Core.Helper;\nusing NectarRCON.Export.Interfaces;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Rcon;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Sockets;\nusing System.Security.Authentication;\nusing System.Windows.Controls;\nusing NectarRCON.Dp;\nusing Serilog;\n\nnamespace NectarRCON.Services\n{\n    /// <summary>\n    /// Rcon多客户端连接服务\n    /// </summary>\n    internal class RconMultiConnection(\n        IServerPasswordService serverPasswordService,\n        ILanguageService languageService,\n        IRconConnectionInfoService rconConnectionInfoService,\n        IMessageBoxService messageBoxService)\n        : IRconConnection, IDisposable\n    {\n        private readonly RconSettingsDp _settingsDp = DpFile.LoadSingleton<RconSettingsDp>();\n        public event MessageEvent? OnMessage;\n        public event RconEvent? OnClosed;\n        public event RconEvent? OnConnected;\n        public event RconEvent? OnConnecting;\n\n        private readonly Dictionary<ServerInformation,IRconAdapter> _connections = new();\n        private bool _isConnecting;\n\n        public void Close()\n        {\n            foreach(var connection in _connections)\n            {\n                connection.Value.Dispose();\n            }\n            _connections.Clear();\n        }\n\n        public void Connect()\n        {\n            Close();\n            _isConnecting = true;\n            var servers = rconConnectionInfoService.GetInformation();\n            try\n            {\n                foreach (ServerInformation info in servers)\n                {\n                    OnConnecting?.Invoke(info);\n                    // 准备开始连接,先解析这个地址有没有SRV记录\n                    string address = DNSHelpers.SRVQuery(info.Address);\n                    if (string.IsNullOrEmpty(address)) // 如果没有SRV记录,就按照原来的样子设置服务器\n                        address = $\"{info.Address.Replace(\"localhost\", \"127.0.0.1\")}:{info.Port}\";\n                    ServerPassword? serverPassword = serverPasswordService.Get(info); // 从设置中读取Rcon密码\n                    string password = serverPassword?.Password ?? string.Empty;\n\n                    // 创建对应的Rcon客户端实例\n                    // 目前支支持了Minecraft,后期会支持更多(嘛..主要是懒)\n                    IRconAdapter adapter = AdapterHelpers.CreateAdapterInstance(info.Adapter)\n                        ?? throw new InvalidOperationException($\"adapter not found: {info.Adapter}\");\n\n                    var host = address.Split(\":\")[0];\n                    var port = int.Parse(address.Split(\":\")[1]);\n\n                    Log.Information(\"[RconMultiConnection] Connecting to {host}:{port}\", host, port);\n                    \n                    try\n                    {\n                        adapter.Connect(host, port);\n                        if (!adapter.Authenticate(password))\n                            throw new AuthenticationException($\"Server: \\\"{info.Name}\\\"\\n\");\n                        OnConnected?.Invoke(info);\n                    }\n                    catch (AuthenticationException)\n                    {\n                        throw;\n                    }\n                    catch (Exception ex)\n                    {\n                        OnClosed?.Invoke(info);\n                        messageBoxService.Show(ex, $\"Server: \\\"{info.Name}\\\"\");\n                    }\n\n                    //设置编码\n                    adapter.SetEncoding(_settingsDp.Encoding.GetEncoding());\n                    _connections.Add(info, adapter);\n                }\n            }\n            finally\n            {\n                _isConnecting = false;\n            }\n\n        }\n\n        public bool IsConnected()\n            => _connections.Any(e => e.Value.IsConnected);\n\n        public bool IsConnecting()\n            => _isConnecting;\n\n        public void Send(string command)\n        {\n            foreach(var value in _connections)\n            {\n                IRconAdapter connection = value.Value;\n                ServerInformation info = value.Key;\n                if (connection.IsConnected)\n                {\n                    try\n                    {\n                        string result = connection.Run(command);\n                        OnMessage?.Invoke(info, result);\n                    }catch (IOException)\n                    {\n                        connection.Dispose(); // 内部会调用Close\n                        OnClosed?.Invoke(info);\n                        OnMessage?.Invoke(info, languageService.GetKey(\"service.rcon.offline\"));\n                    }\n                }\n                else\n                {\n                    OnMessage?.Invoke(info, languageService.GetKey(\"service.rcon.offline\"));\n                }\n            }\n        }\n\n        public void Dispose()\n        {\n            Close();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Rcon/RconSingleConnection.cs",
    "content": "﻿using NectarRCON.Core.Helper;\nusing NectarRCON.Export.Interfaces;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Rcon;\nusing System;\nusing System.IO;\nusing System.Net.Sockets;\nusing System.Security.Authentication;\nusing System.Windows;\nusing NectarRCON.Dp;\nusing Serilog;\n\nnamespace NectarRCON.Services;\npublic class RconSingleConnection(\n    IServerPasswordService serverPasswordService,\n    ILanguageService languageService,\n    IRconConnectionInfoService rconConnectionInfoService)\n    : IRconConnection\n{\n    private readonly RconSettingsDp _settingsDp = DpFile.LoadSingleton<RconSettingsDp>();\n\n    private IRconAdapter? _rconClient\n        = null;\n\n    private bool _connecting;\n\n    public event MessageEvent? OnMessage;\n    public event RconEvent? OnClosed;\n    public event RconEvent? OnConnected;\n    public event RconEvent? OnConnecting;\n\n    private ServerInformation _serverInformation = new();\n    public void Close()\n    {\n        lock (this)\n        {\n            if (IsConnected() && !IsConnecting())\n            {\n                _connecting = false;\n                _rconClient?.Dispose();\n                _rconClient = null;\n                OnClosed?.Invoke(_serverInformation);\n            }\n        }\n    }\n    public void Connect()\n    {\n        ServerInformation info = rconConnectionInfoService.GetLastInformation() ?? throw new ArgumentNullException(\"Internal error, please try again\");\n        _connecting = true;\n        OnConnecting?.Invoke(info);\n        try\n        {\n            if (IsConnected() && _rconClient != null)\n                Close();\n            // 准备开始连接,先解析这个地址有没有SRV记录\n            string address = DNSHelpers.SRVQuery(info.Address);\n            if (string.IsNullOrEmpty(address)) // 如果没有SRV记录,就按照原来的样子设置服务器\n                address = $\"{info.Address.Replace(\"localhost\", \"127.0.0.1\")}:{info.Port}\";\n\n\n            ServerPassword? serverPassword = serverPasswordService.Get(info);\n            string password = serverPassword?.Password ?? string.Empty;\n\n            // 创建对应的Rcon客户端实例\n            // 目前支支持了Minecraft,后期会支持更多(嘛..主要是懒)\n            _rconClient = AdapterHelpers.CreateAdapterInstance(info.Adapter)\n                ?? throw new InvalidOperationException($\"adapter not found: {info.Adapter}\");\n            _rconClient.SetEncoding(_settingsDp.Encoding.GetEncoding());\n            var host = address.Split(\":\")[0];\n            var port = int.Parse(address.Split(\":\")[1]);\n\n            Log.Information(\"[RconSingleConnection] Connecting to {host}:{port}\", host, port);\n            \n            _rconClient.Connect(host, port); // 连接\n\n            if (!_rconClient.Authenticate(password))\n                throw new AuthenticationException();\n\n            OnConnected?.Invoke(info);\n\n            _serverInformation = info;\n        }\n        catch (FormatException ex)\n        {\n            MessageBox.Show(languageService.GetKey(\"ui.text.format_exception\")\n                .Replace(\"%s\", ex.Message), languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n        finally\n        {\n            _connecting = false;\n        }\n    }\n\n    public bool IsConnected()\n        => _rconClient?.IsConnected ?? false;\n    public bool IsConnecting()\n        => _connecting;\n    public void Send(string command)\n    {\n        if (IsConnected() && _rconClient != null)\n        {\n            try\n            {\n                string result = _rconClient.Run(command);\n                OnMessage?.Invoke(_serverInformation, result);\n            }\n            catch (Exception ex)\n            {\n                Close();\n                if (ex is SocketException or IOException && _settingsDp.AutoReconnect)\n                {\n                    Log.Information(\"Rcon连接已经断开,开始自动重连...\");\n                    try\n                    {\n                        Connect();\n                    }\n                    catch (Exception e)\n                    {\n                        Log.Error(\"自动重连失败 {err}\", e);\n                    }\n\n                    if (IsConnected())\n                    {\n                        try\n                        {\n                            Log.Information(\"尝试重发命令...\");\n                            string result = _rconClient.Run(command);\n                            OnMessage?.Invoke(_serverInformation, result);\n                            Log.Information(\"重发命令成功 -> {command}\", command);\n                            return;\n                        }\n                        catch(Exception e)\n                        {\n                            Log.Error(\"尝试重发命令失败 {err}\", e);\n                        }\n                    }\n                }\n                MessageBox.Show($\"{languageService.GetKey(\"text.error\")}\\n{ex.Message}\", ex.GetType().FullName, MessageBoxButton.OK, MessageBoxImage.Error);\n            }\n        }\n        else\n        {\n            Close();\n            MessageBox.Show($\"{languageService.GetKey(\"text.server.not_connect.text\")}\", languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Resources/Languages/en_us.xaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:sys=\"clr-namespace:System;assembly=netstandard\">\n    <sys:String x:Key=\"file.language\">en_us</sys:String>\n    <sys:String x:Key=\"file.name\">English US(en_us)</sys:String>\n    <sys:String x:Key=\"ui.menu.server\">Server</sys:String>\n    <sys:String x:Key=\"ui.menu.server_list\">Servers</sys:String>\n    <sys:String x:Key=\"ui.menu.groups\">Groups</sys:String>\n    <sys:String x:Key=\"ui.menu.log\">Log</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear\">Clear</sys:String>\n    <sys:String x:Key=\"ui.menu.program\">Program</sys:String>\n    <sys:String x:Key=\"ui.menu.program.settings\">Settings</sys:String>\n    <sys:String x:Key=\"ui.menu.program.about\">About</sys:String>\n    <sys:String x:Key=\"ui.menu.program.check_update\">CheckUpdate</sys:String>\n    <sys:String x:Key=\"ui.window.title\">RCONManager</sys:String>\n    <sys:String x:Key=\"ui.settings.language\">UI Language</sys:String>\n    <sys:String x:Key=\"ui.settings.ui\">UI Settings</sys:String>\n    <sys:String x:Key=\"ui.settings.theme\">UI Theme</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.dark\">Dark Theme</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.light\">Light Theme</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.auto\">System Theme</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon_account\">Rcon passwords...</sys:String>\n    <sys:String x:Key=\"ui.settings.command_record_limit\">Command Record Limit</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon\">Rcon</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.auto_reconnect\">Auto Reconnect</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.is_keep_connection_window_open\">KeepConnectionWindowOpen</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.encoding\">Encoding</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program\">Clear Program Logs</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program.ask\">Do you want to clear program logs?</sys:String>\n    \n    <sys:String x:Key=\"ui.server_page.menu.open\">Connect</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit\">Edit</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.delete\">Delete</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit_password\">EditPassword</sys:String>\n    <sys:String x:Key=\"ui.server_page.text\">Servers</sys:String>\n    <sys:String x:Key=\"ui.server_page.add\">Add Server</sys:String>\n    <sys:String x:Key=\"ui.server_page.search\">Search by server name...</sys:String>\n    <sys:String x:Key=\"ui.server_page.confirm\">This action will delete the server! (including recorded password)\\nAre you sure you want to delete?</sys:String>\n\n    <sys:String x:Key=\"ui.add_server_window.title\">Add Server</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.name\">Name</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.address\">Address</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.port\">Port</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.add\">Add</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.cancel\">Cancel</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.already_exist\">A server with the same name already exists, please change your name!</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.null_text\">Server name or server address cannot be empty, please change it!</sys:String>\n\n    <sys:String x:Key=\"ui.about.afdian\">Afdian</sys:String>\n    <sys:String x:Key=\"ui.about.mcbbs\">MCBBS</sys:String>\n    <sys:String x:Key=\"ui.about.github\">Github</sys:String>\n\n    <sys:String x:Key=\"ui.text.format_exception\">An error was encountered while parsing the destination address: %s\\nPlease check whether the server address is legal!</sys:String>\n\n    <sys:String x:Key=\"ui.edit_password.title\">Edit Password</sys:String>\n    <sys:String x:Key=\"ui.edit_password.text\">Password</sys:String>\n    <sys:String x:Key=\"ui.edit_password.is_empty\">Empty Pass</sys:String>\n\n    <sys:String x:Key=\"ui.main_page.run\">Run</sys:String>\n    <sys:String x:Key=\"ui.main_page.successful\">Successful!</sys:String>\n    \n    <sys:String x:Key=\"ui.main_page.offline.single\">Offline</sys:String>\n    <sys:String x:Key=\"ui.main_page.offline.multiple\">All clients are offline</sys:String>\n    <sys:String x:Key=\"ui.main_page.re_connect\">Reconnect</sys:String>\n    <sys:String x:Key=\"ui.main_page.back_home\">Back to Servers</sys:String>\n\n    <sys:String x:Key=\"ui.edit_server_window.title\">Edit</sys:String>\n\n    <sys:String x:Key=\"text.went_wrong\">An exception occurred during operation, and the application is about to end. If you cannot understand the error below, please submit an issue on Github and describe what happened in detail</sys:String>\n    <sys:String x:Key=\"text.error\">Error</sys:String>\n    <sys:String x:Key=\"text.warn\">Warning</sys:String>\n    <sys:String x:Key=\"text.info\">Information</sys:String>\n    <sys:String x:Key=\"text.ok\">Ok</sys:String>\n    <sys:String x:Key=\"text.cancel\">Cancel</sys:String>\n    <sys:String x:Key=\"text.not_available\">Not Available</sys:String>\n    <sys:String x:Key=\"text.connect\">Connect</sys:String>\n    <sys:String x:Key=\"text.remove\">Remove</sys:String>\n    <sys:String x:Key=\"text.delete\">Delete</sys:String>\n    <sys:String x:Key=\"text.add\">Add</sys:String>\n\n    <sys:String x:Key=\"text.server.connect.ing\">Connecting...</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail\">Failed to connect to the server!</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail.text\">Unable to connect to the remote server: %s\\nPlease check that the server RCON address is configured correctly, and check that the server has enabled the RCON option!</sys:String>\n    <sys:String x:Key=\"text.server.connect.auth_fail\">The Rcon password is incorrect, please check whether your server password is correct!</sys:String>\n\n    <sys:String x:Key=\"text.server.connected\">Connected to the server and authenticated successfully!</sys:String>\n    <sys:String x:Key=\"text.server.closed\">The connection is disconnected!</sys:String>\n    <sys:String x:Key=\"text.server.start\">Starting connection</sys:String>\n\n    <sys:String x:Key=\"text.server.not_connect.text\">You cannot connect to the server, so you cannot use the remote command function, please try to go back to the server list and reconnect to this server!</sys:String>\n\n    <sys:String x:Key=\"text.server_information.error.json_error\">Unable to instantiate server list /servers.json %s\\nClick \"Yes\" to reset the server list (you need to re-add the server, but the recorded password will not disappear)\\nClick \"Cancel\" to close the program!</sys:String>\n    <sys:String x:Key=\"text.server_information.error.text\">Unable to instantiate server list /servers.json %s\\nClick \"Yes\" to close the program!</sys:String>\n    <sys:String x:Key=\"text.server_information.error.save\">Unable to save file /servers.json\\nIf you just added a server, please note that this save is not in effect!\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.password.error.json_error\">Unable to resolve password list /passwords.json %s\\nClick \"Yes\" to reset the password list (password needs to be reset, but the recorded server will not disappear)\\nClick \"Cancel\" to close the program!</sys:String>\n    <sys:String x:Key=\"text.password.error.text\">Unable to resolve password list /passwords.json %s\\nClick \"Yes\" to close the program!</sys:String>\n    <sys:String x:Key=\"text.password.error.save\">Unable to save file /passwords.json\\nIf you have just set a password, please note that this save is not in effect!\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.config.json_error\">Unable to resolve configuration file /config.json JSON parsing failed: %s\\nClick \"Yes\" to reset the profile!\\nClick \"No\" to exit the program!</sys:String>\n    <sys:String x:Key=\"text.config.error\">Unable to parse configuration file /config.json: %s\\nClick \"Yes\" to reset the profile!\\nClick \"No\" to exit the program!</sys:String>\n    <sys:String x:Key=\"text.config.save_error\">Unable to save configuration file /config.json: %s\\nIf you have just changed the configuration file, please note that the save does not take effect!</sys:String>\n\n    <sys:String x:Key=\"groups.exception.invalid_data_exception\">An error occurred while trying to read and parse the group file \"{0}\", trying to delete or repair the file may resolve this error!</sys:String>\n    <sys:String x:Key=\"groups.exception.same_group_id_exception\">The same GroupId already exists (existing value: {0}, duplicate value: {1})</sys:String>\n    <sys:String x:Key=\"groups.exception.same_name_exception\">The same Name already exists (existing id: {0}, duplicate id: {1})</sys:String>\n    <sys:String x:Key=\"groups.exception.group_id_exists_exception\">A group with the same internal ID already exists in the file, try creating it again</sys:String>\n    <sys:String x:Key=\"groups.exception.file_name_mismatch_exception\">The {0} file name does not match the internal ID {1}</sys:String>\n    <sys:String x:Key=\"groups.exception.name_already_exists_exception\">The same Name already exists</sys:String>\n\n    <sys:String x:Key=\"ui.group.no_group_title\">Click '+ Add' to add a group!</sys:String>\n    <sys:String x:Key=\"ui.group.no_group_subtitle\">After adding a group, you can broadcast a command to all servers in that group simultaneously.</sys:String>\n    <sys:String x:Key=\"ui.group.no_server\">This group has no servers. Click '+ Add' to add a server.</sys:String>\n    <sys:String x:Key=\"ui.group.delete_group\">Do you want to delete group {0}?</sys:String>\n\n    <sys:String x:Key=\"ui.add_group.title\">New Group</sys:String>\n    <sys:String x:Key=\"ui.add_group.placeholder_text\">Group Name</sys:String>\n\n    <sys:String x:Key=\"ui.join_group.title\">Select Server</sys:String>\n    <sys:String x:Key=\"ui.join_group.no_server\">No server available</sys:String>\n\n    <sys:String x:Key=\"service.rcon.offline\">Offline</sys:String>\n</ResourceDictionary>"
  },
  {
    "path": "NectarRCON/Resources/Languages/zh_cn.xaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:sys=\"clr-namespace:System;assembly=netstandard\">\n    <sys:String x:Key=\"file.language\">zh_cn</sys:String>\n    <sys:String x:Key=\"file.name\">简体中文(zh_cn)</sys:String>\n    <sys:String x:Key=\"ui.menu.server\">服务器</sys:String>\n    <sys:String x:Key=\"ui.menu.server_list\">服务器列表</sys:String>\n    <sys:String x:Key=\"ui.menu.groups\">服务器分组</sys:String>\n    <sys:String x:Key=\"ui.menu.log\">日志</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear\">清空日志</sys:String>\n    <sys:String x:Key=\"ui.menu.program\">程序</sys:String>\n    <sys:String x:Key=\"ui.menu.program.settings\">设置</sys:String>\n    <sys:String x:Key=\"ui.menu.program.about\">关于</sys:String>\n    <sys:String x:Key=\"ui.menu.program.check_update\">检查更新</sys:String>\n    <sys:String x:Key=\"ui.window.title\">RCON管理器</sys:String>\n    <sys:String x:Key=\"ui.settings.language\">UI语言</sys:String>\n    <sys:String x:Key=\"ui.settings.ui\">界面设置</sys:String>\n    <sys:String x:Key=\"ui.settings.theme\">UI主题</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.dark\">深色主题</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.light\">浅色主题</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.auto\">跟随系统</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon_account\">Rcon密码管理</sys:String>\n    <sys:String x:Key=\"ui.settings.command_record_limit\">服务器命令回溯记录数</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon\">Rcon全局设置</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.auto_reconnect\">掉线自动重连</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.is_keep_connection_window_open\">掉线不回到主页</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.encoding\">文本编码</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program\">清理程序日志</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program.ask\">是否清理程序日志?</sys:String>\n    \n    <sys:String x:Key=\"ui.server_page.menu.open\">连接</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit\">编辑</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.delete\">删除</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit_password\">密码</sys:String>\n    <sys:String x:Key=\"ui.server_page.text\">服务器列表</sys:String>\n    <sys:String x:Key=\"ui.server_page.add\">添加服务器</sys:String>\n    <sys:String x:Key=\"ui.server_page.search\">输入服务器名字来搜索...</sys:String>\n    <sys:String x:Key=\"ui.server_page.confirm\">此操作将会删除服务器! (包括记录的密码)\\n您确定要删除吗?</sys:String>\n\n    <sys:String x:Key=\"ui.add_server_window.title\">添加服务器</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.name\">服务器名称</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.address\">服务器地址</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.port\">服务器端口</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.add\">新建</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.cancel\">取消</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.already_exist\">相同名字的服务器已存在,请换一个名字!</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.null_text\">服务器名称或服务器地址不可为空,请更改!</sys:String>\n\n    <sys:String x:Key=\"ui.about.afdian\">爱发电</sys:String>\n    <sys:String x:Key=\"ui.about.mcbbs\">MCBBS</sys:String>\n    <sys:String x:Key=\"ui.about.github\">Github</sys:String>\n\n    <sys:String x:Key=\"ui.text.format_exception\">解析目标地址时遇到错误: %s\\n请检查服务器地址是否合法!</sys:String>\n\n    <sys:String x:Key=\"ui.edit_password.title\">编辑密码</sys:String>\n    <sys:String x:Key=\"ui.edit_password.text\">密码</sys:String>\n    <sys:String x:Key=\"ui.edit_password.is_empty\">无需密码</sys:String>\n\n    <sys:String x:Key=\"ui.main_page.run\">执行</sys:String>\n    <sys:String x:Key=\"ui.main_page.successful\">执行成功</sys:String>\n    \n    <sys:String x:Key=\"ui.main_page.offline.single\">已掉线</sys:String>\n    <sys:String x:Key=\"ui.main_page.offline.multiple\">所有客户端都掉线了</sys:String>\n    <sys:String x:Key=\"ui.main_page.re_connect\">重新连接</sys:String>\n    <sys:String x:Key=\"ui.main_page.back_home\">回到主页</sys:String>\n    \n    <sys:String x:Key=\"ui.edit_server_window.title\">编辑</sys:String>\n\n    <sys:String x:Key=\"text.went_wrong\">运行过程中出现异常，应用即将结束。 如果您无法理解以下错误，请在 Github 上提交问题并详细描述发生的情况</sys:String>\n    <sys:String x:Key=\"text.error\">错误</sys:String>\n    <sys:String x:Key=\"text.warn\">警告</sys:String>\n    <sys:String x:Key=\"text.info\">信息</sys:String>\n    <sys:String x:Key=\"text.ok\">确定</sys:String>\n    <sys:String x:Key=\"text.cancel\">取消</sys:String>\n    <sys:String x:Key=\"text.not_available\">不可使用</sys:String>\n    <sys:String x:Key=\"text.connect\">连接</sys:String>\n    <sys:String x:Key=\"text.remove\">移除</sys:String>\n    <sys:String x:Key=\"text.delete\">删除</sys:String>\n    <sys:String x:Key=\"text.add\">添加</sys:String>\n\n    <sys:String x:Key=\"text.server.connect.ing\">正在连接服务器...</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail\">连接服务器失败!</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail.text\">无法连接到远程服务器: %s\\n请检查服务器RCON地址配置是否正确,以及检查服务端是否开启了RCON选项!</sys:String>\n    <sys:String x:Key=\"text.server.connect.auth_fail\">Rcon密码错误,请检查您的服务器密码是否正确!</sys:String>\n\n    <sys:String x:Key=\"text.server.connected\">连接服务器并认证成功!</sys:String>\n    <sys:String x:Key=\"text.server.closed\">连接已断开!</sys:String>\n    <sys:String x:Key=\"text.server.start\">开始连接</sys:String>\n\n    <sys:String x:Key=\"text.server.not_connect.text\">无法连接到服务器,因此无法使用远程命令功能,请尝试回到服务器列表重新连接到本服务器!</sys:String>\n\n    <sys:String x:Key=\"text.server_information.error.json_error\">无法解析服务器列表 /servers.json %s\\n点击\"是\"重置服务器列表(需重新添加服务器,但记录的密码不会消失)\\n点击\"取消\"关闭程序!</sys:String>\n    <sys:String x:Key=\"text.server_information.error.text\">无法解析服务器列表 /servers.json %s\\n点击\"是\"关闭程序!</sys:String>\n    <sys:String x:Key=\"text.server_information.error.save\">无法保存文件 /servers.json\\n如果您刚刚进行了添加服务器操作,请注意,本次保存并未生效!\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.password.error.json_error\">无法解析密码列表 /passwords.json %s\\n点击\"是\"重置密码列表(需重新设置密码,但记录的服务器不会消失)\\n点击\"取消\"关闭程序!</sys:String>\n    <sys:String x:Key=\"text.password.error.text\">无法解析密码列表 /passwords.json %s\\n点击\"是\"关闭程序!</sys:String>\n    <sys:String x:Key=\"text.password.error.save\">无法保存文件 /passwords.json\\n如果您刚刚进行了设置密码操作,请注意,本次保存并未生效!\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.config.json_error\">无法解析配置文件 /config.json JSON解析失败: %s\\n点击\"是\"重置配置文件!\\n点击\"否\"退出程序!</sys:String>\n    <sys:String x:Key=\"text.config.error\">无法解析配置文件 /config.json: %s\\n点击\"是\"重置配置文件!\\n点击\"否\"退出程序!</sys:String>\n    <sys:String x:Key=\"text.config.save_error\">无法保存配置文件 /config.json: %s\\n如果您刚刚更改了配置文件,请注意,保存并未生效!</sys:String>\n\n    <sys:String x:Key=\"groups.exception.invalid_data_exception\">读取和解析组文件\"{0}\"时发生错误，请尝试删除或修复文件以解决此错误！</sys:String>\n    <sys:String x:Key=\"groups.exception.same_group_id_exception\">相同的 GroupId 已存在（现有值: {0}，重复值: {1}）</sys:String>\n    <sys:String x:Key=\"groups.exception.same_name_exception\">相同的 Name 已存在（现有Id: {0}，重复Id: {1}）</sys:String>\n    <sys:String x:Key=\"groups.exception.group_id_exists_exception\">文件中已存在具有相同内部 ID 的分组，请再次尝试创建</sys:String>\n    <sys:String x:Key=\"groups.exception.file_name_mismatch_exception\">{0} 文件名与内部 ID {1} 不匹配</sys:String>\n    <sys:String x:Key=\"groups.exception.name_already_exists_exception\">相同的组名已存在</sys:String>\n\n    <sys:String x:Key=\"ui.group.no_group_title\">单击'+ 添加'添加一个组吧!</sys:String>\n    <sys:String x:Key=\"ui.group.no_group_subtitle\">添加组后,可以将命令同时广播到该组的所有服务器</sys:String>\n    <sys:String x:Key=\"ui.group.no_server\">该组没有任何服务器,请单击 '+ 添加' 添加一个服务器吧!</sys:String>\n    <sys:String x:Key=\"ui.group.delete_group\">是否要删除组 {0}?</sys:String>\n\n    <sys:String x:Key=\"ui.add_group.title\">新建组</sys:String>\n    <sys:String x:Key=\"ui.add_group.placeholder_text\">组名</sys:String>\n\n    <sys:String x:Key=\"ui.join_group.title\">选择服务器</sys:String>\n    <sys:String x:Key=\"ui.join_group.no_server\">没有可用服务器</sys:String>\n\n    <sys:String x:Key=\"service.rcon.offline\">离线</sys:String>\n</ResourceDictionary>"
  },
  {
    "path": "NectarRCON/Resources/Languages/zh_tw.xaml",
    "content": "﻿<ResourceDictionary\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:sys=\"clr-namespace:System;assembly=netstandard\">\n    <sys:String x:Key=\"file.language\">zh_tw</sys:String>\n    <sys:String x:Key=\"file.name\">繁体中文(zh_tw)</sys:String>\n    <sys:String x:Key=\"ui.menu.server\">伺服器</sys:String>\n    <sys:String x:Key=\"ui.menu.server_list\">伺服器列表</sys:String>\n    <sys:String x:Key=\"ui.menu.groups\">伺服器分組</sys:String>\n    <sys:String x:Key=\"ui.menu.log\">日誌</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear\">清空日誌</sys:String>\n    <sys:String x:Key=\"ui.menu.program\">程式</sys:String>\n    <sys:String x:Key=\"ui.menu.program.settings\">設定</sys:String>\n    <sys:String x:Key=\"ui.menu.program.about\">關於</sys:String>\n    <sys:String x:Key=\"ui.menu.program.check_update\">檢查更新</sys:String>\n    <sys:String x:Key=\"ui.window.title\">RCON管理器</sys:String>\n    <sys:String x:Key=\"ui.settings.language\">UI語言</sys:String>\n    <sys:String x:Key=\"ui.settings.ui\">界面設定</sys:String>\n    <sys:String x:Key=\"ui.settings.theme\">UI主題</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.dark\">深色主題</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.light\">淺色主題</sys:String>\n    <sys:String x:Key=\"ui.settings.theme.auto\">跟隨系統</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon_account\">Rcon密碼管理</sys:String>\n    <sys:String x:Key=\"ui.settings.command_record_limit\">伺服器命令回溯記錄數</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon\">Rcon全局設定</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.auto_reconnect\">斷綫自動重連</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.is_keep_connection_window_open\">斷線不回到主頁</sys:String>\n    <sys:String x:Key=\"ui.settings.rcon.encoding\">文本編碼</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program\">清除程式日誌</sys:String>\n    <sys:String x:Key=\"ui.menu.log.clear_program.ask\">您是否要清除程式日誌？</sys:String>\n\n    <sys:String x:Key=\"ui.server_page.menu.open\">連線</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit\">編輯</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.delete\">刪除</sys:String>\n    <sys:String x:Key=\"ui.server_page.menu.edit_password\">密碼</sys:String>\n    <sys:String x:Key=\"ui.server_page.text\">伺服器列表</sys:String>\n    <sys:String x:Key=\"ui.server_page.add\">新增伺服器</sys:String>\n    <sys:String x:Key=\"ui.server_page.search\">輸入伺服器名稱來搜尋...</sys:String>\n    <sys:String x:Key=\"ui.server_page.confirm\">此操作將會刪除伺服器! (包括記錄的密碼)\\n您確定要刪除嗎?</sys:String>\n\n    <sys:String x:Key=\"ui.add_server_window.title\">新增伺服器</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.name\">伺服器名稱</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.address\">伺服器地址</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.port\">伺服器端口</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.add\">新增</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.cancel\">取消</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.already_exist\">相同名稱的伺服器已經存在,請換一個名稱!</sys:String>\n    <sys:String x:Key=\"ui.add_server_window.null_text\">伺服器名稱或伺服器地址不可為空,請更改!</sys:String>\n\n    <sys:String x:Key=\"ui.about.afdian\">爱发电</sys:String>\n    <sys:String x:Key=\"ui.about.mcbbs\">MCBBS</sys:String>\n    <sys:String x:Key=\"ui.about.github\">Github</sys:String>\n\n    <sys:String x:Key=\"ui.text.format_exception\">解析目标地址時遇到錯誤：%s\\n請檢查伺服器地址是否合法！</sys:String>\n\n    <sys:String x:Key=\"ui.edit_password.title\">編輯密碼</sys:String>\n    <sys:String x:Key=\"ui.edit_password.text\">密碼</sys:String>\n    <sys:String x:Key=\"ui.edit_password.is_empty\">無需密碼</sys:String>\n\n    <sys:String x:Key=\"ui.main_page.run\">執行</sys:String>\n    <sys:String x:Key=\"ui.main_page.successful\">執行成功</sys:String>\n    \n    <sys:String x:Key=\"ui.main_page.offline.single\">斷線</sys:String>\n    <sys:String x:Key=\"ui.main_page.offline.multiple\">所有客戶端都斷線了</sys:String>\n    <sys:String x:Key=\"ui.main_page.re_connect\">重新連接</sys:String>\n    <sys:String x:Key=\"ui.main_page.back_home\">返回伺服器</sys:String>\n    \n    <sys:String x:Key=\"ui.edit_server_window.title\">編輯</sys:String>\n\n    <sys:String x:Key=\"text.went_wrong\">執行過程中出現異常，應用即將結束。如果您無法理解以下錯誤，請在 GitHub 上提交問題並詳細描述發生的情況。</sys:String>\n    <sys:String x:Key=\"text.error\">錯誤</sys:String>\n    <sys:String x:Key=\"text.warn\">警告</sys:String>\n    <sys:String x:Key=\"text.info\">資訊</sys:String>\n    <sys:String x:Key=\"text.ok\">確定</sys:String>\n    <sys:String x:Key=\"text.cancel\">取消</sys:String>\n    <sys:String x:Key=\"text.not_available\">不可使用</sys:String>\n    <sys:String x:Key=\"text.connect\">連接</sys:String>\n    <sys:String x:Key=\"text.remove\">移除</sys:String>\n    <sys:String x:Key=\"text.delete\">刪除</sys:String>\n    <sys:String x:Key=\"text.add\">新增</sys:String>\n\n    <sys:String x:Key=\"text.server.connect.ing\">正在連接伺服器...</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail\">連接伺服器失敗！</sys:String>\n    <sys:String x:Key=\"text.server.connect.fail.text\">無法連接到遠程伺服器：%s\\n請檢查伺服器RCON地址配置是否正確，以及檢查服務端是否開啟了RCON選項！</sys:String>\n    <sys:String x:Key=\"text.server.connect.auth_fail\">Rcon密碼錯誤，請檢查您的伺服器密碼是否正確！</sys:String>\n\n    <sys:String x:Key=\"text.server.connected\">連接伺服器並認證成功！</sys:String>\n    <sys:String x:Key=\"text.server.closed\">連接已斷開！</sys:String>\n    <sys:String x:Key=\"text.server.start\">開始連接</sys:String>\n\n    <sys:String x:Key=\"text.server.not_connect.text\">無法連接到伺服器，因此無法使用遠程命令功能，請嘗試回到伺服器列表重新連接到本伺服器！</sys:String>\n\n    <sys:String x:Key=\"text.server_information.error.json_error\">無法解析伺服器列表 /servers.json %s\\n點選\"是\"重置伺服器列表（需重新添加伺服器，但記錄的密碼不會消失）\\n點選\"取消\"關閉程式！</sys:String>\n    <sys:String x:Key=\"text.server_information.error.text\">無法解析伺服器列表 /servers.json %s\\n點選\"是\"關閉程式！</sys:String>\n    <sys:String x:Key=\"text.server_information.error.save\">無法儲存檔案 /servers.json\\n如果您剛剛進行了添加伺服器操作，請注意，本次儲存並未生效！\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.password.error.json_error\">無法解析密碼列表 /passwords.json %s\\n點選\"是\"重置密碼列表（需重新設定密碼，但記錄的伺服器不會消失）\\n點選\"取消\"關閉程式！</sys:String>\n    <sys:String x:Key=\"text.password.error.text\">無法解析密碼列表 /passwords.json %s\\n點選\"是\"關閉程式！</sys:String>\n    <sys:String x:Key=\"text.password.error.save\">無法儲存檔案 /passwords.json\\n如果您剛剛進行了設定密碼操作，請注意，本次儲存並未生效！\\n%s</sys:String>\n\n    <sys:String x:Key=\"text.config.json_error\">無法解析配置檔案 /config.json JSON解析失敗：%s\\n點選\"是\"重置配置檔案！\\n點選\"否\"退出程式！</sys:String>\n    <sys:String x:Key=\"text.config.error\">無法解析配置檔案 /config.json：%s\\n點選\"是\"重置配置檔案！\\n點選\"否\"退出程式！</sys:String>\n    <sys:String x:Key=\"text.config.save_error\">無法儲存配置檔案 /config.json：%s\\n如果您剛剛更改了配置檔案，請注意，儲存並未生效！</sys:String>\n\n    <sys:String x:Key=\"groups.exception.invalid_data_exception\">讀取和解析組檔案\"{0}\"時發生錯誤，請嘗試刪除或修復檔案以解決此錯誤！</sys:String>\n    <sys:String x:Key=\"groups.exception.same_group_id_exception\">相同的 GroupId 已存在（現有值: {0}，重複值: {1}）</sys:String>\n    <sys:String x:Key=\"groups.exception.same_name_exception\">相同的 Name 已存在（現有Id: {0}，重複Id: {1}）</sys:String>\n    <sys:String x:Key=\"groups.exception.group_id_exists_exception\">檔案中已存在具有相同內部 ID 的分組，請再次嘗試建立</sys:String>\n    <sys:String x:Key=\"groups.exception.file_name_mismatch_exception\">{0} 檔案名與內部 ID {1} 不匹配</sys:String>\n    <sys:String x:Key=\"groups.exception.name_already_exists_exception\">相同的組名已存在</sys:String>\n\n    <sys:String x:Key=\"ui.group.no_group_title\">點擊「+添加」添加一個分組吧！</sys:String>\n    <sys:String x:Key=\"ui.group.no_group_subtitle\">添加分組後，您可以將命令廣播到該分組的所有伺服器</sys:String>\n    <sys:String x:Key=\"ui.group.no_server\">該分組沒有任何伺服器，請點擊「+添加」添加一個伺服器吧！</sys:String>\n    <sys:String x:Key=\"ui.group.delete_group\">是否要刪除分組 {0}？</sys:String>\n\n    <sys:String x:Key=\"ui.add_group.title\">新建分組</sys:String>\n    <sys:String x:Key=\"ui.add_group.placeholder_text\">分組名稱</sys:String>\n\n    <sys:String x:Key=\"ui.join_group.title\">選擇伺服器</sys:String>\n    <sys:String x:Key=\"ui.join_group.no_server\">沒有可用的伺服器</sys:String>\n\n    <sys:String x:Key=\"service.rcon.offline\">離線</sys:String>\n</ResourceDictionary>"
  },
  {
    "path": "NectarRCON/Services/ApplicationHostService.cs",
    "content": "﻿using Microsoft.Extensions.Hosting;\nusing NectarRCON.Core.Helper;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.ComponentModel;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing Microsoft.Extensions.Logging;\nusing Serilog;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.Services;\npublic class ApplicationHostService(\n    IServiceProvider serviceProvider,\n    INavigationService navigationService,\n    IThemeService themeService,\n    ILanguageService languageService,\n    IConfigService configService)\n    : IHostedService\n{\n    private readonly INavigationService _navigationService = navigationService;\n    private INavigationWindow? _navigationWindow;\n\n    public async Task StartAsync(CancellationToken cancellationToken)\n    {\n        Log.Information(\"Starting NectarRCON...\");\n        await HandleActivationAsync();\n    }\n    public async Task StopAsync(CancellationToken cancellationToken)\n    {\n        await Task.CompletedTask;\n    }\n    private void LoadConfig()\n    {\n        switch (configService.GetConfig().Theme)\n        {\n            case ETheme.System:\n                themeService.SetTheme(Win32Helper.GetWindowsTheme() ? Wpf.Ui.Appearance.ThemeType.Dark : Wpf.Ui.Appearance.ThemeType.Light);\n                break;\n            case ETheme.Dark:\n                themeService.SetTheme(Wpf.Ui.Appearance.ThemeType.Dark);\n                break;\n            case ETheme.Light:\n                themeService.SetTheme(Wpf.Ui.Appearance.ThemeType.Light);\n                break;\n            default:\n                break;\n        }\n        if (configService.GetConfig().LanguageName == string.Empty)\n        {\n            languageService.SelectLanguage();\n            configService.GetConfig().LanguageName = languageService.GetKey(\"file.language\");\n            configService.Save();\n        }\n        else\n        {\n            languageService.SelectLanguage(configService.GetConfig().LanguageName, false);\n        }\n    }\n    private async Task HandleActivationAsync()\n    {\n        await Task.CompletedTask;\n\n        if (!Application.Current.Windows.OfType<Container>().Any())\n        {\n            _navigationWindow = (serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!;\n            Log.Information(\"Show MainWindow...\");\n            _navigationWindow!.ShowWindow();\n        }\n\n        LoadConfig();\n\n        await Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Services/ConfigService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Text.Json;\nusing System.Windows;\nnamespace NectarRCON.Services;\npublic partial class ConfigService : IConfigService\n{\n    private ILanguageService _languageService;\n    private Config _config = new();\n    public ConfigService(ILanguageService languageService)\n    {\n        _languageService = languageService;\n        if (!File.Exists(\"./config.json\"))\n            Save();\n        try\n        {\n            _config = JsonSerializer.Deserialize<Config>(File.ReadAllText(\"./config.json\"))\n            ?? new Config();\n        }\n        catch (Exception ex)\n        {\n            var resutl = MessageBox.Show(_languageService.GetKey(ex.GetType() == typeof(JsonException) ? \"text.config.json_error\" : \"text.config.error\")\n            .Replace(\"%s\", ex.Message), _languageService.GetKey(\"text.error\"), MessageBoxButton.YesNo, MessageBoxImage.Error);\n            if (resutl == MessageBoxResult.Yes)\n            {\n                _config = new();\n                Save();\n            }\n            else\n            {\n                Process.GetCurrentProcess().Kill();\n            }\n        }\n    }\n    public Config GetConfig()\n        => _config;\n    public void Save()\n    {\n        try\n        {\n            File.WriteAllText(\"./config.json\", JsonSerializer.Serialize(_config));\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(_languageService.GetKey(\"text.config.save_error\")\n                .Replace(\"%s\", ex.Message), _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON/Services/ConnectingDialogService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing System.Windows;\nusing System.Windows.Controls;\n\nnamespace NectarRCON.Services;\ninternal class ConnectingDialogService : IConnectingDialogService\n{\n    private Grid? _dialog;\n    public void Close()\n    {\n        if (null == _dialog)\n            return;\n        _dialog.Visibility = Visibility.Hidden;\n    }\n\n    public void SetDialog(Grid grid)\n    {\n        _dialog = grid;\n    }\n\n    public void Show()\n    {\n        if (null == _dialog)\n            return;\n        _dialog.Visibility = Visibility.Visible;\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Services/GroupService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Text.Json;\n\nnamespace NectarRCON.Services\n{\n    public class GroupService : IGroupService\n    {\n        private static string _groupPath = Path.Combine(\"./groups\");\n        private readonly IMessageBoxService _messageBoxService;\n        private readonly ILanguageService _languageService;\n\n        private readonly Dictionary<string, Group> _groupMapping = new(); // id, group\n        private readonly Dictionary<string, Group> _groupNameMapping = new(); // name, group\n        public GroupService(IMessageBoxService messageBoxService, ILanguageService languageService)\n        {\n            _messageBoxService = messageBoxService;\n            Refresh();\n            _languageService = languageService;\n        }\n\n        private void Refresh()\n        {\n            _groupMapping.Clear();\n            _groupNameMapping.Clear();\n            if (Directory.Exists(_groupPath))\n            {\n                string[] jsonFiles = Directory.GetFiles(_groupPath, \"*.json\");\n                foreach (string jsonFile in jsonFiles)\n                {\n                    try\n                    {\n                        using (FileStream fs = File.OpenRead(jsonFile))\n                        {\n                            Group group = JsonSerializer.Deserialize<Group>(fs) ?? throw new InvalidDataException(jsonFile);\n                            if (group.Id.ToLower() != Path.GetFileNameWithoutExtension(jsonFile).ToLower())\n                                throw new InvalidDataException(string.Format(_languageService.GetKey(\"groups.exception.file_name_mismatch_exception\"), jsonFile, group.Id));\n                            if (_groupMapping.ContainsKey(group.Id))\n                                throw new InvalidDataException(string.Format(_languageService.GetKey(\"groups.exception.same_group_id_exception\"), _groupMapping[group.Id].Name, group.Name));\n                            if (_groupNameMapping.ContainsKey(group.Name))\n                                throw new InvalidDataException(string.Format(_languageService.GetKey(\"groups.exception.same_name_exception\"), _groupMapping[group.Id].Id, group.Id));\n\n                            _groupMapping.Add(group.Id, group);\n                            _groupNameMapping.Add(group.Name, group);\n                        }\n                    }\n                    catch (Exception ex)\n                    {\n                        _messageBoxService.Show(ex.Message, string.Format(_languageService.GetKey(\"groups.exception.invalid_data_exception\"), ex.ToString()));\n                    }\n                }\n            }\n            else\n            {\n                Directory.CreateDirectory(_groupPath);\n            }\n        }\n\n        private void RemoveGroupInDict(string groupId)\n        {\n            if (_groupMapping.TryGetValue(groupId, out Group? group))\n            {\n                _groupMapping.Remove(groupId);\n                if (_groupNameMapping.ContainsKey(group.Name))\n                {\n                    _groupNameMapping.Remove(group.Name);\n                }\n            }\n        }\n\n        public string Add(Group group)\n        {\n            string filePath = Path.Combine(_groupPath, group.Id + \".json\");\n            if (!File.Exists(filePath))\n            {\n                if (_groupNameMapping.ContainsKey(group.Name))\n                    throw new InvalidOperationException(_languageService.GetKey(\"groups.exception.name_already_exists_exception\"));\n                if (_groupMapping.ContainsKey(group.Id))\n                    throw new InvalidOperationException(_languageService.GetKey(\"groups.exception.group_id_exists_exception\"));\n                File.WriteAllBytes(filePath, Encoding.UTF8.GetBytes(JsonSerializer.Serialize(group)));\n\n                _groupMapping.Add(group.Id, group);\n                _groupNameMapping.Add(group.Name,group);\n                return group.Id;\n            }\n            throw new InvalidOperationException(_languageService.GetKey(\"groups.exception.group_id_exists_exception\"));\n        }\n\n        public void Delete(string groupId)\n        {\n            string filePath = Path.Combine(_groupPath, groupId + \".json\");\n            if (File.Exists(filePath))\n            {\n                File.Delete(filePath);\n                RemoveGroupInDict(groupId);\n            }\n        }\n\n        public Group? FindGroup(string name)\n        {\n            _groupNameMapping.TryGetValue(name, out Group? group);\n            return group;\n        }\n\n        public Group? GetGroup(string groupId)\n        {\n            _groupMapping.TryGetValue(groupId, out Group? group);\n            return group;\n        }\n\n        public IReadOnlyList<Group> GetGroups()\n            => _groupMapping.Select(s => s.Value).ToList();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Services/HistoryService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\n\nnamespace NectarRCON.Services;\n\npublic class HistoryService : IHistoryService\n{\n\n    /// <summary>\n    /// 双向链表保存历史记录\n    /// </summary>\n    private HistoryNode? _head;\n    private HistoryNode? _tail;\n\n    private readonly Dictionary<string, HistoryNode> _map;\n    \n    /// <summary>\n    /// 限制记录条数\n    /// </summary>\n    private int _limit = 20;\n\n    public HistoryService()\n    {\n        _map = [];\n        if (!Path.Exists(\"./logs\"))\n        { \n            Directory.CreateDirectory(\"./logs\");\n        }\n\n        var stream = File.Open($\"./logs/history\", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);\n        using StreamReader reader = new StreamReader(stream);\n        string? line;\n        while ((line = reader.ReadLine()) != null)\n        {\n            line = line.Trim();\n            if (!string.IsNullOrEmpty(line))\n            {\n                AppendNode(line);\n            }\n        }\n    }\n\n    public HistoryNode? InputCmd(string cmd)\n    {\n        var node = AppendNode(cmd);\n        Save();\n        return node;\n    }\n\n    private HistoryNode? AppendNode(string cmd)\n    {\n        cmd = cmd.Trim();\n        HistoryNode node;\n        if (_map.ContainsKey(cmd))\n        {\n            // 已存在的记录，将其移动到链表尾部\n            node = _map[cmd];\n            if (node == _tail)\n            {\n                return null;\n            }\n            else if (node == _head)\n            {\n                _head = _head.Next;\n            }\n\n            if (node.Prev != null)\n            {\n                node.Prev.Next = node.Next;\n            }\n            if (node.Next != null)\n            {\n                node.Next.Prev = node.Prev;\n            }\n            node.Prev = null;\n            node.Next = null;\n        }\n        else\n        {\n            node = new HistoryNode()\n            {\n                Cmd = cmd,\n            };\n            _map.Add(cmd, node);\n        }\n\n        if (_head == null || _tail == null)\n        {\n            _head = _tail = node;\n        }\n        else\n        {\n            _tail.Next = node;\n            node.Prev = _tail;\n            _tail = node;\n        }\n\n        // 超出限制，移除链表头部记录\n        if (_map.Count() > _limit)\n        {\n            var head = _head;\n            _head = _head.Next;\n            if (_head != null)\n            { \n                _head.Prev = null;\n            }\n            if (!string.IsNullOrEmpty(head.Cmd))\n            {\n                _map.Remove(head.Cmd);\n            }\n        }\n\n        return null;\n    }\n\n    public HistoryNode? Next(HistoryNode? current)\n    {\n        if (current == null)\n        {\n            return null;\n        }\n        return current.Next;\n    }\n\n    public HistoryNode? Prev(HistoryNode? current)\n    {\n        if (current == null)\n        {\n            return _tail;\n        }\n        else if (current.Prev == null)\n        {\n            return current;\n        }\n        return current.Prev;\n    }\n\n    private void Save()\n    {\n        using var stream = File.Open($\"./logs/history\", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);\n        stream.Seek(0, SeekOrigin.Begin);\n        HistoryNode? node = _head;\n        while (node != null) {\n            if (!string.IsNullOrEmpty(node.Cmd))\n            {\n                string cmd = node.Cmd + \"\\n\";\n                stream.Write(Encoding.UTF8.GetBytes(cmd));\n            }\n            node = node.Next;\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Services/LanguageService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Windows;\n\nnamespace NectarRCON.Services;\npublic class LanguageService : ILanguageService\n{\n    private readonly Dictionary<string, string> _defaultLanguages = new();\n    private Dictionary<string, ResourceDictionary> _languages = new();\n    private ResourceDictionary _selectedLanguage = new ResourceDictionary()\n    {\n        Source = new Uri(\"pack://application:,,,/NectarRCON;component/Resources/Languages/zh_cn.xaml\", UriKind.RelativeOrAbsolute)\n    };\n    private readonly ResourceDictionary _defLanguage = new ResourceDictionary()\n    {\n        Source = new Uri(\"pack://application:,,,/NectarRCON;component/Resources/Languages/zh_cn.xaml\", UriKind.RelativeOrAbsolute)\n    };\n    public LanguageService()\n    {\n        Refresh();\n    }\n    public ResourceDictionary SelectedLanguage\n    {\n        get => _selectedLanguage;\n    }\n    public Dictionary<string, ResourceDictionary> Languages\n    {\n        get => _languages;\n    }\n    public Dictionary<string, ResourceDictionary> GetLanguages()\n        => Languages;\n    public ResourceDictionary GetSelectedLanguage()\n    {\n        return _selectedLanguage;\n    }\n    public void Refresh()\n    {\n        _languages.Clear();\n        // 从本地目录获取xaml\n        string[] files = new string[0];\n        if (Directory.Exists(\"./languages/\"))\n            files = Directory.GetFiles(\"./languages/\");\n        foreach (string file in files)\n        {\n            if (Path.GetExtension(file).ToLower() == \".xaml\" || Path.GetExtension(file).ToLower() == \".xml\")\n            {\n                ResourceDictionary resourceDictionary = new ResourceDictionary();\n                using (FileStream fs = File.OpenRead(file))\n                {\n                    resourceDictionary = (ResourceDictionary)System.Windows.Markup.XamlReader.Load(fs);\n                }\n                _languages.Add(Path.GetFileNameWithoutExtension(file), resourceDictionary);\n            }\n        }\n        // 从内部文件加载\n        _defaultLanguages.Add(\"zh_cn\", \"pack://application:,,,/NectarRCON;component/Resources/Languages/zh_cn.xaml\");\n        _defaultLanguages.Add(\"zh_tw\", \"pack://application:,,,/NectarRCON;component/Resources/Languages/zh_tw.xaml\");\n        _defaultLanguages.Add(\"en_us\", \"pack://application:,,,/NectarRCON;component/Resources/Languages/en_us.xaml\");\n        foreach (KeyValuePair<string, string> language in _defaultLanguages)\n        {\n            if (_languages.ContainsKey(language.Key))\n                _languages.Remove(language.Key);\n            Languages.Add(language.Key, new ResourceDictionary()\n            {\n                Source = new Uri(language.Value, UriKind.RelativeOrAbsolute)\n            });\n        }\n    }\n    public void SelectLanguage()\n    {\n        Application.Current.Resources.Remove(SelectedLanguage);\n        string name = System.Globalization.CultureInfo.CurrentCulture.Name;\n        SelectLanguage(name);\n    }\n    public void SelectLanguage(string languageName, bool name = false)\n    {\n        languageName = languageName.Replace(\"-\", \"_\");\n        Application.Current.Resources.MergedDictionaries.Remove(SelectedLanguage);\n        var language = Languages.Where(l =>\n        {\n            if (name)\n            {\n                return (l.Value[\"file.name\"].ToString() ?? \"nullname\") == languageName.ToLower();\n            }\n            else\n            {\n                return l.Key.ToLower() == languageName.ToLower();\n            }\n        }).FirstOrDefault();\n        if (null == language.Value)\n        {\n            _selectedLanguage = new ResourceDictionary()\n            {\n                Source = new Uri(\"pack://application:,,,/NectarRCON;component/Resources/Languages/en_us.xaml\", UriKind.RelativeOrAbsolute)\n            };\n            if (Application.Current.Resources.Contains(\"en_us\"))\n            {\n                Application.Current.Resources.Remove(\"en_us\");\n            }\n            Application.Current.Resources.MergedDictionaries.Add(_selectedLanguage);\n        }\n        else\n        {\n            _selectedLanguage = language.Value;\n            Application.Current.Resources.MergedDictionaries.Add(language.Value);\n        }\n    }\n    public string GetKey(string key)\n    {\n        string valuel;\n        if (_selectedLanguage.Contains(key))\n            valuel = _selectedLanguage[key].ToString() ?? string.Empty;\n        else if (_defLanguage.Contains(key))\n            valuel = _defLanguage[key].ToString() ?? string.Empty;\n        else valuel = key;\n        return valuel\n            .Replace(\"\\\\n\", \"\\n\");\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Services/LogService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.IO;\nusing System.Text;\nusing System.Text.Encodings.Web;\nusing System.Text.RegularExpressions;\nusing System.Windows;\n\nnamespace NectarRCON.Services;\npublic class LogService : ILogService\n{\n    private FileStream? _logFileStream;\n    public LogService()\n    {\n        if (!Path.Exists(\"./logs\"))\n            Directory.CreateDirectory(\"./logs\");\n    }\n    public void Clear()\n    {\n        _logFileStream?.SetLength(0);\n        _logFileStream?.Flush();\n    }\n    public string GetText()\n    {\n        using (MemoryStream ms = new MemoryStream())\n        {\n            _logFileStream?.CopyTo(ms);\n            return Encoding.UTF8.GetString(ms.ToArray());\n        }\n    }\n    public string Log(string message)\n    {\n        string logText = $\"[{DateTime.Now.ToString(\"yyyy-MM-dd HH:mm:ss\")}] {message}\\n\";\n        _logFileStream?.Write(Encoding.UTF8.GetBytes(logText));\n        _logFileStream?.Flush();\n        return logText;\n    }\n    public void SetServer(ServerInformation server)\n    {\n        OpenFile(\"server_\" + UrlEncoder.Default.Encode(server.Name));\n    }\n\n    public void SetGroup(string groupId)\n    {\n        OpenFile(\"group_\" + UrlEncoder.Default.Encode(groupId));\n    }\n\n    private void OpenFile(string fileName)\n    {\n        if (!File.Exists($\"./logs/{fileName}.log\"))\n            File.Create($\"./logs/{fileName}.log\").Close();\n        _logFileStream?.Close();\n        _logFileStream?.Dispose();\n        _logFileStream = File.Open($\"./logs/{fileName}.log\", FileMode.Open, FileAccess.ReadWrite, FileShare.Read);\n    }\n}"
  },
  {
    "path": "NectarRCON/Services/MessageBoxService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing System;\nusing System.Windows;\n\nnamespace NectarRCON.Services;\npublic class MessageBoxService : IMessageBoxService\n{\n    private readonly ILanguageService _languageService;\n\n    public MessageBoxService(ILanguageService languageService)\n    {\n        _languageService = languageService;\n    }\n\n    public void Show(string message)\n        => MessageBox.Show(message, \"NectarRcon\", MessageBoxButton.OK, MessageBoxImage.Information);\n\n    public void Show(string message, string title)\n        => MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);\n\n    public MessageBoxResult Show(string message, string title, MessageBoxButton button)\n        => MessageBox.Show(message, title, button, MessageBoxImage.Information);\n\n    public void Show(string message, string title, MessageBoxImage image)\n        => MessageBox.Show(message, title, MessageBoxButton.OK, image);\n\n    public MessageBoxResult Show(string message, string title, MessageBoxButton button, MessageBoxImage image)\n        => MessageBox.Show(message, title, button, image);\n\n    public void Show(Exception exception, string? information = null)\n        => MessageBox.Show($\"{_languageService.GetKey(\"text.went_wrong\")}\\n{information}\\n{exception}\", \"error\", MessageBoxButton.OK, MessageBoxImage.Error);\n}\n"
  },
  {
    "path": "NectarRCON/Services/RconConnectionInfoService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace NectarRCON.Services;\n\n/// <summary>\n/// Rcon连接信息管理服务\n/// </summary>\npublic class RconConnectionInfoService : IRconConnectionInfoService\n{\n    private readonly IServerInformationService _serverInformationService;\n    private readonly List<ServerInformation> _serverInformation = new();\n    public RconConnectionInfoService(IServerInformationService serverInformationService)\n    {\n        _serverInformationService = serverInformationService;\n    }\n\n    public void AddInformation(string serverName)\n        => _serverInformation.Add(_serverInformationService.GetServer(serverName) ?? throw new ArgumentNullException(nameof(serverName)));\n\n    public void Clear()\n        => _serverInformation.Clear();\n\n    public IReadOnlyList<ServerInformation> GetInformation()\n        => _serverInformation;\n\n    public ServerInformation? GetLastInformation()\n        => _serverInformation.FirstOrDefault();\n}\n"
  },
  {
    "path": "NectarRCON/Services/ServerInformationService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Windows;\n\nnamespace NectarRCON.Services;\npublic class ServerInformationService : IServerInformationService\n{\n    private readonly ILanguageService _languageService;\n    private List<ServerInformation> _servers = new();\n    public List<ServerInformation> Servers\n        => _servers;\n    public ServerInformationService(ILanguageService languageService)\n    {\n        _languageService = languageService;\n        if (!File.Exists(\"./servers.json\"))\n            File.WriteAllText(\"./servers.json\", \"[]\");\n        try\n        {\n            string jsonString = File.ReadAllText(\"./servers.json\");\n            _servers = JsonSerializer.Deserialize<List<ServerInformation>>(jsonString)\n                ?? new List<ServerInformation>();\n        }\n        catch (JsonException ex)\n        {\n            MessageBoxResult result = MessageBox.Show(_languageService.GetKey(\"text.server_information.error.json_error\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\"), _languageService.GetKey(\"text.error\"), MessageBoxButton.OKCancel, MessageBoxImage.Error);\n            if (result == MessageBoxResult.OK)\n                File.WriteAllText(\"./servers.json\", \"[]\");\n            else\n                Process.GetCurrentProcess().Kill();\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(_languageService.GetKey(\"text.server_information.error.text\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\")\n                , _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n\n            Process.GetCurrentProcess().Kill();\n        }\n    }\n    public void AddServer(ServerInformation server)\n    {\n        if (ServerIsExist(server.Name))\n            return;\n        _servers.Add(server);\n    }\n    public ServerInformation? GetServer(string name)\n        => _servers.Where(s => s.Name == name).FirstOrDefault();\n    public List<ServerInformation> GetServers()\n    {\n        return _servers;\n    }\n    public void RemoveServer(string name)\n    {\n        ServerInformation? server = GetServer(name);\n        if (null == server) return;\n        _servers.Remove(server);\n    }\n    public void Save()\n    {\n        try\n        {\n            File.WriteAllText(\"./servers.json\", JsonSerializer.Serialize(_servers));\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(_languageService.GetKey(\"text.server_information.error.save\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\")\n                , _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n    }\n    public bool ServerIsExist(string name)\n        => GetServer(name) != null;\n    public void Update(string name, ServerInformation newInfo)\n    {\n        var oldInfo = GetServer(name);\n        if (null == oldInfo) return;\n        for (int i = 0; i < _servers.Count; i++)\n        {\n            if (_servers[i] == oldInfo)\n            {\n                _servers[i] = newInfo;\n                return;\n            }\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON/Services/ServerPasswordService.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Windows;\n\nnamespace NectarRCON.Services;\npublic class ServerPasswordService : IServerPasswordService\n{\n    private readonly ILanguageService _languageService;\n    private ServerInformation? _selectedServer;\n    private List<ServerPassword> _serverPasswordList = new();\n    public ServerPasswordService(ILanguageService languageService)\n    {\n        _languageService = languageService;\n        if (!File.Exists(\"./passwords.json\"))\n            File.WriteAllText(\"./passwords.json\", \"[]\");\n        try\n        {\n            string jsonString = File.ReadAllText(\"./passwords.json\");\n            _serverPasswordList = JsonSerializer.Deserialize<List<ServerPassword>>(jsonString)\n                ?? new List<ServerPassword>();\n        }\n        catch (JsonException ex)\n        {\n            MessageBoxResult result = MessageBox.Show(_languageService.GetKey(\"text.password.error.json_error\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\"), _languageService.GetKey(\"text.error\"), MessageBoxButton.OKCancel, MessageBoxImage.Error);\n            if (result == MessageBoxResult.OK)\n                File.WriteAllText(\"./passwords.json\", \"[]\");\n            else\n                Process.GetCurrentProcess().Kill();\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(_languageService.GetKey(\"text.password.error.text\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\")\n                , _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n\n            Process.GetCurrentProcess().Kill();\n        }\n    }\n    public ServerPassword? Get(ServerInformation server)\n    {\n        var password = _serverPasswordList.Where(s =>\n        {\n            return s.ServerName == server.Name;\n        }).FirstOrDefault();\n        return password;\n    }\n\n    public ServerInformation GetSelect()\n    {\n        return _selectedServer ?? new();\n    }\n\n    public bool IsExist(ServerInformation server)\n        => Get(server) != null;\n\n    public void Remove(ServerInformation server)\n    {\n        ServerPassword? password = Get(server);\n        if (password == null)\n            return;\n        _serverPasswordList.Remove(password);\n    }\n\n    public void Save()\n    {\n        try\n        {\n            File.WriteAllText(\"./passwords.json\", JsonSerializer.Serialize(_serverPasswordList));\n        }\n        catch (Exception ex)\n        {\n            MessageBox.Show(_languageService.GetKey(\"text.password.error.save\")\n                .Replace(\"%s\", ex.Message)\n                .Replace(\"\\\\n\", \"\\n\")\n                , _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n    }\n\n    public void Select(ServerInformation server)\n    {\n        _selectedServer = server;\n    }\n\n    public void Set(ServerInformation server, string? password, bool? isEmpty)\n    {\n        var oldPassword = Get(server);\n        ServerPassword newPassword;\n        if (oldPassword == null)\n        {\n            newPassword = new ServerPassword()\n            {\n                Password = password ?? string.Empty,\n                IsEmpty = isEmpty ?? string.IsNullOrEmpty(password),\n                ServerName = server.Name\n            };\n        }\n        else\n        {\n            newPassword = new ServerPassword()\n            {\n                Password = password ?? oldPassword.Password,\n                IsEmpty = isEmpty ?? oldPassword.IsEmpty,\n                ServerName = server.Name\n            };\n        }\n        if (IsExist(server))\n            _serverPasswordList.Remove(Get(server)!);\n        _serverPasswordList.Add(newPassword);\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/AboutPageViewModel.cs",
    "content": "﻿namespace NectarRCON.ViewModels\n{\n    class AboutPageViewModel\n    {\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/AddGroupPageViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Services;\nusing System;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.ViewModels;\npublic partial class AddGroupPageViewModel:ObservableObject\n{\n    [ObservableProperty]\n    [NotifyCanExecuteChangedFor(nameof(AddCommand))]\n    private string? _groupName;\n\n    private INavigationService _navigationService;\n    private IMessageBoxService _messageBoxService;\n    private IGroupService _groupService;\n\n    public AddGroupPageViewModel()\n    {\n        GroupName = string.Empty;\n        _navigationService = App.GetService<INavigationService>();\n        _messageBoxService = App.GetService<IMessageBoxService>();\n        _groupService = App.GetService<IGroupService>();\n    }\n\n    private bool CanAdd()\n        => !string.IsNullOrWhiteSpace(GroupName);\n\n    [RelayCommand(CanExecute = nameof(CanAdd))]\n    public void Add()\n    {\n        try\n        {\n            _groupService.Add(new Models.Group()\n            {\n                Id = Guid.NewGuid().ToString(),\n                Name = GroupName!,\n                Servers = new()\n            });\n            _navigationService.Navigate(4);\n        }catch(Exception ex)\n        {\n            _messageBoxService.Show(ex, string.Empty);\n        }\n    }\n\n    [RelayCommand]\n    public void Cancel()\n    {\n        _navigationService.Navigate(4);\n    }\n\n    [RelayCommand]\n    public void Load()\n    {\n        GroupName = null;\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/AddServerWindowViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Windows;\nusing System.Windows;\n\nnamespace NectarRCON.ViewModels;\npublic partial class AddServerWindowViewModel : ObservableObject\n{\n    private readonly IServerInformationService _serverInformationService;\n    private readonly ILanguageService _languageService;\n    private AddServerWindow? _serverWindow;\n    [ObservableProperty]\n    private string _serverName = \"Rcon\";\n    [ObservableProperty]\n    private string _serverAddress = \"localhost\";\n    [ObservableProperty]\n    private string _serverPort = \"25575\";\n    public AddServerWindowViewModel(IServerInformationService serverInformationService, ILanguageService languageService)\n    {\n        _serverInformationService = serverInformationService;\n        _languageService = languageService;\n    }\n    public void SetWindow(AddServerWindow window)\n    {\n        _serverWindow = window;\n    }\n    [RelayCommand]\n    private void Ok()\n    {\n        ServerAddress = ServerAddress.Trim();\n        if (string.IsNullOrWhiteSpace(ServerName) || string.IsNullOrWhiteSpace(ServerAddress))\n        {\n            MessageBox.Show(_languageService.GetKey(\"ui.add_server_window.null_text\"), _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n            return;\n        }\n        var information = new ServerInformation()\n        {\n            Name = ServerName,\n            Address = ServerAddress,\n            Port = ushort.Parse(ServerPort)\n        };\n        if (_serverInformationService.ServerIsExist(information.Name))\n        {\n            MessageBox\n                .Show(_languageService.GetKey(\"ui.add_server_window.already_exist\")\n                , _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n        else\n        {\n            _serverInformationService.AddServer(information);\n            _serverInformationService.Save();\n            _serverWindow?.Close();\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON/ViewModels/EditPasswordWindowViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Windows;\n\nnamespace NectarRCON.ViewModels;\npublic partial class EditPasswordWindowViewModel : ObservableObject\n{\n    private readonly IServerPasswordService _serverPasswordService;\n    private readonly ServerInformation _serverInformation;\n    private EditPasswordWindow? _window = null;\n\n    [ObservableProperty]\n    private string _password = string.Empty;\n    [ObservableProperty]\n    private bool _isEmpty = true;\n    public EditPasswordWindowViewModel()\n    {\n        _serverPasswordService = App.GetService<IServerPasswordService>();\n        _serverInformation = _serverPasswordService.GetSelect()!;\n        var password = _serverPasswordService.Get(_serverInformation);\n        if (password != null)\n        {\n            Password = password.Password;\n            IsEmpty = string.IsNullOrEmpty(Password);\n        }\n        else\n        {\n            Password = string.Empty;\n            IsEmpty = true;\n        }\n    }\n    public void SetWindow(EditPasswordWindow window)\n    {\n        _window = window;\n    }\n    [RelayCommand]\n    public void TextChange()\n    {\n        IsEmpty = string.IsNullOrEmpty(Password);\n    }\n    [RelayCommand]\n    public void SetPassword()\n    {\n        _serverPasswordService.Set(_serverInformation, Password, IsEmpty);\n        _serverPasswordService.Save();\n        _window?.Close();\n    }\n}"
  },
  {
    "path": "NectarRCON/ViewModels/EditServerWindowViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System.Windows;\nusing Wpf.Ui.Controls;\nusing MessageBox = System.Windows.MessageBox;\n\nnamespace NectarRCON.ViewModels;\npublic partial class EditServerWindowViewModel : ObservableObject\n{\n    private readonly ILanguageService _languageService;\n    private readonly IServerPasswordService _serverPasswordService;\n    private readonly IServerInformationService _serverInformationService;\n    private UiWindow? _window;\n    [ObservableProperty]\n    private ServerInformation? _selectServer;\n    [ObservableProperty]\n    private string _port = string.Empty;\n    [ObservableProperty]\n    private string _address = string.Empty;\n    public EditServerWindowViewModel()\n    {\n        _languageService = App.GetService<ILanguageService>();\n        _serverPasswordService = App.GetService<IServerPasswordService>();\n        _serverInformationService = App.GetService<IServerInformationService>();\n    }\n\n    [RelayCommand]\n    private void Load(RoutedEventArgs e)\n    {\n        _window = e.Source as UiWindow;\n        SelectServer = _serverPasswordService.GetSelect();\n        Port = _selectServer?.Port.ToString() ?? \"0\";\n        Address = _selectServer?.Address ?? \"localhost\";\n    }\n    [RelayCommand]\n    public void Exit()\n    {\n        _window?.Close();\n    }\n    [RelayCommand]\n    private void Ok()\n    {\n        Address = Address.Trim();\n        if (string.IsNullOrWhiteSpace(_selectServer?.Address) || string.IsNullOrEmpty(Address))\n            MessageBox.Show(_languageService.GetKey(\"ui.add_server_window.null_text\"), _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        else\n        {\n            _selectServer.Port = ushort.Parse(Port);\n            _selectServer.Address = Address;\n            _serverInformationService.Update(_selectServer.Name, _selectServer);\n            _serverInformationService.Save();\n            Exit();\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON/ViewModels/GroupPageViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Views.Pages;\nusing System.Collections.ObjectModel;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.ViewModels;\npublic partial class GroupPageViewModel:ObservableObject\n{\n    [ObservableProperty]\n    private ObservableCollection<GroupModel> _groups;\n    private readonly IGroupService _groupService;\n    private readonly ILanguageService _languageService;\n    private readonly IMessageBoxService _messageBoxService;\n    private readonly INavigationService _navigationService;\n#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    public GroupPageViewModel()\n#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    {\n        _groupService = App.GetService<IGroupService>();\n        _languageService = App.GetService<ILanguageService>();\n        _messageBoxService = App.GetService<IMessageBoxService>();\n        _navigationService = App.GetService<INavigationService>();\n        Groups = new();\n    }\n\n    [RelayCommand]\n    public void Load()\n    {\n        Refresh();\n    }\n\n    private void Refresh()\n    {\n        Groups.Clear();\n        foreach(Group group in _groupService.GetGroups())\n        {\n            Groups.Add(new(group.Name, this));\n        }\n    }\n\n    [RelayCommand]\n    public void RemoveGroup(string groupId)\n    {\n        Group group = _groupService.GetGroup(groupId)!;\n        if (_messageBoxService.Show(string.Format(_languageService.GetKey(\"ui.group.delete_group\"), group.Name), _languageService.GetKey(\"text.delete\"), System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Question)\n            == System.Windows.MessageBoxResult.Yes)\n        {\n            _groupService.Delete(groupId);\n            Refresh();\n        }\n    }\n\n    [RelayCommand]\n    public void NewGroup()\n    {\n        _navigationService.Navigate(typeof(AddGroupPage));\n    }\n}"
  },
  {
    "path": "NectarRCON/ViewModels/JoinGroupWindowViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing NectarRCON.Interfaces;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Windows.Data;\n\nnamespace NectarRCON.ViewModels;\npublic partial class JoinGroupWindowViewModel:ObservableObject\n{\n    private IServerInformationService _serverInformationService;\n\n    [ObservableProperty]\n    private ObservableCollection<string> _servers = new();\n\n    [ObservableProperty]\n    private ListCollectionView? _serverCollectionView;\n\n    [ObservableProperty]\n    public ObservableCollection<string>? _blackList;\n\n    public JoinGroupWindowViewModel()\n    {\n        _serverInformationService = App.GetService<IServerInformationService>();\n        foreach(var server in _serverInformationService.GetServers())\n        {\n            Servers.Add(server.Name);\n        }\n\n        ServerCollectionView = new(Servers);\n        ServerCollectionView.Filter += (s) =>\n        {\n            return !BlackList?.Contains(s?.ToString() ?? string.Empty) ?? true;\n        };\n\n        BlackList = new();\n    }\n\n    partial void OnBlackListChanged(ObservableCollection<string>? value)\n    {\n        ServerCollectionView?.Refresh();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/MainPageViewModel.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing CommunityToolkit.Mvvm.Messaging;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Rcon;\nusing NectarRCON.Services;\nusing NectarRCON.Views.Pages;\nusing System.Net.Sockets;\nusing System.Security.Authentication;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Input;\nusing NectarRCON.Dp;\nusing Serilog;\nusing Wpf.Ui.Mvvm.Contracts;\nusing MessageBox = System.Windows.MessageBox;\nusing TextBox = Wpf.Ui.Controls.TextBox;\n\nnamespace NectarRCON.ViewModels;\n\npublic partial class MainPageViewModel : ObservableObject\n{\n    private static readonly RconSettingsDp RconSettings = DpFile.LoadSingleton<RconSettingsDp>();\n    private readonly ILogService _logService;\n    private readonly IHistoryService _historyService;\n    private readonly IServerPasswordService _serverPasswordService;\n    private IRconConnection _rconConnectService;\n    private readonly INavigationService _navigationService;\n    private readonly ILanguageService _languageService;\n    private readonly IRconConnectionInfoService _rconConnectionInfoService;\n    private readonly IConnectingDialogService _connectingDialogService;\n    private readonly IMessageBoxService _messageBoxService;\n\n    private MainPage? _page;\n    private TextBox? _logTextBox;\n    private HistoryNode? _historyNode;\n\n    [ObservableProperty] private string _commandText = string.Empty;\n    [ObservableProperty] private string _logText = string.Empty;\n    [ObservableProperty] private bool _isMultipleConnection;\n    [ObservableProperty] private bool _isDisconnection;\n\n    public MainPageViewModel()\n    {\n        _logService = App.GetService<ILogService>();\n        _historyService = App.GetService<IHistoryService>();\n        _serverPasswordService = App.GetService<IServerPasswordService>();\n        _navigationService = App.GetService<INavigationService>();\n        _languageService = App.GetService<ILanguageService>();\n        _rconConnectionInfoService = App.GetService<IRconConnectionInfoService>();\n        _messageBoxService = App.GetService<IMessageBoxService>();\n        _connectingDialogService = App.GetService<IConnectingDialogService>();\n        WeakReferenceMessenger.Default.Register<ClearLogValueMessage>(this, OnClear);\n\n        // 选择连接服务\n        _rconConnectService = _rconConnectionInfoService.HasMultipleInformation\n            ? App.GetService<IRconConnection>(typeof(RconMultiConnection))\n            : App.GetService<IRconConnection>(typeof(RconSingleConnection));\n\n        IsMultipleConnection = _rconConnectionInfoService.HasMultipleInformation;\n    }\n\n    private void OnClear(object sender, ClearLogValueMessage msg)\n    {\n        _logService.Clear();\n        LogText = string.Empty;\n    }\n\n    private void OnMessage(ServerInformation info, string msg)\n    {\n        Log.Information(\"[OnMessage] {name}({adapter}) -> {msg}\", info.Name, string.IsNullOrEmpty(info.Adapter) ? \"TCPRcon\" : info.Adapter, string.IsNullOrEmpty(msg) ? \"$empty$\" : msg);\n        string logMsg = string.IsNullOrEmpty(msg)\n            ? _languageService.GetKey(\"ui.main_page.successful\")\n            : msg;\n        LogText += _logService.Log($\"{info.Name}:\" + logMsg);\n        _logTextBox?.ScrollToEnd();\n    }\n\n    private void OnClosed(ServerInformation info)\n    {\n        LogText += _logService.Log($\"{info.Name} {_languageService.GetKey(\"text.server.closed\")}\");\n        IsDisconnection = !_rconConnectService.IsConnected();\n    }\n\n    [RelayCommand]\n    private async void Load(RoutedEventArgs e)\n    {\n        // GetLogs\n        LogText = string.Empty;\n        LogText = _logService.GetText();\n\n        _page = e.Source as MainPage;\n        await ConnectAsync();\n    }\n\n    [RelayCommand]\n    private async void ReConnect()\n    {\n        Log.Information($\"[ReConnectCommand] 正在准备重连\");\n        if (_rconConnectService.IsConnected())\n            _rconConnectService.Close();\n        IsDisconnection = false;\n        await ConnectAsync();\n    }\n\n    private async Task ConnectAsync()\n    {\n        Log.Information($\"[ConnectAsync] 准备连接到服务器\");\n\n        IsMultipleConnection = _rconConnectionInfoService.HasMultipleInformation;\n        _rconConnectService.OnConnected -= OnConnected;\n        _rconConnectService.OnMessage -= OnMessage;\n        _rconConnectService.OnClosed -= OnClosed;\n        await Task.CompletedTask;\n        try\n        {\n            _connectingDialogService.Show();\n            // 选择连接服务\n            _rconConnectService = _rconConnectionInfoService.HasMultipleInformation\n                ? App.GetService<IRconConnection>(typeof(RconMultiConnection))\n                : App.GetService<IRconConnection>(typeof(RconSingleConnection));\n\n            WeakReferenceMessenger.Default.Send(new MainPageLoadValueMessage\n            {\n                IsLoaded = true,\n            });\n\n            Log.Information($\"[ConnectAsync] 连接服务: {_rconConnectService.GetType().FullName}, 是否为多连接: {IsMultipleConnection}\");\n\n            _logTextBox = (TextBox)LogicalTreeHelper.FindLogicalNode(_page, \"LogText\");\n            LogText += _logService.Log(_languageService.GetKey(\"text.server.start\"));\n            _logTextBox?.ScrollToEnd();\n            _rconConnectService.OnConnected += OnConnected;\n            _rconConnectService.OnMessage += OnMessage;\n            _rconConnectService.OnClosed += OnClosed;\n            await Task.Run(_rconConnectService.Connect);\n        }\n        catch (AuthenticationException ex)\n        {\n            Log.Error($\"[ConnectAsync] 认证失败: {ex.Message}\");\n            var msg = ex.Message + _languageService.GetKey(\"text.server.connect.auth_fail\")\n                .Replace(\"\\\\n\", \"\\n\");\n            _messageBoxService.Show(msg, _languageService.GetKey(\"text.error\"), MessageBoxButton.OK,\n                MessageBoxImage.Error);\n            LogText += _logService.Log(msg);\n\n            // 如果认证失败 就根据当前模式返回对应页面\n            _navigationService.Navigate(_rconConnectionInfoService.HasMultipleInformation\n                ? typeof(GroupPage)\n                : typeof(ServersPage));\n        }\n        catch (Exception ex)\n        {\n            Log.Error($\"[ConnectAsync] 连接遇到错误: {ex}\");\n\n            var msg = _languageService.GetKey(\"text.server.connect.fail.text\")\n                .Replace(\"\\\\n\", \"\\n\")\n                .Replace(\"%s\", ex.Message);\n            _messageBoxService.Show(msg, _languageService.GetKey(\"text.error\"), MessageBoxButton.OK,\n                MessageBoxImage.Error);\n            LogText += _logService.Log(msg);\n            _logTextBox?.ScrollToEnd();\n        }\n        finally\n        {\n            _connectingDialogService.Close();\n        }\n\n        // 当只有一个服务器时IsConnected会返回单个客户端的连接状态\n        // 当有多个服务器时只要有一个客户端在线,IsConnected就会返回True\n        if (!_rconConnectService.IsConnected() && !RconSettings.IsKeepConnectionWindowOpen)\n        {\n            _navigationService.Navigate(2);\n        }\n\n        IsDisconnection = !_rconConnectService.IsConnected();\n    }\n\n    partial void OnIsDisconnectionChanged(bool value)\n    {\n        Log.Information(\"当前客户端状态: {0}\", _rconConnectService.IsConnected() ? \"在线\" : \"离线\");\n    }\n\n    private void OnConnected(ServerInformation info)\n    {\n        Log.Information(\"[OnConnected] {name}({adapter})\", info.Name, string.IsNullOrEmpty(info.Adapter) ? \"TCPRcon\" : info.Adapter);\n        LogText += _logService.Log($\"$ {info.Name} {_languageService.GetKey(\"text.server.connected\")}\");\n        IsDisconnection = false;\n    }\n\n    [RelayCommand]\n    private void BackHome()\n    {\n        _navigationService.Navigate(_rconConnectionInfoService.HasMultipleInformation\n            ? typeof(GroupPage)\n            : typeof(ServersPage));\n    }\n\n    [RelayCommand]\n    private void Exit()\n    {\n        WeakReferenceMessenger.Default.Send(new MainPageLoadValueMessage()\n        {\n            IsLoaded = false,\n        });\n        if (_rconConnectService.IsConnected())\n            _rconConnectService.Close();\n        _rconConnectService.OnMessage -= OnMessage;\n        _rconConnectService.OnClosed -= OnClosed;\n        _rconConnectService.OnConnected -= OnConnected;\n        IsDisconnection = false; // 重置状态 此时没有任何连接\n    }\n\n    [RelayCommand]\n    private void Run()\n    {\n        Log.Information(\"[Run] {0}\", CommandText);\n        if (_rconConnectService.IsConnected())\n        {\n            LogText += _logService.Log($\"> {CommandText}\");\n            _logTextBox?.ScrollToEnd();\n            _rconConnectService.Send(CommandText);\n            CommandText = string.Empty;\n        }\n        else\n        {\n            IsDisconnection = true;\n            _rconConnectService.Close();\n            MessageBox.Show(_languageService.GetKey(\"text.server.not_connect.text\"),\n                _languageService.GetKey(\"text.error\"), MessageBoxButton.OK, MessageBoxImage.Error);\n        }\n    }\n\n    [RelayCommand]\n    private void KeyDown(KeyEventArgs e)\n    {\n        var textBox = (System.Windows.Controls.TextBox)e.Source;\n        if (e.Key == Key.Enter)\n        {\n            var text = textBox.Text.Trim();\n            if (string.IsNullOrEmpty(text))\n            {\n                return;\n            }\n            _commandText = text;\n            _historyNode = _historyService.InputCmd(_commandText);\n            Run();\n        }\n        else if (e.Key == Key.Up)\n        {\n            _historyNode = _historyService.Prev(_historyNode);\n            textBox.Text = _historyNode?.Cmd;\n            textBox.Select(textBox.Text?.Length ?? 0, 0);\n        }\n        else if (e.Key == Key.Down)\n        {\n            _historyNode = _historyService.Next(_historyNode);\n            textBox.Text = _historyNode?.Cmd;\n            textBox.Select(textBox.Text?.Length ?? 0, 0);\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON/ViewModels/MainWindowViewModel.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing CommunityToolkit.Mvvm.Messaging;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System.Collections.ObjectModel;\nusing System.IO;\nusing System.Windows;\nusing Serilog;\nusing Wpf.Ui.Common;\nusing Wpf.Ui.Controls;\nusing Wpf.Ui.Controls.Interfaces;\nusing Wpf.Ui.Mvvm.Contracts;\nusing MessageBox = System.Windows.MessageBox;\n\nnamespace NectarRCON.ViewModels\n{\n    public partial class MainWindowViewModel : ObservableObject\n    {\n        [ObservableProperty]\n        private ObservableCollection<INavigationControl> _navigationItems = new();\n        private readonly INavigationService _navigationService;\n        private readonly ILanguageService _languageService;\n        [ObservableProperty]\n        private bool _mainPageIsLoaded;\n        public MainWindowViewModel(INavigationService navigationService, ILanguageService languageService)\n        {\n            NavigationItems = new ObservableCollection<INavigationControl>()\n            {\n                new NavigationItem\n                {\n                    Content = \"主页\",\n                    PageTag = \"mainPage\",\n                    PageType = typeof(Views.Pages.MainPage)\n                },\n                new NavigationItem\n                {\n                    Content = \"设置\",\n                    PageTag = \"settingsPage\",\n                    PageType= typeof(Views.Pages.SettingPage)\n                },\n                new NavigationItem\n                {\n                    Content = \"服务器...\",\n                    PageTag = \"serversPage\",\n                    PageType= typeof(Views.Pages.ServersPage)\n                },\n                new NavigationItem\n                {\n                    Content = \"关于\",\n                    PageTag = \"aboutPage\",\n                    PageType= typeof(Views.Pages.AboutPage)\n                },\n                new NavigationItem\n                {\n                    Content = \"群组\",\n                    PageTag = \"groupPage\",\n                    PageType= typeof(Views.Pages.GroupPage)\n                },\n                new NavigationItem\n                {\n                    Content = \"添加服务器\",\n                    PageTag = \"newGroup\",\n                    PageType= typeof(Views.Pages.AddGroupPage)\n                }\n            };\n            _navigationService = navigationService;\n            _languageService = languageService;\n            WeakReferenceMessenger.Default.Register<MainPageLoadValueMessage>(this, OnMainPageChange);\n        }\n        public void OnMainPageChange(object sender, MainPageLoadValueMessage message)\n        {\n            MainPageIsLoaded = message.IsLoaded;\n        }\n        [RelayCommand]\n        private void OnLoad()\n        {\n            _navigationService.Navigate(2);\n        }\n        [RelayCommand]\n        private void ClearButtonClick()\n        {\n            WeakReferenceMessenger.Default.Send(new ClearLogValueMessage());\n        }\n        [RelayCommand]\n        private void ChangePage(string index)\n        {\n            _navigationService.Navigate(int.Parse(index));\n        }\n\n        [RelayCommand]\n        private void ClearProgramLogs()\n        {\n            if (MessageBox.Show(_languageService.GetKey(\"ui.menu.log.clear_program.ask\"), \"NectarRcon\", MessageBoxButton.YesNo,\n                    MessageBoxImage.Question) != MessageBoxResult.Yes) return;\n            Log.Information(\"Clear program logs\");\n            foreach (var logFile in Directory.GetFiles(Path.Combine(Environment.CurrentDirectory, \"logs\", \"program\"), \"*.log\"))\n            {\n                try\n                {\n                    Log.Information(\"Delete log file: {0}\", logFile);\n                    File.Delete(logFile);\n                }\n                catch(Exception ex)\n                {\n                    Log.Error(ex, \"Delete log file failed: {0}\", logFile);\n                }   \n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/ServersPageViewModel.cs",
    "content": "﻿using CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Windows;\nusing System.Collections.ObjectModel;\nusing System.Windows.Controls;\nusing System.Windows.Data;\n\nnamespace NectarRCON.ViewModels;\npublic partial class ServersPageViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private ListCollectionView _serverCollectionView;\n\n    [ObservableProperty]\n    private ObservableCollection<ServerModel> _servers;\n\n    private string _filterName = string.Empty;\n#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    public ServersPageViewModel()\n#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。\n    {\n        Servers = new();\n        ServerCollectionView = new(Servers);\n\n        ServerCollectionView.Filter += (s) =>\n        {\n            if (_filterName == string.Empty)\n                return true;\n            if (s is ServerModel model)\n            {\n                return model.Name.Contains(_filterName);\n            }\n            return false;\n        };\n\n        Refresh();\n    }\n\n    public void Refresh()\n    {\n        Servers.Clear();\n        foreach (ServerInformation information in App.GetService<IServerInformationService>().GetServers())\n        {\n            Servers.Add(new ServerModel(information, this));\n        }\n    }\n\n    [RelayCommand]\n    public void AddServer()\n    {\n        AddServerWindow addServer = App.GetService<AddServerWindow>();\n        addServer.ShowDialog();\n        Refresh();\n    }\n\n    [RelayCommand]\n    public void FilterTextChanged(TextChangedEventArgs e)\n    {\n        var box = (System.Windows.Controls.TextBox)e.Source;\n        _filterName = box.Text.ToString() ?? string.Empty;\n        _serverCollectionView.Refresh();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/ViewModels/SettingPageViewModel.cs",
    "content": "﻿using System;\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\nusing NectarRCON.Core.Helper;\nusing NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing System.Collections.Generic;\nusing System.Collections.ObjectModel;\nusing System.Linq;\nusing System.Windows;\nusing System.Windows.Controls;\nusing NectarRCON.Dp;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON.ViewModels;\npublic partial class SettingPageViewModel : ObservableObject\n{\n    private bool _isLoaded = false;\n    private readonly ILanguageService _languageService;\n    private readonly IConfigService _configService;\n    private readonly IThemeService _themeService;\n    private readonly RconSettingsDp _rconSettingsDp = DpFile.LoadSingleton<RconSettingsDp>();\n\n    [ObservableProperty]\n    private int _languageSelectedIndex = -1;\n    [ObservableProperty]\n    private int _themeSelectedIndex = -1;\n    \n    [ObservableProperty]\n    private bool _rconAutoReconnect;\n    \n    [ObservableProperty]\n    private bool _isKeepConnectionWindowOpen;\n\n    [ObservableProperty]\n    private ObservableCollection<string> _rconEncoding = [];\n    \n    [ObservableProperty]\n    private string _selectedRconEncoding;\n\n    [ObservableProperty]\n    private ObservableCollection<string> _languages = [];\n    \n    public SettingPageViewModel()\n    {\n        _languageService = App.GetService<ILanguageService>();\n        _configService = App.GetService<IConfigService>();\n        _themeService = App.GetService<IThemeService>();\n\n        RconAutoReconnect = _rconSettingsDp.AutoReconnect;\n        IsKeepConnectionWindowOpen = _rconSettingsDp.IsKeepConnectionWindowOpen;\n        \n        RconEncoding.Clear();\n        foreach (var encoding in Enum.GetNames(typeof(RconEncoding)))\n        {\n            RconEncoding.Add(encoding);\n        }\n        \n        SelectedRconEncoding = _rconSettingsDp.Encoding.ToString();\n    }\n\n    partial void OnRconAutoReconnectChanged(bool value)\n    {\n        _rconSettingsDp.AutoReconnect = value;\n        _rconSettingsDp.Save();\n    }\n\n    partial void OnIsKeepConnectionWindowOpenChanged(bool value)\n    {\n        _rconSettingsDp.IsKeepConnectionWindowOpen = value;\n        _rconSettingsDp.Save();\n    }\n\n    partial void OnSelectedRconEncodingChanged(string value)\n    {\n        _rconSettingsDp.Encoding = Enum.GetValues<RconEncoding>().FirstOrDefault(e => e.ToString() == value);\n        _rconSettingsDp.Save();\n    }\n\n    [RelayCommand]\n    public void PageLoad(RoutedEventArgs e)\n    {\n        Languages.Clear();\n        foreach (var language in _languageService.GetLanguages())\n        {\n            Languages.Add(language.Value[\"file.name\"].ToString() ?? \"NullName\");\n        }\n        foreach (var language in _languageService.GetLanguages())\n        {\n            LanguageSelectedIndex++;\n            if (language.Key == _configService.GetConfig().LanguageName)\n                break;\n        }\n        ThemeSelectedIndex = (int)_configService.GetConfig().Theme;\n        _isLoaded = true;\n    }\n    [RelayCommand]\n    public void Exit()\n    {\n        _isLoaded = false;\n    }\n    [RelayCommand]\n    public void ThemeSelectionChange()\n    {\n        if (!_isLoaded)\n            return;\n        _configService.GetConfig().Theme = (ETheme)_themeSelectedIndex;\n        _configService.Save();\n        switch ((ETheme)_themeSelectedIndex)\n        {\n            case ETheme.System:\n                _themeService.SetTheme(Win32Helper.GetWindowsTheme() ? Wpf.Ui.Appearance.ThemeType.Dark : Wpf.Ui.Appearance.ThemeType.Light);\n                break;\n            case ETheme.Dark:\n                _themeService.SetTheme(Wpf.Ui.Appearance.ThemeType.Dark);\n                break;\n            case ETheme.Light:\n                _themeService.SetTheme(Wpf.Ui.Appearance.ThemeType.Light);\n                break;\n            default:\n                break;\n        }\n    }\n    [RelayCommand]\n    public void LanguageSelectionChange(SelectionChangedEventArgs e)\n    {\n        if (!_isLoaded)\n            return;\n        if (e.AddedItems.Count == 0 || null == e.AddedItems[0])\n            return;\n        KeyValuePair<string, ResourceDictionary>? lang = _languageService.GetLanguages().Where(l =>\n        {\n            return (l.Value[\"file.name\"].ToString() ?? \"NullName\") == e.AddedItems[0]!.ToString();\n        }).FirstOrDefault();\n        if (null == lang.Value.Value) return;\n        _configService.GetConfig().LanguageName = lang.Value.Key;\n        _configService.Save();\n        _languageService.SelectLanguage(lang.Value.Value[\"file.name\"].ToString() ?? \"NullName\", true);\n    }\n\n}"
  },
  {
    "path": "NectarRCON/Views/Pages/AboutPage.xaml",
    "content": "﻿<ui:UiPage\n    x:Class=\"NectarRCON.Views.Pages.AboutPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n    Title=\"AboutPage\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    mc:Ignorable=\"d\">\n    <ui:UiPage.DataContext>\n        <vm:AboutPageViewModel />\n    </ui:UiPage.DataContext>\n    <StackPanel VerticalAlignment=\"Center\">\n        <Border\n            Width=\"150\"\n            Height=\"150\"\n            Margin=\"0,-50,0,0\"\n            CornerRadius=\"100\">\n            <Border.Effect>\n                <DropShadowEffect\n                    BlurRadius=\"30\"\n                    Direction=\"0\"\n                    Opacity=\"0.4\"\n                    ShadowDepth=\"0\"\n                    Color=\"#202020\" />\n            </Border.Effect>\n            <Border.Background>\n                <ImageBrush ImageSource=\"/Resources/head.png\" RenderOptions.BitmapScalingMode=\"NearestNeighbor\" />\n            </Border.Background>\n        </Border>\n        <TextBlock\n            Margin=\"0,20,0,0\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"24\"\n            Foreground=\"{DynamicResource TextFillColorSecondaryBrush}\"\n            Text=\"{DynamicResource ui.window.title}\" />\n        <TextBlock\n            Margin=\"0,10,0,0\"\n            HorizontalAlignment=\"Center\"\n            FontSize=\"18\"\n            Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n            Text=\"{DynamicResource app.version}\" />\n        <Grid>\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"*\" />\n                <ColumnDefinition Width=\"*\" />\n            </Grid.ColumnDefinitions>\n            <ui:Hyperlink\n                Grid.Column=\"0\"\n                Margin=\"5\"\n                HorizontalAlignment=\"Right\"\n                Content=\"{DynamicResource ui.about.afdian}\"\n                NavigateUri=\"https://afdian.net/a/MiuxuE\" />\n            <ui:Hyperlink\n                Grid.Column=\"1\"\n                Margin=\"5\"\n                HorizontalAlignment=\"Left\"\n                Content=\"{DynamicResource ui.about.github}\"\n                NavigateUri=\"https://github.com/zkhssb/NectarRCON\" />\n\n        </Grid>\n\n    </StackPanel>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/AboutPage.xaml.cs",
    "content": "﻿namespace NectarRCON.Views.Pages\n{\n    /// <summary>\n    /// AboutPage.xaml 的交互逻辑\n    /// </summary>\n    public partial class AboutPage\n    {\n        public AboutPage()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Views/Pages/AddGroupPage.xaml",
    "content": "﻿<ui:UiPage\n    x:Class=\"NectarRCON.Views.Pages.AddGroupPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n    Title=\"AddGroupPage\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    mc:Ignorable=\"d\">\n    <ui:UiPage.DataContext>\n        <vm:AddGroupPageViewModel />\n    </ui:UiPage.DataContext>\n    <i:Interaction.Triggers>\n        <i:EventTrigger EventName=\"Loaded\">\n            <i:InvokeCommandAction Command=\"{Binding LoadCommand}\" />\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <Grid Margin=\"10\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n\n        <Border\n            Grid.Row=\"1\"\n            Width=\"280\"\n            Height=\"130\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            CornerRadius=\"5\">\n            <Border.Background>\n                <SolidColorBrush Color=\"{DynamicResource SystemFillColorSolidNeutralBackground}\" />\n            </Border.Background>\n            <Border.Effect>\n                <DropShadowEffect\n                    BlurRadius=\"30\"\n                    Direction=\"0\"\n                    Opacity=\"0.4\"\n                    ShadowDepth=\"0\"\n                    Color=\"#202020\" />\n            </Border.Effect>\n            <StackPanel\n                Width=\"240\"\n                HorizontalAlignment=\"Center\"\n                VerticalAlignment=\"Center\">\n                <TextBlock\n                    Margin=\"0,0,0,12\"\n                    FontSize=\"16\"\n                    Text=\"{DynamicResource ui.add_group.title}\" />\n                <ui:TextBox PlaceholderText=\"{DynamicResource ui.add_group.placeholder_text}\" Text=\"{Binding GroupName, UpdateSourceTrigger=PropertyChanged}\" />\n                <Grid Grid.RowSpan=\"2\" Margin=\"0,10,0,0\">\n                    <Grid.ColumnDefinitions>\n                        <ColumnDefinition Width=\"*\" />\n                        <ColumnDefinition Width=\"*\" />\n                    </Grid.ColumnDefinitions>\n                    <ui:Button\n                        Grid.Column=\"0\"\n                        Margin=\"0,0,2,0\"\n                        HorizontalAlignment=\"Stretch\"\n                        Command=\"{Binding CancelCommand}\"\n                        Content=\"{DynamicResource text.cancel}\" />\n                    <ui:Button\n                        Grid.Column=\"1\"\n                        Margin=\"2,0,0,0\"\n                        HorizontalAlignment=\"Stretch\"\n                        Command=\"{Binding AddCommand}\"\n                        Content=\"{DynamicResource text.add}\" />\n                </Grid>\n            </StackPanel>\n        </Border>\n    </Grid>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/AddGroupPage.xaml.cs",
    "content": "﻿using System;\nusing Wpf.Ui.Controls;\n\nnamespace NectarRCON.Views.Pages\n{\n    /// <summary>\n    /// AddGroupPage.xaml 的交互逻辑\n    /// </summary>\n    public partial class AddGroupPage : UiPage\n    {\n        public AddGroupPage()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Views/Pages/GroupPage.xaml",
    "content": "﻿<ui:UiPage\n    x:Class=\"NectarRCON.Views.Pages.GroupPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:c=\"clr-namespace:NectarRCON.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n    Title=\"GroupPage\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    mc:Ignorable=\"d\">\n    <ui:UiPage.DataContext>\n        <vm:GroupPageViewModel />\n    </ui:UiPage.DataContext>\n    <ui:UiPage.Resources>\n        <c:IntToVisibilityConverter x:Key=\"IntToVisibilityConverter\" />\n    </ui:UiPage.Resources>\n    <i:Interaction.Triggers>\n        <i:EventTrigger EventName=\"Loaded\">\n            <i:InvokeCommandAction Command=\"{Binding LoadCommand}\" />\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <Grid Margin=\"10\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <TextBlock\n            Grid.Row=\"0\"\n            FontSize=\"16\"\n            Text=\"{DynamicResource ui.menu.groups}\" />\n        <StackPanel\n            Grid.Row=\"1\"\n            Margin=\"0,-30,0,0\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            Visibility=\"{Binding Groups.Count, Converter={StaticResource IntToVisibilityConverter}}\">\n            <TextBlock\n                HorizontalAlignment=\"Center\"\n                Focusable=\"{DynamicResource TextFullColorTertiaryBrush}\"\n                FontSize=\"18\"\n                Text=\"{DynamicResource ui.group.no_group_title}\" />\n            <TextBlock\n                Margin=\"0,5,0,0\"\n                HorizontalAlignment=\"Center\"\n                Focusable=\"{DynamicResource TextFullColorTertiaryBrush}\"\n                FontSize=\"14\"\n                Text=\"{DynamicResource ui.group.no_group_subtitle}\"\n                TextWrapping=\"Wrap\" />\n            <ui:Button\n                Height=\"38\"\n                Margin=\"0,15,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Command=\"{Binding NewGroupCommand}\"\n                Content=\"{DynamicResource text.add}\"\n                FontSize=\"16\"\n                Icon=\"Add24\" />\n        </StackPanel>\n        <ScrollViewer\n            Grid.Row=\"1\"\n            Margin=\"0,10,-10,10\"\n            VerticalAlignment=\"Top\">\n            <ItemsControl Margin=\"0,-10,0,0\" ItemsSource=\"{Binding Groups}\">\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <ui:CardExpander Margin=\"0,10,0,0\">\n                            <ui:CardExpander.Header>\n                                <Grid>\n                                    <Grid.ColumnDefinitions>\n                                        <ColumnDefinition Width=\"*\" />\n                                        <ColumnDefinition Width=\"Auto\" />\n                                    </Grid.ColumnDefinitions>\n                                    <StackPanel\n                                        Grid.Row=\"0\"\n                                        Grid.Column=\"1\"\n                                        Margin=\"0,0,5,0\"\n                                        Orientation=\"Horizontal\">\n                                        <ui:Button\n                                            Margin=\"0,0,5,0\"\n                                            Command=\"{Binding AddCommand}\"\n                                            Content=\"{DynamicResource text.add}\"\n                                            Icon=\"Add24\" />\n                                        <ui:Button\n                                            Margin=\"0,0,5,0\"\n                                            Command=\"{Binding BaseModel.RemoveGroupCommand}\"\n                                            CommandParameter=\"{Binding Id}\"\n                                            Content=\"{DynamicResource text.delete}\"\n                                            Icon=\"Delete24\" />\n                                        <ui:Button\n                                            Margin=\"0,0,8,0\"\n                                            Command=\"{Binding ConnectCommand}\"\n                                            Content=\"{DynamicResource text.connect}\"\n                                            Icon=\"Connector24\" />\n                                    </StackPanel>\n                                    <TextBlock\n                                        VerticalAlignment=\"Center\"\n                                        FontSize=\"16\"\n                                        Text=\"{Binding Name}\" />\n                                </Grid>\n                            </ui:CardExpander.Header>\n                            <Grid Margin=\"0,-5,0,0\">\n                                <TextBlock\n                                    Margin=\"0,5,0,0\"\n                                    HorizontalAlignment=\"Center\"\n                                    VerticalAlignment=\"Center\"\n                                    Focusable=\"{DynamicResource TextFullColorTertiaryBrush}\"\n                                    FontSize=\"12\"\n                                    Text=\"{DynamicResource ui.group.no_server}\"\n                                    Visibility=\"{Binding Servers.Count, Converter={StaticResource IntToVisibilityConverter}}\" />\n                                <ItemsControl ItemsSource=\"{Binding Servers}\">\n                                    <ItemsControl.ItemTemplate>\n                                        <DataTemplate>\n                                            <Grid Margin=\"0,5,0,0\">\n                                                <Grid.ColumnDefinitions>\n                                                    <ColumnDefinition Width=\"*\" />\n                                                    <ColumnDefinition Width=\"Auto\" />\n                                                </Grid.ColumnDefinitions>\n                                                <TextBlock VerticalAlignment=\"Center\" Text=\"{Binding Name}\" />\n                                                <Button\n                                                    Grid.Column=\"1\"\n                                                    Command=\"{Binding BaseModel.ItemRemoveCommand}\"\n                                                    CommandParameter=\"{Binding Name}\"\n                                                    Content=\"{DynamicResource text.remove}\" />\n                                            </Grid>\n                                        </DataTemplate>\n                                    </ItemsControl.ItemTemplate>\n                                </ItemsControl>\n                            </Grid>\n                        </ui:CardExpander>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </ScrollViewer>\n\n        <ui:Button\n            Grid.Row=\"0\"\n            Margin=\"0,0,4,0\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{Binding NewGroupCommand}\"\n            Content=\"{DynamicResource text.add}\"\n            Icon=\"Add24\"\n            Visibility=\"{Binding Groups.Count, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=True}\" />\n    </Grid>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/GroupPage.xaml.cs",
    "content": "﻿using System.Windows.Controls;\nusing Wpf.Ui.Controls;\n\nnamespace NectarRCON.Views.Pages\n{\n    /// <summary>\n    /// GroupPage.xaml 的交互逻辑\n    /// </summary>\n    public partial class GroupPage : UiPage\n    {\n        public GroupPage()\n        {\n            InitializeComponent();\n        }\n\n        private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n            if (sender is ListView listView)\n            {\n                listView.SelectedIndex = -1;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Views/Pages/MainPage.xaml",
    "content": "﻿<ui:UiPage x:Class=\"NectarRCON.Views.Pages.MainPage\"\n      xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n      xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n      xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n      xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n      mc:Ignorable=\"d\" \n      d:DesignHeight=\"450\" d:DesignWidth=\"800\"\n      Title=\"MainPage\">\n    <ui:UiPage.DataContext>\n        <vm:MainPageViewModel/>\n    </ui:UiPage.DataContext>\n    <ui:UiPage.Resources>\n        <BooleanToVisibilityConverter x:Key=\"BooleanToVisibilityConverter\"/>\n        <ControlTemplate x:Key=\"NoWordSpacingTextBoxTemplate\" TargetType=\"{x:Type TextBox}\">\n            <Grid>\n                <ScrollViewer x:Name=\"PART_ContentHost\"\n                              Focusable=\"false\"\n                              HorizontalScrollBarVisibility=\"Hidden\"\n                              VerticalScrollBarVisibility=\"Auto\"\n                              VerticalContentAlignment=\"Top\"\n                              IsTabStop=\"False\">\n                    <Grid Background=\"{TemplateBinding Background}\"\n                          Margin=\"{TemplateBinding Padding}\">\n                        <TextBlock Text=\"{TemplateBinding Text}\"\n                                   FontFamily=\"{TemplateBinding FontFamily}\"\n                                   TextWrapping=\"Wrap\"\n                                   VerticalAlignment=\"Top\"\n                                   Margin=\"0,0,0,2\" />\n                    </Grid>\n                </ScrollViewer>\n            </Grid>\n        </ControlTemplate>\n    </ui:UiPage.Resources>\n    <i:Interaction.Triggers>\n        <i:EventTrigger\n            EventName=\"Loaded\">\n            <i:InvokeCommandAction \n                PassEventArgsToCommand=\"True\"\n                Command=\"{Binding LoadCommand}\"/>\n        </i:EventTrigger>\n        <i:EventTrigger\n            EventName=\"Unloaded\">\n            <i:InvokeCommandAction Command=\"{Binding ExitCommand}\"/>\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"*\"/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <ui:TextBox\n            ClearButtonEnabled=\"False\"\n            Name=\"LogText\"\n            FontFamily=\"Consolas\"\n            BorderThickness=\"0\"\n            Margin=\"5\"\n            Grid.Row=\"0\"\n            TextWrapping=\"Wrap\"\n            TextAlignment=\"Left\"\n            AcceptsReturn=\"True\"\n            VerticalScrollBarVisibility=\"Visible\"\n            VerticalContentAlignment=\"Top\"\n            VerticalAlignment=\"Stretch\"\n            IsReadOnly=\"True\"\n            Text=\"{Binding LogText}\"\n            />\n        <Grid\n            Margin=\"5\"\n            Grid.Row=\"1\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"*\"/>\n                <ColumnDefinition Width=\"Auto\"/>\n            </Grid.ColumnDefinitions>\n            <ui:TextBox \n                Text=\"{Binding CommandText}\"\n                Grid.Column=\"0\"\n                BorderThickness=\"0\"\n                PlaceholderText=\"/\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"PreviewKeyDown\">\n                        <i:InvokeCommandAction\n                            PassEventArgsToCommand=\"True\"\n                            Command=\"{Binding KeyDownCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:TextBox>\n            <Button \n                Grid.Column=\"1\"\n                Margin=\"10,0,0,0\"\n                Width=\"120\"\n                Content=\"{DynamicResource ui.main_page.run}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"Click\">\n                        <i:InvokeCommandAction Command=\"{Binding RunCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </Button>\n        </Grid>\n        \n        <Grid \n            Visibility=\"{Binding IsDisconnection, Converter={StaticResource BooleanToVisibilityConverter}}\"\n            Grid.Row=\"0\"\n            Grid.RowSpan=\"2\">\n            <Grid.Background>\n                <LinearGradientBrush StartPoint=\"0,0\" EndPoint=\"1,1\">\n                    <GradientStop Offset=\"0.0\" Color=\"#CF202020\" />\n                    <GradientStop Offset=\"1.0\" Color=\"#8F202020\" />\n                </LinearGradientBrush>\n            </Grid.Background>\n            <Border\n                CornerRadius=\"5\"\n                Height=\"150\"\n                Width=\"200\">\n                <Border.Background>\n                    <SolidColorBrush Color=\"{DynamicResource SystemFillColorSolidNeutralBackground}\"/>\n                </Border.Background>\n                <Border.Effect>\n                    <DropShadowEffect\n                        BlurRadius=\"30\"\n                        Direction=\"0\"\n                        Opacity=\"0.4\"\n                        ShadowDepth=\"0\"\n                        Color=\"#202020\" />\n                </Border.Effect>\n                \n                <Grid>\n                    <Grid.RowDefinitions>\n                        <RowDefinition Height=\"*\"/>\n                        <RowDefinition Height=\"Auto\"/>\n                    </Grid.RowDefinitions>\n                    <StackPanel HorizontalAlignment=\"Center\" VerticalAlignment=\"Center\" Grid.Row=\"0\">\n                                        <TextBlock \n                                            HorizontalAlignment=\"Center\"\n                                            FontSize=\"16\"\n                                            Text=\"{DynamicResource ui.main_page.offline.single}\"/>\n                                        <TextBlock \n                                            Margin=\"0,10,0,0\"\n                                            FontSize=\"14\"\n                                            HorizontalAlignment=\"Center\"\n                                            Visibility=\"{Binding IsMultipleConnection, Converter={StaticResource BooleanToVisibilityConverter}}\"\n                                            Text=\"{DynamicResource ui.main_page.offline.multiple}\"/>\n                    </StackPanel>\n                    <UniformGrid Margin=\"0,0,0,10\" Grid.Row=\"1\" HorizontalAlignment=\"Center\" Columns=\"2\">\n                        <Button Margin=\"2\" HorizontalAlignment=\"Center\" Command=\"{Binding BackHomeCommand}\" Content=\"{DynamicResource ui.main_page.back_home}\"/>\n                        <Button Margin=\"2\" HorizontalAlignment=\"Center\" Command=\"{Binding ReConnectCommand}\" Content=\"{DynamicResource ui.main_page.re_connect}\"/>\n                    </UniformGrid>\n                </Grid>\n                \n                \n            </Border>\n        </Grid>\n        \n    </Grid>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/MainPage.xaml.cs",
    "content": "﻿namespace NectarRCON.Views.Pages\n{\n    /// <summary>\n    /// MainPage.xaml 的交互逻辑\n    /// </summary>\n    public partial class MainPage\n    {\n        public MainPage()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Views/Pages/ServersPage.xaml",
    "content": "﻿<ui:UiPage\n    x:Class=\"NectarRCON.Views.Pages.ServersPage\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n    Title=\"ServersPage\"\n    d:DesignHeight=\"450\"\n    d:DesignWidth=\"800\"\n    mc:Ignorable=\"d\">\n    <ui:UiPage.DataContext>\n        <vm:ServersPageViewModel />\n    </ui:UiPage.DataContext>\n    <Grid Margin=\"10\">\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n            <RowDefinition Height=\"Auto\" />\n        </Grid.RowDefinitions>\n        <TextBlock\n            Grid.Row=\"0\"\n            FontSize=\"16\"\n            Text=\"{DynamicResource ui.server_page.text}\" />\n        <ScrollViewer Grid.Row=\"1\" Margin=\"0,10,-10,10\">\n            <ItemsControl ItemsSource=\"{Binding ServerCollectionView}\">\n                <ItemsControl.ItemsPanel>\n                    <ItemsPanelTemplate>\n                        <UniformGrid\n                            Margin=\"-5\"\n                            VerticalAlignment=\"Top\"\n                            Columns=\"1\" />\n                    </ItemsPanelTemplate>\n                </ItemsControl.ItemsPanel>\n                <ItemsControl.ItemTemplate>\n                    <DataTemplate>\n                        <ui:CardAction\n                            Margin=\"5\"\n                            Command=\"{Binding ConnectCommand}\"\n                            Focusable=\"False\">\n                            <ui:CardAction.ContextMenu>\n                                <ContextMenu>\n                                    <ui:MenuItem\n                                        Command=\"{Binding ConnectCommand}\"\n                                        Header=\"{DynamicResource ui.server_page.menu.open}\"\n                                        SymbolIcon=\"Open24\" />\n                                    <ui:MenuItem\n                                        Command=\"{Binding EditCommand}\"\n                                        Header=\"{DynamicResource ui.server_page.menu.edit}\"\n                                        SymbolIcon=\"Edit24\" />\n                                    <ui:MenuItem\n                                        Command=\"{Binding EditPasswordCommand}\"\n                                        Header=\"{DynamicResource ui.server_page.menu.edit_password}\"\n                                        SymbolIcon=\"Password24\" />\n                                    <ui:MenuItem\n                                        Command=\"{Binding DeleteCommand}\"\n                                        Header=\"{DynamicResource ui.server_page.menu.delete}\"\n                                        SymbolIcon=\"Delete24\" />\n                                </ContextMenu>\n                            </ui:CardAction.ContextMenu>\n                            <StackPanel>\n                                <TextBlock\n                                    Name=\"Name\"\n                                    FontSize=\"16\"\n                                    Foreground=\"{DynamicResource TextFillColorPrimaryBrush}\"\n                                    Text=\"{Binding Name}\" />\n                                <TextBlock\n                                    Margin=\"0,10,0,0\"\n                                    FontSize=\"14\"\n                                    Foreground=\"{DynamicResource TextFillColorTertiaryBrush}\"\n                                    Text=\"{Binding Address}\" />\n                            </StackPanel>\n                        </ui:CardAction>\n                    </DataTemplate>\n                </ItemsControl.ItemTemplate>\n            </ItemsControl>\n        </ScrollViewer>\n        <ui:TextBox\n            Grid.Row=\"2\"\n            Margin=\"0,0,130,0\"\n            PlaceholderText=\"{DynamicResource ui.server_page.search}\">\n            <i:Interaction.Triggers>\n                <i:EventTrigger EventName=\"TextChanged\">\n                    <i:InvokeCommandAction Command=\"{Binding FilterTextChangedCommand}\" PassEventArgsToCommand=\"True\" />\n                </i:EventTrigger>\n            </i:Interaction.Triggers>\n        </ui:TextBox>\n        <ui:Button\n            Grid.Row=\"2\"\n            Margin=\"0,0,4,0\"\n            HorizontalAlignment=\"Right\"\n            Command=\"{Binding AddServerCommand}\"\n            Content=\"{DynamicResource ui.server_page.add}\"\n            Icon=\"Add24\" />\n    </Grid>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/ServersPage.xaml.cs",
    "content": "﻿namespace NectarRCON.Views.Pages\n{\n    /// <summary>\n    /// ServersPage.xaml 的交互逻辑\n    /// </summary>\n    public partial class ServersPage\n    {\n        public ServersPage()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Views/Pages/SettingPage.xaml",
    "content": "﻿<ui:UiPage x:Class=\"NectarRCON.Views.Pages.SettingPage\"\n      xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n      xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n      xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" \n      xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\" \n      xmlns:local=\"clr-namespace:NectarRCON.Views.Pages\"\n      xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n      xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n      xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n      mc:Ignorable=\"d\" \n      d:DesignHeight=\"450\" d:DesignWidth=\"800\"\n      Title=\"SettingPage\">\n    <ui:UiPage.DataContext>\n        <vm:SettingPageViewModel/>\n    </ui:UiPage.DataContext>\n    <i:Interaction.Triggers>\n        <i:EventTrigger\n            EventName=\"Loaded\">\n            <i:InvokeCommandAction \n                PassEventArgsToCommand=\"True\"\n                Command=\"{Binding PageLoadCommand}\"/>\n        </i:EventTrigger>\n        <i:EventTrigger\n            EventName=\"Unloaded\">\n            <i:InvokeCommandAction \n                Command=\"{Binding ExitCommand}\"/>\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <StackPanel\n        Margin=\"10,10,10,0\">\n        <TextBlock \n            FontSize=\"16\"\n            Margin=\"0,0,5,0\"\n            VerticalAlignment=\"Center\"\n            Text=\"{DynamicResource ui.settings.ui}\"/>\n        \n        <TextBlock\n                FontSize=\"14\"\n                Margin=\"0,10,5,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{DynamicResource ui.settings.language}\"/>\n        <ComboBox \n                x:Name=\"LanguageComboBox\"\n                ItemsSource=\"{Binding Languages, Mode=OneWay}\"\n                HorizontalContentAlignment=\"Stretch\"\n                HorizontalAlignment=\"Stretch\"\n                SelectedIndex=\"{Binding LanguageSelectedIndex,Mode=TwoWay}\"\n                Focusable=\"False\">\n            <i:Interaction.Triggers>\n                <i:EventTrigger\n                    EventName=\"SelectionChanged\">\n                    <i:InvokeCommandAction \n                        PassEventArgsToCommand=\"True\"\n                        Command=\"{Binding LanguageSelectionChangeCommand}\"/>\n                </i:EventTrigger>\n            </i:Interaction.Triggers>\n        </ComboBox>\n\n        <TextBlock \n            FontSize=\"14\"\n            Margin=\"0,10,5,0\"\n            VerticalAlignment=\"Center\"\n            Text=\"{DynamicResource ui.settings.theme}\"/>\n        <ComboBox \n            x:Name=\"ThemeComboBox\"\n            HorizontalContentAlignment=\"Stretch\"\n            HorizontalAlignment=\"Stretch\"\n            SelectedIndex=\"{Binding ThemeSelectedIndex}\"\n            Focusable=\"False\">\n            <ComboBoxItem Content=\"{DynamicResource ui.settings.theme.auto}\"/>\n            <ComboBoxItem Content=\"{DynamicResource ui.settings.theme.dark}\"/>\n            <ComboBoxItem Content=\"{DynamicResource ui.settings.theme.light}\"/>\n            <i:Interaction.Triggers>\n                <i:EventTrigger\n                    EventName=\"SelectionChanged\">\n                    <i:InvokeCommandAction \n                        Command=\"{Binding ThemeSelectionChangeCommand}\"/>\n                </i:EventTrigger>\n            </i:Interaction.Triggers>\n        </ComboBox>\n        \n        <TextBlock \n            FontSize=\"16\"\n            Margin=\"0,10,5,0\"\n            VerticalAlignment=\"Center\"\n            Text=\"{DynamicResource ui.settings.rcon}\"/>\n\n        <ui:ToggleSwitch\n            FontSize=\"14\"\n            Margin=\"0,10,5,0\"\n            IsChecked=\"{Binding RconAutoReconnect}\"\n            Content=\"{DynamicResource ui.settings.rcon.auto_reconnect}\"/>\n        \n        <ui:ToggleSwitch\n            FontSize=\"14\"\n            Margin=\"0,10,5,0\"\n            IsChecked=\"{Binding IsKeepConnectionWindowOpen}\"\n            Content=\"{DynamicResource ui.settings.rcon.is_keep_connection_window_open}\"/>\n        \n        <TextBlock\n            FontSize=\"14\"\n            Margin=\"0,10,5,0\"\n            Text=\"{DynamicResource ui.settings.rcon.encoding}\"/>\n        \n        <ComboBox Margin=\"0,5,0,0\" ItemsSource=\"{Binding RconEncoding}\" SelectedItem=\"{Binding SelectedRconEncoding}\"/>\n        \n        <!--\n        <TextBlock \n                FontSize=\"16\"\n                Margin=\"0,10,5,0\"\n                VerticalAlignment=\"Center\"\n                Text=\"{DynamicResource ui.settings.command_record_limit}\"/>\n        <ui:NumberBox\n                IntegersOnly=\"True\"\n                HorizontalAlignment=\"Stretch\"\n                Max=\"9999\" \n                Min=\"10\" Step=\"1\"/>\n\n        <Button \n            HorizontalAlignment=\"Stretch\"\n            Margin=\"0,20,0,0\"\n            Content=\"{DynamicResource ui.settings.rcon_account}\"/>\n        --> \n    </StackPanel>\n</ui:UiPage>\n"
  },
  {
    "path": "NectarRCON/Views/Pages/SettingPage.xaml.cs",
    "content": "﻿namespace NectarRCON.Views.Pages;\npublic partial class SettingPage\n{\n    public SettingPage()\n    {\n        InitializeComponent();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Windows/AddServerWindow.xaml",
    "content": "﻿<ui:UiWindow x:Class=\"NectarRCON.Windows.AddServerWindow\"\n        xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n        xmlns:local=\"clr-namespace:NectarRCON.Windows\"\n        mc:Ignorable=\"d\"\n        ExtendsContentIntoTitleBar=\"True\"\n        WindowBackdropType=\"Mica\"\n\t\tWindowCornerPreference=\"Round\"\n\t\tWindowStartupLocation=\"CenterScreen\"\n        Title=\"{DynamicResource ui.add_server_window.title}\" \n        ResizeMode=\"NoResize\"\n        Height=\"290\" \n        MinHeight=\"290\" \n        MaxHeight=\"290\" \n        Width=\"380\"\n        MinWidth=\"380\"\n        MaxWidth=\"380\">\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"*\"/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <ui:TitleBar \n            Icon=\"pack://application:,,,/Resources/Icon.png\"\n            FontSize=\"14\"\n            ShowMaximize=\"False\"\n            ShowMinimize=\"False\"\n            Title=\"{DynamicResource ui.add_server_window.title}\" Grid.ColumnSpan=\"2\" VerticalAlignment=\"Center\"/>\n        <StackPanel\n            Margin=\"10,-5,10,10\"\n            Grid.Row=\"1\" Grid.ColumnSpan=\"2\">\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.name}\"/>\n            <ui:TextBox\n                Text=\"{Binding ViewModel.ServerName,Mode=TwoWay}\"/>\n\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.address}\"/>\n            <ui:TextBox\n                Text=\"{Binding ViewModel.ServerAddress,Mode=TwoWay}\"/>\n\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.port}\"/>\n            <ui:NumberBox\n                Text=\"{Binding ViewModel.ServerPort,Mode=TwoWay}\"\n                Min=\"0\"\n                Max=\"65535\"\n                IntegersOnly=\"True\" Cursor=\"IBeam\"/>\n        </StackPanel>\n        <Grid\n            Grid.Row=\"2\"\n            Margin=\"10,0,10,10\" Grid.ColumnSpan=\"2\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"*\"/>\n                <ColumnDefinition Width=\"2*\"/>\n            </Grid.ColumnDefinitions>\n            <ui:Button\n                Margin=\"0,0,2,0\"\n                HorizontalAlignment=\"Stretch\"\n                Grid.Column=\"0\"\n                Appearance=\"Transparent\"\n                Click=\"CancelButtonClick\"\n                Content=\"{DynamicResource ui.add_server_window.cancel}\"/>\n            <ui:Button\n                Margin=\"2,0,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Grid.Column=\"1\"\n                Appearance=\"Secondary\"\n                Content=\"{DynamicResource ui.add_server_window.add}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"Click\">\n                        <i:InvokeCommandAction\n                            Command=\"{Binding ViewModel.OkCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:Button>\n        </Grid>\n    </Grid>\n</ui:UiWindow>\n"
  },
  {
    "path": "NectarRCON/Windows/AddServerWindow.xaml.cs",
    "content": "﻿using NectarRCON.ViewModels;\nusing System.Windows;\n\nnamespace NectarRCON.Windows\n{\n    /// <summary>\n    /// AddServerWindow.xaml 的交互逻辑\n    /// </summary>\n    public partial class AddServerWindow\n    {\n        public AddServerWindowViewModel ViewModel\n        {\n            get;\n        }\n        public AddServerWindow(AddServerWindowViewModel viewModel)\n        {\n            ViewModel = viewModel;\n            viewModel.SetWindow(this);\n            InitializeComponent();\n            DataContext = this;\n        }\n\n        private void CancelButtonClick(object sender, RoutedEventArgs e)\n            => Close();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Windows/EditPasswordWindow.xaml",
    "content": "﻿<ui:UiWindow x:Class=\"NectarRCON.Windows.EditPasswordWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:local=\"clr-namespace:NectarRCON.Windows\"\n        xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n        xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n        xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n        mc:Ignorable=\"d\"\n        ExtendsContentIntoTitleBar=\"True\"\n        WindowBackdropType=\"Mica\"\n\t\tWindowCornerPreference=\"Round\"\n\t\tWindowStartupLocation=\"CenterScreen\"\n        Title=\"{DynamicResource ui.edit_password.title}\" \n        ResizeMode=\"NoResize\"\n        Width=\"380\"\n        MinWidth=\"380\"\n        MaxWidth=\"380\"\n        Height=\"170\"\n        MinHeight=\"170\"\n        MaxHeight=\"170\">\n    <Window.DataContext>\n        <vm:EditPasswordWindowViewModel/>\n    </Window.DataContext>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"*\"/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <ui:TitleBar \n            Icon=\"pack://application:,,,/Resources/Icon.png\"\n            FontSize=\"14\"\n            ShowMaximize=\"False\"\n            ShowMinimize=\"False\"\n            ShowClose=\"False\"\n            Title=\"{DynamicResource ui.edit_password.title}\" Grid.ColumnSpan=\"2\" VerticalAlignment=\"Center\"/>\n        <StackPanel\n            Margin=\"10,-5,10,10\"\n            Grid.Row=\"1\" Grid.ColumnSpan=\"2\">\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.edit_password.text}\"/>\n            <ui:PasswordBox\n                Password=\"{Binding Password,Mode=TwoWay}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"PasswordChanged\">\n                        <i:InvokeCommandAction Command=\"{Binding TextChangeCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:PasswordBox>\n        </StackPanel>\n        <Grid\n            Grid.Row=\"2\"\n            Margin=\"10,0,10,10\" Grid.ColumnSpan=\"2\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"Auto\"/>\n                <ColumnDefinition Width=\"*\"/>\n            </Grid.ColumnDefinitions>\n            <CheckBox\n                Grid.Column=\"0\"\n                IsEnabled=\"False\"\n                IsChecked=\"{Binding Path=IsEmpty,Mode=TwoWay}\"\n                Content=\"{DynamicResource ui.edit_password.is_empty}\"/>\n            <ui:Button\n                Margin=\"2,0,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Grid.Column=\"1\"\n                Appearance=\"Secondary\"\n                Content=\"{DynamicResource text.ok}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"Click\">\n                        <i:InvokeCommandAction Command=\"{Binding SetPasswordCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:Button>\n        </Grid>\n    </Grid>\n</ui:UiWindow>\n"
  },
  {
    "path": "NectarRCON/Windows/EditPasswordWindow.xaml.cs",
    "content": "﻿using NectarRCON.ViewModels;\nusing System.Windows;\n\nnamespace NectarRCON.Windows\n{\n    /// <summary>\n    /// EditPasswordWindow.xaml 的交互逻辑\n    /// </summary>\n    public partial class EditPasswordWindow\n    {\n        public EditPasswordWindow()\n        {\n            InitializeComponent();\n            ((EditPasswordWindowViewModel)this.DataContext).SetWindow(this);\n        }\n\n        private void CancelButtonClick(object sender, RoutedEventArgs e)\n            => Close();\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Windows/EditServerWindow.xaml",
    "content": "﻿<ui:UiWindow x:Class=\"NectarRCON.Windows.EditServerWindow\"\n        xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n        xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n        xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n        xmlns:local=\"clr-namespace:NectarRCON.Windows\"\n        xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n        mc:Ignorable=\"d\"\n        ExtendsContentIntoTitleBar=\"True\"\n        WindowBackdropType=\"Mica\"\n\t\tWindowCornerPreference=\"Round\"\n\t\tWindowStartupLocation=\"CenterScreen\"\n        Title=\"{DynamicResource ui.edit_server_window.title}\" \n        ResizeMode=\"NoResize\"\n        Height=\"290\" \n        MinHeight=\"290\" \n        MaxHeight=\"290\" \n        Width=\"380\"\n        MinWidth=\"380\"\n        MaxWidth=\"380\">\n    <ui:UiWindow.DataContext>\n        <vm:EditServerWindowViewModel/>\n    </ui:UiWindow.DataContext>\n    <i:Interaction.Triggers>\n        <i:EventTrigger\n            EventName=\"Loaded\">\n            <i:InvokeCommandAction\n                PassEventArgsToCommand=\"True\"\n                Command=\"{Binding LoadCommand}\"/>\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\"/>\n            <RowDefinition Height=\"*\"/>\n            <RowDefinition Height=\"Auto\"/>\n        </Grid.RowDefinitions>\n        <ui:TitleBar \n            Icon=\"pack://application:,,,/Resources/Icon.png\"\n            FontSize=\"14\"\n            ShowMaximize=\"False\"\n            ShowMinimize=\"False\"\n            Title=\"{DynamicResource ui.edit_server_window.title}\" Grid.ColumnSpan=\"2\" VerticalAlignment=\"Center\"/>\n        <StackPanel\n            Margin=\"10,-5,10,10\"\n            Grid.Row=\"1\" Grid.ColumnSpan=\"2\">\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.name}\"/>\n            <ui:TextBox\n                IsEnabled=\"False\"\n                Text=\"{Binding SelectServer.Name,Mode=OneWay}\"/>\n\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.address}\"/>\n            <ui:TextBox\n                Text=\"{Binding Address,Mode=TwoWay}\"/>\n\n            <TextBlock\n                Margin=\"0,10,0,2\"\n                Text=\"{DynamicResource ui.add_server_window.port}\"/>\n            <ui:NumberBox\n                Text=\"{Binding Port,Mode=TwoWay}\"\n                Min=\"0\"\n                Max=\"65535\"\n                IntegersOnly=\"True\" Cursor=\"IBeam\"/>\n        </StackPanel>\n        <Grid\n            Grid.Row=\"2\"\n            Margin=\"10,0,10,10\" Grid.ColumnSpan=\"2\">\n            <Grid.ColumnDefinitions>\n                <ColumnDefinition Width=\"*\"/>\n                <ColumnDefinition Width=\"2*\"/>\n            </Grid.ColumnDefinitions>\n            <ui:Button\n                Margin=\"0,0,2,0\"\n                HorizontalAlignment=\"Stretch\"\n                Grid.Column=\"0\"\n                Appearance=\"Transparent\"\n                Content=\"{DynamicResource text.cancel}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"Click\">\n                        <i:InvokeCommandAction\n                            Command=\"{Binding ExitCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:Button>\n            <ui:Button\n                Margin=\"2,0,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Grid.Column=\"1\"\n                Appearance=\"Secondary\"\n                Content=\"{DynamicResource text.ok}\">\n                <i:Interaction.Triggers>\n                    <i:EventTrigger\n                        EventName=\"Click\">\n                        <i:InvokeCommandAction\n                            Command=\"{Binding OkCommand}\"/>\n                    </i:EventTrigger>\n                </i:Interaction.Triggers>\n            </ui:Button>\n        </Grid>\n    </Grid>\n</ui:UiWindow>\n"
  },
  {
    "path": "NectarRCON/Windows/EditServerWindow.xaml.cs",
    "content": "﻿namespace NectarRCON.Windows\n{\n    /// <summary>\n    /// EditServerWindow.xaml 的交互逻辑\n    /// </summary>\n    public partial class EditServerWindow\n    {\n        public EditServerWindow()\n        {\n            InitializeComponent();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Windows/JoinGroupWindow.xaml",
    "content": "﻿<ui:UiWindow\n    x:Class=\"NectarRCON.Windows.JoinGroupWindow\"\n    xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n    xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n    xmlns:c=\"clr-namespace:NectarRCON.Converters\"\n    xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n    xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n    xmlns:local=\"clr-namespace:NectarRCON.Windows\"\n    xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n    xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n    xmlns:vm=\"clr-namespace:NectarRCON.ViewModels\"\n    Title=\"{DynamicResource ui.join_group.title}\"\n    Width=\"460\"\n    Height=\"580\"\n    ExtendsContentIntoTitleBar=\"True\"\n    WindowBackdropType=\"Mica\"\n    WindowCornerPreference=\"Round\"\n    WindowStartupLocation=\"CenterScreen\"\n    mc:Ignorable=\"d\">\n    <ui:UiWindow.DataContext>\n        <vm:JoinGroupWindowViewModel />\n    </ui:UiWindow.DataContext>\n    <ui:UiWindow.Resources>\n        <c:IntToVisibilityConverter x:Key=\"IntToVisibilityConverter\" />\n    </ui:UiWindow.Resources>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <ui:TitleBar\n            Title=\"{DynamicResource ui.join_group.title}\"\n            FontSize=\"14\"\n            Icon=\"pack://application:,,,/Resources/Icon.png\"\n            ShowMaximize=\"False\"\n            ShowMinimize=\"False\" />\n\n        <StackPanel\n            Grid.Row=\"1\"\n            HorizontalAlignment=\"Center\"\n            VerticalAlignment=\"Center\"\n            Visibility=\"{Binding ServerCollectionView.Count, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=False}\">\n            <TextBlock Text=\"{DynamicResource ui.join_group.no_server}\" />\n            <Button\n                x:Name=\"CloseButton\"\n                Margin=\"0,15,0,0\"\n                HorizontalAlignment=\"Stretch\"\n                Click=\"CloseButton_Click\"\n                Content=\"{DynamicResource text.cancel}\" />\n        </StackPanel>\n        <ListView\n            x:Name=\"ServersListView\"\n            Grid.Row=\"1\"\n            ItemsSource=\"{Binding ServerCollectionView}\"\n            SelectionChanged=\"ServersListView_SelectionChanged\"\n            Visibility=\"{Binding ServerCollectionView.Count, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=True}\">\n            <ListView.ItemTemplate>\n                <DataTemplate>\n                    <ui:Card>\n                        <TextBlock Text=\"{Binding}\" />\n                    </ui:Card>\n                </DataTemplate>\n            </ListView.ItemTemplate>\n        </ListView>\n    </Grid>\n</ui:UiWindow>\n"
  },
  {
    "path": "NectarRCON/Windows/JoinGroupWindow.xaml.cs",
    "content": "﻿using NectarRCON.ViewModels;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Data;\nusing System.Windows.Documents;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Imaging;\nusing System.Windows.Shapes;\nusing Wpf.Ui.Controls;\n\nnamespace NectarRCON.Windows\n{\n    /// <summary>\n    /// JoinGroupWindow.xaml 的交互逻辑\n    /// </summary>\n    public partial class JoinGroupWindow : UiWindow\n    {\n        public string? SelectedServer = null;\n        public JoinGroupWindow()\n        {\n            InitializeComponent();\n        }\n        private void CloseButton_Click(object sender, RoutedEventArgs e)\n        {\n            Close();\n        }\n\n        public void AddBlackList(string value)\n        {\n            ((JoinGroupWindowViewModel)DataContext).BlackList?.Add(value);\n            ((JoinGroupWindowViewModel)DataContext).ServerCollectionView?.Refresh();\n        }\n\n        private void ServersListView_SelectionChanged(object sender, SelectionChangedEventArgs e)\n        {\n            if(e.AddedItems.Count > 0)\n            {\n                SelectedServer = e.AddedItems[0]?.ToString();\n                Close();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON/Windows/MainWindow.xaml",
    "content": "﻿<ui:UiWindow x:Class=\"NectarRCON.MainWindow\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n             xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n             xmlns:ui=\"http://schemas.lepo.co/wpfui/2022/xaml\"\n             xmlns:d=\"http://schemas.microsoft.com/expression/blend/2008\"\n             xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\"\n             xmlns:i=\"http://schemas.microsoft.com/xaml/behaviors\"\n             xmlns:local=\"clr-namespace:NectarRCON\"\n             mc:Ignorable=\"d\"\n             Title=\"{DynamicResource ui.window.title}\"\n             ExtendsContentIntoTitleBar=\"True\"\n             WindowBackdropType=\"Mica\"\n             WindowCornerPreference=\"Round\"\n             WindowStartupLocation=\"CenterScreen\"\n             MinHeight=\"690\"\n             MinWidth=\"650\"\n             Height=\"690\"\n             Width=\"650\">\n    <i:Interaction.Triggers>\n        <i:EventTrigger EventName=\"Loaded\">\n            <i:InvokeCommandAction Command=\"{Binding ViewModel.LoadCommand}\" />\n        </i:EventTrigger>\n    </i:Interaction.Triggers>\n    <Grid>\n        <Grid.RowDefinitions>\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"Auto\" />\n            <RowDefinition Height=\"*\" />\n        </Grid.RowDefinitions>\n        <ui:TitleBar\n            Icon=\"pack://application:,,,/Resources/Icon.png\"\n            FontSize=\"14\"\n            Title=\"{DynamicResource ui.window.title}\"\n            Grid.Row=\"0\" />\n        <Menu\n            Grid.Row=\"1\"\n            Focusable=\"False\">\n            <MenuItem\n                Focusable=\"False\"\n                Header=\"{DynamicResource ui.menu.server}\">\n                <MenuItem\n                    Focusable=\"False\"\n                    Header=\"{DynamicResource ui.menu.server_list}\"\n                    CommandParameter=\"2\"\n                    Command=\"{Binding ViewModel.ChangePageCommand}\" />\n\n                <MenuItem\n                    Focusable=\"False\"\n                    Header=\"{DynamicResource ui.menu.groups}\"\n                    CommandParameter=\"4\"\n                    Command=\"{Binding ViewModel.ChangePageCommand}\" />\n            </MenuItem>\n            <MenuItem\n                IsEnabled=\"{Binding ViewModel.MainPageIsLoaded}\"\n                Focusable=\"False\"\n                Header=\"{DynamicResource ui.menu.log}\">\n                <MenuItem\n                    Width=\"120\"\n                    Command=\"{Binding ViewModel.ClearButtonClickCommand}\"\n                    Header=\"{DynamicResource ui.menu.log.clear}\" />\n            </MenuItem>\n            <MenuItem\n                Focusable=\"False\"\n                Header=\"{DynamicResource ui.menu.program}\">\n                <MenuItem\n                    Width=\"120\"\n                    Header=\"{DynamicResource ui.menu.program.settings}\"\n                    CommandParameter=\"1\"\n                    Command=\"{Binding ViewModel.ChangePageCommand}\"/>\n                <MenuItem\n                    Width=\"120\"\n                    Header=\"{DynamicResource ui.menu.program.about}\"\n                    CommandParameter=\"3\"\n                    Command=\"{Binding ViewModel.ChangePageCommand}\"/>\n                <MenuItem\n                    Width=\"120\"\n                    IsEnabled=\"False\"\n                    Header=\"{DynamicResource ui.menu.program.check_update}\" />\n                <MenuItem\n                    Width=\"120\"\n                    Header=\"{DynamicResource ui.menu.log.clear_program}\"\n                    Command=\"{Binding ViewModel.ClearProgramLogsCommand}\"/>\n            </MenuItem>\n        </Menu>\n        <ui:NavigationCompact\n            Visibility=\"Hidden\"\n            Height=\"1\"\n            Width=\"1\"\n            x:Name=\"RootNavigation\"\n            Items=\"{Binding ViewModel.NavigationItems, Mode=TwoWay}\"\n            Frame=\"{Binding ElementName=MainFrame,Mode=OneWay}\" />\n        <Frame\n            x:Name=\"MainFrame\"\n            Grid.Row=\"2\" />\n        <Grid\n            Visibility=\"Hidden\"\n            Grid.Row=\"1\"\n            Grid.RowSpan=\"2\"\n            x:Name=\"ConnectingDialog\">\n            <Grid.Background>\n                <LinearGradientBrush StartPoint=\"0,0\" EndPoint=\"1,1\">\n                    <GradientStop Offset=\"0.0\" Color=\"#CF202020\" />\n                    <GradientStop Offset=\"1.0\" Color=\"#8F202020\" />\n                </LinearGradientBrush>\n            </Grid.Background>\n            <Border\n                CornerRadius=\"5\"\n                Height=\"150\"\n                Width=\"200\">\n                <Border.Background>\n                    <SolidColorBrush Color=\"{DynamicResource SystemFillColorSolidNeutralBackground}\" />\n                </Border.Background>\n                <Border.Effect>\n                    <DropShadowEffect\n                        BlurRadius=\"30\"\n                        Direction=\"0\"\n                        Opacity=\"0.4\"\n                        ShadowDepth=\"0\"\n                        Color=\"#202020\" />\n                </Border.Effect>\n                <StackPanel>\n                    <ui:ProgressRing\n                        Margin=\"0,30,0,0\"\n                        IsIndeterminate=\"True\" />\n                    <TextBlock\n                        Margin=\"0,15,0,0\"\n                        HorizontalAlignment=\"Center\"\n                        FontSize=\"16\"\n                        Text=\"{DynamicResource text.server.connect.ing}\" />\n                </StackPanel>\n            </Border>\n        </Grid>\n    </Grid>\n</ui:UiWindow>"
  },
  {
    "path": "NectarRCON/Windows/MainWindow.xaml.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.ViewModels;\nusing System;\nusing System.Windows.Controls;\nusing Wpf.Ui.Common.Interfaces;\nusing Wpf.Ui.Controls.Interfaces;\nusing Wpf.Ui.Mvvm.Contracts;\n\nnamespace NectarRCON\n{\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml\n    /// </summary>\n    public partial class MainWindow : INavigationWindow, INavigableView<MainWindowViewModel>\n    {\n        private readonly INavigationService _navigationService;\n        public MainWindowViewModel ViewModel\n        {\n            get;\n        }\n\n        MainWindowViewModel INavigableView<MainWindowViewModel>.ViewModel => ViewModel;\n        public MainWindow(INavigationService navigationService, MainWindowViewModel viewModel, IConnectingDialogService connectingDialogService)\n        {\n            InitializeComponent();\n            DataContext = this;\n            _navigationService = navigationService;\n            _navigationService.SetNavigationControl(RootNavigation);\n            ViewModel = viewModel;\n            connectingDialogService.SetDialog(ConnectingDialog);\n        }\n        #region INavigationWindow methods\n        public Frame GetFrame()\n            => MainFrame;\n\n        public INavigation GetNavigation()\n            => RootNavigation;\n\n        public bool Navigate(Type pageType)\n            => RootNavigation.Navigate(pageType);\n\n        public void SetPageService(IPageService pageService)\n            => RootNavigation.PageService = pageService;\n\n        public void ShowWindow()\n            => Show();\n\n        public void CloseWindow()\n            => Close();\n        #endregion\n    }\n}\n"
  },
  {
    "path": "NectarRCON/app.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n  <assemblyIdentity version=\"1.0.0.0\" name=\"MyApplication.app\"/>\n  <trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v2\">\n    <security>\n      <requestedPrivileges xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n        <!-- UAC 清单选项\n             如果想要更改 Windows 用户帐户控制级别，请使用\n             以下节点之一替换 requestedExecutionLevel 节点。\n\n        <requestedExecutionLevel  level=\"asInvoker\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"requireAdministrator\" uiAccess=\"false\" />\n        <requestedExecutionLevel  level=\"highestAvailable\" uiAccess=\"false\" />\n\n            指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。\n            如果你的应用程序需要此虚拟化来实现向后兼容性，则移除此\n            元素。\n        -->\n        <requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />\n      </requestedPrivileges>\n    </security>\n  </trustInfo>\n\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的\n           Windows 版本的列表。取消评论适当的元素，\n           Windows 将自动选择最兼容的环境。 -->\n\n      <!-- Windows Vista -->\n      <!--<supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" />-->\n\n      <!-- Windows 7 -->\n      <!--<supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" />-->\n\n      <!-- Windows 8 -->\n      <!--<supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" />-->\n\n      <!-- Windows 8.1 -->\n      <!--<supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" />-->\n\n      <!-- Windows 10 -->\n      <!--<supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" />-->\n\n    </application>\n  </compatibility>\n\n  <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行\n       自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI，无需\n       选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应\n       在其 app.config 中将 \"EnableWindowsFormsHighDpiAutoResizing\" 设置设置为 \"true\"。\n       \n       将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->\n  <!--\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <windowsSettings>\n      <dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true</dpiAware>\n      <longPathAware xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">true</longPathAware>\n    </windowsSettings>\n  </application>\n  -->\n\n  <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->\n  <!--\n  <dependency>\n    <dependentAssembly>\n      <assemblyIdentity\n          type=\"win32\"\n          name=\"Microsoft.Windows.Common-Controls\"\n          version=\"6.0.0.0\"\n          processorArchitecture=\"*\"\n          publicKeyToken=\"6595b64144ccf1df\"\n          language=\"*\"\n        />\n    </dependentAssembly>\n  </dependency>\n  -->\n\n</assembly>\n"
  },
  {
    "path": "NectarRCON.Adapter.Minecraft/MinecraftRconClient.cs",
    "content": "﻿using NectarRCON.Export.Client;\nusing NectarRCON.Export.Interfaces;\nusing System.ComponentModel;\nusing System.Text;\n\nnamespace NectarRCON.Adapter.Minecraft\n{\n    [Description(\"rcon.minecraft\")]\n    public class MinecraftRconClient : BaseTcpClient, IRconAdapter\n    {\n        // https://wiki.vg/RCON #Fragmentation\n        private static readonly int MaxMessageSize = 4110;\n\n        private readonly MemoryStream _buffer = new();\n        private readonly SemaphoreSlim _semaphore = new(1);\n        private int _lastId;\n        /// <summary>\n        /// 编码\n        /// 默认编码 UTF8\n        /// </summary>\n        private new Encoding _encoding = Encoding.UTF8;\n\n        public void Disconnect()\n        {\n            _semaphore.Wait();\n            try\n            {\n                Stop();\n            }\n            finally\n            {\n                _semaphore.Release();\n            }\n        }\n\n        public override void Dispose()\n        {\n            _semaphore.Dispose();\n            _buffer.Dispose();\n            base.Dispose();\n        }\n\n        public override byte[] Read(StreamReader reader)\n        {\n            byte[] buffer = new byte[MaxMessageSize];\n            int len = reader.BaseStream.Read(buffer, 0, buffer.Length);\n            Array.Resize(ref buffer, len);\n            return buffer;\n        }\n\n        public string Run(string command)\n        {\n            _semaphore.Wait();\n            try\n            {\n                Packet response = Send(new Packet(PacketType.Command, command));\n                return response.Body;\n            }\n            finally\n            {\n                _semaphore.Release();\n            }\n        }\n\n        public Encoding GetEncoding()\n            => _encoding;\n\n        public void SetEncoding(Encoding encoding)\n        {\n            _encoding = encoding;\n        }\n\n        public bool Connect(string address, int port)\n        {\n            _semaphore.Wait();\n            try\n            {\n                Start(address, port);\n                return IsConnected;\n            }\n            finally\n            {\n                _semaphore.Release();\n            }\n        }\n\n        public bool Authenticate(string password)\n        {\n            _semaphore.Wait();\n            try\n            {\n                Packet packet = Send(new Packet(PacketType.Authenticate, password));\n                return packet.Id == _lastId;\n            }\n            finally\n            {\n                _semaphore.Release();\n            }\n        }\n\n        private Packet Send(Packet packet)\n        {\n            Interlocked.Increment(ref _lastId);\n            packet.SetId(_lastId);\n            return PacketEncoder.Decode(Send(packet.Encode(_encoding)), _encoding);\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON.Adapter.Minecraft/NectarRCON.Adapter.Minecraft.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NectarRCON.Export\\NectarRCON.Export.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON.Adapter.Minecraft/Packet.cs",
    "content": "﻿using System.Text;\n\nnamespace NectarRCON.Adapter.Minecraft;\npublic class Packet\n{\n    public readonly int Length;\n    public int Id;\n    public readonly PacketType Type;\n    public readonly string Body;\n\n    public Packet(int length, int id, PacketType type, string body)\n    {\n        Length = length;\n        Id = id;\n        Type = type;\n        Body = body;\n    }\n\n    public Packet(PacketType type, string body)\n    {\n        Type = type;\n        Body = body;\n    }\n\n    public void SetId(int id)\n    {\n        Id = id;\n    }\n\n    public byte[] Encode(Encoding? encoding = null)\n    {\n        List<byte> bytes = new List<byte>();\n        var data = (encoding?? Encoding.UTF8).GetBytes(Body);\n        bytes.AddRange(BitConverter.GetBytes(PacketEncoder.HeaderLength + data.Length));\n        bytes.AddRange(BitConverter.GetBytes(Id));\n        bytes.AddRange(BitConverter.GetBytes((int)Type));\n        bytes.AddRange(data);\n        bytes.AddRange(new byte[] { 0, 0 });\n        return bytes.ToArray();\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Adapter.Minecraft/PacketEncoder.cs",
    "content": "﻿using System.Text;\n\nnamespace NectarRCON.Adapter.Minecraft\n{\n    public enum PacketType : int\n    {\n        Response, // 0: Command response\n        _,\n        Command, // 2: Command\n        Authenticate // 3: Login\n    }\n\n    public abstract class PacketEncoder\n    {\n        public const int HeaderLength = 10;\n\n        public static byte[] Encode(Packet msg, Encoding? encoding = null)\n        {\n            List<byte> bytes = new List<byte>();\n\n            bytes.AddRange(BitConverter.GetBytes(msg.Length));\n            bytes.AddRange(BitConverter.GetBytes(msg.Id));\n            bytes.AddRange(BitConverter.GetBytes((int)msg.Type));\n            bytes.AddRange((encoding??Encoding.UTF8).GetBytes(msg.Body));\n            bytes.AddRange(new byte[] { 0, 0 });\n\n            return bytes.ToArray();\n        }\n\n        public static Packet Decode(byte[] bytes, Encoding? encoding = null)\n        {\n            if (bytes.Length < HeaderLength) { throw new ArgumentException(\"packet length too short\"); }\n            int len = BitConverter.ToInt32(bytes, 0);\n            int id = BitConverter.ToInt32(bytes, 4);\n            int type = BitConverter.ToInt32(bytes, 8);\n            int bodyLen = bytes.Length - (HeaderLength + 4);\n            string body = string.Empty;\n            if (bodyLen > 0)\n            {\n                body = (encoding??Encoding.UTF8).GetString(bytes, 12, bodyLen);\n            }\n            return new Packet(len, id, (PacketType)type, body);\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Adapter.Minecraft/README.md",
    "content": "﻿# Adapter\n\nMinecraftRcon实现\n"
  },
  {
    "path": "NectarRCON.Core/Helper/AdapterHelpers.cs",
    "content": "﻿using NectarRCON.Adapter.Minecraft;\nusing NectarRCON.Export.Interfaces;\nnamespace NectarRCON.Core.Helper\n{\n    public static class AdapterHelpers\n    {\n        public static IRconAdapter? CreateAdapterInstance(string adapter)\n        {\n            return new MinecraftRconClient(); // 暂时这么写\n        }\n    }\n}"
  },
  {
    "path": "NectarRCON.Core/Helper/DNSHelpers.cs",
    "content": "﻿using DnsClient;\n\nnamespace NectarRCON.Core.Helper\n{\n    public class DNSHelpers\n    {\n        public static string AQuery(string host)\n        {\n            var lookup = new LookupClient();\n            var result = lookup.Query(host, QueryType.A);\n            var record = result.Answers.ARecords().FirstOrDefault();\n            return record?.Address.ToString() ?? string.Empty;\n        }\n\n        /// <summary>\n        /// MinecraftSRV解析\n        /// </summary>\n        /// <returns>成功返回ip:port</returns>\n        public static string SRVQuery(string host)\n        {\n            // Minecraft特有的srv\n            string srvAddress = $\"_minecraft._tcp.{host}\";\n            var lookup = new LookupClient();\n            var result = lookup.Query(srvAddress, QueryType.SRV);\n            var record = result.Answers.SrvRecords().FirstOrDefault();\n            if (record != null)\n            {\n                return $\"{record.Target.Value}:{record.Port}\";\n            }\n            return string.Empty;\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Core/Helper/Win32Helper.cs",
    "content": "﻿namespace NectarRCON.Core.Helper;\npublic static class Win32Helper\n{\n    public static bool GetWindowsTheme()\n    {\n        const string RegistryKeyPath = @\"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\";\n        const string RegistryValueName = \"AppsUseLightTheme\";\n        // 这里也可能是LocalMachine(HKEY_LOCAL_MACHINE)\n        // see \"https://www.addictivetips.com/windows-tips/how-to-enable-the-dark-theme-in-windows-10/\"\n        object? registryValueObject = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegistryKeyPath)?.GetValue(RegistryValueName);\n        if (registryValueObject is null) return false;\n        return (int)registryValueObject > 0 ? false : true;\n    }\n}"
  },
  {
    "path": "NectarRCON.Core/NectarRCON.Core.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"DnsClient\" Version=\"1.7.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NectarRCON.Adapter.Minecraft\\NectarRCON.Adapter.Minecraft.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON.Export\\NectarRCON.Export.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON.Export/Client/BaseTcpClient.cs",
    "content": "﻿using NectarRCON.Export.Interfaces;\nusing System.Net.Sockets;\nusing System.Text;\n\nnamespace NectarRCON.Export.Client;\n/// <summary>\n/// 简单的TCP客户端实现\n/// </summary>\npublic abstract class BaseTcpClient : IRconClient\n{\n    private TcpClient? _tcpClient;\n    private NetworkStream? _networkStream;\n    private StreamReader? _reader;\n\n    protected Encoding? _encoding;\n\n    public bool IsConnected { get; private set; }\n\n    public virtual void Dispose()\n    {\n        Stop();\n    }\n\n    public byte[] Send(byte[] bytes)\n    {\n        using MemoryStream response = new();\n        if (IsConnected)\n        {\n            _networkStream!.Write(bytes, 0, bytes.Length);\n            return Read(_reader!);\n        }\n        return response.ToArray();\n    }\n\n    public abstract byte[] Read(StreamReader reader);\n\n    public void Start(string address, int port)\n    {\n        if (IsConnected)\n        {\n            throw new InvalidOperationException(\"Client is already connected\");\n        }\n\n        try\n        {\n            _tcpClient = new(address, port);\n            _networkStream = _tcpClient.GetStream();\n            _reader = new StreamReader(_networkStream, _encoding ?? Encoding.UTF8);\n            IsConnected = true;\n        }\n        catch\n        {\n            throw;\n        }\n    }\n\n    public void Stop()\n    {\n        if (!IsConnected)\n        {\n            return;\n        }\n        _tcpClient?.Dispose();\n        _reader?.Dispose();\n        IsConnected = false;\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Export/Interfaces/IRconAdapter.cs",
    "content": "﻿using System.Text;\n\nnamespace NectarRCON.Export.Interfaces;\n/// <summary>\n/// Rcon协议兼容接口\n/// </summary>\npublic interface IRconAdapter : IDisposable\n{\n    /// <summary>\n    /// 已经连接\n    /// </summary>\n    bool IsConnected { get; }\n\n    /// <summary>\n    /// 连接\n    /// </summary>\n    bool Connect(string address, int port);\n\n\n    /// <summary>\n    /// 验证\n    /// </summary>\n    bool Authenticate(string password);\n\n    /// <summary>\n    /// 断开连接\n    /// </summary>\n    void Disconnect();\n\n    /// <summary>\n    /// 执行命令\n    /// </summary>\n\n    string Run(string command);\n    \n    Encoding GetEncoding();\n    void SetEncoding(Encoding encoding);\n}\n"
  },
  {
    "path": "NectarRCON.Export/Interfaces/IRconClient.cs",
    "content": "﻿namespace NectarRCON.Export.Interfaces;\n/// <summary>\n/// Rcon客户端接口\n/// </summary>\npublic interface IRconClient : IDisposable\n{\n    /// <summary>\n    /// 启动连接\n    /// </summary>\n    /// <param name=\"address\">\n    /// 地址\n    /// <code>\n    /// localhost:11451\n    /// mc.xxxx.net:11451\n    /// </code>\n    /// </param>\n    /// <returns></returns>\n    void Start(string address, int port);\n    /// <summary>\n    /// 关闭连接\n    /// </summary>\n    void Stop();\n    /// <summary>\n    /// 执行命令\n    /// </summary>\n    /// <param name=\"bytes\">原始字节</param>\n    /// <returns>服务端返回</returns>\n    byte[] Send(byte[] bytes);\n    /// <summary>\n    /// 是否已连接\n    /// </summary>\n    bool IsConnected { get; }\n}\n"
  },
  {
    "path": "NectarRCON.Export/NectarRCON.Export.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NectarRCON.Updater\\NectarRCON.Updater.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON.Tests/Adapter/MinecraftRconTest.cs",
    "content": "﻿using NectarRCON.Adapter.Minecraft;\nusing NectarRCON.Export.Interfaces;\n\nnamespace NectarRCON.Tests.Adapter\n{\n    [TestClass]\n    public class MinecraftRconTest\n    {\n        private static readonly IRconAdapter _client = new MinecraftRconClient();\n        private bool _isAuthenticated = false;\n\n        /// <summary>\n        /// 此单元测试请自行搭建服务器测试(没必要=3=)\n        /// </summary>\n\n        private void connectAndAuthenticate()\n        {\n            if (!_client.IsConnected)\n            {\n                _client.Connect(\"127.0.0.1\", 25575);\n                _isAuthenticated = _client.Authenticate(\"123\");\n            }\n        }\n\n        //[TestMethod]\n        public void Connect()\n        {\n            connectAndAuthenticate();\n            Assert.IsTrue(_client.IsConnected);\n        }\n\n        //[TestMethod]\n        public void Authenticate()\n        {\n            connectAndAuthenticate();\n            Assert.IsTrue(_isAuthenticated);\n        }\n\n        //[TestMethod]\n        public void Run()\n        {\n            connectAndAuthenticate();\n            string response = _client.Run(\"list\");\n            Console.WriteLine(response);\n            Assert.IsTrue(response != string.Empty);\n\n            _client.Run(\"say 你好\"); // UTF-8 测试\n        }\n\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Tests/DNSHelperTests.cs",
    "content": "﻿using NectarRCON.Core.Helper;\nnamespace NectarRCON.Tests\n{\n    [TestClass]\n    public class DNSHelperTests\n    {\n        /// <summary>\n        /// A解析测试\n        /// </summary>\n        [TestMethod]\n        public void ATest()\n        {\n            string? ip = DNSHelpers.AQuery(\"hypixel.net\");\n            Console.WriteLine(ip);\n            Assert.IsNotNull(string.IsNullOrEmpty(ip));\n        }\n\n        /// <summary>\n        /// SRV解析测试\n        /// </summary>\n        [TestMethod]\n        public void SRVTest()\n        {\n            string? ip = DNSHelpers.SRVQuery(\"mc.125nf.com\");\n            Console.WriteLine(ip);\n            Assert.IsFalse(string.IsNullOrEmpty(ip));\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Tests/GroupServiceTests.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Models;\nusing NectarRCON.Services;\nusing System.Windows;\n\nnamespace NectarRCON.Tests\n{\n    [TestClass]\n    public class GroupServiceTests\n    {\n        private class MyLanguageService : ILanguageService\n        {\n            public string GetKey(string key)\n            {\n                return key;\n            }\n\n            public Dictionary<string, ResourceDictionary> GetLanguages()\n            {\n                throw new NotImplementedException();\n            }\n\n            public ResourceDictionary GetSelectedLanguage()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void Refresh()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void SelectLanguage()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void SelectLanguage(string languageName, bool name)\n            {\n                throw new NotImplementedException();\n            }\n        }\n        private readonly IGroupService _groupService = new GroupService(new MessageBoxService(new MyLanguageService()), new MyLanguageService());\n\n        [TestMethod]\n        public void Dump()\n        {\n            _groupService.GetGroups();\n        }\n\n        [TestMethod]\n        public void TestAll()\n        {\n            Group group = new Group()\n            {\n                Id = Guid.NewGuid().ToString(),\n                Name = \"Test\",\n                Servers = new(),\n            };\n            _groupService.Add(group);\n            Assert.ThrowsException<InvalidOperationException>(() =>\n            {\n                _groupService.Add(group); // 相同的Id 和Name\n            });\n            Assert.IsTrue(File.Exists(\"./groups/\" + group.Id + \".json\"));\n            _groupService.Delete(group.Id);\n            Assert.IsFalse(File.Exists(\"./groups/\" + group.Id + \".json\"));\n            Assert.IsNull(_groupService.FindGroup(group.Name));\n            Assert.IsNull(_groupService.GetGroup(group.Id));\n            Assert.IsTrue(_groupService.GetGroups().Count == 0);\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Tests/MessageBoxServiceTests.cs",
    "content": "﻿using NectarRCON.Interfaces;\nusing NectarRCON.Services;\nusing System.Windows;\n\nnamespace NectarRCON.Tests\n{\n    [TestClass]\n    public class MessageBoxServiceTests\n    {\n        private class MyLanguageService : ILanguageService\n        {\n            public string GetKey(string key)\n            {\n                return key;\n            }\n\n            public Dictionary<string, ResourceDictionary> GetLanguages()\n            {\n                throw new NotImplementedException();\n            }\n\n            public ResourceDictionary GetSelectedLanguage()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void Refresh()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void SelectLanguage()\n            {\n                throw new NotImplementedException();\n            }\n\n            public void SelectLanguage(string languageName, bool name)\n            {\n                throw new NotImplementedException();\n            }\n        }\n\n        private readonly IMessageBoxService _service = new MessageBoxService(new MyLanguageService());\n        [TestMethod]\n        public void ExceptionTest()\n        {\n            /// 我为什么要把这东西写进单元测试啊!!!!\n            //_service.Show(new Exception(\"测试异常\"), \"我故意哒!\");\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Tests/NectarRCON.Tests.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n\n    <IsPackable>false</IsPackable>\n    <IsTestProject>true</IsTestProject>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.5.0\" />\n    <PackageReference Include=\"MSTest.TestAdapter\" Version=\"2.2.10\" />\n    <PackageReference Include=\"MSTest.TestFramework\" Version=\"2.2.10\" />\n    <PackageReference Include=\"coverlet.collector\" Version=\"3.2.0\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\NectarRCON.Adapter.Minecraft\\NectarRCON.Adapter.Minecraft.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON.Core\\NectarRCON.Core.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON.Export\\NectarRCON.Export.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON.Updater\\NectarRCON.Updater.csproj\" />\n    <ProjectReference Include=\"..\\NectarRCON\\NectarRCON.csproj\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON.Tests/UpdaterTests.cs",
    "content": "﻿using NectarRCON.Updater;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace NectarRCON.Tests\n{\n    [TestClass]\n    public class UpdaterTests\n    {\n        [TestMethod]\n        public void Github()\n        {\n            IUpdater updater = new GithubUpdater();\n            updater.SetVersion(\"NectarRcon-x86-1.0.0\");\n            updater.IsLatestVersion();\n        }\n\n        [TestMethod]\n        public void AppVersionTest()\n        {\n            AppVersion versionA = AppVersion.ParseVersion(\"TestApp-x64-1.0.0-beta1\");\n            AppVersion versionB = AppVersion.ParseVersion(\"TestApp-x64-1.0.0-beta2\");\n\n            Assert.IsTrue(versionA.Equals(versionA));\n            Assert.IsFalse(versionA.Equals(versionB));\n\n#pragma warning disable CS1718 // 对同一变量进行了比较\n            Assert.IsTrue(versionA == versionA);\n            Assert.IsFalse(versionA != versionA);\n            Assert.IsFalse(versionA > versionA);\n#pragma warning restore CS1718 // 对同一变量进行了比较\n\n            Assert.IsTrue(versionB > versionA);\n            Assert.IsFalse(versionB < versionA);\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Tests/Usings.cs",
    "content": "global using Microsoft.VisualStudio.TestTools.UnitTesting;"
  },
  {
    "path": "NectarRCON.Updater/AppVersion.cs",
    "content": "﻿using System.Runtime.CompilerServices;\nusing System.Text.RegularExpressions;\n\nnamespace NectarRCON.Updater\n{\n    public class AppVersion\n    {\n        public string AppName { get; set; } = string.Empty;\n        public int Version { get; set; }\n        public int Major { get;set; }\n        public int Minor { get;set; }\n        public int Patch { get;set; }\n        public int? Build { get; set; }\n        public string PreReleaseType { get; set; } = string.Empty;\n        public string Platform { get; set; } = string.Empty;\n        public bool IsPreRelease\n            => !string.IsNullOrEmpty(PreReleaseType);\n\n        public override string ToString()\n        {\n            return $\"{AppName}-{Platform}-{Major}.{Minor}.{Patch}\" + (IsPreRelease ? $\"-{PreReleaseType}{Build}\" : string.Empty);\n        }\n\n        public override bool Equals(object? obj)\n        {\n            return obj?.ToString() == ToString();\n        }\n\n        public static bool operator <(AppVersion a, AppVersion b)\n        {\n            return a.Version < b.Version || (a.Build ?? 0) < (b.Build ?? 0);\n        }\n\n        public static bool operator >(AppVersion a, AppVersion b)\n        {\n            return a.Version > b.Version || (a.Build ?? 0) > (b.Build ?? 0);\n        }\n\n        public static bool operator ==(AppVersion a, AppVersion b)\n        {\n            return a.Version == b.Version && (a.Build ?? 0) == (b.Build ?? 0);\n        }\n\n        public static bool operator !=(AppVersion a, AppVersion b)\n        {\n            return a.Version != b.Version || (a.Build ?? 0) != (b.Build ?? 0);\n        }\n\n        private AppVersion() { }\n\n        public static AppVersion ParseVersion(string version)\n        {\n            string[] versionParts = version.Split(\"-\");\n            if (versionParts.Length > 2)\n            {\n                AppVersion result = new();\n                string name = versionParts[0];\n                string platform = versionParts[1];\n                string ver = versionParts[2];\n                string preRelease = string.Empty;\n\n                if (versionParts.Length > 3)\n                {\n                    preRelease = versionParts[3];\n                }\n\n                Regex versionRegex = new(@\"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)\");\n                Match versionMatch = versionRegex.Match(ver);\n\n                if (versionMatch.Success)\n                {\n                    result.Version = int.Parse(versionMatch.Groups[\"major\"].Value + versionMatch.Groups[\"minor\"].Value + versionMatch.Groups[\"patch\"].Value);\n                    result.Major = int.Parse(versionMatch.Groups[\"major\"].Value);\n                    result.Minor = int.Parse(versionMatch.Groups[\"minor\"].Value);\n                    result.Patch = int.Parse(versionMatch.Groups[\"patch\"].Value);\n                }\n\n                Regex preReleaseRegex = new(@\"(?<preRelease>[a-zA-Z]+)(?<build>\\d+)\");\n                Match preReleaseMatch = preReleaseRegex.Match(preRelease);\n\n                if (preReleaseMatch.Success)\n                {\n                    if (preReleaseMatch.Groups[\"build\"].Success)\n                    {\n                        result.Build = int.Parse(preReleaseMatch.Groups[\"build\"].Value);\n                    }\n                    if (preReleaseMatch.Groups[\"preRelease\"].Success)\n                    {\n                        result.PreReleaseType = preReleaseMatch.Groups[\"preRelease\"].Value;\n                    }\n                }\n\n                result.Platform = platform;\n                result.AppName = name;\n                return result;\n            }\n            throw new InvalidOperationException(\"Invalid version format\");\n        }\n\n        public override int GetHashCode()\n        {\n            return RuntimeHelpers.GetHashCode(ToString());\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Updater/GithubUpdater.cs",
    "content": "﻿using NectarRCON.Updater.Model;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.Json;\nusing System.Threading.Tasks;\n\nnamespace NectarRCON.Updater\n{\n    public class GithubUpdater : IUpdater\n    {\n        private static readonly HttpClient _client = new()\n        {\n            BaseAddress = new Uri(\"https://api.github.com/repos/zkhssb/NectarRcon/\")\n        };\n        private bool _preEnable = false;\n        private AppVersion? _version;\n\n        /// <summary>\n        /// 获取最新版本, null为没找到\n        /// </summary>\n        /// <param name=\"enablePre\">是否允许pre版本</param>\n        private AppVersion? GetLatestVersion(bool enablePre)\n        {\n            if (_version is null)\n                return null;\n            using(HttpRequestMessage request = new(HttpMethod.Get, \"releases/latest\"))\n            {\n                request.Headers.Add(\"User-Agent\", $\"{_version.AppName}-AppUpdater\");\n                using(HttpResponseMessage response = _client.Send(request))\n                {\n                    if (!response.IsSuccessStatusCode)\n                        throw new HttpRequestException(response.StatusCode.ToString());\n                    string resultString = string.Empty;\n                    Task.Run(async () =>\n                    {\n                        resultString = await response.Content.ReadAsStringAsync();\n                    }).Wait();\n                    Release release = JsonSerializer.Deserialize<Release>(resultString) ?? throw new JsonException();\n                    foreach(var asset in release.Assets)\n                    {\n                        string fileName = Path.GetFileNameWithoutExtension(asset.Name);\n                        try\n                        {\n                            fileName = \"NectarRcon-x86-1.0.0-beta2\";\n                            AppVersion version = AppVersion.ParseVersion(fileName);\n                            if(version.AppName.ToLower() == _version.AppName.ToLower() && version.Platform.ToLower() == _version.Platform.ToLower())\n                            {\n                                if (version.IsPreRelease && !enablePre)\n                                    continue;\n                                if (version > _version)\n                                {\n                                    return version;\n                                }\n                            }\n                        }\n                        catch (InvalidOperationException) { } // Invalid version format\n                    }\n                    return null;\n                }\n            }\n        }\n\n        public bool IsLatestVersion()\n        {\n            GetLatestVersion(_preEnable);\n            return true;\n        }\n\n        public void Setup()\n        {\n            throw new NotImplementedException();\n        }\n\n        public void SetVersion(string version)\n        {\n            _version = AppVersion.ParseVersion(version);\n        }\n\n        public void SetPreEnable(bool value)\n        {\n            _preEnable = value;\n        }\n\n        public AppVersion GetLatestVersion()\n        {\n            throw new NotImplementedException();\n        }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Updater/IUpdater.cs",
    "content": "﻿namespace NectarRCON.Updater\n{\n    public interface IUpdater\n    {\n        /// <summary>\n        /// 设置版本\n        /// </summary>\n        void SetVersion(string version);\n        /// <summary>\n        /// 是最新版\n        /// </summary>\n        bool IsLatestVersion();\n        /// <summary>\n        /// 获取最新版本\n        /// </summary>\n        AppVersion GetLatestVersion();\n        /// <summary>\n        /// 开始安装\n        /// </summary>\n        void Setup();\n        /// <summary>\n        /// 设置是否启用获取预发布版本更新\n        /// </summary>\n        void SetPreEnable(bool value);\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Updater/Model/Asset.cs",
    "content": "﻿using System.Text.Json.Serialization;\n\nnamespace NectarRCON.Updater.Model\n{\n    public class Asset\n    {\n        [JsonPropertyName(\"name\")]\n        public required string Name { get; set; }\n        [JsonPropertyName(\"url\")]\n        public required string Url { get; set; }\n        [JsonPropertyName(\"created_at\")]\n        public required DateTime CreatedAt { get; set; }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Updater/Model/Release.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Text.Json.Serialization;\nusing System.Threading.Tasks;\n\nnamespace NectarRCON.Updater.Model\n{\n    public class Release\n    {\n        [JsonPropertyName(\"tag_name\")]\n        public required string TagName { get; set; }\n\n        [JsonPropertyName(\"name\")]\n        public required string Name { get; set; }\n\n        [JsonPropertyName(\"created_at\")]\n        public required DateTime CreatedAt { get; set; }\n\n        [JsonPropertyName(\"assets\")]\n        public required IEnumerable<Asset> Assets { get; set; }\n\n        [JsonPropertyName(\"body\")]\n        public required string Body { get; set; }\n    }\n}\n"
  },
  {
    "path": "NectarRCON.Updater/NectarRCON.Updater.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net7.0-windows</TargetFramework>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <Nullable>enable</Nullable>\n  </PropertyGroup>\n\n</Project>\n"
  },
  {
    "path": "NectarRCON.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.4.33205.214\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NectarRCON\", \"NectarRCON\\NectarRCON.csproj\", \"{66C6A8ED-0D8D-43E9-86CD-03E823468761}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NectarRCON.Tests\", \"NectarRCON.Tests\\NectarRCON.Tests.csproj\", \"{3363EC0E-04D8-4A7C-9A8E-ABF42B6FE431}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NectarRCON.Export\", \"NectarRCON.Export\\NectarRCON.Export.csproj\", \"{49A2E218-1DED-4DEF-BD97-CCDE90C55630}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NectarRCON.Adapter.Minecraft\", \"NectarRCON.Adapter.Minecraft\\NectarRCON.Adapter.Minecraft.csproj\", \"{D4B97850-FF59-4AA1-A19F-2C22F80A8B20}\"\nEndProject\nProject(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"NectarRCON.Core\", \"NectarRCON.Core\\NectarRCON.Core.csproj\", \"{8C15668B-69F3-4138-BCE6-0BB6A65F3B2F}\"\nEndProject\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"NectarRCON.Updater\", \"NectarRCON.Updater\\NectarRCON.Updater.csproj\", \"{D6C910A7-3590-492B-9CA1-C3D586FF2C41}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{66C6A8ED-0D8D-43E9-86CD-03E823468761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{66C6A8ED-0D8D-43E9-86CD-03E823468761}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{66C6A8ED-0D8D-43E9-86CD-03E823468761}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{66C6A8ED-0D8D-43E9-86CD-03E823468761}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{3363EC0E-04D8-4A7C-9A8E-ABF42B6FE431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{3363EC0E-04D8-4A7C-9A8E-ABF42B6FE431}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{3363EC0E-04D8-4A7C-9A8E-ABF42B6FE431}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{3363EC0E-04D8-4A7C-9A8E-ABF42B6FE431}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{49A2E218-1DED-4DEF-BD97-CCDE90C55630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{49A2E218-1DED-4DEF-BD97-CCDE90C55630}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{49A2E218-1DED-4DEF-BD97-CCDE90C55630}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{49A2E218-1DED-4DEF-BD97-CCDE90C55630}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D4B97850-FF59-4AA1-A19F-2C22F80A8B20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D4B97850-FF59-4AA1-A19F-2C22F80A8B20}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D4B97850-FF59-4AA1-A19F-2C22F80A8B20}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D4B97850-FF59-4AA1-A19F-2C22F80A8B20}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{8C15668B-69F3-4138-BCE6-0BB6A65F3B2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{8C15668B-69F3-4138-BCE6-0BB6A65F3B2F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{8C15668B-69F3-4138-BCE6-0BB6A65F3B2F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{8C15668B-69F3-4138-BCE6-0BB6A65F3B2F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D6C910A7-3590-492B-9CA1-C3D586FF2C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D6C910A7-3590-492B-9CA1-C3D586FF2C41}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D6C910A7-3590-492B-9CA1-C3D586FF2C41}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D6C910A7-3590-492B-9CA1-C3D586FF2C41}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {459208B6-E3FC-4AE1-B1D4-DCE3E0C7DC6E}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "README.md",
    "content": "\n<h1 align=\"center\">\n<img src=\"NectarRCON/Resources/Icon.png\" style=\"image-rendering: optimizeSpeed;image-rendering: -moz-crisp-edges;image-rendering: -o-crisp-edges;image-rendering: -webkit-optimize-contrast;image-rendering: pixelated;-ms-interpolation-mode: nearest-neighbor;width: 150px; height: 150px;\"/>\n<br />\nNectarRCON\n<br />\n<h4 align=\"center\">\n    使用\n    WPF\n    开发的Rcon管理器\n</h4>\n<h5 align=\"center\">\n♥ 此项目长期维护,如有任何BUG或奇妙的想法都可以提交Issue!\n</h5>\n<h4 align=\"center\">\n​ <a href=\"https://mit-license.org/\">\n​ <img src=\"https://img.shields.io/github/license/zkhssb/NectarRCON?style=flat-square&color=blueviolet\"\n​ alt=\"license\" />\n​ </a>\n​ <img src=\"https://img.shields.io/badge/.NET-7.0-blue\" />\n​ <a href=\"https://bot.q.qq.com/wiki/develop/api/\">\n​ </a>\n​ </h4>\n<h5 align=\"center\">\n<a href=\"README_EN.md\">English</a>|<a href=\"README.md\">中文</a>\n</h5>\n​ </h1>\n\n## 支持的游戏\n\n- 和Minecraft使用同一个Rcon协议的游戏或应用\n\n## 能力\n\n- 连接单个服务器\n- 分组服务器 (将命令广播到组内的所有服务器)\n- 多语言支持(目前内部支持中文和英文,支持自定义其他语言)\n- 支持解析SRV服务器地址\n- 内置两种主题(这不算能力吧喂!!!)\n\n## 后续想法\n\n  没错!!! 我们的项目正在蒸蒸日上(bushi)\n\n- 其他Rcon协议支持 (最优先)\n- 日志部分重构\n- 自动更新支持 (有想法)\n- 自动脚本执行 (有想法但暂无需求)\n- 基于Rcon的插件 (有需求但懒)\n\n## 关于本项目\n\n学习 WPF 时突发奇想做的一个工具，代码可能不太美观（各种乱七八糟，一锅乱炖的写法）\n\n## 项目依赖\n\n| 名字                      | 地址                            |\n| ------------------------- | ------------------------------- |\n| WPF-UI (作者已废弃的版本) | https://github.com/lepoco/wpfui |\n\n## 部分截图\n\n<details>\n  <summary>点击展开</summary>\n主页面 / Home Page\n\n![](https://s2.loli.net/2023/01/03/onSMhEWAp6YGdtr.png)\n\n需要密码 / PasswordPage\n\n![](https://s2.loli.net/2023/01/03/fmcq18a92AgzLjs.png)\n\n设置页面 / Settings page\n\n![](https://s2.loli.net/2023/01/03/Qq2o9BLiWeGx8va.png)\n\n连接成功/Command page\n\n![](https://s2.loli.net/2023/01/03/Lw5eNtvPJCUYdT6.png)\n\n</details>"
  },
  {
    "path": "README_EN.md",
    "content": "<h1 align=\"center\">\n<img src=\"NectarRCON/Resources/Icon.png\" style=\"image-rendering: optimizeSpeed;image-rendering: -moz-crisp-edges;image-rendering: -o-crisp-edges;image-rendering: -webkit-optimize-contrast;image-rendering: pixelated;-ms-interpolation-mode: nearest-neighbor;width: 150px; height: 150px;\"/>\n<br />\nNectarRCON\n<br />\n<h4 align=\"center\">\n    An Rcon manager developed using WPF\n</h4>\n<h5 align=\"center\">\n♥ This project is maintained long-term. Feel free to submit any bugs or wonderful ideas as issues!\n</h5>\n<h4 align=\"center\">\n​ <a href=\"https://mit-license.org/\">\n​ <img src=\"https://img.shields.io/github/license/zkhssb/NectarRCON?style=flat-square&color=blueviolet\"\n​ alt=\"license\" />\n​ </a>\n​ <img src=\"https://img.shields.io/badge/.NET-7.0-blue\" />\n​ <a href=\"https://bot.q.qq.com/wiki/develop/api/\">\n​ </a>\n​ </h4>\n<h5 align=\"center\">\n<a href=\"README_EN.md\">English</a>|<a href=\"README.md\">中文</a>\n</h5>\n​ </h1>\n\n## Supported Games\n\n- Games or applications that use the same Rcon protocol as Minecraft\n\n## Capabilities\n\n- Connect to a single server\n- Group servers (broadcast commands to all servers in the group)\n- Multi-language support (currently supports Chinese and English, with support for customizing other languages)\n- Support for SRV server addresses\n- Two built-in themes\n\n## Future Plans\n\n  Yes!!! Our project is thriving (Haha)\n\n- Support for other Rcon protocols (top priority)\n- Reconstruction of the log section\n- Automatic update support (ideas in mind)\n- Automatic script execution (ideas in mind but no current demand)\n- Rcon-based plugins (demand exists but I'm lazy)\n\n## About This Project\n\nA tool I came up with while learning WPF. The code may not be aesthetically pleasing (a mishmash of various things, written in a haphazard manner)\n\n## Project Dependencies\n\n| Name                        | Address                         |\n| --------------------------- | ------------------------------- |\n| WPF-UI (Deprecated Version) | https://github.com/lepoco/wpfui |\n\n## Screenshots\n\n<details>\n  <summary>Click to expand</summary>\nHome Page\n\n![](https://s2.loli.net/2023/01/03/onSMhEWAp6YGdtr.png)\n\nPassword Page\n\n![](https://s2.loli.net/2023/01/03/fmcq18a92AgzLjs.png)\n\nSettings Page\n\n![](https://s2.loli.net/2023/01/03/Qq2o9BLiWeGx8va.png)\n\nCommand Page (Connection Successful)\n\n![](https://s2.loli.net/2023/01/03/Lw5eNtvPJCUYdT6.png)\n\n</details>"
  }
]