Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,11 @@ Cemu validation succeeds.

```text
mh3g-save-convert repair-converted <original-3DS-user#> --current <current-Cemu-user#> \
[--output <repaired-Cemu-user#>] \
[--source-extdata-dir <original-3DS-ExtData-user>] [--from-version <0.0.3|0.0.4|0.0.5|0.0.6>] \
[--dry-run | --write --expected-source-set-sha256 <SHA256> \
--expected-current-set-sha256 <SHA256> --expected-preview-sha256 <SHA256>]
--expected-current-set-sha256 <SHA256> --expected-output-set-sha256 <SHA256> \
--expected-preview-sha256 <SHA256>]
```

Use this command when a save was converted with 0.0.3 through 0.0.6 and then
Expand All @@ -409,12 +411,22 @@ are preserved and reported as conflicts. Current HR, equipment, materials,
storage, quest progress, farm, fleet, and other later gameplay therefore remain
owned by the current Cemu save.

The CLI requires two exact, same-numbered `user1`, `user2`, or `user3` files.
The three paths have deliberately separate roles: the original 3DS slot is a
read-only conversion source, `--current` is the read-only Wii U/Cemu state that
owns later gameplay progress, and `--output` is the only core slot that may be
written. All three must be exact, same-numbered `user1`, `user2`, or `user3`
paths. Omitting `--output` retains the CLI's legacy in-place behavior and uses
`--current` as the destination; the native macOS and Windows workbenches never
hide this coupling and always display/pass a separate output selection.

The native macOS and Windows workbenches may accept a file or its direct parent
directory, but they still resolve and pass one exact file to the CLI. To repair
guild cards, `--source-extdata-dir` must be the complete 3DS
`.../00000481/user` directory with all eight source files, and the current
`user#` parent must contain all eight same-named Cemu files. The current
`user#` parent must contain all eight same-named Cemu files. A separate output
directory must already be an initialized Cemu save directory containing
`card1`, `card2`, `card3`, and `cardbox`; this keeps the multi-file transaction
fail-closed. The current
converter field-repairs `user#`, `card1`, `card2`, `card3`, and `cardbox`;
`quest1` through `quest4` participate in set validation but remain
byte-identical. `system`, `cec`, `phrase1` through `phrase3`, and unknown files
Expand All @@ -423,7 +435,8 @@ are not read or written by this command.
Start with a read-only preview:

```bash
REPAIR_JSON=$("${CLI[@]}" repair-converted "$SOURCE" --current "$TARGET" \
# SOURCE, CURRENT, and OUTPUT are three distinct same-slot user# paths.
REPAIR_JSON=$("${CLI[@]}" repair-converted "$SOURCE" --current "$CURRENT" --output "$OUTPUT" \
--source-extdata-dir "$EXTRAS_SOURCE" --dry-run)
```

Expand All @@ -432,24 +445,26 @@ the original version from its `candidates`, then repeat Dry Run with
`--from-version`. Every selected component shares this one revision decision;
the converter never repairs `user#` and `card*` as different historical
releases. Detection cannot read an embedded converter version because older
releases did not store a trustworthy marker. A write must reuse all three set
hashes from that final Dry Run:
releases did not store a trustworthy marker. A write must reuse all four
authorization hashes from that final Dry Run:

```bash
SOURCE_SET_SHA256=$(jq -er '.source_set_sha256' <<<"$REPAIR_JSON")
CURRENT_SET_SHA256=$(jq -er '.current_set_sha256' <<<"$REPAIR_JSON")
OUTPUT_SET_SHA256=$(jq -er '.output_set_sha256' <<<"$REPAIR_JSON")
PREVIEW_SHA256=$(jq -er '.preview_sha256' <<<"$REPAIR_JSON")

"${CLI[@]}" repair-converted "$SOURCE" --current "$TARGET" \
"${CLI[@]}" repair-converted "$SOURCE" --current "$CURRENT" --output "$OUTPUT" \
--source-extdata-dir "$EXTRAS_SOURCE" \
--expected-source-set-sha256 "$SOURCE_SET_SHA256" \
--expected-current-set-sha256 "$CURRENT_SET_SHA256" \
--expected-output-set-sha256 "$OUTPUT_SET_SHA256" \
--expected-preview-sha256 "$PREVIEW_SHA256" \
--write
```

If Dry Run used `--from-version`, pass the same value to the write. Any source,
current target, or preview change between the two steps fails closed. A
current reference, output state, or preview change between the two steps fails closed. A
successful write returns a coordinator manifest named
`.mh3g-compatibility-repair-<UUID>.json`, covering the core slot and optional
guild-card subtransactions. Roll it back with:
Expand Down
19 changes: 13 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,38 +279,45 @@ NEW_SOURCE_SHA256=$(jq -er '.hashes.source' <<<"$NEW_DRY_RUN_JSON")

```text
mh3g-save-convert repair-converted <原始-3DS-user#> --current <当前-Cemu-user#> \
[--output <修复后-Cemu-user#>] \
[--source-extdata-dir <原始-3DS-ExtData-user>] [--from-version <0.0.3|0.0.4|0.0.5|0.0.6>] \
[--dry-run | --write --expected-source-set-sha256 <SHA256> \
--expected-current-set-sha256 <SHA256> --expected-preview-sha256 <SHA256>]
--expected-current-set-sha256 <SHA256> --expected-output-set-sha256 <SHA256> \
--expected-preview-sha256 <SHA256>]
```

该命令用于“曾用 0.0.3 至 0.0.6 转换,之后又在 Wii U/Cemu 中继续游玩”的存档。它不会把当前存档整体替换为旧 3DS 状态,而是为每个已知历史转换字段比较“旧版预期值、当前值、当前转换器预期值”:仍等于旧版结果的字段才修复;已在 Wii U 侧变化的整字段保留并报告冲突。因此当前 HR、装备、素材、仓库、任务进度、农场、狩猎船和其他继续游玩的数据以当前 Cemu 存档为准。

CLI 必须接收两个编号相同、文件名相同的准确 `user1`、`user2` 或 `user3` 文件。原生 macOS/Windows 工作台可以让用户选择文件或其直接父目录,但最终仍只把解析出的准确文件传给 CLI。若启用公会名片修复,`--source-extdata-dir` 必须是含全部八个源文件的 3DS `.../00000481/user` 目录,并且当前 `user#` 的父目录必须含全部八个同名 Cemu 文件。当前转换器会字段级修复 `user#`、`card1`、`card2`、`card3`、`cardbox`;`quest1` 至 `quest4` 只参与集合校验并逐字节保留。`system`、`cec`、`phrase1` 至 `phrase3` 和未知文件不会被该命令读取或写入。
三个路径的职责刻意分开:原始 3DS 槽位是只读转换源,`--current` 是保存玩家后续 Wii U/Cemu 进度的只读引用,`--output` 才是唯一允许写入的核心槽位。三者必须是编号和文件名相同的准确 `user1`、`user2` 或 `user3`。CLI 省略 `--output` 时仅为兼容旧脚本而继续原地写回 `--current`;原生 macOS/Windows 工作台不会隐藏这种复用,始终独立显示并传入“当前 Wii U 引用”和“修复后输出”。

原生工作台可以让用户选择文件或其直接父目录,但最终仍只把解析出的准确文件传给 CLI。若启用公会名片修复,`--source-extdata-dir` 必须是含全部八个源文件的 3DS `.../00000481/user` 目录,并且当前 `user#` 的父目录必须含全部八个同名 Cemu 文件。若输出目录与当前引用目录不同,输出目录必须是已经初始化的 Cemu 存档目录,并至少含 `card1`、`card2`、`card3`、`cardbox`,否则多文件事务会失败关闭。当前转换器会字段级修复 `user#`、`card1`、`card2`、`card3`、`cardbox`;`quest1` 至 `quest4` 只参与集合校验并逐字节保留。`system`、`cec`、`phrase1` 至 `phrase3` 和未知文件不会被该命令读取或写入。

先运行只读预览:

```bash
REPAIR_JSON=$("${CLI[@]}" repair-converted "$SOURCE" --current "$TARGET" \
# SOURCE、CURRENT、OUTPUT 是三个独立但同槽位的 user# 路径。
REPAIR_JSON=$("${CLI[@]}" repair-converted "$SOURCE" --current "$CURRENT" --output "$OUTPUT" \
--source-extdata-dir "$EXTRAS_SOURCE" --dry-run)
```

如果 JSON 顶层的 `detection.confidence` 为 `ambiguous`,不要直接写入;从其 `candidates` 中确认当时使用的版本,并用 `--from-version` 重新运行 Dry Run。所有选中组件始终共用这一份版本判断,不会把 `user#` 和 `card*` 分别按不同历史版本修复。自动检测不是读取存档内嵌版本号,因为旧版本没有写入可靠标记。写入必须复用同一次最终 Dry Run 的三个集合哈希
如果 JSON 顶层的 `detection.confidence` 为 `ambiguous`,不要直接写入;从其 `candidates` 中确认当时使用的版本,并用 `--from-version` 重新运行 Dry Run。所有选中组件始终共用这一份版本判断,不会把 `user#` 和 `card*` 分别按不同历史版本修复。自动检测不是读取存档内嵌版本号,因为旧版本没有写入可靠标记。写入必须复用同一次最终 Dry Run 的四个授权哈希

```bash
SOURCE_SET_SHA256=$(jq -er '.source_set_sha256' <<<"$REPAIR_JSON")
CURRENT_SET_SHA256=$(jq -er '.current_set_sha256' <<<"$REPAIR_JSON")
OUTPUT_SET_SHA256=$(jq -er '.output_set_sha256' <<<"$REPAIR_JSON")
PREVIEW_SHA256=$(jq -er '.preview_sha256' <<<"$REPAIR_JSON")

"${CLI[@]}" repair-converted "$SOURCE" --current "$TARGET" \
"${CLI[@]}" repair-converted "$SOURCE" --current "$CURRENT" --output "$OUTPUT" \
--source-extdata-dir "$EXTRAS_SOURCE" \
--expected-source-set-sha256 "$SOURCE_SET_SHA256" \
--expected-current-set-sha256 "$CURRENT_SET_SHA256" \
--expected-output-set-sha256 "$OUTPUT_SET_SHA256" \
--expected-preview-sha256 "$PREVIEW_SHA256" \
--write
```

若 Dry Run 使用了 `--from-version`,写入必须传入同一个值。任一源文件、当前目标或预览在两步之间变化都会失败关闭。成功写入会返回总 manifest `.mh3g-compatibility-repair-<UUID>.json`;它协调核心槽位与可选公会名片子事务。完整回滚使用:
若 Dry Run 使用了 `--from-version`,写入必须传入同一个值。任一原始源、当前 Wii U 引用、输出状态或预览在两步之间变化都会失败关闭。成功写入会返回总 manifest `.mh3g-compatibility-repair-<UUID>.json`;它协调核心槽位与可选公会名片子事务。完整回滚使用:

```bash
"${CLI[@]}" rollback-repair --manifest "$COMPATIBILITY_MANIFEST"
Expand Down
17 changes: 10 additions & 7 deletions apps/mh3g-save-converter-macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ no save conversion is reimplemented in Swift.

- **New conversion**: convert an original 3DS `user#` into the same-named Cemu
`user#`.
- **Repair converted save**: merge the original 3DS `user#` with the current
Cemu `user#` after continued play, repairing only fields that still retain a
0.0.3 through 0.0.6 conversion result.
- **Repair converted save**: use the original 3DS `user#` and the current,
continued-play Cemu `user#` as separate read inputs, then write the repaired
result to an independently selected same-slot output. Only fields that still
retain a 0.0.3 through 0.0.6 conversion result are repaired.

Repair mode may also select the complete 3DS ExtData `user` directory for
guild-card repair. Current Cemu `card1`, `card2`, `card3`, `cardbox`, and
Repair mode therefore displays three independent selectors: original 3DS,
read-only current Wii U/Cemu reference, and repaired output. It may also select
the complete 3DS ExtData `user` directory for guild-card repair. Current Cemu `card1`, `card2`, `card3`, `cardbox`, and
`quest1` through `quest4` are resolved beside the selected current `user#`;
quest files are validated and preserved, not rewritten by compatibility
repair. Ambiguous detection requires an explicit historical version followed
by another Dry Run.
repair. A different output directory must already contain initialized
`card1`, `card2`, `card3`, and `cardbox` files. Ambiguous detection requires an
explicit historical version followed by another Dry Run.

The core picker accepts an exact `user1`, `user2`, or `user3` file or its
direct parent. It does not recursively scan an SD card or MLC and does not open
Expand Down
13 changes: 8 additions & 5 deletions apps/mh3g-save-converter-macos/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ sidecar 并展示 JSON 报告,不在 Swift 中重复实现存档转换。
## 两种处理方式

- **全新转换**:原始 3DS `user#` 转成同名 Cemu `user#`。
- **修复已转换存档**:合并原始 3DS `user#` 与玩家继续游玩后的当前 Cemu
`user#`,只修复仍保持 0.0.3 至 0.0.6 旧转换结果的字段。
- **修复已转换存档**:把原始 3DS `user#` 与玩家继续游玩后的当前 Cemu
`user#` 作为两个独立只读输入,并把结果写到独立选择的同槽位输出;只修复仍
保持 0.0.3 至 0.0.6 旧转换结果的字段。

修复模式可以额外选择完整 3DS ExtData `user` 目录以修复公会名片。当前 Cemu
因此修复模式会明确显示三个选择器:原始 3DS、只读当前 Wii U/Cemu 引用、修复后
输出。它还可以额外选择完整 3DS ExtData `user` 目录以修复公会名片。当前 Cemu
`card1`、`card2`、`card3`、`cardbox` 和 `quest1` 至 `quest4` 从所选当前
`user#` 的父目录解析;任务文件只校验并保留,不会被兼容修复改写。自动检测结果
为歧义时,必须明确选择历史版本并重新运行 Dry Run。
`user#` 的父目录解析;任务文件只校验并保留,不会被兼容修复改写。若输出目录与
当前引用目录不同,输出目录必须已有初始化的 `card1`、`card2`、`card3`、
`cardbox`。自动检测结果为歧义时,必须明确选择历史版本并重新运行 Dry Run。

核心选择器接受准确 `user1`、`user2`、`user3` 文件或其直接父目录。它不会递归
扫描 SD 卡/MLC,不接受 ZIP、7z、RAR。目录只解析成当前选中槽位的同名直接子文件。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,17 @@ public struct InputInspection: Equatable, Sendable {
}
}

/// The two mandatory slot files. The UI does not discover an MLC root or
/// expand a directory recursively: both URLs originate from explicit user
/// selection.
/// Explicit core paths. New conversion uses source + target. Compatibility
/// repair additionally requires a read-only current Wii U/Cemu reference;
/// target always remains the independent write destination.
public struct ConversionInput: Equatable, Sendable {
public let source: URL
public let current: URL?
public let target: URL

public init(source: URL, target: URL) {
public init(source: URL, target: URL, current: URL? = nil) {
self.source = source.standardizedFileURL
self.current = current?.standardizedFileURL
self.target = target.standardizedFileURL
}
}
Expand Down Expand Up @@ -260,29 +262,35 @@ public struct DryRunFingerprint: Equatable, Sendable {
public struct RepairDryRunFingerprint: Equatable, Sendable {
public let source: URL
public let current: URL
public let output: URL
public let extDataSource: URL?
public let fromVersion: HistoricalConverterRevision?
public let sourceSetSHA256: String
public let currentSetSHA256: String
public let outputSetSHA256: String
public let previewSHA256: String
public let components: [RepairComponentFingerprint]

public init(
source: URL,
current: URL,
output: URL,
extDataSource: URL?,
fromVersion: HistoricalConverterRevision?,
sourceSetSHA256: String,
currentSetSHA256: String,
outputSetSHA256: String,
previewSHA256: String,
components: [RepairComponentFingerprint]
) {
self.source = source.standardizedFileURL
self.current = current.standardizedFileURL
self.output = output.standardizedFileURL
self.extDataSource = extDataSource?.standardizedFileURL
self.fromVersion = fromVersion
self.sourceSetSHA256 = sourceSetSHA256
self.currentSetSHA256 = currentSetSHA256
self.outputSetSHA256 = outputSetSHA256
self.previewSHA256 = previewSHA256
self.components = components
}
Expand Down Expand Up @@ -506,6 +514,7 @@ public struct ConverterReport: Decodable, Sendable {
public let targetSetSHA256Before: String?
public let sourceSetSHA256: String?
public let currentSetSHA256: String?
public let outputSetSHA256: String?
public let previewSHA256: String?
public let detection: ConverterRevisionDetection?
public let manifests: [String]?
Expand Down Expand Up @@ -542,6 +551,7 @@ public struct ConverterReport: Decodable, Sendable {
case targetSetSHA256Before = "target_set_sha256_before"
case sourceSetSHA256 = "source_set_sha256"
case currentSetSHA256 = "current_set_sha256"
case outputSetSHA256 = "output_set_sha256"
case previewSHA256 = "preview_sha256"
}

Expand Down
Loading
Loading