diff --git a/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs b/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs index fe74bdd..b2b1d73 100644 --- a/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs +++ b/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs @@ -68,7 +68,7 @@ internal static class DiscTool Directory.CreateDirectory(assetsDirectory); File.Copy(dolPath, Path.Combine(assetsDirectory, "main.dol"), overwrite: true); File.Copy(relPath, Path.Combine(assetsDirectory, "StaticR.rel"), overwrite: true); - File.WriteAllText(Path.Combine(assetsDirectory, ExtractionMarker), dolSha); + File.WriteAllText(Path.Combine(assetsDirectory, ExtractionMarker), ExtractionRecord(dolSha, dataDir)); reporter.Progress(InstallStages.ExtractDisc, "Disc validated and extracted", 6); } @@ -102,14 +102,28 @@ internal static class DiscTool var marker = Path.Combine(assetsDirectory, ExtractionMarker); var dol = Path.Combine(assetsDirectory, "main.dol"); var rel = Path.Combine(assetsDirectory, "StaticR.rel"); + var dataDir = Path.Combine(assetsDirectory, "DATA"); if (!File.Exists(marker) || !File.Exists(dol) || !File.Exists(rel) || - !Directory.Exists(Path.Combine(assetsDirectory, "DATA", "files"))) + !Directory.Exists(Path.Combine(dataDir, "files"))) return false; - return string.Equals(File.ReadAllText(marker).Trim(), manifest.DolSha256, StringComparison.Ordinal) && + return string.Equals(File.ReadAllText(marker), ExtractionRecord(manifest.DolSha256, dataDir), + StringComparison.Ordinal) && string.Equals(Sha256Of(dol), manifest.DolSha256, StringComparison.Ordinal) && string.Equals(Sha256Of(rel), manifest.RelSha256, StringComparison.Ordinal); } + // The DOL hash plus every extracted file's path and size, so a later deletion or truncation inside + // DATA forces a fresh extraction. + private static string ExtractionRecord(string dolSha, string dataDir) + { + var record = new System.Text.StringBuilder(dolSha).Append('\n'); + foreach (var path in Directory.EnumerateFiles(dataDir, "*", SearchOption.AllDirectories) + .Select(path => Path.GetRelativePath(dataDir, path).Replace('\\', '/')) + .Order(StringComparer.Ordinal)) + record.Append(new FileInfo(Path.Combine(dataDir, path)).Length).Append(' ').Append(path).Append('\n'); + return record.ToString(); + } + private static string Sha256Of(string path) { using var stream = File.OpenRead(path); diff --git a/Launcher/macos/extract-disc.command b/Launcher/macos/extract-disc.command index 14ed633..fb3fc05 100755 --- a/Launcher/macos/extract-disc.command +++ b/Launcher/macos/extract-disc.command @@ -39,9 +39,12 @@ done assets_dir=$(mkdir -p "$assets_dir" && cd "$assets_dir" && pwd) -# DATA is published before main.dol and StaticR.rel, so verified files imply a complete extraction. -if [[ -d "$assets_dir/DATA/files" && -f "$assets_dir/main.dol" && -f "$assets_dir/StaticR.rel" ]] && \ - [[ $(sha256 "$assets_dir/main.dol") == "$EXPECTED_DOL_SHA256" && $(sha256 "$assets_dir/StaticR.rel") == "$EXPECTED_REL_SHA256" ]]; then +# Sorted " " of every extracted file; recorded at extraction, compared on reuse. +data_manifest() { (cd "$1" && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 stat -f '%z %N'); } + +if [[ -d "$assets_dir/DATA/files" && -f "$assets_dir/.data-manifest" && -f "$assets_dir/main.dol" && -f "$assets_dir/StaticR.rel" ]] && \ + [[ $(sha256 "$assets_dir/main.dol") == "$EXPECTED_DOL_SHA256" && $(sha256 "$assets_dir/StaticR.rel") == "$EXPECTED_REL_SHA256" ]] && \ + [[ "$(data_manifest "$assets_dir/DATA")" == "$(cat "$assets_dir/.data-manifest")" ]]; then printf 'MKWCBUILD:STEP:disc-ready Reusing the verified extracted game assets\n' exit 0 fi @@ -73,15 +76,17 @@ mkdir -p "$stage" ditto "$data_root" "$stage/DATA" ditto "$dol" "$stage/main.dol" ditto "$rel" "$stage/StaticR.rel" +data_manifest "$stage/DATA" > "$stage/.data-manifest" backup="$assets_dir/.previous-extraction-$(date +%Y%m%d-%H%M%S)" -if [[ -e "$assets_dir/DATA" || -e "$assets_dir/main.dol" || -e "$assets_dir/StaticR.rel" ]]; then +if [[ -e "$assets_dir/DATA" || -e "$assets_dir/.data-manifest" || -e "$assets_dir/main.dol" || -e "$assets_dir/StaticR.rel" ]]; then mkdir -p "$backup" - for item in DATA main.dol StaticR.rel; do + for item in DATA .data-manifest main.dol StaticR.rel; do [[ -e "$assets_dir/$item" ]] && mv "$assets_dir/$item" "$backup/$item" done fi mv "$stage/DATA" "$assets_dir/DATA" +mv "$stage/.data-manifest" "$assets_dir/.data-manifest" mv "$stage/main.dol" "$assets_dir/main.dol" mv "$stage/StaticR.rel" "$assets_dir/StaticR.rel" rmdir "$stage" diff --git a/runtime/cmake/RuntimeFingerprint.cmake b/runtime/cmake/RuntimeFingerprint.cmake index 40eb50f..6be83fc 100644 --- a/runtime/cmake/RuntimeFingerprint.cmake +++ b/runtime/cmake/RuntimeFingerprint.cmake @@ -2,11 +2,13 @@ # a precompiled runtime archive may replace compiling it. Script mode prints # exactly one stdout line, runtime_fingerprint=: # cmake -DMKW_RUNTIME_DIR= [-DMKW_CPPWINRT_INCLUDE_DIR=] -# [-DMKW_X86_CPU_PROFILE=v2|v3] -P runtime/cmake/RuntimeFingerprint.cmake +# [-DMKW_X86_CPU_PROFILE=v2|v3] [-DCMAKE_OSX_DEPLOYMENT_TARGET=] +# -P runtime/cmake/RuntimeFingerprint.cmake # # Inputs: src/, include/, cmake/ and CMakeLists.txt (sorted relative paths and -# content hashes), winrt/base.h and the CPU profile. third_party/ has its own -# package fingerprint; game/ is per-user. Dot-files differ between machines. +# content hashes), winrt/base.h, the CPU profile and the macOS deployment target. +# third_party/ has its own package fingerprint; game/ is per-user. Dot-files +# differ between machines. function(mkw_runtime_fingerprint_inputs out_var runtime_dir) get_filename_component(_root "${runtime_dir}" ABSOLUTE) @@ -37,6 +39,10 @@ function(mkw_runtime_fingerprint out_var runtime_dir cppwinrt_dir cpu_profile) string(APPEND _manifest "cppwinrt:winrt/base.h ${_hash}\n") endif() string(APPEND _manifest "x86_cpu_profile ${cpu_profile}\n") + # Omitted when empty so Windows and Linux fingerprints are unaffected. + if(CMAKE_OSX_DEPLOYMENT_TARGET) + string(APPEND _manifest "osx_deployment_target ${CMAKE_OSX_DEPLOYMENT_TARGET}\n") + endif() string(SHA256 _fingerprint "${_manifest}") set(${out_var} "${_fingerprint}" PARENT_SCOPE) endfunction()