mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
switch to standalone nod tool rather than dolphin-tool
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
[CmdletBinding(PositionalBinding = $false)]
|
||||
param(
|
||||
[string]$OutputDirectory = 'Launcher/dist',
|
||||
[string]$DolphinToolPath,
|
||||
[string]$PortableToolsDirectory = 'Launcher/artifacts/portable-tools',
|
||||
[string]$DependencySourceDirectory = 'Launcher/artifacts/dependencies',
|
||||
[string]$VcRuntimeDirectory,
|
||||
@@ -15,10 +14,6 @@ Set-StrictMode -Version 3.0
|
||||
# helpers shared with LocalBuild.ps1 and Prepare-NativePrebuilt.ps1.
|
||||
. (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1')
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DolphinToolPath)) {
|
||||
throw 'Build-Installer.ps1 requires -DolphinToolPath pointing to DolphinTool.exe.'
|
||||
}
|
||||
|
||||
$repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$outputRoot = [IO.Path]::GetFullPath((Join-Path $repoRoot $OutputDirectory))
|
||||
$portableTools = [IO.Path]::GetFullPath((Join-Path $repoRoot $PortableToolsDirectory))
|
||||
@@ -91,7 +86,6 @@ function Compress-Zip([string]$Source, [string]$Destination, [string[]]$Entries)
|
||||
|
||||
Assert-File $setupProject '.NET setup project'
|
||||
Assert-File $translatorProject 'Translator CLI project'
|
||||
Assert-File $DolphinToolPath 'DolphinTool'
|
||||
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $portableTools 'llvm-mingw\bin\x86_64-w64-mingw32-clang++.exe'))) {
|
||||
& (Join-Path $PSScriptRoot 'Prepare-PortableTools.ps1') -Destination $portableTools
|
||||
@@ -169,6 +163,15 @@ $translator = Join-Path $publish 'translator\Translator.Cli.exe'
|
||||
Assert-File $setupHost 'Published setup host'
|
||||
Assert-File $translator 'Self-contained translator'
|
||||
|
||||
# Resolved via the shared WiiCompiled.NodTool.Cli helper (also used by build-appimage.sh on Linux)
|
||||
# rather than a separate download/version-pin copy here: it downloads and caches the same way
|
||||
# NodToolProvider.cs always does (Launcher/artifacts/nodtool.exe), replacing the old manual
|
||||
# -DolphinToolPath handoff with an automated, pinned acquisition step.
|
||||
$nodToolCliProject = Join-Path $PSScriptRoot 'WiiCompiled.NodTool.Cli'
|
||||
$nodTool = (& dotnet run --project $nodToolCliProject -c Release -- --workspace $repoRoot | Select-Object -Last 1)
|
||||
if ($LASTEXITCODE -ne 0) { throw "nodtool resolution failed with exit code $LASTEXITCODE." }
|
||||
Assert-File $nodTool 'Resolved nodtool'
|
||||
|
||||
Write-Host '[2/6] Staging the explicit, game-code-free payload allowlist...'
|
||||
# The staged layout mirrors the installed layout exactly (Toolkit, BuildWorkspace): payload
|
||||
# identities hash relative paths, so the names here are part of the fingerprint contract.
|
||||
@@ -191,7 +194,7 @@ Get-ChildItem -LiteralPath (Join-Path $toolkit 'llvm-mingw\bin') -File |
|
||||
Remove-Item -Force
|
||||
[IO.Directory]::CreateDirectory((Join-Path $toolkit 'Translator')) | Out-Null
|
||||
Copy-Item -LiteralPath $translator -Destination (Join-Path $toolkit 'Translator\Translator.Cli.exe')
|
||||
Copy-Item -LiteralPath $DolphinToolPath -Destination (Join-Path $toolkit 'DolphinTool.exe')
|
||||
Copy-Item -LiteralPath $nodTool -Destination (Join-Path $toolkit 'nodtool.exe')
|
||||
[IO.Directory]::CreateDirectory((Join-Path $toolkit 'Redist')) | Out-Null
|
||||
Copy-Item -Path (Join-Path $vcRuntime '*.dll') -Destination (Join-Path $toolkit 'Redist')
|
||||
Copy-Item -Path (Join-Path $vcRuntime '*.dll') -Destination (Join-Path $toolkit 'CMake\bin')
|
||||
@@ -225,18 +228,38 @@ Copy-Item (Join-Path $dependencySources 'cppwinrt\LICENSE.txt') (Join-Path $payl
|
||||
# The precompiled aurora/third-party archives are built from the very sources
|
||||
# already shipped under build-workspace\Dependencies and aurora-main, so they add
|
||||
# no third-party component and therefore no new license obligation.
|
||||
$dolphinLicense = Join-Path (Split-Path -Parent $DolphinToolPath) 'COPYING'
|
||||
if (Test-Path $dolphinLicense) { Copy-Item $dolphinLicense (Join-Path $payloadRoot 'licenses\Dolphin-COPYING.txt') }
|
||||
@"
|
||||
DolphinTool source offer
|
||||
nodtool (disc image extraction)
|
||||
|
||||
Project and complete corresponding source: https://github.com/dolphin-emu/dolphin
|
||||
Dolphin is licensed under GPLv2+ with additional per-file SPDX licenses.
|
||||
"@ | Set-Content (Join-Path $payloadRoot 'licenses\Dolphin-SOURCE.txt') -Encoding UTF8
|
||||
Project: https://github.com/encounter/nod
|
||||
Dual-licensed under MIT OR Apache-2.0.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright 2021 Luke Street.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"@ | Set-Content (Join-Path $payloadRoot 'licenses\nodtool-LICENSE-MIT.txt') -Encoding UTF8
|
||||
@"
|
||||
Microsoft Visual C++ Runtime
|
||||
|
||||
Redistributable x64 runtime DLLs are included app-locally for DolphinTool and third-party renderer DLLs.
|
||||
Redistributable x64 runtime DLLs are included app-locally for nodtool and third-party renderer DLLs.
|
||||
Microsoft license terms: https://visualstudio.microsoft.com/license-terms/
|
||||
"@ | Set-Content (Join-Path $payloadRoot 'licenses\Microsoft-VC-Runtime.txt') -Encoding UTF8
|
||||
# Compute the payload's content identities once, here, with the same code every installed host
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[CmdletBinding(PositionalBinding = $false)]
|
||||
param(
|
||||
[string]$DolphinToolPath,
|
||||
[string]$PortableToolsDirectory = 'Launcher/artifacts/portable-tools',
|
||||
[string]$DependencySourceDirectory = 'Launcher/artifacts/dependencies',
|
||||
[string]$VcRuntimeDirectory,
|
||||
@@ -10,12 +9,7 @@ param(
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version 3.0
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DolphinToolPath)) {
|
||||
throw 'Prepare-Release.ps1 requires -DolphinToolPath pointing to DolphinTool.exe.'
|
||||
}
|
||||
|
||||
$arguments = @{
|
||||
DolphinToolPath = $DolphinToolPath
|
||||
PortableToolsDirectory = $PortableToolsDirectory
|
||||
DependencySourceDirectory = $DependencySourceDirectory
|
||||
ToolkitReleaseTag = $ToolkitReleaseTag
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using WiiCompiled.NodTool;
|
||||
|
||||
// A packaging-time-only helper - never shipped, never run by an end user. Both
|
||||
// Launcher/build-appimage.sh and Launcher/Build-Installer.ps1 invoke this to obtain the nodtool
|
||||
// binary they bundle, so there is exactly one place (NodToolProvider) that knows the pinned
|
||||
// version/URL/platform-asset mapping, instead of a separate copy per packaging script.
|
||||
//
|
||||
// Usage: WiiCompiled.NodTool.Cli --workspace <repo-root>
|
||||
// Prints the resolved nodtool path to stdout.
|
||||
|
||||
string? workspace = null;
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] == "--workspace" && i + 1 < args.Length)
|
||||
{
|
||||
workspace = args[++i];
|
||||
}
|
||||
}
|
||||
|
||||
if (workspace is null)
|
||||
{
|
||||
Console.Error.WriteLine("Usage: WiiCompiled.NodTool.Cli --workspace <repo-root>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var path = await NodToolProvider.ResolveAsync(workspace, CancellationToken.None);
|
||||
Console.WriteLine(path);
|
||||
return 0;
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WiiCompiled.NodTool.Cli</RootNamespace>
|
||||
<AssemblyName>WiiCompiled.NodTool.Cli</AssemblyName>
|
||||
<Version>0.2.22</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description>
|
||||
<DebugType>embedded</DebugType>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WiiCompiled.NodTool\WiiCompiled.NodTool.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WiiCompiled.NodTool;
|
||||
|
||||
/// <summary>Disc metadata parsed from `nodtool info`'s stdout.</summary>
|
||||
public sealed record NodToolDiscInfo(string GameId, string Title, int Revision);
|
||||
|
||||
/// <summary>
|
||||
/// Parses the plain-text stdout of `nodtool info <iso>`. nodtool has no JSON output mode, but
|
||||
/// prints one unconditional disc-level Title/Game ID/Disc-Revision block (via its own
|
||||
/// `print_header`) before any per-partition breakdown - Wii discs also have differently-scoped
|
||||
/// "Title"/"Game ID" lines per update/channel partition further down, so the first match of each
|
||||
/// pattern is always the disc-level one both installers want.
|
||||
/// </summary>
|
||||
public static partial class NodToolInfoParser
|
||||
{
|
||||
public static NodToolDiscInfo Parse(string infoStdout)
|
||||
{
|
||||
var gameIdMatch = GameIdLine().Match(infoStdout);
|
||||
if (!gameIdMatch.Success)
|
||||
throw new InvalidOperationException("nodtool did not return disc metadata.");
|
||||
var titleMatch = TitleLine().Match(infoStdout);
|
||||
var revisionMatch = RevisionLine().Match(infoStdout);
|
||||
return new NodToolDiscInfo(
|
||||
GameId: gameIdMatch.Groups[1].Value,
|
||||
Title: titleMatch.Success ? titleMatch.Groups[1].Value : "",
|
||||
Revision: revisionMatch.Success ? int.Parse(revisionMatch.Groups[1].Value) : 0);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^Game ID: (\S+)", RegexOptions.Multiline)]
|
||||
private static partial Regex GameIdLine();
|
||||
|
||||
[GeneratedRegex(@"^Title: (.+)$", RegexOptions.Multiline)]
|
||||
private static partial Regex TitleLine();
|
||||
|
||||
[GeneratedRegex(@"^Disc \d+, Revision (\d+)", RegexOptions.Multiline)]
|
||||
private static partial Regex RevisionLine();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WiiCompiled.NodTool;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the `nodtool` binary both installers use for Wii disc validation/extraction (see
|
||||
/// NodToolInfoParser.cs), replacing the earlier dependency on `dolphin-tool`/`DolphinTool.exe`. A
|
||||
/// caller can supply one directly; otherwise this downloads the matching prebuilt release binary
|
||||
/// from encounter/nod and caches it at Launcher/artifacts/nodtool[.exe].
|
||||
///
|
||||
/// Shared by: WiiCompiled.Setup.Linux/DiscTool.cs (falls back to this at end-user install time on
|
||||
/// a plain git checkout), and WiiCompiled.NodTool.Cli (invoked once at packaging time by both
|
||||
/// build-appimage.sh and Build-Installer.ps1 to acquire the copy each bundles).
|
||||
/// </summary>
|
||||
public static class NodToolProvider
|
||||
{
|
||||
public const string Version = "v2.0.0-alpha.10";
|
||||
|
||||
public static async Task<string> ResolveAsync(string workspace, CancellationToken cancellationToken)
|
||||
{
|
||||
var cacheName = OperatingSystem.IsWindows() ? "nodtool.exe" : "nodtool";
|
||||
var cachePath = Path.Combine(workspace, "Launcher", "artifacts", cacheName);
|
||||
if (File.Exists(cachePath)) return cachePath;
|
||||
|
||||
var url = $"https://github.com/encounter/nod/releases/download/{Version}/{AssetName()}";
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!);
|
||||
var tempPath = cachePath + ".tmp";
|
||||
using (var http = new HttpClient())
|
||||
using (var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var fileStream = File.Create(tempPath);
|
||||
await response.Content.CopyToAsync(fileStream, cancellationToken);
|
||||
}
|
||||
File.Move(tempPath, cachePath, overwrite: true);
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
File.SetUnixFileMode(cachePath,
|
||||
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
|
||||
UnixFileMode.GroupRead | UnixFileMode.GroupExecute |
|
||||
UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
|
||||
}
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
private static string AssetName()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return RuntimeInformation.OSArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "nodtool-windows-x86_64.exe",
|
||||
Architecture.Arm64 => "nodtool-windows-arm64.exe",
|
||||
Architecture.X86 => "nodtool-windows-x86.exe",
|
||||
var other => throw new PlatformNotSupportedException($"No prebuilt nodtool release for Windows {other}"),
|
||||
};
|
||||
}
|
||||
return RuntimeInformation.OSArchitecture switch
|
||||
{
|
||||
Architecture.X64 => "nodtool-linux-x86_64",
|
||||
Architecture.Arm64 => "nodtool-linux-aarch64",
|
||||
Architecture.X86 => "nodtool-linux-i686",
|
||||
var other => throw new PlatformNotSupportedException($"No prebuilt nodtool release for Linux {other}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>WiiCompiled.NodTool</RootNamespace>
|
||||
<AssemblyName>WiiCompiled.NodTool</AssemblyName>
|
||||
<Version>0.2.22</Version>
|
||||
<Authors>patchzy</Authors>
|
||||
<Product>WiiCompiled</Product>
|
||||
<Description>Shared nodtool acquisition/parsing logic used by both the Windows and Linux installers</Description>
|
||||
<DebugType>embedded</DebugType>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,28 +1,32 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using WiiCompiled.NodTool;
|
||||
|
||||
namespace WiiCompiled.Setup.Linux;
|
||||
|
||||
/// <summary>
|
||||
/// Validates and extracts the user's own Mario Kart Wii disc via the system-installed
|
||||
/// `dolphin-tool` (required prerequisite, per the project's scoping decision to depend on system
|
||||
/// tools rather than bundling one) - the Linux analogue of the Windows installer shelling out to a
|
||||
/// bundled DolphinTool.exe.
|
||||
/// Validates and extracts the user's own Mario Kart Wii disc via `nodtool` (see
|
||||
/// WiiCompiled.NodTool/NodToolProvider.cs) - a prebuilt, MIT/Apache-2.0-licensed CLI from
|
||||
/// encounter/nod, replacing the earlier dependency on a system-installed `dolphin-tool`
|
||||
/// (GPL-2.0-or-later, and not reliably packaged standalone by every distro).
|
||||
/// </summary>
|
||||
internal static class DiscTool
|
||||
{
|
||||
public static async Task ValidateAndExtractAsync(
|
||||
string isoPath, ProjectManifest manifest, string assetsDirectory,
|
||||
IInstallReporter reporter, CancellationToken cancellationToken)
|
||||
string isoPath, ProjectManifest manifest, string assetsDirectory, string workspace,
|
||||
string? nodToolBin, IInstallReporter reporter, CancellationToken cancellationToken)
|
||||
{
|
||||
var nodTool = nodToolBin ?? await NodToolProvider.ResolveAsync(workspace, cancellationToken);
|
||||
|
||||
// `nodtool info` only decodes the disc/partition headers (milliseconds); `nodtool extract`
|
||||
// copies the whole data partition to disk (tens of seconds for a custom-track-heavy MKWii
|
||||
// ISO). Checking the game ID first, before extracting, means a wrong disc fails fast -
|
||||
// matching the original dolphin-tool `header` step this replaces.
|
||||
reporter.Progress(InstallStages.ExtractDisc, "Reading the disc header", 2);
|
||||
var headerJson = await RunAsync(["header", "-i", isoPath, "-j"], cancellationToken);
|
||||
using var header = JsonDocument.Parse(headerJson);
|
||||
var gameId = header.RootElement.GetProperty("game_id").GetString();
|
||||
if (!string.Equals(gameId, manifest.GameId, StringComparison.Ordinal))
|
||||
var info = NodToolInfoParser.Parse(await RunInfoAsync(nodTool, isoPath, cancellationToken));
|
||||
if (!string.Equals(info.GameId, manifest.GameId, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"This disc is '{gameId}', not the expected '{manifest.GameId}' (Mario Kart Wii, region {manifest.Region}). " +
|
||||
$"This disc is '{info.GameId}', not the expected '{manifest.GameId}' (Mario Kart Wii, region {manifest.Region}). " +
|
||||
"Only your own legally-owned copy of that exact game/region can be used.");
|
||||
}
|
||||
|
||||
@@ -31,12 +35,12 @@ internal static class DiscTool
|
||||
Directory.CreateDirectory(scratch);
|
||||
try
|
||||
{
|
||||
await RunAsync(["extract", "-i", isoPath, "-g", "-o", scratch, "-q"], cancellationToken);
|
||||
await RunExtractAsync(nodTool, isoPath, scratch, cancellationToken);
|
||||
|
||||
var dolPath = Path.Combine(scratch, "DATA", "sys", "main.dol");
|
||||
var relPath = Path.Combine(scratch, "DATA", "files", "rel", "StaticR.rel");
|
||||
if (!File.Exists(dolPath)) throw new FileNotFoundException("dolphin-tool did not produce main.dol", dolPath);
|
||||
if (!File.Exists(relPath)) throw new FileNotFoundException("dolphin-tool did not produce StaticR.rel", relPath);
|
||||
var dolPath = Path.Combine(scratch, "sys", "main.dol");
|
||||
var relPath = Path.Combine(scratch, "files", "rel", "StaticR.rel");
|
||||
if (!File.Exists(dolPath)) throw new FileNotFoundException("nodtool did not produce main.dol", dolPath);
|
||||
if (!File.Exists(relPath)) throw new FileNotFoundException("nodtool did not produce StaticR.rel", relPath);
|
||||
|
||||
var dolSha = Sha256Of(dolPath);
|
||||
var relSha = Sha256Of(relPath);
|
||||
@@ -64,32 +68,53 @@ internal static class DiscTool
|
||||
}
|
||||
}
|
||||
|
||||
private static string Sha256Of(string path)
|
||||
private static async Task<string> RunInfoAsync(string nodTool, string isoPath, CancellationToken cancellationToken)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static async Task<string> RunAsync(string[] arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo("dolphin-tool")
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo(nodTool)
|
||||
{
|
||||
ArgumentList = { "info", isoPath },
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
};
|
||||
foreach (var argument in arguments) startInfo.ArgumentList.Add(argument);
|
||||
|
||||
using var process = System.Diagnostics.Process.Start(startInfo)
|
||||
?? throw new InvalidOperationException("Failed to start dolphin-tool. Is it installed and on PATH?");
|
||||
?? throw new InvalidOperationException($"Failed to start {nodTool}.");
|
||||
var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken);
|
||||
var stderr = await process.StandardError.ReadToEndAsync(cancellationToken);
|
||||
await process.WaitForExitAsync(cancellationToken);
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"dolphin-tool {string.Join(' ', arguments)} failed (exit {process.ExitCode}): {stderr}");
|
||||
$"nodtool could not read this disc image (exit {process.ExitCode}): {stderr}{stdout}".Trim());
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
|
||||
private static string Sha256Of(string path)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static async Task RunExtractAsync(string nodTool, string isoPath, string outDir, CancellationToken cancellationToken)
|
||||
{
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo(nodTool)
|
||||
{
|
||||
ArgumentList = { "extract", isoPath, outDir, "-q" },
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
};
|
||||
|
||||
using var process = System.Diagnostics.Process.Start(startInfo)
|
||||
?? throw new InvalidOperationException($"Failed to start {nodTool}.");
|
||||
var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken);
|
||||
var stderr = await process.StandardError.ReadToEndAsync(cancellationToken);
|
||||
await process.WaitForExitAsync(cancellationToken);
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"nodtool extract {isoPath} failed (exit {process.ExitCode}): {stderr}{stdout}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ internal static class Program
|
||||
reporter.Progress(InstallStages.Validate, "Checking prerequisites", 1);
|
||||
if (flags.TryGetValue("game", out var isoPath) && !string.IsNullOrEmpty(isoPath))
|
||||
{
|
||||
await DiscTool.ValidateAndExtractAsync(isoPath, manifest, assetsDir, reporter, token);
|
||||
await DiscTool.ValidateAndExtractAsync(isoPath, manifest, assetsDir, workspace,
|
||||
flags.GetValueOrDefault("disc-tool-bin"), reporter, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -261,7 +262,8 @@ internal static class Program
|
||||
|
||||
install --profile {base|retro-rewind|both} [--game ISO_PATH] [--install-dir DIR]
|
||||
[--retro-rewind-package-dir DIR] [--retro-wfc-offline-dir DIR | --skip-retro-wfc-payload]
|
||||
[--force-clean-build] [--translator-bin PATH] [--progress-json] [--workspace DIR]
|
||||
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
|
||||
[--progress-json] [--workspace DIR]
|
||||
uninstall [--profile {base|retro-rewind|both|all}]
|
||||
launch-base
|
||||
launch-retro
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WiiCompiled.NodTool\WiiCompiled.NodTool.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -63,8 +63,8 @@ internal static class ConsoleCommands
|
||||
{
|
||||
using var payload = PayloadArchive.OpenCurrent();
|
||||
var manifest = payload.ReadManifest();
|
||||
var tool = Path.Combine(temp, "DolphinTool.exe");
|
||||
payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "DolphinTool.exe", tool);
|
||||
var tool = Path.Combine(temp, "nodtool.exe");
|
||||
payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool);
|
||||
payload.ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", temp);
|
||||
reporter?.Progress(InstallStages.Validate, "Checking the Wii disc image...", 10);
|
||||
var header = InputValidation.ReadDiscHeaderAsync(tool, command.GamePath!).GetAwaiter().GetResult();
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using WiiCompiled.NodTool;
|
||||
|
||||
namespace WiiCompiled.Setup;
|
||||
|
||||
@@ -47,23 +48,37 @@ internal static class InputValidation
|
||||
"Select a complete Wii disc image in ISO, GCM, GCZ, CISO, WBFS, WIA, or RVZ format.");
|
||||
}
|
||||
|
||||
public static async Task<DiscHeader> ReadDiscHeaderAsync(string dolphinTool, string gamePath,
|
||||
public static async Task<DiscHeader> ReadDiscHeaderAsync(string nodTool, string gamePath,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ValidateExtension(gamePath);
|
||||
var result = await ProcessRunner.RunAsync(dolphinTool,
|
||||
["header", "-i", Path.GetFullPath(gamePath), "-j"], null, cancellationToken);
|
||||
var result = await ProcessRunner.RunAsync(nodTool,
|
||||
["info", Path.GetFullPath(gamePath)], null, cancellationToken);
|
||||
if (result.ExitCode != 0)
|
||||
throw new InvalidDataException("DolphinTool could not read this disc image. " + result.CombinedOutput.Trim());
|
||||
throw new InvalidDataException("nodtool could not read this disc image. " + result.CombinedOutput.Trim());
|
||||
|
||||
var json = result.StandardOutput.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
|
||||
.FirstOrDefault(line => line.TrimStart().StartsWith('{'));
|
||||
if (json is null)
|
||||
throw new InvalidDataException("DolphinTool did not return disc metadata.");
|
||||
return JsonSerializer.Deserialize<DiscHeader>(json)
|
||||
?? throw new InvalidDataException("DolphinTool returned invalid disc metadata.");
|
||||
var info = NodToolInfoParser.Parse(result.StandardOutput);
|
||||
return new DiscHeader
|
||||
{
|
||||
GameId = info.GameId,
|
||||
InternalName = info.Title,
|
||||
Region = RegionFromGameId(info.GameId),
|
||||
Revision = info.Revision,
|
||||
};
|
||||
}
|
||||
|
||||
private static string RegionFromGameId(string gameId) => gameId.Length >= 4
|
||||
? gameId[3] switch
|
||||
{
|
||||
'P' => "PAL",
|
||||
'E' => "NTSC-U",
|
||||
'J' => "NTSC-J",
|
||||
'K' => "Korea",
|
||||
'W' => "Taiwan",
|
||||
_ => gameId[3].ToString(),
|
||||
}
|
||||
: "Unknown";
|
||||
|
||||
public static void EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest)
|
||||
{
|
||||
if (!header.GameId.Equals(manifest.ExpectedGameId, StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
@@ -57,9 +57,9 @@ internal sealed class InstallerEngine
|
||||
|
||||
var runtimeAssetsCurrent = sameToolkit && RuntimeAssetsAreCurrent(existing,
|
||||
candidateRuntimeAssetsFingerprint, cancellationToken);
|
||||
var installedDolphinTool = Path.Combine(existing.ToolkitDirectory, "DolphinTool.exe");
|
||||
var installedNodTool = Path.Combine(existing.ToolkitDirectory, "nodtool.exe");
|
||||
var extractToolkit = MustRefreshToolkit(sameToolkit, samePackageContent,
|
||||
File.Exists(installedDolphinTool));
|
||||
File.Exists(installedNodTool));
|
||||
var extractWorkspace = !sameToolkit || !runtimeAssetsCurrent;
|
||||
|
||||
_reporter.Progress(InstallStages.ExtractToolkit,
|
||||
@@ -77,9 +77,9 @@ internal sealed class InstallerEngine
|
||||
payload.ExtractEntry(InstalledLayout.PayloadManifestFileName,
|
||||
Path.Combine(staging, InstalledLayout.PayloadManifestFileName));
|
||||
|
||||
var dolphinTool = extractToolkit ? Path.Combine(toolkit, "DolphinTool.exe") : installedDolphinTool;
|
||||
var nodTool = extractToolkit ? Path.Combine(toolkit, "nodtool.exe") : installedNodTool;
|
||||
_reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 2);
|
||||
var header = await InputValidation.ReadDiscHeaderAsync(dolphinTool, options.GamePath,
|
||||
var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath,
|
||||
cancellationToken);
|
||||
InputValidation.EnsureCompatibleDisc(header, manifest);
|
||||
var canonicalRetroRoot = options.RetroDirectoryPath is null
|
||||
@@ -153,7 +153,7 @@ internal sealed class InstallerEngine
|
||||
|
||||
if (reusableGameAssets is null)
|
||||
{
|
||||
await ExtractGameAssetsAsync(dolphinTool, options.GamePath,
|
||||
await ExtractGameAssetsAsync(nodTool, options.GamePath,
|
||||
Path.Combine(staging, "GameAssets"), manifest, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ internal sealed class InstallerEngine
|
||||
|
||||
|
||||
internal static bool MustRefreshToolkit(bool sameToolkit, bool samePackageContent,
|
||||
bool dolphinToolPresent) =>
|
||||
!sameToolkit || !samePackageContent || !dolphinToolPresent;
|
||||
bool nodToolPresent) =>
|
||||
!sameToolkit || !samePackageContent || !nodToolPresent;
|
||||
|
||||
private static void AddComponent(List<InstallTransactionEntry> entries, string staging,
|
||||
string installDirectory, string name) =>
|
||||
@@ -469,18 +469,23 @@ internal sealed class InstallerEngine
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractGameAssetsAsync(string dolphinTool, string gamePath, string destination,
|
||||
private async Task ExtractGameAssetsAsync(string nodTool, string gamePath, string destination,
|
||||
PayloadManifest manifest, CancellationToken cancellationToken)
|
||||
{
|
||||
_reporter.Progress(InstallStages.ExtractDisc,
|
||||
"Extracting the game disc. This is the longest preparation step...", 6);
|
||||
var extraction = await ProcessRunner.RunAsync(dolphinTool,
|
||||
["extract", "-i", Path.GetFullPath(gamePath), "-o", destination, "-g", "-q"],
|
||||
// Extracted straight into a "DATA" subfolder so the on-disk layout matches what
|
||||
// Installation.GameDataDirectory and every other reader of it already expect - nodtool
|
||||
// itself has no such wrapper (it extracts sys/+files/ directly to whatever <outdir> is
|
||||
// given), so this is purely destination-side, not a nodtool convention.
|
||||
var dataRoot = Path.Combine(destination, "DATA");
|
||||
var extraction = await ProcessRunner.RunAsync(nodTool,
|
||||
["extract", Path.GetFullPath(gamePath), dataRoot, "-q"],
|
||||
line => { if (!string.IsNullOrWhiteSpace(line)) _reporter.Diagnostic(line); },
|
||||
cancellationToken);
|
||||
if (extraction.ExitCode != 0)
|
||||
throw new InvalidDataException("Game extraction failed. " + extraction.CombinedOutput.Trim());
|
||||
ValidateExtractedGame(Path.Combine(destination, "DATA"), manifest);
|
||||
ValidateExtractedGame(dataRoot, manifest);
|
||||
}
|
||||
|
||||
private static void ValidateExtractedGame(string dataRoot, PayloadManifest manifest)
|
||||
|
||||
@@ -143,17 +143,17 @@ internal static class SelfTests
|
||||
private static void TestToolkitRefreshDecision()
|
||||
{
|
||||
if (!InstallerEngine.MustRefreshToolkit(sameToolkit: false, samePackageContent: true,
|
||||
dolphinToolPresent: true))
|
||||
nodToolPresent: true))
|
||||
throw new Exception("A republished workspace kept the installed toolkit; the shipped " +
|
||||
"translator and project file could come from different releases.");
|
||||
if (!InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: false,
|
||||
dolphinToolPresent: true))
|
||||
nodToolPresent: true))
|
||||
throw new Exception("Changed toolkit package content was not extracted.");
|
||||
if (!InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: true,
|
||||
dolphinToolPresent: false))
|
||||
throw new Exception("A missing DolphinTool.exe did not force toolkit extraction.");
|
||||
nodToolPresent: false))
|
||||
throw new Exception("A missing nodtool.exe did not force toolkit extraction.");
|
||||
if (InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: true,
|
||||
dolphinToolPresent: true))
|
||||
nodToolPresent: true))
|
||||
throw new Exception("An unchanged toolkit was needlessly re-extracted.");
|
||||
}
|
||||
|
||||
@@ -1010,7 +1010,7 @@ internal static class SelfTests
|
||||
throw new Exception("The toolkit fingerprint is not stable.");
|
||||
|
||||
// A file that has nothing to do with generated code must not invalidate every install.
|
||||
File.WriteAllText(Path.Combine(root, "Toolkit", "DolphinTool.exe"), "irrelevant");
|
||||
File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "irrelevant");
|
||||
if (ToolkitFingerprint.Compute(root) != first)
|
||||
throw new Exception("An unrelated toolkit file changed the fingerprint.");
|
||||
|
||||
@@ -1177,7 +1177,7 @@ internal static class SelfTests
|
||||
"x86_64-w64-mingw32-clang++.exe", "x86_64-w64-mingw32-windres.exe"
|
||||
})
|
||||
File.WriteAllText(Path.Combine(root, "Toolkit", "llvm-mingw", "bin", executable), executable);
|
||||
File.WriteAllText(Path.Combine(root, "Toolkit", "DolphinTool.exe"), "tool");
|
||||
File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool");
|
||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build");
|
||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags");
|
||||
File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", "mkwii", "recomp.yml"), "profiles: {}");
|
||||
|
||||
@@ -78,12 +78,12 @@ internal static class ToolkitFingerprint
|
||||
var workspace = InstalledLayout.Workspace(root);
|
||||
var entries = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
// DolphinTool validates/extracts the user disc but does not influence generated products.
|
||||
// nodtool validates/extracts the user disc but does not influence generated products.
|
||||
// Everything else in Toolkit can affect translation, compilation, linking, or copied
|
||||
// runtime support and therefore belongs to the compile identity.
|
||||
AddDirectory(entries, root, toolkit, null,
|
||||
cancellationToken,
|
||||
file => !Path.GetFileName(file).Equals("DolphinTool.exe", StringComparison.OrdinalIgnoreCase));
|
||||
file => !Path.GetFileName(file).Equals("nodtool.exe", StringComparison.OrdinalIgnoreCase));
|
||||
AddFile(entries, root, Path.Combine(workspace, "LocalBuild.ps1"), cancellationToken);
|
||||
AddFile(entries, root, Path.Combine(workspace, "NativeBuildFlags.ps1"), cancellationToken);
|
||||
AddDirectory(entries, root, Path.Combine(workspace, "projects"), null, cancellationToken);
|
||||
|
||||
@@ -14,4 +14,7 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WiiCompiled.NodTool\WiiCompiled.NodTool.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Packages Launcher/WiiCompiled.Setup.Linux as a self-contained AppImage: a single file Wheel
|
||||
# Wizard (or anyone else) can fetch and execute with no git clone and no `dotnet` install at all -
|
||||
# both the installer and the translator are published as self-contained binaries and bundled, so
|
||||
# AppRun passes --translator-bin to skip local-build.sh's own dotnet-build-from-source step. It
|
||||
# still shells out to system clang/cmake/ninja/dolphin-tool - no C/C++ toolchain is bundled,
|
||||
# matching Launcher/local-build.sh's own remaining prerequisites.
|
||||
# Wizard (or anyone else) can fetch and execute with no git clone, no `dotnet` install, and no
|
||||
# `dolphin-tool` package required at all. The installer and translator are published as
|
||||
# self-contained binaries, and `nodtool` (a prebuilt MIT/Apache-2.0 CLI from encounter/nod, see
|
||||
# NodToolProvider.cs) is downloaded and bundled too - AppRun passes --translator-bin and
|
||||
# --disc-tool-bin so local-build.sh/DiscTool.cs skip their from-source/download fallbacks entirely.
|
||||
# It still shells out to system clang/cmake/ninja - no C/C++ toolchain is bundled, matching
|
||||
# Launcher/local-build.sh's own remaining prerequisites.
|
||||
#
|
||||
# An AppImage mounts read-only, but local-build.sh writes generated/, native-build/, Assets/, etc.
|
||||
# into the workspace it's given. So AppRun (written below) copies the bundled workspace snapshot
|
||||
@@ -59,6 +61,16 @@ dotnet publish "$workspace/translator/src/Translator.Cli" -c Release -r linux-x6
|
||||
cp "$translator_publish_tmp/Translator.Cli" "$appdir/usr/bin/translator-cli"
|
||||
chmod +x "$appdir/usr/bin/translator-cli"
|
||||
|
||||
# Resolved via the shared WiiCompiled.NodTool.Cli helper (also used by Build-Installer.ps1 on
|
||||
# Windows) rather than a second curl/version-pin copy here: it downloads and caches the same way
|
||||
# NodToolProvider.cs always does (Launcher/artifacts/nodtool), so there is exactly one place that
|
||||
# knows the nodtool version/URL/platform-asset mapping.
|
||||
echo "Resolving nodtool..."
|
||||
nodtool_path=$(dotnet run --project "$workspace/Launcher/WiiCompiled.NodTool.Cli" -c Release -- \
|
||||
--workspace "$workspace" | tail -n1)
|
||||
cp "$nodtool_path" "$appdir/usr/bin/nodtool"
|
||||
chmod +x "$appdir/usr/bin/nodtool"
|
||||
|
||||
echo "Staging the bundled workspace snapshot..."
|
||||
for dir in runtime aurora-main projects; do
|
||||
cp -r "$workspace/$dir" "$appdir/workspace/$dir"
|
||||
@@ -94,7 +106,9 @@ if [ ! -f "$CACHE/.bundle-version" ] || \
|
||||
cp "$HERE/workspace/Launcher/local-build.sh" "$CACHE/Launcher/local-build.sh"
|
||||
cp "$HERE/workspace/.bundle-version" "$CACHE/.bundle-version"
|
||||
fi
|
||||
exec "$HERE/usr/bin/wiicompiled-setup" --workspace "$CACHE" --translator-bin "$HERE/usr/bin/translator-cli" "$@"
|
||||
exec "$HERE/usr/bin/wiicompiled-setup" --workspace "$CACHE" \
|
||||
--translator-bin "$HERE/usr/bin/translator-cli" \
|
||||
--disc-tool-bin "$HERE/usr/bin/nodtool" "$@"
|
||||
APPRUN
|
||||
chmod +x "$appdir/AppRun"
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ included in the installer's `licenses/` folder.
|
||||
| SQLite | 3.51.3 amalgamation | Public domain | <https://sqlite.org/> |
|
||||
| Tracy Profiler | pinned commit | BSD-3-Clause | <https://github.com/wolfpld/tracy> |
|
||||
| C++/WinRT | - | MIT (Microsoft) | <https://github.com/microsoft/cppwinrt> |
|
||||
| nodtool (disc image extraction) | v2.0.0-alpha.10 | MIT OR Apache-2.0 | <https://github.com/encounter/nod> |
|
||||
|
||||
### Dual-licensed components - elections made by this project
|
||||
|
||||
@@ -164,16 +165,15 @@ unmodified, with their license texts, in the installer's `licenses/` folder.
|
||||
| llvm-mingw (Clang, LLD, libc++, libunwind, MinGW-w64 runtime) | Apache-2.0 with LLVM Exception; MinGW-w64 runtime under its own permissive terms; bundled GNU utilities under GPL-2.0-or-later or GPL-3.0-or-later | <https://github.com/mstorsjo/llvm-mingw> |
|
||||
| CMake | BSD-3-Clause | <https://cmake.org/> |
|
||||
| Ninja | Apache-2.0 | <https://ninja-build.org/> |
|
||||
| DolphinTool (disc image extraction) | GPL-2.0-or-later | <https://github.com/dolphin-emu/dolphin> |
|
||||
| nodtool (disc image extraction) | MIT OR Apache-2.0 | <https://github.com/encounter/nod> |
|
||||
| Microsoft Visual C++ Runtime (`vcruntime140.dll`, `vcruntime140_1.dll`, `msvcp140.dll`) | Microsoft redistributable terms | Microsoft Visual Studio |
|
||||
| `dxil.dll` | Microsoft redistributable (proprietary signing library) | Microsoft |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Several toolkit components are GPL-licensed (DolphinTool, and the GNU utilities inside
|
||||
> llvm-mingw). Their complete corresponding source is available from the upstream projects linked
|
||||
> above at their pinned versions, and this project will supply it on request for the exact versions
|
||||
> shipped in any given release. Pins live in `Launcher/Prepare-PortableTools.ps1` and
|
||||
> `Launcher/NativeBuildFlags.ps1`.
|
||||
> The GNU utilities bundled inside llvm-mingw are GPL-licensed. Their complete corresponding source
|
||||
> is available from the upstream project linked above at its pinned version, and this project will
|
||||
> supply it on request for the exact version shipped in any given release. Pins live in
|
||||
> `Launcher/Prepare-PortableTools.ps1` and `Launcher/NativeBuildFlags.ps1`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user