├── CSUL ├── Dependences │ └── handle.exe ├── Resources │ ├── Icons │ │ ├── cog.png │ │ ├── map.png │ │ ├── mod.png │ │ ├── asset.png │ │ ├── close.png │ │ ├── earth.png │ │ ├── play.png │ │ ├── save.png │ │ ├── minimize.png │ │ ├── map_black.png │ │ ├── mod_black.png │ │ ├── asset_black.png │ │ ├── earth_black.png │ │ ├── folder_black.png │ │ ├── remove_black.png │ │ └── check_file_black.png │ └── Imgs │ │ ├── logo1.png │ │ ├── csul_logo.png │ │ ├── win_logo.ico │ │ ├── win_logo.png │ │ ├── back_image.png │ │ ├── cslbbs_logo.png │ │ ├── csl_logo_low.png │ │ └── cslbbs_logo_low.png ├── FodyWeavers.xml ├── Languages │ ├── switch.txt │ ├── language.xaml │ └── en-us.xaml ├── App.xaml.cs ├── Properties │ └── launchSettings.json ├── CSUL.licenseheader ├── Views │ ├── SetView.xaml.cs │ ├── MapView.xaml.cs │ ├── SaveView.xaml.cs │ ├── AssetView.xaml.cs │ ├── ModPlayerView.xaml.cs │ ├── PlayView.xaml.cs │ ├── PlayView.xaml │ ├── MapView.xaml │ ├── SaveView.xaml │ ├── AssetView.xaml │ └── SetView.xaml ├── Models │ ├── Local │ │ ├── Game │ │ │ ├── GameDataFileType.cs │ │ │ ├── InstalledGameDataFiles.cs │ │ │ ├── GameDataFileInfo.cs │ │ │ └── GameDataFile.cs │ │ ├── ModPlayer │ │ │ ├── ModPlayerType.cs │ │ │ ├── ModPlayerData.cs │ │ │ ├── IModData.cs │ │ │ ├── NullModPlayer.cs │ │ │ ├── ModPlayerManager.cs │ │ │ ├── BaseModPlayer.cs │ │ │ ├── Pmod │ │ │ │ ├── PmodData.cs │ │ │ │ └── PmodPlayer.cs │ │ │ └── BepInEx │ │ │ │ └── BepModData.cs │ │ ├── RegistryNotFoundException.cs │ │ ├── GameEx │ │ │ ├── GameDataManager.cs │ │ │ └── Chinesization.cs │ │ ├── TempDirectory.cs │ │ └── Cities2Path.cs │ ├── Network │ │ ├── NetBepInfo.cs │ │ └── NetworkData.cs │ ├── ExceptionEx.cs │ ├── ProcessEx.cs │ ├── DirectoryEx.cs │ ├── FileEx.cs │ └── ConfigActivator.cs ├── Styles │ ├── Paths.xaml │ └── BaseStyle.xaml ├── AssemblyInfo.cs ├── ExControls │ ├── CButtonType.cs │ ├── CButton.cs │ ├── ContentControlEx.cs │ └── ListViewEx.cs ├── App.xaml ├── Windows │ ├── StartInfoBox.xaml.cs │ ├── ModPlayerCteator.xaml.cs │ ├── ModCompatibilityBox.xaml.cs │ ├── StartInfoBox.xaml │ ├── CbResourceInstaller.xaml │ ├── ModPlayerCteator.xaml │ └── CbResourceInstaller.xaml.cs ├── ViewModels │ ├── BaseViewModel.cs │ ├── RelayCommand.cs │ ├── SetViewModels │ │ └── SetModel.cs │ ├── AssetViewModels │ │ └── AssetModel.cs │ ├── MainModel.cs │ ├── MapViewModels │ │ └── MapModel.cs │ ├── SaveViewModels │ │ └── SaveModel.cs │ └── ModPlayerCreatorViewModels │ │ └── ModPlayerCreatorModel.cs ├── UserControls │ └── DragFiles │ │ ├── DragFile.xaml │ │ ├── DragFileData.cs │ │ └── DragFile.xaml.cs ├── app.manifest ├── ViceCommand.cs ├── CSUL.csproj └── MainWindow.xaml ├── testEnvironments.json ├── README.md ├── CSUL.sln └── .gitattributes /CSUL/Dependences/handle.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Dependences/handle.exe -------------------------------------------------------------------------------- /CSUL/Resources/Icons/cog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/cog.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/map.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/mod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/mod.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/asset.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/close.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/earth.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/play.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/save.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/logo1.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/minimize.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/csul_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/csul_logo.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/win_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/win_logo.ico -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/win_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/win_logo.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/map_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/map_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/mod_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/mod_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/back_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/back_image.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/cslbbs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/cslbbs_logo.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/asset_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/asset_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/earth_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/earth_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/folder_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/folder_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/remove_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/remove_black.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/csl_logo_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/csl_logo_low.png -------------------------------------------------------------------------------- /CSUL/Resources/Imgs/cslbbs_logo_low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Imgs/cslbbs_logo_low.png -------------------------------------------------------------------------------- /CSUL/Resources/Icons/check_file_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sci-fiBrain/CSUL/HEAD/CSUL/Resources/Icons/check_file_black.png -------------------------------------------------------------------------------- /CSUL/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /CSUL/Languages/switch.txt: -------------------------------------------------------------------------------- 1 | Language switching method: 2 | 3 | Change the corresponding language file to language.xaml 4 | 5 | For example, renaming "en-us.xaml" to "language.xaml" -------------------------------------------------------------------------------- /CSUL/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace CSUL 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /CSUL/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CSUL": { 4 | "commandName": "Project", 5 | "remoteDebugEnabled": false, 6 | "remoteDebugMachine": "WIN-DRO4QEGBMKF:4026", 7 | "authenticationMode": "Windows" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /CSUL/CSUL.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: .cs 2 | /* CSUL 标准文件头注释 3 | * -------------------------------------- 4 | * 文件名称: %FileName% 5 | * 创建时间: %CreationYear%年%CreationMonth%月%CreationDay%日 %CreationTime% 6 | * 创建开发: 7 | * 文件介绍: 8 | * -------------------------------------- 9 | */ -------------------------------------------------------------------------------- /CSUL/Views/SetView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace CSUL.Views 4 | { 5 | /// 6 | /// SetView.xaml 的交互逻辑 7 | /// 8 | public partial class SetView : UserControl 9 | { 10 | public SetView() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/Game/GameDataFileType.cs: -------------------------------------------------------------------------------- 1 | namespace CSUL.Models.Local.Game 2 | { 3 | /// 4 | /// 游戏文件类型 5 | /// 6 | public enum GameDataFileType 7 | { 8 | Unknown, 9 | Save, 10 | Map, 11 | 12 | /// 13 | /// 资产 14 | /// 15 | Asset, 16 | } 17 | } -------------------------------------------------------------------------------- /testEnvironments.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "environments": [ 4 | // 请参阅 https://aka.ms/remotetesting 获取更多信息 5 | // 了解如何配置远程环境。 6 | //{ 7 | // "name": "WSL Ubuntu", 8 | // "type": "wsl", 9 | // "wslDistribution": "Ubuntu" 10 | //}, 11 | //{ 12 | // "name": "Docker dotnet/sdk", 13 | // "type": "docker", 14 | // "dockerImage": "mcr.microsoft.com/dotnet/sdk" 15 | //} 16 | ] 17 | } -------------------------------------------------------------------------------- /CSUL/Views/MapView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.UserControls.DragFiles; 2 | using System.Windows.Controls; 3 | 4 | namespace CSUL.Views 5 | { 6 | /// 7 | /// MapView.xaml 的交互逻辑 8 | /// 9 | public partial class MapView : UserControl 10 | { 11 | public MapView() 12 | { 13 | InitializeComponent(); 14 | DragFile.FileNameWithTypes = DefaultDragFilesType.GameFile; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CSUL/Views/SaveView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.UserControls.DragFiles; 2 | using System.Windows.Controls; 3 | 4 | namespace CSUL.Views 5 | { 6 | /// 7 | /// MapView.xaml 的交互逻辑 8 | /// 9 | public partial class SaveView : UserControl 10 | { 11 | public SaveView() 12 | { 13 | InitializeComponent(); 14 | DragFile.FileNameWithTypes = DefaultDragFilesType.GameFile; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CSUL/Views/AssetView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.UserControls.DragFiles; 2 | using System.Windows.Controls; 3 | 4 | namespace CSUL.Views 5 | { 6 | /// 7 | /// AssetView.xaml 的交互逻辑 8 | /// 9 | public partial class AssetView : UserControl 10 | { 11 | public AssetView() 12 | { 13 | InitializeComponent(); 14 | DragFile.FileNameWithTypes = DefaultDragFilesType.GameFile; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CSUL/Views/ModPlayerView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.UserControls.DragFiles; 2 | using System.Windows.Controls; 3 | 4 | namespace CSUL.Views 5 | { 6 | /// 7 | /// ModPlayerView.xaml 的交互逻辑 8 | /// 9 | public partial class ModPlayerView : UserControl 10 | { 11 | public ModPlayerView() 12 | { 13 | InitializeComponent(); 14 | DragFile.FileNameWithTypes = DefaultDragFilesType.BepModFile; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/ModPlayer/ModPlayerType.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ModPlayerType.cs 4 | * 创建时间: 2023年12月16日 19:33 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 模组播放集类型 7 | * -------------------------------------- 8 | */ 9 | 10 | namespace CSUL.Models.Local.ModPlayer 11 | { 12 | /// 13 | /// 模组播放集类型 14 | /// 15 | internal enum ModPlayerType 16 | { 17 | None, 18 | BepInEx, 19 | Pmod 20 | } 21 | } -------------------------------------------------------------------------------- /CSUL/Styles/Paths.xaml: -------------------------------------------------------------------------------- 1 | 3 | M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z 4 | M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z 5 | -------------------------------------------------------------------------------- /CSUL/Views/PlayView.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.ViewModels.PlayViewModels; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace CSUL.Views 6 | { 7 | /// 8 | /// PlayView.xaml 的交互逻辑 9 | /// 10 | public partial class PlayView : UserControl 11 | { 12 | public PlayView() 13 | { 14 | InitializeComponent(); 15 | } 16 | 17 | private void UserControl_Loaded(object sender, RoutedEventArgs e) 18 | { //加载完成后 19 | (DataContext as PlayModel)?.SetWindow(Window.GetWindow(this)); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /CSUL/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] -------------------------------------------------------------------------------- /CSUL/ExControls/CButtonType.cs: -------------------------------------------------------------------------------- 1 | namespace CSUL.ExControls 2 | { 3 | /// 4 | /// 的类型枚举 5 | /// 6 | public enum CButtonType 7 | { 8 | /// 9 | /// 图像按钮 10 | /// 11 | Icon, 12 | 13 | /// 14 | /// 不旋转的图像按钮 15 | /// 16 | IconUnRotate, 17 | 18 | /// 19 | /// 纯文本按钮 20 | /// 21 | Text, 22 | 23 | /// 24 | /// 图标带文本按钮 25 | /// 26 | IconText, 27 | 28 | /// 29 | /// 路径按钮 30 | /// 31 | Path 32 | } 33 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/RegistryNotFoundException.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: RegistryNotFoundException.cs 4 | * 创建时间: 2023年12月14日 12:20 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 注册表未找到的异常 7 | * -------------------------------------- 8 | */ 9 | 10 | using System; 11 | 12 | namespace CSUL.Models.Local 13 | { 14 | /// 15 | /// 注册表未找到的异常 16 | /// 17 | internal class RegistryNotFoundException : Exception 18 | { 19 | /// 20 | /// 实例化对象 21 | /// 22 | /// 未找到的注册表名称 23 | public RegistryNotFoundException(string? name) : base($"Couldn't fint {name}") { } 24 | } 25 | } -------------------------------------------------------------------------------- /CSUL/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CSUL/Windows/StartInfoBox.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using static System.Net.Mime.MediaTypeNames; 3 | 4 | namespace CSUL.Windows 5 | { 6 | /// 7 | /// StartInfo.xaml 的交互逻辑 8 | /// 9 | public partial class StartInfoBox : Window 10 | { 11 | public StartInfoBox() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | public string? Text 17 | { 18 | get => text.Content.ToString(); 19 | set 20 | { 21 | text.Content = value; 22 | SubText = ""; 23 | } 24 | } 25 | 26 | public string? SubText 27 | { 28 | get => subtext.Content.ToString(); 29 | set => subtext.Content = value; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/ModPlayer/ModPlayerData.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ModPlayerData.cs 4 | * 创建时间: 2023年12月23日 0:36 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 模组播放器装载后的信息 7 | * -------------------------------------- 8 | */ 9 | 10 | using System.Collections.Generic; 11 | 12 | namespace CSUL.Models.Local.ModPlayer 13 | { 14 | /// 15 | /// 模组播放集装载后的信息 16 | /// 17 | internal class ModPlayerData 18 | { 19 | /// 20 | /// 装载的文件 =>> 卸载时应该被删除的文件 21 | /// 22 | public required IEnumerable Files { get; set; } 23 | 24 | /// 25 | /// 装载的文件夹 =>> 卸载时应该被删除的文件夹 26 | /// 27 | public required IEnumerable Directories { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /CSUL/Models/Network/NetBepInfo.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: NetBepInfo.cs 4 | * 创建时间: 2023年12月14日 0:01 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 单个BepInEx文件信息的结构体 7 | * -------------------------------------- 8 | */ 9 | 10 | namespace CSUL.Models.Network 11 | { 12 | /// 13 | /// 单个BepInEx文件信息 14 | /// 15 | internal struct NetBepInfo 16 | { 17 | /// 18 | /// 版本号 19 | /// 20 | public string Version { get; set; } 21 | 22 | /// 23 | /// 是否为测试版 24 | /// 25 | public bool IsBeta { get; set; } 26 | 27 | /// 28 | /// 文件名 29 | /// 30 | public string FileName { get; set; } 31 | 32 | /// 33 | /// 下载链接 34 | /// 35 | public string Uri { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /CSUL/Windows/ModPlayerCteator.xaml.cs: -------------------------------------------------------------------------------- 1 | using CSUL.ViewModels.ModPlayerCreatorViewModels; 2 | using System.Windows; 3 | using System.Windows.Input; 4 | 5 | namespace CSUL.Windows 6 | { 7 | /// 8 | /// ModPlayerCteator.xaml 的交互逻辑 9 | /// 10 | public partial class ModPlayerCteator : Window 11 | { 12 | internal ModPlayerCteator() 13 | { 14 | InitializeComponent(); 15 | ModPlayerCreatorModel? vm = DataContext as ModPlayerCreatorModel; 16 | if (vm is not null) vm.Window = this; 17 | } 18 | 19 | private void Border_MouseMove(object sender, MouseEventArgs e) 20 | { 21 | if (e.LeftButton == MouseButtonState.Pressed) 22 | { 23 | DragMove(); 24 | } 25 | } 26 | 27 | private void Close_Click(object sender, RoutedEventArgs e) 28 | { 29 | Close(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cities: Skylines II Universal Launcher 2 | Cities: Skylines II Universal Launcher 都市天际线2万能启动器(简称CSUL)是 [CSLBBS都市天际线2中文论坛](https://www.cslbbs.net/) 旗下一款功能丰富强大、外观简洁大方的天际线综合资源管理器。主要实现了模组存档地图等资源的导入管理等功能,并且仍在不断发展和完善,旨在为广大天际线2玩家提供便利的一键资源管理和游戏启动功能。 3 | 4 | 为避免用户受病毒、漏洞等恶意代码的危害,在分享本启动器时应使用[CSLBBS官方发布页面](https://www.cslbbs.net/csul/)或[Github仓库链接](https://github.com/Sci-fiBrain/CSUL/)。 5 | 6 | ## 已实现功能 7 | - BepInEx、模组、地图存档一键安装/卸载(支持多种格式导入) 8 | - 模组兼容性检测,并对不同问题进行归类,便于排查 9 | - 自定义游戏目录/数据目录(兼容不同游戏平台)Steam平台自动检测 10 | - 一键启动游戏,以开发者模式、以Steam兼容模式启动游戏 11 | - 在线检测启动器更新 12 | - 模组播放集(可选用不同的模组搭配) 13 | - 一键自动安装论坛模组 14 | - 在线检测模组版本更新 15 | - 游戏错误日志解析 16 | - 内置[喵小夕全局汉化](https://github.com/thx114/mio-i18-cn) (自动更新) 17 | 18 | ## 待完成功能 19 | - [ ] 多语言支持(Multi-Language) 20 | - [ ] 一键跳过P社启动器启动游戏 21 | - [ ] 自定义启动器主题色、背景图 22 | - [ ] 自动检测Xbox、微软商店游戏路径 23 | 24 | ## 目前已知问题 25 | 暂无 26 | 27 | ## 鸣谢 28 | [@科幻大脑](https://github.com/Sci-fiBrain) 程序开发 29 | 30 | [@YYT](https://github.com/SuperYYT) 功能策划  31 | 32 | [@喵小夕](https://space.bilibili.com/209728596/) 提供模组相关协助 33 | -------------------------------------------------------------------------------- /CSUL/Models/Local/Game/InstalledGameDataFiles.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: InstalledGameDataFiles.cs 4 | * 创建时间: 2023年12月14日 13:20 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 安装完成的游戏数据文件路径 7 | * -------------------------------------- 8 | */ 9 | 10 | using System.Collections.Generic; 11 | 12 | namespace CSUL.Models.Local.Game 13 | { 14 | /// 15 | /// 安装完成的游戏数据文件路径 16 | /// 17 | public readonly struct InstalledGameDataFiles 18 | { 19 | public InstalledGameDataFiles() 20 | { 21 | } 22 | 23 | /// 24 | /// 已安装的地图名称 25 | /// 26 | public List MapNames { get; init; } = new(); 27 | 28 | /// 29 | /// 已安装的存档名称 30 | /// 31 | public List SaveNames { get; init; } = new(); 32 | 33 | /// 34 | /// 已安装的资产名称 35 | /// 36 | public List AssetNames { get; init; } = new(); 37 | } 38 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace CSUL.ViewModels 7 | { 8 | /// 9 | /// ViewModel的基类 10 | /// 11 | public class BaseViewModel : INotifyPropertyChanged 12 | { 13 | #pragma warning disable CA1822 14 | 15 | /// 16 | /// 获取当前CSUL版本 17 | /// 18 | public Version? CsulVersion { get => Assembly.GetExecutingAssembly().GetName().Version; } 19 | 20 | #pragma warning restore CA1822 21 | 22 | /// 23 | /// 指示属性发生改变的事件 24 | /// 25 | public event PropertyChangedEventHandler? PropertyChanged; 26 | 27 | /// 28 | /// 指示属性值发生了改变 29 | /// 30 | /// 31 | internal void OnPropertyChanged([CallerMemberName] string? propertyName = null) 32 | { 33 | PropertyChangedEventArgs args = new(propertyName); 34 | PropertyChanged?.Invoke(this, args); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /CSUL/UserControls/DragFiles/DragFile.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | -------------------------------------------------------------------------------- /CSUL/Models/Local/ModPlayer/IModData.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: IModData.cs 4 | * 创建时间: 2023年12月16日 18:36 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 模组数据接口 7 | * -------------------------------------- 8 | */ 9 | 10 | namespace CSUL.Models.Local.ModPlayer 11 | { 12 | /// 13 | /// 模组数据接口 14 | /// 15 | internal interface IModData 16 | { 17 | /// 18 | /// 模组名称 19 | /// 20 | public string Name { get; } 21 | 22 | /// 23 | /// 模组路径 24 | /// 25 | public string ModPath { get; } 26 | 27 | /// 28 | /// 是否启用该模组 29 | /// 30 | public bool IsEnabled { get; set; } 31 | 32 | /// 33 | /// 模组版本 34 | /// 35 | public string? ModVersion { get; } 36 | 37 | /// 38 | /// 模组最新版本 39 | /// 40 | public string? LatestVersion { get; } 41 | 42 | /// 43 | /// 模组描述 44 | /// 45 | public string? Description { get; } 46 | 47 | /// 48 | /// 模组地址 49 | /// 50 | public string? ModUrl { get; } 51 | } 52 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace CSUL.ViewModels 5 | { 6 | /// 7 | /// Command调用类 8 | /// 9 | internal class RelayCommand : ICommand 10 | { 11 | //命令执行时要调用的方法 12 | private readonly Action action; 13 | 14 | //判断是否能调用方法的方法 15 | private readonly Func? canExecute; 16 | 17 | /// 18 | /// 实例化 19 | /// 20 | /// 命令调用时要调用的方法 21 | /// 判断是否能调用方法的方法 22 | /// 23 | public RelayCommand(Action action, Func? canExecute = null) 24 | { 25 | this.action = action ?? throw new ArgumentNullException(nameof(action)); 26 | this.canExecute = canExecute; 27 | } 28 | 29 | #pragma warning disable CS0067 30 | 31 | public event EventHandler? CanExecuteChanged; 32 | 33 | #pragma warning restore CS0067 34 | 35 | public bool CanExecute(object? parameter) => action is not null || canExecute?.Invoke(parameter) is true; 36 | 37 | public void Execute(object? parameter) => action?.Invoke(parameter); 38 | } 39 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/ModPlayer/NullModPlayer.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: NullModPlayer.cs 4 | * 创建时间: 2023年12月22日 2:29 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 空白播放集类 7 | * -------------------------------------- 8 | */ 9 | 10 | using System; 11 | using System.Threading.Tasks; 12 | 13 | namespace CSUL.Models.Local.ModPlayer 14 | { 15 | /// 16 | /// 空白播放集类 17 | /// 18 | internal class NullModPlayer : BaseModPlayer 19 | { 20 | public override ModPlayerType PlayerType => ModPlayerType.None; 21 | 22 | public override IModData[] ModDatas => Array.Empty(); 23 | 24 | public override Version? PlayerVersion => null; 25 | 26 | public override async Task AddMod(string path, IModData? data = null) => await Task.Delay(0); 27 | 28 | public override async Task Install(string rootPath, string dataPath) => 29 | await Task.Run(() => new ModPlayerData { Directories = default!, Files = default! }); 30 | 31 | public override async Task RemoveMod(IModData modData) => await Task.Delay(0); 32 | 33 | public override async Task UpgradeMod(IModData modData, string path, IModData? newData = null) => await Task.Delay(0); 34 | 35 | public override IModData? FirstOrDefault(Func precidate) => null; 36 | } 37 | } -------------------------------------------------------------------------------- /CSUL.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33829.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSUL", "CSUL\CSUL.csproj", "{D065893E-3CDA-4F82-AC6D-6996F4368742}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Debug|x64.ActiveCfg = Debug|x64 19 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Debug|x64.Build.0 = Debug|x64 20 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Release|x64.ActiveCfg = Release|x64 23 | {D065893E-3CDA-4F82-AC6D-6996F4368742}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D046F913-1F52-422E-98BE-FAEA5A8FB4CE} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CSUL/Models/Local/GameEx/GameDataManager.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: GameDataManager.cs 4 | * 创建时间: 2024年4月17日 0:00 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 游戏数据管理方法 7 | * -------------------------------------- 8 | */ 9 | 10 | using System.IO; 11 | using System.Threading.Tasks; 12 | 13 | namespace CSUL.Models.Local.GameEx 14 | { 15 | internal static class GameDataManager 16 | { 17 | private readonly static ComParameters CP = ComParameters.Instance; 18 | 19 | /// 20 | /// 退出P社账号 21 | /// 22 | public static void LogoutParadox() 23 | { 24 | string cachePath = Path.Combine(CP.GameData.FullName, ".cache"); 25 | string pdxSdk = Path.Combine(CP.GameData.FullName, ".pdxsdk"); 26 | if (Directory.Exists(cachePath)) Directory.Delete(cachePath, true); 27 | if (Directory.Exists(pdxSdk)) Directory.Delete(pdxSdk, true); 28 | } 29 | 30 | /// 31 | /// 刷新Pmod文件数据 32 | /// 33 | public static async Task ReloadPmodData() 34 | { 35 | await Task.Run(() => 36 | { 37 | string modPath = CP.Pmod.FullName; 38 | string cachePath = Path.Combine(CP.GameData.FullName, ".cache"); 39 | using TempDirectory temp = new(); 40 | if (Directory.Exists(modPath)) 41 | { 42 | DirectoryEx.CopyTo(modPath, Path.Combine(temp.FullName, "Mods")); 43 | Directory.Delete(modPath, true); 44 | } 45 | if (Directory.Exists(cachePath)) 46 | { 47 | DirectoryEx.CopyTo(cachePath, Path.Combine(temp.FullName, ".cache")); 48 | Directory.Delete(cachePath, true); 49 | } 50 | temp.DirectoryInfo.CopyTo(CP.GameData.FullName); 51 | }); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /CSUL/ExControls/CButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | 5 | namespace CSUL.ExControls 6 | { 7 | /// 8 | /// 自定义按钮控件 9 | /// 10 | public class CButton : Button 11 | { 12 | static CButton() 13 | { 14 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CButton), new FrameworkPropertyMetadata(typeof(CButton))); 15 | } 16 | 17 | public static readonly DependencyProperty IconProperty = 18 | DependencyProperty.Register("Icon", typeof(ImageSource), typeof(CButton), new PropertyMetadata(null)); 19 | 20 | public ImageSource Icon 21 | { //图标属性 22 | get => (ImageSource)GetValue(IconProperty); 23 | set => SetValue(IconProperty, value); 24 | } 25 | 26 | public static readonly DependencyProperty ButtonTypeProperty = 27 | DependencyProperty.Register("ButtonType", typeof(CButtonType), typeof(CButton), new PropertyMetadata(CButtonType.Icon)); 28 | 29 | public CButtonType ButtonType 30 | { //按钮类型属性 31 | get => (CButtonType)GetValue(ButtonTypeProperty); 32 | set => SetValue(ButtonTypeProperty, value); 33 | } 34 | 35 | public static readonly DependencyProperty LabelVisibilityProperty = 36 | DependencyProperty.Register("LabelVisibility", typeof(Visibility), typeof(CButton), new PropertyMetadata(Visibility.Collapsed)); 37 | 38 | public Visibility LabelVisibility 39 | { //按钮类型属性 40 | get => (Visibility)GetValue(LabelVisibilityProperty); 41 | set => SetValue(LabelVisibilityProperty, value); 42 | } 43 | 44 | public static readonly DependencyProperty PathDataProperty = 45 | DependencyProperty.Register("PathData", typeof(Geometry), typeof(CButton), new PropertyMetadata(null)); 46 | 47 | public Geometry PathData 48 | { //Path类型下的绘制内容 49 | get => (Geometry)GetValue(PathDataProperty); 50 | set => SetValue(PathDataProperty, value); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /CSUL/Languages/language.xaml: -------------------------------------------------------------------------------- 1 | 4 | 开始游戏 5 | 主页 6 | 地图 7 | 存档 8 | 模组 9 | 资产 10 | 设置 11 | 文件 12 | 检查 13 | 移除 14 | 删除 15 | 兼容性检查结果(点击方块查看指定结果列表) 16 | 模组总数 17 | 通过数 18 | 错误数 19 | 未知数 20 | 请大脑喝一杯奶茶~ 21 | 游戏安装根目录(Cities2.exe所在的目录,目录不包含Cities2.exe) 22 | 游戏数据根目录(存储游戏数据: Map、Save等数据的目录) 23 | 启动参数(请勿键入程序名 如: Cities2.exe 或 start.bat)[可选] 24 | 开发者模式 25 | BepInEx版本 26 | 自定义Steam路径(包含steam.exe)[可选] 27 | Steam正版兼容模式(推荐) 28 | 启用 29 | 禁用 30 | 播放集 31 | 创建 32 | 喵小夕套件 33 | 退出Paradox账号 34 | 总是重新加载Pmod 35 | -------------------------------------------------------------------------------- /CSUL/ExControls/ContentControlEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Media.Animation; 6 | 7 | namespace CSUL.ExControls 8 | { 9 | /// 10 | /// 的附加属性 11 | /// 12 | public static class ContentControlEx 13 | { 14 | public static readonly DependencyProperty ContentChangedAnimationProperty = DependencyProperty.RegisterAttached( 15 | "ContentChangedAnimation", typeof(Storyboard), typeof(ContentControlEx), 16 | new PropertyMetadata(default(Storyboard), ContentChangedAnimationPropertyChangedCallback)); 17 | 18 | public static void SetContentChangedAnimation(DependencyObject element, Storyboard value) 19 | => element.SetValue(ContentChangedAnimationProperty, value); 20 | 21 | public static Storyboard GetContentChangedAnimation(DependencyObject element) 22 | => (Storyboard)element.GetValue(ContentChangedAnimationProperty); 23 | 24 | private static void ContentChangedAnimationPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) 25 | { 26 | if (dependencyObject is not ContentControl contentControl) 27 | throw new Exception("Can only be applied to a ContentControl"); 28 | DependencyPropertyDescriptor propertyDescriptor = DependencyPropertyDescriptor.FromProperty(ContentControl.ContentProperty, 29 | typeof(ContentControl)); 30 | propertyDescriptor.RemoveValueChanged(contentControl, ContentChangedHandler); 31 | propertyDescriptor.AddValueChanged(contentControl, ContentChangedHandler); 32 | } 33 | 34 | private static void ContentChangedHandler(object? sender, EventArgs eventArgs) 35 | { 36 | if (sender is null) throw new ArgumentNullException(nameof(sender)); 37 | FrameworkElement animateObject = (FrameworkElement)sender; 38 | Storyboard storyboard = GetContentChangedAnimation(animateObject); 39 | storyboard.Begin(animateObject); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /CSUL/ExControls/ListViewEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Media.Animation; 6 | 7 | namespace CSUL.ExControls 8 | { 9 | /// 10 | /// 的扩展代码 11 | /// 12 | public static class ListViewEx 13 | { 14 | public static readonly DependencyProperty ItemsSourceChangedAnimationProperty = 15 | DependencyProperty.RegisterAttached( 16 | "ItemsSourceChangedAnimation", typeof(Storyboard), typeof(ListViewEx), 17 | new PropertyMetadata(default(Storyboard), ItemsSourceChangedAnimationPropertyChangedCallback)); 18 | 19 | public static void SetItemsSourceChangedAnimation(DependencyObject element, Storyboard value) 20 | => element.SetValue(ItemsSourceChangedAnimationProperty, value); 21 | 22 | public static Storyboard GetItemsSourceChangedAnimation(DependencyObject element) 23 | => (Storyboard)element.GetValue(ItemsSourceChangedAnimationProperty); 24 | 25 | private static void ItemsSourceChangedAnimationPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) 26 | { 27 | if (dependencyObject is not ListView animatedListView) 28 | throw new Exception("Can only be applied to an ListView"); 29 | 30 | DependencyPropertyDescriptor propertyDescriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ListView)); 31 | propertyDescriptor.RemoveValueChanged(animatedListView, ItemsSourceChangedHandler); 32 | propertyDescriptor.AddValueChanged(animatedListView, ItemsSourceChangedHandler); 33 | } 34 | 35 | private static void ItemsSourceChangedHandler(object? sender, EventArgs eventArgs) 36 | { 37 | if (sender is null) throw new ArgumentNullException(nameof(sender)); 38 | FrameworkElement animateObject = (FrameworkElement)sender; 39 | Storyboard storyboard = GetItemsSourceChangedAnimation(animateObject); 40 | storyboard?.Begin(animateObject); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /CSUL/Languages/en-us.xaml: -------------------------------------------------------------------------------- 1 | 4 | Play 5 | Home 6 | Map 7 | Save 8 | Mod 9 | Set 10 | File 11 | Check 12 | Remove 13 | Delete 14 | Compatibility check results (Click the box to see a list of specified results) 15 | Mod Count 16 | Pass Count 17 | Wrong Count 18 | Unknow Count 19 | Buy Me Coffe~ 20 | Game installation root (the directory where Cities2.exe is located, the directory does not contain Cities2.exe) 21 | Game data root directory (directory where game data is stored: Map, Save, etc.) 22 | Startup parameters (do not enter program name such as Cities2.exe or start.bat) [choosable] 23 | Developer mode 24 | BepInEx 25 | Custom steam path (including steam.exe) [choosable] 26 | Steam compatibility mode (recommend) 27 | On 28 | Off 29 | Mod Player 30 | Creat 31 | Chinesization 32 | -------------------------------------------------------------------------------- /CSUL/Models/ExceptionEx.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ExceptionEx.cs 4 | * 创建时间: 2023年11月26日 12:47 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: Exception扩展类 提供Exception的扩展方法 7 | * -------------------------------------- 8 | */ 9 | 10 | using System; 11 | using System.Reflection; 12 | using System.Text; 13 | 14 | namespace CSUL.Models 15 | { 16 | /// 17 | /// Exception扩展类 提供Exception的扩展方法 18 | /// 19 | public static class ExceptionEx 20 | { 21 | /// 22 | /// 格式化异常信息 23 | /// 24 | public static string ToFormative(this Exception? ex, string? meg = null) 25 | { 26 | StringBuilder builder = new(); 27 | builder.AppendLine($"发生时间: {DateTime.Now:yyyy_MM_dd HH:mm:ss:ffff}"); 28 | if (meg is not null) 29 | { 30 | builder.AppendLine($"附加信息: \n{meg}"); 31 | } 32 | if (ex is UnauthorizedAccessException) 33 | { 34 | builder.AppendLine($"异常信息: 请关闭安全软件或使用管理员模式启动CSUL"); 35 | } 36 | else if (ex is System.Net.Http.HttpRequestException httpEx) 37 | { 38 | builder.AppendLine($"异常信息: 网站访问失败 错误码{(int?)httpEx.StatusCode}({httpEx.StatusCode})"); 39 | } 40 | if (ex is not null) 41 | { 42 | builder.AppendLine($"异常内容: {ex.Message}"); 43 | if (ex.InnerException is Exception inner) 44 | { 45 | builder.AppendLine($"内敛信息: {inner.Message}"); 46 | builder.AppendLine($"内敛类型: {inner.GetType().Name}"); 47 | } 48 | builder.AppendLine(); 49 | builder.AppendLine("如遇无法解决的问题,请将该报错完整截图后再咨询"); 50 | builder.AppendLine("以下为开发者所需信息,无需用户处理"); 51 | builder.Append('-', 60).AppendLine(); 52 | builder.AppendLine($"软件版本: {Assembly.GetExecutingAssembly().GetName().Version}"); 53 | builder.AppendLine($"异常类型: {ex.GetType().Name}"); 54 | builder.AppendLine($"异常对象: {ex.Source}"); 55 | builder.AppendLine($"异常方法: {ex.TargetSite?.Name}"); 56 | builder.AppendLine($"堆栈信息: \n{ex.StackTrace}"); 57 | } 58 | return builder.ToString(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/SetViewModels/SetModel.cs: -------------------------------------------------------------------------------- 1 | using CSUL.Models; 2 | using CSUL.Models.Local.GameEx; 3 | using System; 4 | using System.Windows; 5 | using System.Windows.Input; 6 | 7 | namespace CSUL.ViewModels.SetViewModels 8 | { 9 | public class SetModel : BaseViewModel 10 | { 11 | private static ComParameters CP { get; } = ComParameters.Instance; 12 | 13 | public string GamePath 14 | { 15 | get => CP.GameRoot.FullName; 16 | set 17 | { 18 | if (value == GamePath) return; 19 | CP.GameRoot = new(value); 20 | OnPropertyChanged(); 21 | } 22 | } 23 | 24 | public string GameData 25 | { 26 | get => CP.GameData.FullName; 27 | set 28 | { 29 | if (value == GameData) return; 30 | CP.GameData = new(value); 31 | OnPropertyChanged(); 32 | } 33 | } 34 | 35 | public string? StartArgument 36 | { 37 | get => CP.StartArguemnt; 38 | set 39 | { 40 | if (value == StartArgument) return; 41 | CP.StartArguemnt = value; 42 | OnPropertyChanged(); 43 | } 44 | } 45 | 46 | public string? SteamPath 47 | { 48 | get => CP.SteamPath; 49 | set 50 | { 51 | if (value == SteamPath) return; 52 | CP.SteamPath = value; 53 | OnPropertyChanged(); 54 | } 55 | } 56 | 57 | public ICommand OpenWebUri { get; } = new RelayCommand(sender => 58 | System.Diagnostics.Process.Start("explorer.exe", sender as string ?? "")); 59 | 60 | public ICommand LogoutParadox { get; } = new RelayCommand(sender => 61 | { 62 | try 63 | { 64 | var ret = MessageBox.Show("这将会删除已登录的Paradox账号数据", "确认登出Paradox账号吗?", MessageBoxButton.OKCancel, MessageBoxImage.Question); 65 | if (ret != MessageBoxResult.OK) return; 66 | GameDataManager.LogoutParadox(); 67 | MessageBox.Show("Paradox账号已退出", "提示", MessageBoxButton.OK, MessageBoxImage.Information); 68 | } 69 | catch (Exception ex) 70 | { 71 | MessageBox.Show(ex.ToFormative(), "退出Paradox账号时出现问题", MessageBoxButton.OK, MessageBoxImage.Warning); 72 | } 73 | }); 74 | } 75 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /CSUL/UserControls/DragFiles/DragFileData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CSUL.UserControls.DragFiles 6 | { 7 | /// 8 | /// 拖动选择文件事件处理委托 9 | /// 10 | public delegate void DragFilesEventHander(object sender, DragFilesEventArgs e); 11 | 12 | /// 13 | /// 包含的事件数据 14 | /// 15 | public class DragFilesEventArgs : EventArgs 16 | { 17 | public DragFilesEventArgs(string[] paths) 18 | { 19 | Paths = paths; 20 | } 21 | 22 | /// 23 | /// 选择的文件路径 24 | /// 25 | public string[] Paths { get; } = default!; 26 | } 27 | 28 | /// 29 | /// 拖动文件类型 30 | /// 31 | public class DragFilesType : Dictionary> 32 | { 33 | public DragFilesType() 34 | { } 35 | 36 | public DragFilesType(IEnumerable>> collection, IEqualityComparer? comparer = null) : base(collection, comparer) 37 | { 38 | } 39 | 40 | /// 41 | /// 创建“所有合法文件”条目 42 | /// 43 | public DragFilesType AddAllFileItem() 44 | { 45 | DragFilesType ret = new() 46 | { 47 | { "合法文件", this.SelectMany(x => x.Value).ToList() } 48 | }; 49 | return new(ret.Concat(this)); 50 | } 51 | } 52 | 53 | /// 54 | /// 拖动文件基本类型 55 | /// 56 | public static class DefaultDragFilesType 57 | { 58 | #region ---私有字段--- 59 | 60 | private static readonly DragFilesType zipFile = new() 61 | { 62 | {"压缩文件", new (){".zip", ".rar", ".7z", ".tar"}} 63 | }; 64 | 65 | private static readonly DragFilesType bepModFile = new() 66 | { 67 | {"压缩文件", new(){".zip", ".rar", ".7z", ".tar"} }, 68 | {"DLL文件", new() {".dll" } } 69 | }; 70 | 71 | private static readonly DragFilesType gameFile = new() 72 | { 73 | {"压缩文件", new(){".zip", ".rar", ".7z", ".tar"} }, 74 | {"游戏文件", new(){ ".cok", ".Prefab" } } 75 | }; 76 | 77 | #endregion ---私有字段--- 78 | 79 | #region ---公共属性--- 80 | 81 | /// 82 | /// 压缩文件 83 | /// 84 | public static DragFilesType ZipFile { get => zipFile.AddAllFileItem(); } 85 | 86 | /// 87 | /// BepMod文件 88 | /// 89 | public static DragFilesType BepModFile { get => bepModFile.AddAllFileItem(); } 90 | 91 | /// 92 | /// 游戏文件 93 | /// 94 | public static DragFilesType GameFile { get => gameFile.AddAllFileItem(); } 95 | 96 | #endregion ---公共属性--- 97 | } 98 | } -------------------------------------------------------------------------------- /CSUL/Models/ProcessEx.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ProcessEx.cs 4 | * 创建时间: 2024年4月19日 23:22 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: Process扩展类 7 | * -------------------------------------- 8 | */ 9 | using System; 10 | using System.Collections.Generic; 11 | using System.ComponentModel; 12 | using System.Diagnostics; 13 | using System.Text; 14 | 15 | namespace CSUL.Models 16 | { 17 | /// 18 | /// Process扩展类 19 | /// 20 | internal static class ProcessEx 21 | { 22 | /// 23 | /// 强制结束进程 24 | /// 25 | public static bool TaskKills(this IEnumerable processes) 26 | { 27 | try 28 | { 29 | foreach (Process process in processes) 30 | { 31 | if (process.HasExited) continue; 32 | process.Kill(); 33 | } 34 | return true; 35 | } 36 | catch (Win32Exception) { } 37 | catch (UnauthorizedAccessException) { } 38 | catch { throw; } 39 | 40 | StringBuilder builder = new(); 41 | builder.Append("/C "); 42 | foreach (Process process in processes) 43 | { 44 | builder.Append($"taskkill /F /PID {process.Id} & "); 45 | } 46 | 47 | ProcessStartInfo startInfo = new() 48 | { //管理员模式启动cmd 强制终止进程 49 | FileName = "cmd.exe", 50 | Verb = "runas", 51 | Arguments = builder.ToString(), 52 | }; 53 | try 54 | { 55 | Process.Start(startInfo)?.WaitForExit(1000); 56 | return true; 57 | } 58 | catch (Win32Exception) 59 | { 60 | return false; 61 | } 62 | } 63 | 64 | /// 65 | /// 强制结束进程 66 | /// 67 | public static bool TaskKill(this Process process) 68 | { 69 | if (process.HasExited) return true; 70 | try 71 | { 72 | process.Kill(); 73 | return true; 74 | } 75 | catch (Win32Exception) { } 76 | catch (UnauthorizedAccessException) { } 77 | catch { throw; } 78 | 79 | ProcessStartInfo startInfo = new() 80 | { //管理员模式启动cmd 强制终止进程 81 | FileName = "cmd.exe", 82 | Verb = "runas", 83 | Arguments = $"/C taskkill /F /PID {process.Id} & ", 84 | }; 85 | try 86 | { 87 | Process.Start(startInfo)?.WaitForExit(1000); 88 | return true; 89 | } 90 | catch (Win32Exception) 91 | { 92 | return false; 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CSUL/Models/Local/Game/GameDataFileInfo.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: GameDataFileInfo.cs 4 | * 创建时间: 2023年12月14日 13:11 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 游戏数据文件信息类 7 | * -------------------------------------- 8 | */ 9 | 10 | using System.IO; 11 | 12 | namespace CSUL.Models.Local.Game 13 | { 14 | /// 15 | /// 游戏数据文件信息类 16 | /// 17 | public class GameDataFileInfo 18 | { 19 | #region ---构造函数--- 20 | 21 | /// 22 | /// 实例化类 23 | /// 24 | /// 游戏数据文件(.cok)路径 25 | public GameDataFileInfo(string path) 26 | { 27 | if (!File.Exists(path)) throw new FileNotFoundException(path); 28 | DataType = GameDataFile.GetGameDataFileType(path); 29 | cokPath = path; 30 | cidPath = path + ".cid"; 31 | if (File.Exists(cidPath)) 32 | { 33 | using StreamReader reader = new(File.OpenRead(cidPath)); 34 | Cid = reader.ReadToEnd(); 35 | } 36 | else { Cid = "未找到cid文件"; } 37 | string name = Path.GetFileName(path); 38 | Name = name[..name.LastIndexOf('.')]; 39 | LastWriteTime = File.GetLastWriteTime(path).ToString("yyyy-MM-dd-HH:mm:ss"); 40 | } 41 | 42 | #endregion ---构造函数--- 43 | 44 | #region ---公共属性--- 45 | 46 | /// 47 | /// 游戏文件类型 48 | /// 49 | public GameDataFileType DataType { get; } 50 | 51 | /// 52 | /// 数据文件Id 53 | /// 54 | public string? Cid { get; } 55 | 56 | /// 57 | /// 文件名称 58 | /// 59 | public string Name { get; } 60 | 61 | /// 62 | /// 文件路径 63 | /// 64 | public string FilePath { get => cokPath; } 65 | 66 | /// 67 | /// 最后修改时间 68 | /// 69 | public string LastWriteTime { get; } 70 | 71 | #endregion ---公共属性--- 72 | 73 | #region ---私有字段--- 74 | 75 | /// 76 | /// 游戏数据文件路径 77 | /// 78 | private readonly string cokPath; 79 | 80 | /// 81 | /// 数据文件Id路径 82 | /// 83 | private readonly string cidPath; 84 | 85 | #endregion ---私有字段--- 86 | 87 | #region ---公共方法--- 88 | 89 | /// 90 | /// 删除该文件 91 | /// 92 | public void Delete() 93 | { 94 | if (File.Exists(cokPath)) File.Delete(cokPath); 95 | if (File.Exists(cidPath)) File.Delete(cidPath); 96 | } 97 | 98 | #endregion ---公共方法--- 99 | } 100 | } -------------------------------------------------------------------------------- /CSUL/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 62 | 63 | 64 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /CSUL/Windows/ModCompatibilityBox.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | 6 | namespace CSUL.Windows 7 | { 8 | /// 9 | /// ModCompatibilityBox.xaml 的交互逻辑 10 | /// 11 | public partial class ModCompatibilityBox : Window 12 | { 13 | private class ItemData 14 | { 15 | public int Index { get; set; } 16 | public string Name { get; set; } = default!; 17 | public string Version { get; set; } = default!; 18 | } 19 | 20 | public static void ShowBox(Dictionary allData, 21 | List passIndex, List wrongIndex, List unknowIndex) 22 | { 23 | ModCompatibilityBox box = new(allData, passIndex, wrongIndex, unknowIndex); 24 | box.Show(); 25 | } 26 | 27 | public ModCompatibilityBox(Dictionary allData, 28 | List passIndex, List wrongIndex, List unknowIndex) 29 | { 30 | InitializeComponent(); 31 | allLabel.Content = allData.Count; 32 | passLabel.Content = passIndex.Count; 33 | wrongLabel.Content = wrongIndex.Count; 34 | unknowLabel.Content = unknowIndex.Count; 35 | modData = new List[4] 36 | { 37 | allData.Select(x => new ItemData{Index = x.Key + 1, Name = x.Value.name, Version = x.Value.version}).ToList(), 38 | passIndex.Select(x => new ItemData{Index = x + 1, Name = allData[x].name, Version = allData[x].version}).ToList(), 39 | wrongIndex.Select(x => new ItemData { Index = x + 1, Name = allData[x].name, Version = allData[x].version}).ToList(), 40 | unknowIndex.Select(x => new ItemData { Index = x + 1, Name = allData[x].name, Version = allData[x].version }).ToList() 41 | }; 42 | listView.ItemsSource = modData[0]; 43 | } 44 | 45 | #region ---私有字段--- 46 | 47 | private readonly List[] modData; 48 | 49 | #endregion ---私有字段--- 50 | 51 | #region ---私有方法--- 52 | 53 | private void Border_MouseMove(object sender, MouseEventArgs e) 54 | { 55 | if (e.LeftButton == MouseButtonState.Pressed) 56 | { 57 | DragMove(); 58 | } 59 | } 60 | 61 | private void Close_Click(object sender, RoutedEventArgs e) 62 | { 63 | Close(); 64 | } 65 | 66 | private void ChangeToAll(object sender, MouseButtonEventArgs e) 67 | { 68 | listView.ItemsSource = modData[0]; 69 | } 70 | 71 | private void ChangeToPass(object sender, MouseButtonEventArgs e) 72 | { 73 | listView.ItemsSource = modData[1]; 74 | } 75 | 76 | private void ChangeToWrong(object sender, MouseButtonEventArgs e) 77 | { 78 | listView.ItemsSource = modData[2]; 79 | } 80 | 81 | private void ChangeToUnknow(object sender, MouseButtonEventArgs e) 82 | { 83 | listView.ItemsSource = modData[3]; 84 | } 85 | 86 | #endregion ---私有方法--- 87 | } 88 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/AssetViewModels/AssetModel.cs: -------------------------------------------------------------------------------- 1 | using CSUL.Models; 2 | using CSUL.Models.Local.Game; 3 | using CSUL.UserControls.DragFiles; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | 12 | namespace CSUL.ViewModels.AssetViewModels 13 | { 14 | internal class AssetModel : BaseViewModel 15 | { 16 | private static ComParameters CP { get; } = ComParameters.Instance; 17 | 18 | public AssetModel() 19 | { 20 | //设定命令处理方法 21 | DeleteCommand = new RelayCommand((sender) => 22 | { 23 | if (sender is not GameDataFileInfo data) return; 24 | StringBuilder sb = new(); 25 | sb.Append("资产名称: ").Append(data.Name).AppendLine(); 26 | sb.Append("资产ID: ").Append(data.Cid).AppendLine(); 27 | sb.Append("最后修改时间: ").Append(data.LastWriteTime).AppendLine(); 28 | sb.Append("资产路径: ").AppendLine().Append(data.FilePath).AppendLine(); 29 | MessageBoxResult ret = MessageBox.Show(sb.ToString(), "删除资产", MessageBoxButton.OKCancel, MessageBoxImage.Warning); 30 | if (ret == MessageBoxResult.OK) 31 | { 32 | try 33 | { 34 | data.Delete(); 35 | MessageBox.Show("删除成功"); 36 | } 37 | catch (Exception ex) 38 | { 39 | MessageBox.Show(ex.ToFormative(), "文件删除失败", 40 | MessageBoxButton.OK, MessageBoxImage.Error); 41 | } 42 | RefreshData(); 43 | } 44 | }); 45 | AddCommand = new RelayCommand(async (sender) => 46 | { 47 | string[] paths = (sender as DragFilesEventArgs ?? throw new ArgumentNullException()).Paths; 48 | await GameDataFile.Install(paths); 49 | RefreshData(); 50 | }); 51 | Refresh = new RelayCommand(sender => RefreshData()); 52 | } 53 | 54 | public ICommand AddCommand { get; } 55 | public ICommand DeleteCommand { get; } 56 | public ICommand OpenFolder { get; } = new RelayCommand((sender) => Process.Start("Explorer.exe", CP.Asset.FullName)); 57 | public ICommand Refresh { get; } 58 | 59 | private IEnumerable gameData = default!; 60 | 61 | public IEnumerable GameData 62 | { 63 | get => gameData; 64 | set 65 | { 66 | if (gameData == value) return; 67 | gameData = value; 68 | OnPropertyChanged(); 69 | } 70 | } 71 | 72 | /// 73 | /// 刷新资产数据 74 | /// 75 | private void RefreshData() => GameData = from cok in CP.Asset.GetAllFiles() 76 | where cok.Name.EndsWith(".Prefab") 77 | let data = new GameDataFileInfo(cok.FullName) 78 | select data; 79 | } 80 | } -------------------------------------------------------------------------------- /CSUL/UserControls/DragFiles/DragFile.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | 10 | namespace CSUL.UserControls.DragFiles 11 | { 12 | /// 13 | /// DragFile.xaml 的交互逻辑 14 | /// 15 | public partial class DragFile : UserControl 16 | { 17 | /// 18 | /// 文件拖动选择事件 19 | /// 20 | public event DragFilesEventHander? DragEvent; 21 | 22 | public DragFile() 23 | { 24 | InitializeComponent(); 25 | } 26 | 27 | /// 28 | /// 要筛选的文件类型名称以及对应的文件类型 29 | /// 30 | public DragFilesType FileNameWithTypes { get; set; } = default!; 31 | 32 | /// 33 | /// 要显示的图标路径 34 | /// 35 | public ImageSource Icon 36 | { 37 | get => IconImage.Source; 38 | set => IconImage.Source = value; 39 | } 40 | 41 | /// 42 | /// 要拖动的文件名称 43 | /// 44 | public object FileName 45 | { 46 | get => La.Content; 47 | set => La.Content = $"将{value}拖入此处导入,或点击导入{value}"; 48 | } 49 | 50 | private void MouseLeftButtonUp_Event(object sender, MouseButtonEventArgs e) 51 | { //文件选择 52 | OpenFileDialog dialog = new() 53 | { 54 | Title = "选择地图文件", 55 | Multiselect = true, 56 | Filter = GetFilter(FileNameWithTypes) 57 | }; 58 | if (dialog.ShowDialog() is true) 59 | { 60 | DragEvent?.Invoke(this, new(dialog.FileNames)); 61 | } 62 | } 63 | 64 | private void Drop_Event(object sender, DragEventArgs e) 65 | { //文件拖入 66 | string[] files = DragHandel(e, FileNameWithTypes.SelectMany(x => x.Value).ToArray()); 67 | DragEvent?.Invoke(this, new(files)); 68 | } 69 | 70 | /// 71 | /// 拖动处理方法 72 | /// 73 | /// 事件数据 74 | /// 文件扩展名 75 | /// 符合条件的文件列表 76 | /// 传入的e无效 77 | private static string[] DragHandel(DragEventArgs? e, params string[] extensions) 78 | { 79 | string[] data = e?.Data?.GetData(DataFormats.FileDrop) as string[] 80 | ?? throw new ArgumentException("拖动数据无效", nameof(e)); 81 | string[] files = (from file in data where extensions.Any(file.EndsWith) select file).ToArray(); 82 | return files; 83 | } 84 | 85 | /// 86 | /// 获取筛选器字符串 87 | /// 88 | private static string GetFilter(Dictionary> data) 89 | { 90 | IEnumerable types = 91 | from pair in data 92 | let ex = from name in pair.Value select '*' + name 93 | select pair.Key + '|' + string.Join(';', ex); 94 | return string.Join('|', types); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/TempDirectory.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: TempDirectory.cs 4 | * 创建时间: 2023年12月14日 13:03 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 临时文件夹类 7 | * -------------------------------------- 8 | */ 9 | 10 | using SharpCompress.Archives; 11 | using System; 12 | using System.IO; 13 | using System.Threading.Tasks; 14 | 15 | namespace CSUL.Models.Local 16 | { 17 | /// 18 | /// 临时文件夹类 19 | /// 20 | internal class TempDirectory : IDisposable 21 | { 22 | #region ---构造函数--- 23 | 24 | /// 25 | /// 获取对象实例 26 | /// 27 | public TempDirectory() 28 | { 29 | string tempDirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TempFile"); 30 | FullName = Path.Combine(tempDirPath, $"tempDir{GetHashCode()}"); 31 | Directory.CreateDirectory(FullName); 32 | } 33 | 34 | public void Dispose() 35 | { 36 | GC.SuppressFinalize(this); 37 | if (Directory.Exists(FullName)) Directory.Delete(FullName, true); 38 | } 39 | 40 | #endregion ---构造函数--- 41 | 42 | #region ---公共属性--- 43 | 44 | /// 45 | /// 临时文件夹所处路径 46 | /// 47 | public string FullName { get; private set; } 48 | 49 | /// 50 | /// 临时文件夹信息实例 51 | /// 52 | public DirectoryInfo DirectoryInfo { get => new(FullName); } 53 | 54 | /// 55 | /// 该临时文件夹是否只有单个文件 56 | /// 57 | public bool OnlySingleFile { get => Directory.GetFiles(FullName).Length == 1 && Directory.GetDirectories(FullName).Length <= 0; } 58 | 59 | /// 60 | /// 该临时文件夹是否为空 61 | /// 62 | public bool IsEmpty { get => Directory.GetFiles(FullName).Length <= 0 && Directory.GetDirectories(FullName).Length <= 0; } 63 | 64 | #endregion ---公共属性--- 65 | 66 | #region ---公共方法--- 67 | 68 | /// 69 | /// 解压文件 70 | /// 71 | /// 要解压的文件路径 72 | public async Task Decompress(string file) 73 | { 74 | if (!File.Exists(file)) throw new FileNotFoundException(file); 75 | await Task.Run(() => 76 | { 77 | using Stream stream = File.OpenRead(file); 78 | using IArchive archive = ArchiveFactory.Open(stream); 79 | archive.WriteToDirectory(FullName, new() 80 | { 81 | ExtractFullPath = true, 82 | Overwrite = true 83 | }); 84 | }); 85 | } 86 | 87 | /// 88 | /// 添加一个文件 89 | /// 90 | /// 文件路径 91 | public async Task AddFile(string file) 92 | { 93 | if (!File.Exists(file)) throw new FileNotFoundException(file); 94 | string fileName = Path.GetFileName(file); 95 | await Task.Run(() => File.Copy(file, Path.Combine(FullName, fileName))); 96 | } 97 | 98 | #endregion ---公共方法--- 99 | } 100 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/MainModel.cs: -------------------------------------------------------------------------------- 1 | using CSUL.Models.Network; 2 | using CSUL.Views; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Input; 10 | 11 | namespace CSUL.ViewModels 12 | { 13 | /// 14 | /// MainWindow的ViewModel 15 | /// 16 | internal class MainModel : BaseViewModel 17 | { 18 | public MainModel() 19 | { 20 | ViewContents = new() 21 | { //View列表 22 | new PlayView(), 23 | new MapView(), 24 | new SaveView(), 25 | new ModPlayerView(), 26 | new AssetView(), 27 | new SetView() 28 | }; 29 | viewContent = ViewContents[0]; 30 | ViewCommand = new RelayCommand((x) => 31 | { 32 | //判断传参x是否符合条件 33 | if (x is not string text || !int.TryParse(text, out int num)) throw new ArgumentNullException(nameof(x)); 34 | ChangeViewContent(num); 35 | }); 36 | Task.Run(CheckVersion); 37 | } 38 | 39 | #region ---公共属性--- 40 | 41 | private UserControl viewContent; 42 | 43 | /// 44 | /// 当前显示的页面 45 | /// 46 | public UserControl ViewContent 47 | { 48 | get => viewContent; 49 | set 50 | { 51 | viewContent = value; 52 | OnPropertyChanged(); 53 | } 54 | } 55 | 56 | /// 57 | /// 页面列表 58 | /// 59 | public List ViewContents { get; } 60 | 61 | public ICommand ViewCommand { get; } 62 | 63 | #endregion ---公共属性--- 64 | 65 | #region ---私有方法--- 66 | 67 | /// 68 | /// 改变当前显示的页面 69 | /// 70 | /// 目标页面索引 71 | private void ChangeViewContent(int index) 72 | { 73 | ViewContent = ViewContents[index]; 74 | } 75 | 76 | /// 77 | /// 检查CSUL版本 78 | /// 79 | /// 80 | private async Task CheckVersion() 81 | { 82 | Version? latest = await NetworkData.GetCsulLastestVersion(); 83 | if (latest is null) return; 84 | Version? version = CsulVersion; 85 | if (version is null) return; 86 | if (version < latest) 87 | { 88 | StringBuilder builder = new(); 89 | builder.Append("CSUL有新版本更新").AppendLine(); 90 | builder.Append("当前版本: ").Append(version).AppendLine(); 91 | builder.Append("最新版本: ").Append(latest).AppendLine(); 92 | builder.Append("点击确认跳转到CSUL发布页面"); 93 | MessageBoxResult ret = MessageBox.Show(builder.ToString(), "更新提示", MessageBoxButton.OKCancel, MessageBoxImage.Information); 94 | if (ret == MessageBoxResult.OK) 95 | { 96 | System.Diagnostics.Process.Start("explorer.exe", "https://www.cslbbs.net/csul/"); 97 | } 98 | } 99 | } 100 | 101 | #endregion ---私有方法--- 102 | } 103 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/MapViewModels/MapModel.cs: -------------------------------------------------------------------------------- 1 | using CSUL.Models; 2 | using CSUL.Models.Local.Game; 3 | using CSUL.UserControls.DragFiles; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | 12 | namespace CSUL.ViewModels.MapViewModels 13 | { 14 | /// 15 | /// MapView的ViewModel 16 | /// 17 | public class MapModel : BaseViewModel 18 | { 19 | private static ComParameters CP { get; } = ComParameters.Instance; 20 | 21 | public MapModel() 22 | { 23 | //获取初始数据 24 | RefreshData(); 25 | 26 | //设定命令处理方法 27 | DeleteCommand = new RelayCommand((sender) => 28 | { 29 | if (sender is not GameDataFileInfo data) return; 30 | StringBuilder sb = new(); 31 | sb.Append("地图名称: ").Append(data.Name).AppendLine(); 32 | sb.Append("地图ID: ").Append(data.Cid).AppendLine(); 33 | sb.Append("最后修改时间: ").Append(data.LastWriteTime).AppendLine(); 34 | sb.Append("地图路径: ").AppendLine().Append(data.FilePath).AppendLine(); 35 | MessageBoxResult ret = MessageBox.Show(sb.ToString(), "删除存档", MessageBoxButton.OKCancel, MessageBoxImage.Warning); 36 | if (ret == MessageBoxResult.OK) 37 | { 38 | try 39 | { 40 | data.Delete(); 41 | MessageBox.Show("删除成功"); 42 | } 43 | catch (Exception ex) 44 | { 45 | MessageBox.Show(ex.ToFormative(), "文件删除失败", 46 | MessageBoxButton.OK, MessageBoxImage.Error); 47 | } 48 | RefreshData(); 49 | } 50 | }); 51 | AddCommand = new RelayCommand(async (sender) => 52 | { 53 | string[] paths = (sender as DragFilesEventArgs ?? throw new ArgumentNullException()).Paths; 54 | await GameDataFile.Install(paths); 55 | RefreshData(); 56 | }); 57 | Refresh = new RelayCommand(sender => RefreshData()); 58 | } 59 | 60 | public ICommand AddCommand { get; } 61 | public ICommand DeleteCommand { get; } 62 | public ICommand OpenFolder { get; } = new RelayCommand((sender) => Process.Start("Explorer.exe", CP.Maps.FullName)); 63 | public ICommand Refresh { get; } 64 | 65 | private IEnumerable gameData = default!; 66 | 67 | public IEnumerable GameData 68 | { 69 | get => gameData; 70 | set 71 | { 72 | if (gameData == value) return; 73 | gameData = value; 74 | OnPropertyChanged(); 75 | } 76 | } 77 | 78 | #region ---私有方法--- 79 | 80 | /// 81 | /// 刷新地图数据 82 | /// 83 | private void RefreshData() => GameData = from cok in CP.Maps.GetAllFiles() 84 | where cok.Name.EndsWith(".cok") 85 | let data = new GameDataFileInfo(cok.FullName) 86 | select data; 87 | 88 | #endregion ---私有方法--- 89 | } 90 | } -------------------------------------------------------------------------------- /CSUL/ViewModels/SaveViewModels/SaveModel.cs: -------------------------------------------------------------------------------- 1 | using CSUL.Models; 2 | using CSUL.Models.Local.Game; 3 | using CSUL.UserControls.DragFiles; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | 12 | namespace CSUL.ViewModels.SaveViewModels 13 | { 14 | /// 15 | /// SaveView的ViewModel 16 | /// 17 | public class SaveModel : BaseViewModel 18 | { 19 | private static ComParameters CP { get; } = ComParameters.Instance; 20 | 21 | public SaveModel() 22 | { 23 | //获取初始数据 24 | RefreshData(); 25 | 26 | //设定命令处理方法 27 | DeleteCommand = new RelayCommand((sender) => 28 | { 29 | if (sender is not GameDataFileInfo data) return; 30 | StringBuilder sb = new(); 31 | sb.Append("存档名称: ").Append(data.Name).AppendLine(); 32 | sb.Append("存档ID: ").Append(data.Cid).AppendLine(); 33 | sb.Append("最后修改时间: ").Append(data.LastWriteTime).AppendLine(); 34 | sb.Append("存档路径: ").AppendLine().Append(data.FilePath).AppendLine(); 35 | MessageBoxResult ret = MessageBox.Show(sb.ToString(), "删除存档", MessageBoxButton.OKCancel, MessageBoxImage.Warning); 36 | if (ret == MessageBoxResult.OK) 37 | { 38 | try 39 | { 40 | data.Delete(); 41 | MessageBox.Show("删除成功"); 42 | } 43 | catch (Exception ex) 44 | { 45 | MessageBox.Show(ex.ToFormative(), "文件删除失败", 46 | MessageBoxButton.OK, MessageBoxImage.Error); 47 | } 48 | RefreshData(); 49 | } 50 | }); 51 | AddCommand = new RelayCommand(async (sender) => 52 | { 53 | string[] paths = (sender as DragFilesEventArgs ?? throw new ArgumentNullException()).Paths; 54 | await GameDataFile.Install(paths); 55 | RefreshData(); 56 | }); 57 | Refresh = new RelayCommand(sender => RefreshData()); 58 | } 59 | 60 | public ICommand DeleteCommand { get; } 61 | public ICommand AddCommand { get; } 62 | public ICommand OpenFolder { get; } = new RelayCommand((sender) => Process.Start("Explorer.exe", CP.Saves.FullName)); 63 | public ICommand Refresh { get; } 64 | 65 | private IEnumerable gameData = default!; 66 | 67 | public IEnumerable GameData 68 | { 69 | get => gameData; 70 | set 71 | { 72 | if (gameData == value) return; 73 | gameData = value; 74 | OnPropertyChanged(); 75 | } 76 | } 77 | 78 | #region ---私有方法--- 79 | 80 | /// 81 | /// 刷新存档数据 82 | /// 83 | public void RefreshData() => GameData = from cid in CP.Saves.GetAllFiles() 84 | where cid.Name.EndsWith(".cok") 85 | let data = new GameDataFileInfo(cid.FullName) 86 | select data; 87 | 88 | #endregion ---私有方法--- 89 | } 90 | } -------------------------------------------------------------------------------- /CSUL/ViceCommand.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ViceCommand.cs 4 | * 创建时间: 2023年12月16日 9:45 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 副进程传参处理 7 | * -------------------------------------- 8 | */ 9 | 10 | using CSUL.Models.Network; 11 | using CSUL.Models.Network.CB; 12 | using CSUL.Windows; 13 | using System.Collections.Generic; 14 | using System.Linq; 15 | using System.Text.Json; 16 | using System.Threading.Tasks; 17 | using System.Windows.Threading; 18 | using ParPairs = System.Collections.Generic.Dictionary; 19 | 20 | namespace CSUL 21 | { 22 | /// 23 | /// 副进程传参处理 24 | /// 25 | internal static class ViceCommand 26 | { 27 | /// 28 | /// 解析命令 29 | /// 30 | public static async Task Parse(string command, Dispatcher dispatcher) 31 | { 32 | try 33 | { 34 | int index = command.IndexOf('?'); 35 | Queue keys; 36 | ParPairs pars; 37 | if (index == -1 || index + 1 == command.Length) 38 | { //无参数 39 | keys = new(command.Split("/").Where(x => x.Length > 0)); 40 | pars = new(); 41 | } 42 | else 43 | { //有参数 44 | keys = new(command[..index].Split("/").Where(x => x.Length > 0)); 45 | IEnumerable data = command[(index + 1)..].Split('&').Where(x => x.Length > 0); 46 | pars = await GetDicFromData(data); 47 | } 48 | switch (keys.Dequeue()) 49 | { 50 | case "installresources": await InstallResources(pars, dispatcher); break; 51 | } 52 | } 53 | catch { } 54 | } 55 | 56 | #region ---私有方法--- 57 | 58 | /// 59 | /// 安装资源 60 | /// 61 | private static async Task InstallResources(ParPairs pairs, Dispatcher dispatcher) 62 | { 63 | if (!pairs.TryGetValue("json", out string? str)) return; 64 | using JsonDocument json = JsonDocument.Parse(str); 65 | //获取需要安装的所有资源id 66 | IEnumerable ids = json.RootElement.EnumerateArray().Select(x => x.ValueKind == JsonValueKind.String ? int.Parse(x.GetString()!) : x.GetInt32()); 67 | List datas = await NetworkData.GetCbResourceDatas(ids); 68 | await dispatcher.InvokeAsync(() => 69 | { //同步线程打开UI 70 | try 71 | { 72 | CbResourceInstaller installer = new(datas); 73 | installer.Show(); 74 | } 75 | catch { } 76 | }); 77 | } 78 | 79 | /// 80 | /// 从参数数据得到字典 81 | /// 82 | private static async Task GetDicFromData(IEnumerable data) 83 | { 84 | return await Task.Run(() => 85 | { 86 | IEnumerable> pairs = data.Select(x => 87 | { 88 | int index = x.IndexOf('='); 89 | if (index == -1) return default; 90 | if (index == x.Length - 1) return default; 91 | return KeyValuePair.Create(x[..index], x[(index + 1)..]); 92 | }); 93 | return new ParPairs(pairs); 94 | }); 95 | } 96 | 97 | #endregion ---私有方法--- 98 | } 99 | } -------------------------------------------------------------------------------- /CSUL/Models/Local/ModPlayer/ModPlayerManager.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: ModPlayerManager.cs 4 | * 创建时间: 2023年12月16日 19:27 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: 模组播放集管理器 7 | * -------------------------------------- 8 | */ 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Text.Json; 15 | using System.Windows; 16 | 17 | namespace CSUL.Models.Local.ModPlayer 18 | { 19 | /// 20 | /// 模组播放集管理器 21 | /// 22 | internal class ModPlayerManager 23 | { 24 | #region ---构造函数--- 25 | 26 | /// 27 | /// 实例化对象 28 | /// 29 | /// 播放集根目录 30 | public ModPlayerManager(string playerRootPath) 31 | { 32 | this.playerRootPath = playerRootPath; 33 | ReloadPlayers(); 34 | } 35 | 36 | #endregion ---构造函数--- 37 | 38 | #region ---私有字段--- 39 | 40 | private readonly string playerRootPath; 41 | private readonly Dictionary players = new(); 42 | 43 | #endregion ---私有字段--- 44 | 45 | #region ---公共方法--- 46 | 47 | /// 48 | /// 获取所有的加载器 49 | /// 50 | /// 51 | public List GetModPlayers() => players.Select(x => x.Value).ToList(); 52 | 53 | /// 54 | /// 加载播放集 55 | /// 56 | /// 播放集目录 57 | /// 58 | public void LoadModPlayer(string playerPath) 59 | { 60 | string config = Path.Combine(playerPath, "modPlayer.config"); 61 | if (!File.Exists(config)) throw new FileNotFoundException(config); 62 | using Stream stream = File.OpenRead(config); 63 | using JsonDocument json = JsonDocument.Parse(stream); 64 | JsonElement root = json.RootElement; 65 | ModPlayerType playerType = Enum.Parse(root.GetProperty(typeof(ModPlayerType).Name).GetString()!); 66 | BaseModPlayer player = playerType switch 67 | { 68 | ModPlayerType.BepInEx => new BepInEx.BepModPlayer(), 69 | ModPlayerType.Pmod => new Pmod.PmodPlayer(), 70 | _ => throw new Exception("不受支持的播放集类型") 71 | }; 72 | player.Initialize(playerPath); 73 | int hashCode = player.GetHashCode(); 74 | if (players.ContainsKey(hashCode)) throw new Exception("该播放集已加载"); 75 | players.Add(hashCode, player); 76 | } 77 | 78 | /// 79 | /// 重新加载播放集 80 | /// 81 | public void ReloadPlayers() 82 | { 83 | players.Clear(); 84 | BaseModPlayer nullPlayer = new NullModPlayer(); 85 | nullPlayer.Initialize("禁用播放集"); 86 | players.Add(nullPlayer.GetHashCode(), nullPlayer); 87 | string[] pathes = Directory.GetDirectories(playerRootPath); 88 | foreach (string playerPath in pathes) 89 | { 90 | try 91 | { 92 | LoadModPlayer(playerPath); 93 | } 94 | catch (Exception ex) 95 | { 96 | MessageBox.Show(ex.ToFormative($"路径 {playerPath}"), "播放集加载出错", MessageBoxButton.OK, MessageBoxImage.Warning); 97 | } 98 | } 99 | } 100 | 101 | #endregion ---公共方法--- 102 | } 103 | } -------------------------------------------------------------------------------- /CSUL/Views/PlayView.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 45 | 46 | 47 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 59 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /CSUL/Models/DirectoryEx.cs: -------------------------------------------------------------------------------- 1 | /* CSUL 标准文件头注释 2 | * -------------------------------------- 3 | * 文件名称: DirectoryEx.cs 4 | * 创建时间: 2023年12月14日 20:58 5 | * 创建开发: ScifiBrain 6 | * 文件介绍: Directory扩展类 提供Directory的一些扩展方法 7 | * -------------------------------------- 8 | */ 9 | 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | 14 | namespace CSUL.Models 15 | { 16 | /// 17 | /// Directory扩展类 提供Directory的一些扩展方法 18 | /// 19 | internal static class DirectoryEx 20 | { 21 | #region ---文件夹复制--- 22 | 23 | /// 24 | /// 递归复制文件夹 25 | /// 26 | /// 文件夹路径 27 | /// 目标路径 28 | /// 是否覆写 29 | public static void CopyTo(string path, string destPath, bool overwrite = false) 30 | => CopyTo(new DirectoryInfo(path), destPath, overwrite); 31 | 32 | /// 33 | /// 递归复制文件夹 34 | /// 35 | /// 文件夹路径 36 | /// 目标路径 37 | /// 是否覆写 38 | public static void CopyTo(this DirectoryInfo dir, string destPath, bool overwrite = false) 39 | { 40 | if (!dir.Exists) throw new DirectoryNotFoundException(dir.FullName); 41 | void RecursionCopy(DirectoryInfo root, string relativePath) 42 | { //递归复制 43 | DirectoryInfo total = new(Path.Combine(destPath, relativePath)); 44 | if (!total.Exists) total.Create(); 45 | foreach (FileInfo file in root.GetFiles()) 46 | file.CopyTo(Path.Combine(destPath, relativePath, file.Name), overwrite); 47 | foreach (DirectoryInfo dir in root.GetDirectories()) 48 | RecursionCopy(dir, relativePath + $"{dir.Name}\\"); 49 | } 50 | RecursionCopy(dir, ""); 51 | } 52 | 53 | #endregion ---文件夹复制--- 54 | 55 | #region ---递归获取文件夹下的所有文件--- 56 | 57 | /// 58 | /// 递归获取文件夹下的所有文件 59 | /// 60 | /// 文件夹路径 61 | /// 忽略的文件夹 62 | public static FileInfo[] GetAllFiles(string dirPath, params string[] Ignored) 63 | => GetAllFiles(new DirectoryInfo(dirPath), Ignored.Select(x => new DirectoryInfo(x)).ToArray()); 64 | 65 | /// 66 | /// 递归获取文件夹下的所有文件 67 | /// 68 | /// 文件夹 69 | /// 忽略的文件夹 70 | public static FileInfo[] GetAllFiles(this DirectoryInfo dir, params DirectoryInfo[] Ignored) 71 | { 72 | if (!dir.Exists) throw new DirectoryNotFoundException(dir.FullName); 73 | List files = new(); 74 | void RecursionSearch(DirectoryInfo root) 75 | { 76 | files.AddRange(root.GetFiles()); 77 | foreach (DirectoryInfo dir in root.GetDirectories().Where(x => !Ignored.Any(y => x.FullName == y.FullName))) 78 | { 79 | RecursionSearch(dir); 80 | } 81 | } 82 | RecursionSearch(dir); 83 | return files.ToArray(); 84 | } 85 | 86 | #endregion ---递归获取文件夹下的所有文件--- 87 | 88 | #region ---判断是否为文件夹或者文件--- 89 | 90 | /// 91 | /// 判断该路径是否为文件夹 92 | /// 93 | /// 路径 94 | /// true: 为文件夹, false: 为文件 95 | /// 该路径不存在 96 | public static bool IsDirectory(string path) 97 | { 98 | if (Directory.Exists(path)) return true; 99 | else if (File.Exists(path)) return false; 100 | else throw new FileNotFoundException(path); 101 | } 102 | 103 | #endregion ---判断是否为文件夹或者文件--- 104 | } 105 | } -------------------------------------------------------------------------------- /CSUL/Views/MapView.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |