Merge upstream/main into openxr-work (reverse-Z fix adapted for VR)

Brings in patchzyy/Wiicompiled main: os_sleep parked-thread fix (#195),
HTTPS Retro WFC payload (#198), macOS build guide (#177), and the
reverse-Z depth fix (#134).

Conflicts were in aurora-main/lib/gfx/common.cpp and lib/gx/shader.cpp,
both from #134, which lands squarely on the VR stereo replay path.

#134 makes UseReversedZ genuinely reversed: the near/far correction now
applies exactly once, inside effective_projection(), instead of being
applied there AND per-vertex in the shader (the double application had
been cancelling out, so "reversed" Z silently behaved like forward Z).
Three pieces of the VR path were built against that old behaviour and
would have broken silently, so they are adapted here:

- shader.cpp exact-screen-depth parked the virtual screen at -0.5*w
  specifically so the shader's following negation would land it at
  +0.5*w. With that negation gone it now writes +0.5*w directly; keeping
  the minus sign would park the screen at NDC -0.5, outside the clip
  volume, discarding every 2D/HUD draw.

- stereo_replay.hpp backend_ndc_depth_row re-applied the correction to
  the projection it was handed. That projection is effective_projection()
  output, which now already carries it, so the function is a pass-through
  of the Z row and no longer depends on the reversed-Z setting; the dead
  bool parameter is dropped. Re-applying it would invert the virtual
  screen's depth ordering, so 2D layers meant to sit on top would lose
  the depth test to the ones behind them.

- shader_info.cpp stages the host depth window for that exact-depth path.
  It now uses the same reversed-Z remap as upstream's new SetViewport
  code, since frag_depth is written directly and has to reproduce the
  window the fixed viewport transform would have applied. Restricted
  depth windows (how the game forces an element in front of everything)
  are exactly the 2D draws the virtual screen carries.

The SetViewport resolution keeps upstream's remap but retains the
ordering/clamp guard our version had: for any ordered guest range the
result is identical to upstream, and it avoids handing WebGPU
minDepth > maxDepth for the swapped pair MKW is known to emit. The VR eye
replay reuses these recorded values, so the guard covers that path too.

Test updates:
- stereo_replay_test now asserts the composed Z row against the staged
  projection's own Z row rather than against the helper's output, so it
  actually catches a re-introduced double correction (verified: it fails
  when the old negation is put back; the previous self-consistent form
  passed).
- gx_fifo_test's clearDepthValue expectation followed #134's deliberate
  clear_depth_value() inversion, expressed through UseReversedZ rather
  than hardcoded. Upstream changed the behaviour without updating this
  test, so it fails on upstream/main as-is.

Verified: aurora suite 247 passed with the same 2 failures that already
fail on the pre-merge branch (IndexedPaletteHistoryKeepsAbsoluteVertexSlots,
PacksOneUniformWhenBothHalvesNeedInitialValue - both pre-existing, unrelated
to depth); shader.cpp and common.cpp compile clean; translator suite 577
passed. Not yet validated on-device in VR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iChris4andClaude Opus 5 committed 2026-09-10 04:19:51 +02:00
commit c8eaa52727
22 files changed
+635 -71

No files matched your search

+1
View File
@@ -3,3 +3,4 @@
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
*.patch -text
translator/tests/Translator.Tests/TestAssets/**/*.bin binary
+7 -1
View File
@@ -1,6 +1,6 @@
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
# that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on
# consumers can't read YAML (the C++ runtime header, the C# constants, shell scripts, and hand-written lists on
# both sides of the C#/PowerShell boundary), so those are checked here instead.
[CmdletBinding()]
param([string]$RepositoryRoot)
@@ -58,6 +58,12 @@ $hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
}
$macosSetup = Read-SourceFile (Join-Path $launcher 'macos\setup.command') 'macOS setup.command'
$macosUri = Get-CapturedValue $macosSetup "'([^']*/api/wfc/payload\?g=RMCPD00)'" `
'The macOS Retro-WFC endpoint'
if ($macosUri -cne $pins.RetroWfcPayloadUri) {
Add-Failure "macOS setup.command downloads '$macosUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
}
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
# --- manifest that predates the field, and that fallback decides which disc is accepted.
@@ -24,7 +24,7 @@ public static class RetroWfcPayload
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00";
public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00";
private static readonly string RetroWfcOfflinePayloadFile =
Path.Combine("binary", "payload.RMCPD00.bin");
+1 -1
View File
@@ -90,7 +90,7 @@ if [[ -n "$retro_dir" ]]; then
trap 'rm -rf "$payload_stage"' EXIT
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
--retry 1 --output "$temporary_payload" \
'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
'https://rwfc.net/api/wfc/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
fail 'downloaded Retro-WFC payload failed signature validation'
mkdir -p "$retro_wfc_dir/binary"
+1
View File
@@ -233,5 +233,6 @@ EOF
rm -rf "$test_dir"
trap - EXIT
rm -rf "$toolchain_dir"
mv "$work" "$toolchain_dir"
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
+3 -1
View File
@@ -167,7 +167,9 @@ The default test suite needs no binaries and no host C++ compiler, so you can ha
translator without any game data around.
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
translation, generating the manifest and build graph, and compiling. see [`translator/README.md`](translator/README.md).
translation, generating the manifest and build graph, and compiling, see [`translator/README.md`](translator/README.md).
For a step-by-step guide on compiling both WiiCompiled and Retro Rewind from source on macOS (Apple Silicon), see the [macOS Build Guide](docs/building-macos.md).
## FAQ
+31 -7
View File
@@ -170,7 +170,12 @@ struct RenderPass {
Range resolveUniformRange;
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
float clearDepthValue = 1.f;
// 1.f is the forward-Z "farthest" clear value; under UseReversedZ farthest is 0.f instead (see
// gx::clear_depth_value(), which the main render pass explicitly overrides this default with -
// any OTHER pass that keeps this default, e.g. an offscreen render-to-texture pass composited
// later, needs the same reversed-Z-aware value or its depth buffer starts "already nearest",
// failing every subsequent depth test and making whatever's drawn into it vanish).
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
CommandList commands;
bool clearColor = true;
bool clearDepth = true;
@@ -835,7 +840,9 @@ void begin_offscreen(uint32_t width, uint32_t height) {
.targetSize = {width, height, 1},
.msaaSamples = 1,
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
.clearDepthValue = 1.f,
// See the RenderPass::clearDepthValue default's comment: this offscreen pass gets its own
// depth buffer, and the farthest clear value is 0.f, not 1.f, under UseReversedZ.
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
.clearColor = true,
.clearDepth = true,
};
@@ -1412,7 +1419,7 @@ static bool prepare_stereo_replay_uniforms(const StereoReplayFrame& stereoFrame)
static_cast<float>(displayRegion.x), static_cast<float>(displayRegion.y),
static_cast<float>(displayRegion.width), static_cast<float>(displayRegion.height));
const auto projection = stereo_replay::compose_hud_screen_projection(
eye.projection, eye.viewFromCenter, hudScreen, gameProjection, gx::UseReversedZ, ndcRemap);
eye.projection, eye.viewFromCenter, hudScreen, gameProjection, ndcRemap);
std::memcpy(uniform.data() + layout.projectionOffset, &projection, sizeof(projection));
}
@@ -1883,10 +1890,27 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
switch (cmd.type) {
case CommandType::SetViewport: {
const auto& vp = cmd.data.setViewport;
// WebGPU requires 0 <= minDepth <= maxDepth <= 1, and the guest's (near, far) order is already
// reproduced in clip space. Passing the raw swapped pair diverged per backend in release builds.
const float minDepth = std::clamp(std::min(vp.znear, vp.zfar), 0.0f, 1.0f);
const float maxDepth = std::clamp(std::max(vp.znear, vp.zfar), 0.0f, 1.0f);
// WebGPU requires 0 <= minDepth <= maxDepth <= 1. vp.znear/vp.zfar are in GX's own distance
// terms (0 = near); under UseReversedZ the host depth-buffer storage direction is flipped
// (near = 1, far = 0), so this range has to be remapped through 1-x the same way the
// projection matrix, depth compare function, and clear value all are - a plain min/max clamp
// (the previous code here) maps a *restricted* range (e.g. a viewport deliberately narrowed
// to force something to draw "in front of everything") to the wrong end of the buffer: what
// should land near the near-storage-extreme (1.0) instead lands near the far-storage-extreme
// (0.0), so anything else drawn afterward at its true depth wins the compare test and the
// "in front" geometry silently vanishes. A full [0,1] viewport is unaffected either way,
// which is why this only broke specific elements, not the whole scene. Matches upstream
// aurora's apply_viewport (lib/gfx/encoding.cpp).
//
// The remapped pair is then ordered and clamped before it reaches WebGPU. For any ordered
// guest range this is a no-op (znear <= zfar implies 1-zfar <= 1-znear), so upstream's fix is
// reproduced exactly; it only guards the swapped pair MKW is known to emit, which the raw
// remap would hand to SetViewport as minDepth > maxDepth and fail validation. The VR eye
// replay below reuses these recorded values, so the guard covers that path too.
const float remappedNear = gx::UseReversedZ ? 1.0f - vp.zfar : vp.znear;
const float remappedFar = gx::UseReversedZ ? 1.0f - vp.znear : vp.zfar;
const float minDepth = std::clamp(std::min(remappedNear, remappedFar), 0.0f, 1.0f);
const float maxDepth = std::clamp(std::max(remappedNear, remappedFar), 0.0f, 1.0f);
recordedViewport = {
.left = (vp.left - static_cast<float>(sourceRegionLeft)) * scaleX,
.top = (vp.top - static_cast<float>(sourceRegionTop)) * scaleY,
+1 -1
View File
@@ -92,7 +92,7 @@ struct Params {
constexpr std::string_view ReversedZBody = R"(
fn gx_z24(depth: f32) -> u32 {
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
}
)"sv;
+20 -13
View File
@@ -107,17 +107,24 @@ inline bool is_orthographic_projection(const Mat4x4<float>& projection) noexcept
return projection.m3[0] == 0.0f && projection.m3[1] == 0.0f && projection.m3[2] == 0.0f && projection.m3[3] == 1.0f;
}
// The stored GX projection has not yet passed through Aurora's final clip-depth
// conversion. Turn its Z row into the 0..1 backend NDC value that the original
// orthographic draw would have produced. The virtual-screen shader captures
// this row before replacing raster depth with a stable midrange value.
inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection, bool reversedDepth) noexcept {
Vec4<float> row{};
for (size_t i = 0; i < 4; ++i) {
row[i] = reversedDepth ? -projection.m2[i] : projection.m2[i] + projection.m3[i];
}
return row;
}
// The Z row that reproduces the backend NDC depth the original orthographic draw
// would have produced. The virtual-screen shader captures it before replacing
// raster depth with a stable midrange value.
//
// This is a straight pass-through of the stored Z row, and deliberately does not
// depend on the reversed-Z setting. The projection reaching here is the one staged
// into the draw's own uniform, i.e. effective_projection()'s output, which since
// the reverse-Z fix carries the near/far depth correction already applied - exactly
// once, in the matrix - and the vertex shader now adds nothing on top of it. So
// dot(v, projection.m2) IS the depth the unmodified draw would have written.
//
// It previously re-applied a correction here (negating the row under reversed Z, or
// folding m3 in under forward Z). That was correct only while the vertex shader
// still applied its own redundant per-vertex correction for this one to cancel
// against. With that per-vertex step gone, any correction here is a double
// application: it would invert the virtual screen's depth ordering, so the 2D
// layers meant to sit on top would lose the depth test to the ones behind them.
inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection) noexcept { return projection.m2; }
// Replaces an orthographic draw's projection so its 2D output lands on the
// fixed virtual screen instead of being stretched across the whole eye.
@@ -142,7 +149,7 @@ inline Vec4<float> backend_ndc_depth_row(const Mat4x4<float>& projection, bool r
// equal-depth 2D layers deterministic under head rotation and translation.
inline Mat4x4<float> compose_hud_screen_projection(const Mat4x4<float>& eyeFrustum, const Mat3x4<float>& viewFromCenter,
const HudScreen& screen, const Mat4x4<float>& gameProjection,
bool reversedDepth, const HudNdcRemap& ndcRemap = {}) noexcept {
const HudNdcRemap& ndcRemap = {}) noexcept {
const Mat4x4<float> frameProjection = remap_hud_ndc(gameProjection, ndcRemap);
// The screen point's three coordinates, each as a functional of (mv_pos, 1).
Mat3x4<float> screenPoint{};
@@ -166,7 +173,7 @@ inline Mat4x4<float> compose_hud_screen_projection(const Mat4x4<float>& eyeFrust
dst[3] += view[3];
}
const Vec4<float> exactDepthRow = backend_ndc_depth_row(gameProjection, reversedDepth);
const Vec4<float> exactDepthRow = backend_ndc_depth_row(gameProjection);
Mat4x4<float> out{};
for (size_t i = 0; i < 4; ++i) {
out.m0[i] = eyeFrustum.m0[0] * eyePoint.m0[i] + eyeFrustum.m0[2] * eyePoint.m2[i];
+1 -1
View File
@@ -137,7 +137,7 @@ fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
let tex_size = vec2i(textureDimensions(src));
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
let depth = textureLoad(src, coord, 0);
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
}
)"s
: R"(
+13 -4
View File
@@ -1416,23 +1416,32 @@ static inline GXBlendFactor remove_dst_alpha_usage(GXBlendFactor fac) {
}
}
// GX_LEQUAL etc. describe "pass if this pixel is closer than/equal to what's stored" in GX's own
// distance terms, independent of how that distance is encoded as a host depth value. Under
// UseReversedZ the encoding is flipped (near=1, far=0), so "closer" now corresponds to a *larger*
// stored value, not a smaller one - the ordered compare functions (LESS/LEQUAL/GREATER/GEQUAL)
// must invert to match, or the depth test silently runs backwards (verified directly: this was
// the actual cause of a bug report after the projection/shader half of the reverse-Z fix
// eliminated the double-negation that used to accidentally keep the unreversed comparisons
// correct - LEQUAL now needs GreaterEqual, not LessEqual, once the encoding it's testing against
// is genuinely reversed). Matches upstream aurora's to_compare_function exactly.
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
switch (func) {
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
case GX_NEVER:
return wgpu::CompareFunction::Never;
case GX_LESS:
return wgpu::CompareFunction::Less;
return UseReversedZ ? wgpu::CompareFunction::Greater : wgpu::CompareFunction::Less;
case GX_EQUAL:
return wgpu::CompareFunction::Equal;
case GX_LEQUAL:
return wgpu::CompareFunction::LessEqual;
return UseReversedZ ? wgpu::CompareFunction::GreaterEqual : wgpu::CompareFunction::LessEqual;
case GX_GREATER:
return wgpu::CompareFunction::Greater;
return UseReversedZ ? wgpu::CompareFunction::Less : wgpu::CompareFunction::Greater;
case GX_NEQUAL:
return wgpu::CompareFunction::NotEqual;
case GX_GEQUAL:
return wgpu::CompareFunction::GreaterEqual;
return UseReversedZ ? wgpu::CompareFunction::LessEqual : wgpu::CompareFunction::GreaterEqual;
case GX_ALWAYS:
return wgpu::CompareFunction::Always;
}
+8 -1
View File
@@ -487,7 +487,14 @@ const gfx::TextureBind& get_texture(GXTexMapID id) noexcept;
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
inline float clear_depth_value() {
return std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
// g_gxState.clearDepth is in GX's own distance terms (0 = near, larger = farther), independent of
// how UseReversedZ encodes that as a host depth value - it must be re-mapped the same way the
// projection matrix and depth compare function are, or the buffer clears to the wrong extreme
// (verified directly: matches upstream aurora's clear_depth_value, which does this same inversion
// and was the second missing piece alongside to_compare_function's compare-op inversion).
const float normalizedDepth =
std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
return UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth;
}
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
+39 -11
View File
@@ -990,19 +990,27 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
"\n let clip_base = select(clip_a, clip_b, use_b);"
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
}
// The near/far depth correction used to be applied here per-vertex (out.pos.z = -out.pos.z for
// reversed, or += out.pos.w for forward), redundantly on top of the same correction already
// folded into ubuf.proj by effective_projection() (shader_info.cpp) - applying it twice canceled
// out for the common case (any draw where effective_projection() decides to flip), silently
// making "reversed" Z behave identically to forward Z. It is now applied exactly once, in the
// projection matrix alone (matching upstream aurora commit 1dde08fa: "Move depth correction to
// projection matrix"), so nothing needs to happen to out.pos.z here.
if (config.exactScreenDepth) {
vtxOutAttrs += fmt::format("\n @location({}) @interpolate(flat) exact_screen_depth: f32,", vtxOutIdx++);
// Virtual-screen composition stores the original backend-convention NDC
// depth in the otherwise replaceable projection Z row. Capture it before
// parking raster depth at midrange; this avoids growing every GX uniform.
//
// The parked value is written directly as +0.5 * w now that the per-vertex
// depth correction above is gone. It used to be -0.5 * w specifically so the
// `out.pos.z = -out.pos.z` that followed would land it at +0.5 * w; with
// that negation removed, keeping the minus sign would park the virtual
// screen at NDC -0.5, outside the clip volume, and discard every 2D draw.
vtxXfrAttrsPre +=
"\n out.exact_screen_depth = clamp(out.pos.z, 0.0, 1.0);"
"\n out.pos.z = -0.5 * out.pos.w;";
}
if constexpr (UseReversedZ) {
vtxXfrAttrsPre += "\n out.pos.z = -out.pos.z;";
} else {
vtxXfrAttrsPre += "\n out.pos.z += out.pos.w;";
"\n out.pos.z = 0.5 * out.pos.w;";
}
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
vtxXfrAttrsPre +=
@@ -1469,8 +1477,18 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
textureDependency.texMapId, uvIn);
}
std::string fogDepthExpr = UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)";
std::string fogZCoordExpr = fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
// in.pos.z is the host NDC z (forward: 0=near/1=far; reversed: 1=near/0=far post-fix), but this
// expression needs to produce GX's own native distance term (always 0=near/1=far, matching how
// g_gxState.clearDepth/clear_depth_value() are interpreted before their own UseReversedZ
// inversion) - forward already matches directly; reversed needs the same 1-x flip everything
// else reversed-Z-aware uses. This was backwards (verified directly against upstream aurora's
// identical expression in build_shader_source), which fed both fog density and the GX_ZT_ADD
// z-texture path the wrong distance value.
std::string fogDepthExpr = UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z";
std::string fogZCoordExpr =
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
if (usesZTextureDepth) {
const u32 zTexBias = config.zTexture & 0x00FFFFFFu;
const u32 zTexFmt = (config.zTexture >> 24) & 0x3u;
@@ -1501,7 +1519,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
fragmentFn += fmt::format(
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)");
UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z");
}
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
fogZCoordExpr = "ztexCoord";
@@ -1645,12 +1663,22 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
if (config.exactScreenDepth) {
// Fragment depth is already in window coordinates. Reapply the recorded
// viewport's clamped depth window exactly as the fixed pipeline did.
// viewport's clamped depth window exactly as the fixed pipeline did. This
// value never passed through the per-vertex depth correction upstream
// removed, so it needs no reversed-Z adjustment of its own - the recorded
// range it is rebuilt from is already reversed-Z-aware (see the SetViewport
// remap in gfx/common.cpp).
fragmentFn +=
"\n let fragDepth = ubuf.exact_screen_depth_range.x + "
"in.exact_screen_depth * ubuf.exact_screen_depth_range.y;";
} else {
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "" : "1.0 - ");
// ztexDepth is in GX's native distance terms (0=near/1=far, see fogDepthExpr's comment
// above), but frag_depth must be written in the same host NDC-z convention in.pos.z itself
// uses - forward matches directly (no change), reversed needs the same 1-x flip. This was
// backwards the same way fogDepthExpr was (verified by the same derivation, since aurora
// upstream has no directly equivalent line here to cross-check against - this
// z-texture-depth-output path appears to be specific to this fork).
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "1.0 - " : "");
}
fragmentReturnType = "FragmentOutput";
fragmentReturn =
+23 -6
View File
@@ -559,14 +559,22 @@ constexpr u16 kEfbHeight = 528;
constexpr size_t kStagedUniformBytes = 96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
//
// Folds the near/far depth correction the vertex shader used to apply per-vertex directly into the
// projection matrix instead (matching upstream aurora commit 1dde08fa, "Move depth correction to
// projection matrix") - valid because the correction is a linear combination of the z/w rows, so
// applying it once here to the row is equivalent to applying it once per-vertex to the dot product,
// and it must be applied exactly once: doing it here AND in the shader (the previous bug) canceled
// the negation out for `flip`, silently making "reversed" Z behave identically to forward Z.
// `flip` decides which of the two single-application forms this draw needs: true bakes in the
// reversed-Z inversion (z' = -z), false bakes in the forward-Z near/far combination (z' = z + w) -
// exactly one always applies, never both, and never neither.
static Mat4x4<float> effective_projection() noexcept {
const auto& vp = g_gxState.renderViewport;
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
Mat4x4<float> proj = g_gxState.proj;
if (flip) {
for (size_t i = 0; i < 4; ++i) {
proj.m2.m[i] = -(proj.m2.m[i] + proj.m3.m[i]);
}
for (size_t i = 0; i < 4; ++i) {
proj.m2.m[i] = flip ? -proj.m2.m[i] : (proj.m2.m[i] + proj.m3.m[i]);
}
return proj;
}
@@ -597,8 +605,17 @@ UniformRanges build_uniform(const ShaderInfo& info, u32 vtxStart, const BindGrou
const Mat4x4<float> effectiveProj = effective_projection();
const auto& viewport = g_gxState.renderViewport;
const float depthNear = std::clamp(std::min(viewport.znear, viewport.zfar), 0.0f, 1.0f);
const float depthFar = std::clamp(std::max(viewport.znear, viewport.zfar), 0.0f, 1.0f);
// The host depth window this draw's viewport applies. Must stay byte-for-byte the same mapping as
// the SetViewport remap in gfx/common.cpp, because the exact-screen-depth path writes frag_depth
// directly and has to reproduce the window the fixed viewport transform would have applied. Under
// UseReversedZ the guest's GX-distance (near, far) pair is remapped through 1-x, then ordered and
// clamped for the [0,1] host range. A full guest window lands on [0,1] either way; a *restricted*
// one (how the game forces an element to draw in front of everything) is the case that differs,
// and those restricted windows are 2D/HUD draws - exactly the ones the VR virtual screen carries.
const float remappedNear = UseReversedZ ? 1.0f - viewport.zfar : viewport.znear;
const float remappedFar = UseReversedZ ? 1.0f - viewport.znear : viewport.zfar;
const float depthNear = std::clamp(std::min(remappedNear, remappedFar), 0.0f, 1.0f);
const float depthFar = std::clamp(std::max(remappedNear, remappedFar), 0.0f, 1.0f);
stage_u32(vtxStart);
// With a compacted position region the live matrix is uploaded to slot 0, so every `postex_mtx[in_pnmtxidx]` /
+8 -1
View File
@@ -4192,7 +4192,14 @@ TEST_F(GXFifoTest, CopyTexClearTruePassesScratchRectAndUpdateMasksToResolve) {
EXPECT_NEAR(resolve.clearColorValue.y(), 128.f / 255.f, 1.f / 255.f);
EXPECT_NEAR(resolve.clearColorValue.z(), 192.f / 255.f, 1.f / 255.f);
EXPECT_NEAR(resolve.clearColorValue.w(), 32.f / 255.f, 1.f / 255.f);
EXPECT_NEAR(resolve.clearDepthValue, 0x123456 / 16777216.f, 1.f / 16777216.f);
// clear_depth_value() maps the guest's GX-distance clear depth into the host depth-buffer
// convention, so under reversed Z the stored value is the 1-x mirror of the guest's normalized
// depth. Expressed through UseReversedZ rather than hardcoded, so this expectation follows the
// convention instead of pinning one side of it.
const float expectedNormalizedClearDepth = 0x123456 / 16777216.f;
EXPECT_NEAR(resolve.clearDepthValue,
aurora::gx::UseReversedZ ? 1.f - expectedNormalizedClearDepth : expectedNormalizedClearDepth,
1.f / 16777216.f);
EXPECT_EQ(resolve.resolveFormat, GX_TF_RGBA8);
EXPECT_FALSE(resolve.halfScale);
EXPECT_FALSE(resolve.forceOpaqueAlpha);
+15 -8
View File
@@ -107,8 +107,7 @@ TEST(StereoReplayTest, HudScreenProjectionMatchesTheChainItComposes) {
viewFromCenter.m2 = {-s, 0.0f, c, 7.0f};
const HudScreen screen{.halfWidth = 600.0f, .halfHeight = 337.5f, .distance = 1000.0f};
const auto composed = compose_hud_screen_projection(eyeFrustum, viewFromCenter, screen, game, true);
const auto exactDepth = backend_ndc_depth_row(game, true);
const auto composed = compose_hud_screen_projection(eyeFrustum, viewFromCenter, screen, game);
for (const auto& v : kVertices) {
// The same chain, one step at a time: game NDC, a point on the screen
@@ -124,7 +123,12 @@ TEST(StereoReplayTest, HudScreenProjectionMatchesTheChainItComposes) {
EXPECT_NEAR(dot4(composed.m1, v), eyeFrustum.m1[1] * eyeY + eyeFrustum.m1[2] * eyeZ, 1e-2f);
const float clipW = -eyeZ;
EXPECT_NEAR(dot4(composed.m3, v), clipW, 1e-2f);
EXPECT_NEAR(dot4(composed.m2, v), dot4(exactDepth, v), 1e-6f);
// The virtual screen must carry the depth the unmodified draw would have
// written. Since the reverse-Z fix moved the near/far correction wholly into
// the projection, that is the staged Z row applied directly - no further
// inversion. Comparing against `game.m2` rather than the helper's own output
// is what makes this catch a re-introduced double correction.
EXPECT_NEAR(dot4(composed.m2, v), dot4(game.m2, v), 1e-6f);
}
}
@@ -142,16 +146,19 @@ TEST(StereoReplayTest, HudScreenParksRasterDepthAtMidrangeUnderHeadMotion) {
moved.m2 = {-s, 0.0f, c, 13.0f};
const HudScreen screen{.halfWidth = 600.0f, .halfHeight = 337.5f, .distance = 1000.0f};
const auto composed = compose_hud_screen_projection(eyeFrustum, moved, screen, game, true);
const auto composed = compose_hud_screen_projection(eyeFrustum, moved, screen, game);
// The exact-depth shader captures composed Z, then parks clip Z at -0.5W.
// Aurora's following reversed-depth conversion negates that to +0.5W, so
// The exact-depth shader captures composed Z, then parks clip Z at +0.5W, so
// rasterization stays stable even though W varies across the rotated screen.
// It writes +0.5W directly: the reverse-Z fix removed the per-vertex depth
// negation that used to follow, which is why the shader no longer pre-negates
// to -0.5W. What this test pins is the invariant that survived that change -
// the parked value must land at NDC 0.5 for every vertex, whatever W does.
for (const auto& v : kVertices) {
const float w = dot4(composed.m3, v);
ASSERT_GT(w, 0.0f);
const float parkedClipZ = -0.5f * w;
EXPECT_NEAR(-parkedClipZ / w, 0.5f, 1e-5f);
const float parkedClipZ = 0.5f * w;
EXPECT_NEAR(parkedClipZ / w, 0.5f, 1e-5f);
}
}
+349
View File
@@ -0,0 +1,349 @@
# Building WiiCompiled and Retro Rewind on macOS
This guide covers building **WiiCompiled** (base game) and **Retro Rewind** from source on macOS for Apple Silicon (`arm64`). Follow these instructions to compile the native executables directly.
> [!NOTE]
> If you only want to build the base game (**WiiCompiled**), look for sections marked **`(Skip if only building WiiCompiled)`** to bypass Retro Rewind and online payload steps.
---
## 1. Prerequisites
### System Requirements
- **Hardware**: Apple Silicon Mac (M1/M2/M3/M4)
- **Operating System**: macOS 14 (Sonoma) or later
- **Xcode Command Line Tools**:
```bash
xcode-select --install
```
### Toolchain Dependencies
Install the required tools using [Homebrew](https://brew.sh):
```bash
brew install cmake ninja
brew install --cask dotnet-sdk@8
```
Verify that Clang, CMake, Ninja, and the .NET 8 runtime are available:
```bash
clang --version
cmake --version
ninja --version
dotnet --list-runtimes # Must list Microsoft.NETCore.App 8.x
```
---
## 2. Required Game and Mod Assets
Due to legal requirements, no proprietary Nintendo assets or code are included in this repository. You must provide your own legally dumped game files.
1. **Mario Kart Wii PAL (`RMCP01`) Disc Image** *(Required)*:
- Supported formats: `.iso`, `.wbfs`, `.ciso`, `.rvz`, `.gcm`, `.gcz`.
2. **nodtool** *(Required for disc extraction)*:
- Download the macOS Apple Silicon binary of [nodtool](https://github.com/encounter/nod/releases):
```bash
curl -fsSL "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.10/nodtool-macos-arm64" -o nodtool
chmod +x nodtool
```
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `http://nas.play.rwfc.net/payload?g=RMCPD00`.
---
## 3. Step 1: Extract Disc Assets
Extract your clean PAL `RMCP01` disc into the `Assets/` directory of the repository:
```bash
# Using nodtool directly into a temporary scratch directory
mkdir -p /tmp/mkw-extract
./nodtool extract /path/to/RMCP01.iso /tmp/mkw-extract
# Copy extracted assets into the repository Assets directory
rm -rf Assets/DATA/files Assets/DATA/sys
mkdir -p Assets/DATA
cp /tmp/mkw-extract/*/sys/main.dol Assets/main.dol
cp /tmp/mkw-extract/*/files/rel/StaticR.rel Assets/StaticR.rel
cp -R /tmp/mkw-extract/*/files Assets/DATA/files
cp -R /tmp/mkw-extract/*/sys Assets/DATA/sys
# Clean up temporary files
rm -rf /tmp/mkw-extract
```
> [!TIP]
> Alternatively, you can use the repository's helper script:
> ```bash
> Launcher/macos/extract-disc.command --game /path/to/RMCP01.iso --assets-dir Assets --nodtool ./nodtool
> ```
### Verify Extracted Asset Hashes
Confirm that the extracted files match the expected clean PAL revision:
```bash
shasum -a 256 Assets/main.dol Assets/StaticR.rel
```
- `Assets/main.dol`: `80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05`
- `Assets/StaticR.rel`: `16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d`
---
## 4. Step 2: Build the Translator CLI
Compile the static recompiler CLI:
```bash
dotnet build translator/src/Translator.Cli/Translator.Cli.csproj -c Release
```
Define a shell function to invoke the translator (ensuring paths with spaces are handled safely):
```bash
translator() {
dotnet "$(pwd)/translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll" "$@"
}
```
---
## 5. Step 3: Translation
### A. Translate Base Game Functions
```bash
mkdir -p generated/functions build/base
translator translate-recursive 0x8000629c \
--project projects/mkwii/recomp.yml \
--outdir generated/functions \
--output-metadata generated/base_translation_output.json \
--production-source-bundle generated/base_translation_sources.bin \
--no-function-files \
--prune-stale \
--threads $(sysctl -n hw.ncpu)
```
### B. Emit Base Manifest
```bash
translator emit-base-manifest \
--project projects/mkwii/recomp.yml \
--out build/base \
--functions-dir generated/functions \
--translation-output-metadata generated/base_translation_output.json \
--region P
```
---
### C. Stage and Translate Retro Rewind *(Skip this step if you only want to build WiiCompiled)*
1. Stage `Code.pul`:
```bash
RETRO_DIR="/path/to/RetroRewind6"
mkdir -p PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries
cp "$RETRO_DIR/Binaries/Code.pul" PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
```
2. **Retro-WFC Payload Setup (for Online Multiplayer)**:
Online play in Retro Rewind requires the shared Retro-WFC payload. Download and validate it:
```bash
mkdir -p build/retro-wfc/binary
curl -fsSL --retry 3 "https://nas.play.rwfc.net/payload?g=RMCPD00" \
-o build/retro-wfc/binary/payload.RMCPD00.bin
# Validate payload signature and integrity
translator validate-retro-wfc-payload --directory build/retro-wfc
```
3. Run Retro Rewind translation:
```bash
mkdir -p build/mods/retro_rewind_full_cpp
translator translate-mod \
--project projects/mkwii/recomp.yml \
--profile retro-rewind \
--base-manifest build/base/mkwii_base_manifest.json \
--base-translation-output-metadata generated/base_translation_output.json \
--code-pul "$RETRO_DIR/Binaries/Code.pul" \
--mod-root "$RETRO_DIR" \
--mod-name "Retro Rewind" \
--region P \
--out build/mods/retro_rewind_full_cpp \
--prefer-cached-inputs \
--emit-cpp \
--threads $(sysctl -n hw.ncpu) \
--retro-wfc-payload build/retro-wfc/binary/payload.RMCPD00.bin
```
> [!TIP]
> If you do not want online play or do not have an internet connection, replace `--retro-wfc-payload ...` with `--skip-retro-wfc`.
---
### D. Generate Data Initialization and Build Shards
First, generate the embedded game data initializer:
```bash
translator generate-data-init --project projects/mkwii/recomp.yml
```
Next, generate the CMake build shards using **one** of the following options:
#### Option 1: Base Game Only (WiiCompiled)
```bash
mkdir -p generated/build_shards
translator emit-build-shards \
--project projects/mkwii/recomp.yml \
--base-metadata generated/base_translation_output.json \
--base-functions-dir generated/functions \
--native-source-dir runtime/src \
--out generated/build_shards
```
#### Option 2: Base Game + Retro Rewind
```bash
mkdir -p generated/build_shards
translator emit-build-shards \
--project projects/mkwii/recomp.yml \
--base-metadata generated/base_translation_output.json \
--base-functions-dir generated/functions \
--native-source-dir runtime/src \
--out generated/build_shards \
--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json \
--retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp
```
---
## 6. Step 4: Configure and Compile with CMake & Ninja
Configure the native C++ build targeting Apple Silicon:
```bash
cmake -S runtime -B build-macos -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DAURORA_SDL3_PROVIDER=vendor
```
Compile the desired target:
```bash
# To build WiiCompiled only:
cmake --build build-macos --target WiiCompiled --parallel $(sysctl -n hw.ncpu)
# OR to build both WiiCompiled and Retro Rewind:
cmake --build build-macos --target WiiCompiled RetroRewind --parallel $(sysctl -n hw.ncpu)
```
Once compilation completes, the executables are ready in your build directory:
- `build-macos/WiiCompiled`
- `build-macos/RetroRewind` (if built)
During the build, CMake automatically copies the required runtime assets into `build-macos/`:
- `build-macos/dsp_coef.bin`
- `build-macos/initial_pipeline_cache.db`
- `build-macos/wii_bootstrap/`
---
## 7. Step 5: Running Executables from the Build Folder
### Configure `Config.toml`
The runtime reads configuration from `~/Library/Application Support/WiiCompiled/Config.toml`.
Create the directory and configuration file:
```bash
mkdir -p "$HOME/Library/Application Support/WiiCompiled"
```
#### For Base Game Only (WiiCompiled):
```toml
# ~/Library/Application Support/WiiCompiled/Config.toml
[video]
widescreen = true
resolution_multiplier = 1.0
graphics_api = "metal"
[paths]
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
```
#### For Base Game and Retro Rewind:
```toml
# ~/Library/Application Support/WiiCompiled/Config.toml
[video]
widescreen = true
resolution_multiplier = 1.0
graphics_api = "metal"
[paths]
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
retro_rewind_root = "/path/to/RetroRewind6"
```
> [!NOTE]
> Ensure `dvd_root` points to the directory containing `files` and `sys/fst.bin`.
### Launching the Game
Run the compiled binaries directly from your terminal or by double clicking:
```bash
# Run base WiiCompiled
./build-macos/WiiCompiled
# Run Retro Rewind
./build-macos/RetroRewind
```
Press **F10** in-game at any time to open the configuration bar (controls, resolution, display settings, audio).
---
## Quick Reference: Automated Helper Script
The repository provides a script (`Launcher/local-build-macos.command`) that handles extraction, translation, and compilation in a single command.
### Building Base Game Only:
```bash
Launcher/local-build-macos.command \
--profile base \
--output-dir build-macos/Products \
--game /path/to/RMCP01.iso \
--nodtool ./nodtool
```
### Building Both (with Online Retro-WFC Payload):
```bash
# 1. Download Retro-WFC payload into a staging directory:
mkdir -p build/retro-wfc/binary
curl -fsSL --retry 3 "http://nas.play.rwfc.net/payload?g=RMCPD00" \
-o build/retro-wfc/binary/payload.RMCPD00.bin
# 2. Run the automated build with the payload directory:
Launcher/local-build-macos.command \
--profile both \
--output-dir build-macos/Products \
--base-output-dir build-macos/Products \
--game /path/to/RMCP01.iso \
--nodtool ./nodtool \
--retro-rewind-package-dir /path/to/RetroRewind6 \
--retro-wfc-offline-dir build/retro-wfc
```
### Building Both (Offline, Skipping Payload):
```bash
Launcher/local-build-macos.command \
--profile both \
--output-dir build-macos/Products \
--base-output-dir build-macos/Products \
--game /path/to/RMCP01.iso \
--nodtool ./nodtool \
--retro-rewind-package-dir /path/to/RetroRewind6 \
--skip-retro-wfc-payload
```
When finished, the compiled executables reside in `native-build-macos/` and the bundled `.app` packages are placed in `build-macos/Products/`.
+1 -1
View File
@@ -71,7 +71,7 @@ profiles:
module_link_base: 0x803992E0
output: build/mods/retro_rewind_full_cpp
enable_retro_wfc: true
retro_wfc_payload: http://nas.play.rwfc.net/payload?g=RMCPD00
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCPD00
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
riivolution:
xml: xml/RetroRewind6.xml
+29 -13
View File
@@ -78,22 +78,38 @@ bool ProcessSleepTimers(CpuContext* cpu)
{
using Clock = std::chrono::steady_clock;
std::vector<SleepTimerEntry> dueTimers;
// Pop and process ONE due timer at a time, straight from the shared table. Resuming a
// sleeper re-enters the scheduler (OSResumeThread -> SelectThread) and can switch fibers
// away from this call. Timers that had already been popped into a private list would then
// sit on the suspended fiber's stack with their threads parked and no entry in the table:
// exactly the "park-shaped with no pending wake timer" strand the reconciler below heals
// 100ms late, followed by a "sleep-timer stale" drop when this fiber finally resumes.
// Leaving unprocessed timers in the table keeps them visible to every other pump (idle
// loop, other threads' SelectThread) while this one is switched away.
bool processedAny = false;
constexpr size_t kMaxTimersPerCall = 64;
size_t processedCount = 0;
const auto now = Clock::now();
{
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
auto it = gSleepTimers.begin();
while (it != gSleepTimers.end()) {
if (it->deadline > now) {
++it;
continue;
while (processedCount < kMaxTimersPerCall) {
SleepTimerEntry timer{0, {}};
bool found = false;
{
std::lock_guard<std::mutex> lock(gSleepTimerMutex);
for (auto it = gSleepTimers.begin(); it != gSleepTimers.end(); ++it) {
if (it->deadline <= now) {
timer = *it;
gSleepTimers.erase(it);
found = true;
break;
}
}
dueTimers.push_back(*it);
it = gSleepTimers.erase(it);
}
}
if (!found) {
break;
}
++processedCount;
processedAny = true;
for (const SleepTimerEntry& timer : dueTimers) {
const uint32_t threadPtr = timer.threadPtr;
if (threadPtr == 0 ||
!Memory::Contains(threadPtr + kThreadSuspendOffset, sizeof(uint32_t))) {
@@ -219,7 +235,7 @@ bool ProcessSleepTimers(CpuContext* cpu)
}
}
return !dueTimers.empty();
return processedAny;
}
} // namespace OsHleInternal
@@ -35,6 +35,39 @@ public class RetroWfcPayloadLoweringTests
Assert.Equal("moduleFunction", pointer.TargetKind);
}
[Fact]
public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch()
{
var payloadRoot = Path.Combine(
AppContext.BaseDirectory,
"TestAssets",
"RetroWfcPayload");
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot);
var payloadPath = Path.Combine(
payloadRoot,
"binary",
"payload.RMCPD00.bin");
var payload = File.ReadAllBytes(payloadPath);
var result = RetroWfcPayload.Parse(
payload,
ProductionPayloadManifest(),
0x81800000u,
0x00200000u,
"TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin");
Assert.Equal("RMCPD00", result.Summary.Game);
Assert.Equal(payload.Length, result.Summary.PayloadImageSize);
Assert.True(result.LoweringPlan.IsPlannable);
Assert.Empty(result.LoweringPlan.Issues);
Assert.NotEmpty(result.LoweringPlan.StaticBytePatches);
Assert.NotEmpty(result.LoweringPlan.ExecutableHooks);
Assert.NotEmpty(result.LoweringPlan.StaticPointers);
Assert.All(result.LoweringPlan.ExecutableHooks, hook => Assert.NotNull(hook.TargetAddress));
Assert.All(result.LoweringPlan.StaticPointers, pointer => Assert.NotNull(pointer.TargetAddress));
Assert.NotEmpty(result.Summary.InitializationCallbacks);
}
private static BaseManifest TestManifest() =>
new(
"test",
@@ -52,6 +85,51 @@ public class RetroWfcPayloadLoweringTests
],
"ranges.json");
// The payload parser needs the base image's address classes and containing
// function ranges to prove every patch can be lowered. A single synthetic
// executable and writable ranges are sufficient here: the assertions above
// test the real production payload without checking proprietary game bytes
// into CI. The split also proves pointer patches lower as data writes.
private static BaseManifest ProductionPayloadManifest() =>
new(
"test",
1,
"RMCP01",
"P",
"",
0,
[
new BaseSectionMetadata(
".synthetic-text",
"synthetic.dol",
0x80000000u,
0x80800000u,
true,
false,
"synthetic_text.bin",
0),
new BaseSectionMetadata(
".synthetic-data",
"synthetic.dol",
0x80800000u,
0x81000000u,
false,
true,
"synthetic_data.bin",
0)
],
[
new BaseFunctionRangeMetadata(
0x80000000u,
0x80800000u,
"synthetic_base",
".synthetic-text",
0,
"test",
["Executable"])
],
"ranges.json");
private static byte[] BuildSharedPayloadFixture()
{
var payload = new byte[0x240];
@@ -26,6 +26,11 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Translator.Core\Translator.Core.csproj" />
<ProjectReference Include="..\..\src\Translator.Cli\Translator.Cli.csproj" />
<ProjectReference Include="..\..\..\Launcher\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="TestAssets\RetroWfcPayload\binary\payload.RMCPD00.bin" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<!--