项目结构与分层
星云启动器的代码分三层,界线画得很死:Starcloud.Core 装全部业务逻辑,Starcloud.App 只画界面,Starcloud.Bridge 是给非 .NET 客户端复用 Core 的命令行外壳。
| 工程 | 目标框架 | 职责 |
|---|---|---|
Starcloud.Core | net8.0 | 纯 C# 类库。下载、安装、启动、账户、Java、ModLoader、Nova、插件内核 |
Starcloud.App | net8.0-windows10.0.19041.0 | WinUI 3 界面 + Windows 特有能力(录屏、窗口亚克力、WebView2 登录窗口) |
Starcloud.Bridge | net8.0 | 无界面 CLI,用 stdio 行式 JSON 把 Core 暴露给 Flutter / Android 端 |
Core 只有三个第三方包:Jint(JS 沙箱)、System.Security.Cryptography.ProtectedData(DPAPI)、System.Text.Encoding.CodePages(GBK 回退解码),外加皮肤 GPU 光栅化用的 Vortice.Direct3D11 一族(只在 Windows 上真创建设备,其它平台自动退回软件光栅化)。
桌面端为什么没有 IPC
这是 2026-09 从 Tauri + Rust 迁到 .NET 之后最大的结构简化。旧架构里 Rust 核心是独立进程,界面通过 Tauri command 调它,每次调用都要序列化、跨进程、回来再反序列化;下载进度这种高频事件更是要走事件系统转一手。
现在 WinUI 界面和 Core 在同一个进程、同一个 .NET 类型系统里:
Installer = new InstallerService(Download);
Launcher = new GameLauncher(Installer, Java);进度回调就是 IProgress<BatchProgress>,取消就是 CancellationToken,不需要任何序列化协议。Core 里也不需要为跨进程边界做妥协——不用把「下载进度」设计成字符串事件。
代价是 Core 必须保持纯 .NET:net8.0、不引用 WinUI、不碰注册表和窗口句柄。做不到的(录屏、窗口特效)留在 App 层,通过接口注入回 Core。legacy/ 里是归档的 Tauri + Rust + Preact 实现和后来的 Uno 跨平台路线,只作参考。
Core 的三条纪律
写在 Kernel.cs 头部,评审就按这三条卡:
- 内核里不出现具体功能的名字。 出现下载、Nova、版本,就是设计错误。内核只认契约、贡献点、事件。
- 激活顺序:引擎 → 功能 → 助手 → UI,依赖先于被依赖者,懒插件等第一次用到。
- 停用必须可逆:契约注销 + 贡献撤下 + 订阅断开,重启用不能重复注册。
同一套纪律也约束 Core 与 App 的关系:内核不依赖 UI(Core 不引用 Microsoft.UI.Xaml 任何一个类型,界面类型只作为 Payload 挂在贡献点上);外壳不写业务(判断、算法、状态机一律在 Core);大改动先写设计稿再动代码(docs/designs/ 下按日期归档单点决策)。
一段逻辑该放 Core 还是 UI
这段逻辑换到 Linux 端还需不需要?
需要 → 放 Core。Bridge 和 Android 端要复用它,接口和数据模型都得跨平台。 不需要 → 放 App。WinUI 特有能力留在 App 层,Core 通过接口接回去。
按这条切完之后,Bridge 才可能存在——它整个就是「Core 换个壳」。
规模与数据根
全仓(排除 obj/ bin/ node_modules/):C# 533 文件 / 122,848 行,XAML 83 文件 / 22,484 行,其中 Core 161 文件 / 31,504 行;测试 582 个 [Fact] / [Theory]。Core 只占 C# 的四分之一,但所有跨端能力都在这里。
LauncherPaths.DataDir 优先读环境变量 STARCLOUD_DATA_DIR,没有才按平台取默认值(Windows %APPDATA%/starcloud-launcher、macOS ~/Library/Application Support、Linux XDG_DATA_HOME)。单测靠它把每个用例隔在自己的临时目录里;Bridge 也用它——Flutter 壳注入同一个数据根,两端共享一份 accounts.json 和设置。