diff --git a/apps/mh3g-save-converter-windows/README.md b/apps/mh3g-save-converter-windows/README.md index ebfe96e..04ee41c 100644 --- a/apps/mh3g-save-converter-windows/README.md +++ b/apps/mh3g-save-converter-windows/README.md @@ -122,6 +122,15 @@ after that test, the disposable synthetic `write -> rollback` smoke is skipped rather than touching it. Do not use `-SkipTests` or `-SkipTransactionSmoke` for a normal distribution build. +If the UI reports `Unable to prepare patched user save`, +`tools\mh3g-save-convert.exe` came from the temporary v0.0.3 compatibility +wrapper rather than the native Rust CLI used by 0.0.4 and newer. Do not copy +`tools\compatibility-wrapper\dist\mh3g-save-convert.exe` into a WinUI package. +Delete the old `artifacts\mh3g-save-convert-windows-x64` directory and ZIP, +then rerun the exact `package-mh3g-save-converter-windows.ps1 -Bootstrap` +command above. Current packaging and the WinUI runtime both reject that legacy +wrapper before it can surface a misleading JSON error. + If it fails, send the **first failed command block** from `artifacts\mh3g-save-convert-windows-build-transcript.txt`: begin at its `>>` line and include the failure lines below it. Do not switch to a different manual diff --git a/apps/mh3g-save-converter-windows/README.zh-CN.md b/apps/mh3g-save-converter-windows/README.zh-CN.md index 826291f..17ac08f 100644 --- a/apps/mh3g-save-converter-windows/README.zh-CN.md +++ b/apps/mh3g-save-converter-windows/README.zh-CN.md @@ -104,6 +104,15 @@ Cemu_release、Nemessix 和 Azahar,发现仍在运行就列出进程名并提 这些进程。若测试之后模拟器才启动,则只跳过临时目录中的合成 `write -> rollback` smoke, 不会触碰真实数据。常规发包不要使用 `-SkipTests` 或 `-SkipTransactionSmoke`。 +如果界面报告 `Unable to prepare patched user save`,当前包中的 +`tools\mh3g-save-convert.exe` 是仅供 v0.0.3 临时更新使用的旧兼容包装器,不是 +0.0.4 之后的原生 Rust CLI。不要把 +`tools\compatibility-wrapper\dist\mh3g-save-convert.exe` 复制进 WinUI 包; +删除旧的 `artifacts\mh3g-save-convert-windows-x64` 和 ZIP 后,使用上面的 +`package-mh3g-save-converter-windows.ps1 -Bootstrap` 原命令重新打包。新版 +打包脚本和 WinUI 运行时都会识别并拒绝旧包装器,不再把它的 stderr 误报成 JSON +解析故障。 + 若仍失败,请提供 `artifacts\mh3g-save-convert-windows-build-transcript.txt` 中**首个失败命令的完整输出块**: 从对应的 `>>` 行开始,连同其下方的失败信息一起发回;不要改用另一组手工构建命令。 diff --git a/apps/mh3g-save-converter-windows/Services/ConverterCliClient.cs b/apps/mh3g-save-converter-windows/Services/ConverterCliClient.cs index 566bf5e..0bd0709 100644 --- a/apps/mh3g-save-converter-windows/Services/ConverterCliClient.cs +++ b/apps/mh3g-save-converter-windows/Services/ConverterCliClient.cs @@ -11,6 +11,9 @@ namespace MHToolkit.MH3GSaveConverter.Windows.Services; /// public sealed class ConverterCliClient { + private static readonly byte[] LegacyWrapperMarker = + "mh3g-save-convert-core.exe"u8.ToArray(); + public async Task ExecuteAsync( string executable, IEnumerable arguments, @@ -37,6 +40,22 @@ public async Task ExecuteAsync( }; } + try + { + var executableBytes = await File.ReadAllBytesAsync(executable, cancellationToken); + if (ContainsLegacyWrapperMarker(executableBytes)) + { + return LaunchFailure( + executable, + argumentList, + "Legacy compatibility wrapper detected. Rebuild the Windows package from 0.0.4 or newer so tools\\mh3g-save-convert.exe is the native Rust CLI. / 检测到旧版兼容包装器,请使用 0.0.4 或更高版本源码重新打包,确保 tools\\mh3g-save-convert.exe 为原生 Rust CLI。"); + } + } + catch (Exception exception) when (exception is IOException or UnauthorizedAccessException) + { + return LaunchFailure(executable, argumentList, $"The converter CLI could not be inspected: {exception.Message}"); + } + var startInfo = new ProcessStartInfo { FileName = executable, @@ -97,6 +116,24 @@ private static CliExecutionResult LaunchFailure(string executable, IReadOnlyList }; } + private static bool ContainsLegacyWrapperMarker(byte[] bytes) + { + if (bytes.Length < LegacyWrapperMarker.Length) + { + return false; + } + + for (var offset = 0; offset <= bytes.Length - LegacyWrapperMarker.Length; offset++) + { + if (bytes.AsSpan(offset, LegacyWrapperMarker.Length).SequenceEqual(LegacyWrapperMarker)) + { + return true; + } + } + + return false; + } + private static JsonElement? TryParseLastJsonLine(string stdout, out string? parseError) { parseError = null; diff --git a/scripts/package-mh3g-save-converter-windows.ps1 b/scripts/package-mh3g-save-converter-windows.ps1 index 6b97094..2d49e98 100644 --- a/scripts/package-mh3g-save-converter-windows.ps1 +++ b/scripts/package-mh3g-save-converter-windows.ps1 @@ -703,6 +703,18 @@ function Write-Sha256File { return $hash } +function Assert-NativeConverterSidecar { + param([Parameter(Mandatory = $true)][string]$FilePath) + + $legacyMarker = "mh3g-save-convert-core.exe" + $binaryText = [System.Text.Encoding]::ASCII.GetString( + [System.IO.File]::ReadAllBytes($FilePath) + ) + if ($binaryText.Contains($legacyMarker)) { + throw "Refusing the legacy MH3G compatibility wrapper: $FilePath. Build and package the native Rust converter from 0.0.4 or newer." + } +} + function Get-RunningSupportedEmulators { # The Rust integration suite intentionally exercises guarded writes against # synthetic files. Its process probe must see no real emulator, just as a @@ -748,6 +760,7 @@ function Test-PackagedLayout { if ($actualSidecarHash -ne $expectedSidecarHash) { throw "Package self-check found a sidecar checksum mismatch." } + Assert-NativeConverterSidecar -FilePath $sidecar $powershell = Get-ExternalCommand "powershell.exe" if ($null -eq $powershell) { @@ -906,6 +919,7 @@ try { if (-not (Test-Path -LiteralPath $sidecar -PathType Leaf)) { throw "Rust x64 sidecar is missing after cargo build: $sidecar" } + Assert-NativeConverterSidecar -FilePath $sidecar Invoke-External -FilePath $sidecar -Arguments @("--help") -Description "release sidecar smoke" # Recheck directly before recursive deletion in case an existing stage diff --git a/scripts/verify-mh3g-save-converter-windows-source.py b/scripts/verify-mh3g-save-converter-windows-source.py index e2ace5a..1bde854 100644 --- a/scripts/verify-mh3g-save-converter-windows-source.py +++ b/scripts/verify-mh3g-save-converter-windows-source.py @@ -159,6 +159,11 @@ def verify_local_packaging_script() -> None: "reparse point" in script.lower(), "Windows package output cleanup must reject junctions and symlinks", ) + require( + "Assert-NativeConverterSidecar" in script + and "mh3g-save-convert-core.exe" in script, + "Windows packaging must reject the legacy compatibility wrapper before publishing", + ) require( '$versionLine[0] -match \'^rustc\\s+(?\\d+\\.\\d+\\.\\d+)\'' in script and '$versionText = $Matches["version"]' in script @@ -270,7 +275,13 @@ def main() -> int: require((APP / "assets" / "Artwork" / artwork).is_file(), f"missing packaged artwork {artwork}") bridge = read("Services/ConverterCliClient.cs") - for expected in ("UseShellExecute = false", "startInfo.ArgumentList.Add(argument)", "JsonDocument.Parse(candidate)"): + for expected in ( + "UseShellExecute = false", + "startInfo.ArgumentList.Add(argument)", + "JsonDocument.Parse(candidate)", + "mh3g-save-convert-core.exe", + "Legacy compatibility wrapper", + ): require(expected in bridge, f"argv/JSON bridge is missing {expected}") require("startInfo.Arguments" not in bridge, "CLI bridge must not build a command-string argument list") require("cmd.exe" not in bridge and "powershell" not in bridge.lower(), "CLI bridge must not invoke a shell")