Files
Unlucky-Launcher/crates/core/src/game/mod.rs
T
AskaEth f217d83680 feat(saves): 实例存档 / 截图的只读列表与打开文件夹(M235 STOREMVP)
用户诉求是 PCL 差距四项里的「存档截图 / 导入」,M230 审计确认三层全缺。塔裁决 U3
把这一版钉成「只读列表 + 打开文件夹」,不做缩略图墙(缩略图要引图片解码依赖,而
AGENTS §2 只允许 palette-extract 引 image 类依赖;截图/导入产物形态也没定型)。

core
- `game::saves`(新):只读列 `<run_dir>/saves`(世界目录)与 `<run_dir>/screenshots`
  (图片文件)。只调 symlink_metadata / read_dir / metadata,不建目录、不写文件、不读
  文件内容(`icon.png` 只判存在)。目录不存在 ⇒ 空列表不是错误;存在但不可用(符号
  链接 / 不是目录 / 读不了)⇒ 具名错误。条目级异常逐条登记原因,不静默丢。
- `platform::opener`(新):`open_dir` 是**目录**打开器的唯一收敛点(explorer / open /
  xdg-open),内部复用 `child::quiet`,cfg 分支只此一处。塔明确要求按 AGENTS §6 放
  platform 而不是业务模块,并要一条可执行判据防退化
  (`the_launcher_literals_live_here_only` 扫 `crates/core/src`:除本文件外不许出现
  `"explorer"` / `"xdg-open"` 字面量)。

presenter
- `dto::saves`:SavesCommand(List / OpenFolder)、SavesListView、SavesFailureView
  (结构 + OS 原文,不拼中文)、`SavesListView` 的计数助手。
- `host::saves`:走 `job::spawn`,UI 只收快照;运行目录仍是
  `isolation::derive_game_dir` 的同一个答案(与启动 / mod 列表同源,**不写盘**)。
  「打开文件夹」打开哪个目录由 host 定:`saves/` → `screenshots/` → 运行目录。
  失败分三类(InstanceUnavailable / DirUnusable / OpenFailed),可重试。
- `state::saves`:快照整体替换、迟到结果丢弃、失败保留绑定(重试才有意义)。
- 接线:CoreEvent::Saves / AppCommand::Saves / HostTask::Saves。

app-iced
- `pages::instances::saves`:详情页 SAVES 卡片(MODS 之后、启动参数之前;**不套第二层
  滚动条**,整列那一条是唯一入口)。世界行带 UTC 时间与「有图标」,截图行带大小与时间;
  第一行固定画「这一版只读」(U3 边界让用户看得见)。读盘失败画一条红字 + 「重试」;
  打开文件夹失败**不画**重试(那个动作的重试就是再点一次按钮)。
- 接线:Message::{ReloadSaves, OpenSavesFolder}、`sync_saves`(与 `sync_mods` 同一判据)、
  Model::saves、`saves_shot` 出图夹具(ULL_SAVES_FIXTURE=1)。

i18n
- `InstancesSaves*` 20 条中英成对,catalog 与金表在**同一条锚点**后追加、逐字节一致。

证据
- core 只读判据:`game::saves::tests` 在真盘建世界/截图,调用前后各拍一次
  (相对路径, 是否目录, 字节数, mtime)指纹并逐条比对;另有用例钉「目录缺失不建目录」。
- presenter:`host::saves::tests` 真盘端到端(custom 档位派生运行目录 → 快照)。
- GUI:`--software-renderer` 1180×760 浅/深各一张 + 1180×1280 两张「整块卡片可见」
  (1180×760 下 SAVES 卡片位于折叠线附近,只能看到卡头;第二对用 ULL_WINDOW_SIZE
  把窗口拉高,口径与 M98 的矩阵一致),落 `DevDocs/artifacts/M235/`。

没做的(U3 边界内,见 handoff):缩略图墙、导入、删除/重命名、`level.dat` 的
LevelName 解析(世界名暂时就是目录名)。
2026-11-11 06:00:27 +08:00

92 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 版本与实例仓库:不可变快照 + 单写者草稿、版本 JSON 继承解析、实例目录布局。
//!
//! 设计定稿:`DevDocs/PlanDoc/04-data-and-config.md` §4(实例模型)、
//! `DevDocs/PlanDoc/01-tech-stack-and-architecture.md` §3(Snapshot/Draft 纪律),
//! 源证据 `DevDocs/analyse/01-architecture-overview.md` §3.3。
//!
//! 与其它模块的契约:快照(Snapshot)类型与继承解析结果要与 launch 对齐
//! (`DevDocs/SubAgentsTalk/README.md` 的对齐点;详细约定见 `SubAgentsTalk/game-接口约定.md`)。
//!
//! 公共面(M14):
//! - 版本清单:[`VersionList`] / [`RemoteVersion`] / [`ReleaseType`] / [`VersionCategory`],
//! 外加内嵌补充清单 [`unlisted`];
//! - 版本 JSON 与继承:[`VersionJson`]、[`resolve`]、[`merge`]、[`apply_patches`];
//! - 参数与库:[`Arguments`] / [`Environment`] / [`Rule`]、[`Library`] / [`MavenCoordinate`];
//! - 版本 id → 加载器:[`loader`]([`InstanceLoader::parse`],全仓唯一解析入口);
//! - 能力位:[`capability`](版本 JSON 库表 → `has_vulkan_backend`);
//! - 实例:目录布局 [`GameLayout`]、档案 [`ProfileList`] / [`InstanceProfile`] / [`ProfileId`];
//! - 发布:快照仓库 [`Store`] / [`Snapshot`] / [`Draft`]。
//!
//! 安装层(M27,[`install`]):版本链 → 下载计划 → 分区落盘 → 实例登记。
//! 它把上面这些"模型"串成可执行的流程,但**自己不碰 UI、不拼镜像 URL**:
//! 下载全部交给 `core::download` 的引擎(镜像/健康度/续传/校验都在那边)。
//!
//! Java 管理(M33,[`java`]):托管 JDK 的查询/下载/解压/探测(`.ULL/data/java-managed/`)
//! 与 MC↔Java 映射表。同样只借 `core::download` 的引擎做下载,自己不拼镜像 URL。
//!
//! 已铺在盘上的版本接入(M221,[`adopt`]):扫描/检查 `versions/` 里**别人装的**版本 → 只登记
//! (`profiles.json` + 实例设置),不搬文件、不写游戏根。缺省运行目录落 ULL 数据区,
//! 共用/写进对方的 `.minecraft` 必须显式选择(见 [`adopt::AdoptTarget`])。
//!
//! 存档与截图(M235,[`saves`]):**只读**列 `<run_dir>/saves`(世界目录)与
//! `<run_dir>/screenshots`(图片),不建目录、不写盘;「打开文件夹」走
//! [`crate::platform::opener::open_dir`](平台差异不在这里)。缩略图墙与导入不在这一版(塔裁决 U3)。
//!
//! 模块分工按"数据形状 / 纯计算 / 状态"切开,任何模块都不碰网络与 UI:
//! 下载归 `core::download`、进程归 `core::launch`、配置落盘归 `core::setting`。
pub mod adopt;
pub mod arguments;
pub mod capability;
pub mod error;
pub mod inherit;
pub mod install;
pub mod isolation;
pub mod java;
pub mod launch_overrides;
pub mod layout;
pub mod library;
pub mod loader;
pub mod manifest;
pub mod profiles;
pub mod saves;
pub mod snapshot;
pub mod unlisted;
pub mod version;
pub use adopt::{
AdoptCandidate, AdoptError, AdoptOutcome, AdoptPlan, AdoptProblem, AdoptRequest, AdoptScan,
AdoptTarget, RegisteredInstance,
};
pub use arguments::{rules_allow, Arguments, Environment, ExpandedArguments, Rule, RuleAction};
pub use capability::{
is_vulkan_library, library_names_have_vulkan, VersionCapabilities, VULKAN_LIBRARY_ARTIFACT,
VULKAN_LIBRARY_GROUP,
};
pub use error::GameError;
pub use inherit::{apply_patches, merge, resolve, sorted_patches, VersionSource};
pub use isolation::{
derive_game_dir, observe_mode, probe_both_sides, probe_side, Derivation, IsolationMode,
IsolationSource, ModeChange, ModeChangeProbe, ModeChangeWarning, SideContent,
PROBED_CONTENT_DIRS, SHARED_LAYERS,
};
pub use launch_overrides::{
resolve as resolve_launch_overrides, valid_memory, GlobalLaunchDefaults, GraphicsBackend,
LaunchOverrides, LaunchOverridesPatch, ResolvedLaunch, DEFAULT_MAX_MEMORY_MB,
MAX_MAX_MEMORY_MB, MIN_MAX_MEMORY_MB,
};
pub use layout::GameLayout;
pub use library::{Library, MavenCoordinate};
pub use loader::{InstanceLoader, LoaderKind};
pub use manifest::{AssetIndexRef, DownloadInfo, Downloads, JavaVersion, Logging, VersionJson};
pub use profiles::{InstanceProfile, ProfileId, ProfileList, PROFILE_SCHEMA_VERSION};
pub use saves::{
Area, SavesError, SavesScan, SavesSkip, ScreenshotEntry, SkipKind, WorldEntry,
IMAGE_EXTENSIONS, SAVES_DIR, SCREENSHOTS_DIR, WORLD_ICON,
};
pub use snapshot::{Draft, Snapshot, Store};
pub use version::{
LatestVersions, ReleaseType, RemoteVersion, VersionCategory, VersionList,
VERSION_MANIFEST_V2_URL,
};