[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1temdeireyiaa":3,"$f1gl5rn3mc3583":9,"$f3i8dru603fjlc":44,"$f1pirie4csvydk":45},{"postCount":4,"commentCount":5,"tagCount":6,"categoryCount":7,"totalViews":8},10,1,11,5,54,{"id":10,"title":11,"slug":12,"excerpt":13,"cover":14,"status":15,"publishedAt":16,"viewCount":17,"likeCount":17,"favoriteCount":17,"createdAt":18,"updatedAt":19,"category":20,"author":23,"tags":26,"_count":41,"content":42,"attachments":43},8,"Hello KSP Launcher v1.3.0","hello-ksp-launcher-v1-3-0","本发行版将存档管理重构为实例详情页的子选项卡，升级备份目录方案以避免冲突，使内置更新器能够自我更新，并优化了自定义标题栏和关于页面。","","published","2026-09-12T08:55:39.126Z",0,"2026-09-12T08:04:48.377Z","2026-09-19T04:12:20.912Z",{"id":21,"name":22,"slug":22},7,"发布日志",{"id":5,"username":24,"nickname":24,"avatar":25},"zhuwenqian",null,[27,31,35,38],{"id":28,"name":29,"slug":30},3,"GUI","gui",{"id":32,"name":33,"slug":34},4,"Qt6","qt6",{"id":36,"name":37,"slug":37},6,"C++",{"id":39,"name":40,"slug":40},13,"KSP",{"comments":17},"# 发行说明\n\n> **重要提示 \u002F Important Note:** 本发行版包含更新器（`updater.exe`）的更新。如果您仍在使用较早的 v1.2.0 系列版本，由于其自带的老版更新器无法获取到自身的新二进制，自动更新可能无法替换本组件——届时请手动替换 `updater.exe`。\n>\n\n## v1.3.0 — 存档管理重构、更新器自更新与 UI 优化（2026-09-12）\n\n本发行版将存档管理重构为实例详情页的子选项卡，升级备份目录方案以避免冲突，使内置更新器能够自我更新，并优化了自定义标题栏和关于页面。\n\n### 存档管理现在位于实例详情页内\n\n“存档管理”以前是一个独立的全屏页面（`SavesListPage`），重复了实例详情侧边栏，并且必须通过 Mod 管理选项卡来路由导出\u002F导入\u002F浏览。现在它是实例详情页的二级选项卡，与游戏设置和 Mod 管理一致。\n\n- **新增 `savestabpage.{h,cpp}`** — 仅包含存档列表（`loadSaves`、双击 `saveSelected`）。对象名 `savesTabPage`。\n- **`instancedetailpage.{h,cpp}`** — 向其 `QStackedWidget` 添加第 4 个选项卡（存档管理）；侧边栏“存档管理”按钮现在调用 `showSection(4)`；`showSection` 增加了 idx=4 选择和 `loadSaves`；新增 `saveSelected` 信号；移除了 `savesManageRequested` 信号。\n- **`mainwindow.{h,cpp}`** — 移除了 `SavesListPage` 以及 `onSavesManageRequested` \u002F `onSavesNavToDetail` \u002F `onSavesModpackAction` \u002F `onBackFromSavesList` 路由；`InstanceDetailPage::saveSelected` 直接连接到 `onSaveSelected`；`onBackFromSaveDetail` 返回到实例详情页并停留在存档选项卡。\n- **导出\u002F导入\u002F浏览** — 在存档选项卡内就地触发，复用实例详情侧边栏的 ZIP\u002FCKAN 和浏览菜单（同一个 `ModpackController`），无需切换选项卡。\n- **移除** `saveslistpage.{h,cpp}`；相应清理了 `CMakeLists.txt` 和 `dark\u002Flight.qss` 中的 `savesListPage` 规则。\n- **测试** — 两个测试套件均可构建；UI 已手动验证：存档管理不会导航离开，导出\u002F导入就地打开，从存档详情返回时回到存档选项卡。\n\n### 备份目录升级（防冲突）\n\n备份以前位于 `backups\u002F{instanceName}\u002F{saveName}\u002F*.zip`；两个名称相似的实例如果具有相同存档名，可能会互相覆盖备份。现在第一级会附加实例 id 的前 8 个字符（第一个 UUID 段，例如 `550e8400`）：`backups\u002F{instanceName-idPrefix}\u002F{saveName}\u002F*.zip`。实例 id 全局唯一，并且重命名不会改变它，因此即使显式重命名后，备份仍可定位。\n\n- **目录计算**（`instancemanager_backup.cpp` `getBackupDirForSave`）— `sanitizeFileName(instanceName) + \"-\" + instanceId.left(8)`；存档名层级保持普通名称。空 id 会回退为仅使用实例名，以增强健壮性。\n- **惰性迁移**（`migrateLegacyBackups`）— 两种旧布局（`backups\u002F{instanceName}\u002F{saveName}` 和更早的单层 `backups\u002F{saveName}`）在访问时迁移；如果目标已存在（名称冲突），则跳过迁移以保持幂等；迁移的 `*.zip` 文件会被移动，旧目录会被清空。\n- **实例 id 传递** — 与备份相关的 `InstanceManager` 接口（`getBackupDirForSave` \u002F `listBackups` \u002F `createBackup` \u002F `restoreBackup`）新增了 `instanceId` 参数；`saveSelected` \u002F `setSavePath` 调用链 `savestabpage → instancedetailpage → mainwindow → savedetailpage` 会一路传递 id（`savestabpage` 直接使用 `m_instance.id`）。\n- **备份 zip 文件命名不变** — `saveName[_note]_yyyyMMdd_HHmmss.zip`；只有目录层级发生了变化。\n\n### 内置更新器现在可以自我更新\n\n更新器被排除在自身的保留列表之外（它在清理\u002F移动期间总是跳过 `updater.exe`），因此无法替换自身。此前发行说明要求 v1.2.0 用户手动替换它。现在，当发行说明正文标记“本发行版附带更新后的内置更新器”时，更新会**保留更新包 zip**，重新启动后的新启动器会静默替换 `updater.exe`——无需手动步骤。\n\n- **检测**（`updatemanager.{h,cpp}` `bodyIndicatesUpdaterUpdate`）— 匹配发行说明正文中的双语短语（中文“本 Release 含有更新器”或英文“ships an updated built-in updater”）；命中后 `ReleaseInfo.updaterUpdate=true`。\n- **保留 zip**（`updatemanager.cpp` `applyUpdate`）— 在 Windows 上，如果 `updaterUpdate` 为真，则向 `.updater_update\u002F` 写入 `updater_pending` 标记（包含 zip 文件名），并向更新器传递 `--keep-zip`；`src\u002Fupdater\u002Fmain.cpp` 解析 `--keep-zip`，完成后不再删除 `.updater_update` 目录。\n- **新启动器替换**（`main.cpp`，启动守卫之后，仅 Windows）— `applyPendingUpdaterUpdate` 检测 `updater_pending` → 在保留的 zip 中按 basename 查找 `updater.exe`（容忍单一顶层发布布局，大小写不敏感）→ 解压到临时文件 → 覆盖应用目录中的 `updater.exe` → 成功后完全删除 `.updater_update`。失败时弹出对话框提供“重试 \u002F 忽略”（忽略会调用 `cleanupPendingUpdaterUpdate`）；通常静默且非阻塞。\n- **Zip 条目 API**（`updatemanager.{h,cpp}`）— `findZipEntryByBaseName` \u002F `extractZipEntry`（基于 miniz），用于定位并解压任意深度的更新器条目。\n- **翻译** — 新增 7 个 UI 字符串并完成 en_US 翻译（`556` 个全部已翻译），`.qm` 已重建并同步到 `dist\u002Ftranslations`。\n- **测试** — `test_launcher` 新增 `updaterUpdateBodyDetection` 和 `zipUpdaterEntry`（双语言检测、普通日志无误报、任意深度查找、大小写不敏感、缺失条目、解压内容匹配）。两个 `ctest` 套件均通过。\n\n### 自定义标题栏边缘缩放已修复\n\n`Qt::FramelessWindowHint` 会剥离启用窗口缩放的本地 `WS_THICKFRAME` 样式，因此无边框窗口无法从其边缘调整大小。\n\n- **补丁**（`mainwindow.{h,cpp}` `applyNativeFrameStyle`，仅 Windows）— 保留无边框自定义绘制外观，然后在首次 `showEvent` 之后以及 `toggleMaximize` 中，通过 `SetWindowLongPtr(GWL_STYLE)` 重新添加 `WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX`，并调用 `SetWindowPos(SWP_FRAMECHANGED)`，以便系统重新计算非客户区。刻意不恢复 `WS_CAPTION`，因此不会出现系统标题栏。\n- **命中测试**（`WM_NCHITTEST`）— 边缘\u002F角落抓取区域从 6px 加宽到 8px，以配合恢复的 `WS_THICKFRAME`，实现平滑的原生缩放。\n- **结果** — 在保留自定义绘制的无边框标题栏的同时，四条边均可自由、平滑地调整大小。\n\n### 关于页面条目图标\n\n- **鸣谢部分** — KSP-CKAN 团队（`ckan.png`）、Hello Minecraft Launcher 项目（`hmcl.png`）；两个镜像 gh-proxy.com 和 ghfast.top 共用 GitHub 图标（`github.png`）。\n- **依赖部分** — Qt 6（`qt6.png`）。\n- **实现** — 4 个 PNG 注册到 `resources\u002Fresources.qrc`；`aboutpage.cpp` 在其构造函数中加载 `QPixmap`，并将其作为第 4 个参数传给 `makeLinkRow`。\n\n### 完整英文（en_US）本地化\n\n源码包含 548 个 UI 字符串，但 `translations\u002Fhello_ksp_launcher_en_US.ts` 只有 506 个。使用 `lupdate` 重新提取（`build\u002Fen_fresh.ts`）并比较，以补全近期版本新增的 43 个缺失英文翻译（崩溃日志分析、整合包导入\u002F导出、调试模式、推荐\u002F建议对话框、批量与卸载进度、注册表锁提示、自更新 SHA256 等）。现在全部 548 个字符串都有翻译（`lrelease` 报告 0 个未完成）；现有 506 个未改动。`.qm` 已重建并同步到 `dist\u002Ftranslations`。由于英文是默认语言，首次启动现在会显示完整的英文 UI。\n\n### 技术栈\n\n- **框架**：Qt 6（Widgets、Svg、Network、Concurrent）\n- **语言**：C++17\n- **构建**：CMake ≥ 3.16；Windows 使用 Qt 自带的 mingw 工具链\n- **测试**：`test_libckan` + `test_launcher`（全部通过）\n\n**完整变更日志**：https:\u002F\u002Fgithub.com\u002FZhuwenqian\u002FHello-KSP-Launcher\u002Fcompare\u002Fv1.2.3...v1.3.0",[],[],{"prev":46,"next":49},{"id":21,"title":47,"slug":48},"ApolloTab README","apollotab-readme",{"id":50,"title":51,"slug":52},9,"libckan API 文档","libckan-api-文档"]