Compare commits

..

9 Commits

Author SHA1 Message Date
patchzyy 4993b2226c update to 0.9.2 2026-10-24 17:45:02 +02:00
patchzyy e15be3bf55 Fix online sha 2026-10-24 17:31:12 +02:00
patchzyy 85992f85b0 add --headless for boot races (#351)
* add headless race option

* bump aurora
2026-10-24 16:51:34 +02:00
patchzyy 5a930903fb update to 0.9.1 2026-10-24 16:00:30 +02:00
patchzyy f8944f239a Fix macOS build (#349)
* fix macos build with homebrew clang

* fingerprint compiler paths
2026-10-24 15:55:31 +02:00
patchzyy 7678608943 bump aurora for unset texgen fix (#348) 2026-10-24 14:54:46 +02:00
patchzyy 4e0e1edb86 Prompt restart after toggling Wii Remotes (#347)
* prompt restart after toggling wii remotes

* dont prompt restart when save fails
2026-10-24 14:27:30 +02:00
patchzyy 1d9cae387a fix ntsc-u crash on courses with torches (#346)
* fix ntsc-u map for vtable-only functions

* add --race and --race-cpu boot options

* add MKW_PERF_STATS frame busy timing and --race-cpu

* fix race seeds for --race-cpu benchmarks

* bind interrupt natives at compile time without register guards

* inline fast path for unguarded indirect calls

* batch gx worker head publishes

* split cold paths out of translated functions

* cache observed gx worker tail on the producer

* print game id in fatal log

* fix ntsc-u map verdict counts
2026-10-24 13:48:29 +02:00
Michael G 0aba3cb3ea Use the new app icons on macOS (#340) 2026-10-24 11:14:39 +02:00
26 changed files with 560 additions and 49 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- The one product version. Release tags must match it. -->
<Version>0.9.0</Version>
<Version>0.9.2</Version>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
</Project>
+7 -4
View File
@@ -80,7 +80,10 @@ if (( builds_retro )); then
[[ -n "$retro_wfc" ]] && (( skip_retro_wfc )) && fail 'choose only one Retro-WFC mode'
[[ -n "$retro_wfc" || $skip_retro_wfc -eq 1 ]] || fail 'choose a Retro-WFC payload directory or --skip-retro-wfc-payload'
fi
for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool" >/dev/null || fail "required tool not found: $tool"; done
# Apple clang must match the SDK headers; another clang earlier on PATH may not.
cc_bin=$(xcrun -f clang 2>/dev/null) || fail 'Apple clang not found; install the Xcode Command Line Tools (xcode-select --install)'
cxx_bin=$(xcrun -f clang++ 2>/dev/null) || fail 'Apple clang++ not found; install the Xcode Command Line Tools (xcode-select --install)'
for tool in "$cmake_bin" "$ninja_bin" "$cc_bin" "$cxx_bin" shasum; do command -v "$tool" >/dev/null || fail "required tool not found: $tool"; done
assets="$workspace/Assets"; generated="$workspace/generated"
functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base"
@@ -88,7 +91,7 @@ manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shar
native_build="$workspace/native-build-macos-$macos_arch"
x86_cpu_profile=v3
if [[ "$macos_arch" == x86_64 ]]; then
native_macros=$(clang -march=native -dM -E -x c /dev/null 2>/dev/null || true)
native_macros=$("$cc_bin" -march=native -dM -E -x c /dev/null 2>/dev/null || true)
for macro in __AVX__ __AVX2__ __BMI__ __BMI2__ __F16C__ __FMA__ __LZCNT__ __MOVBE__; do
if ! grep -q "^#define $macro " <<<"$native_macros"; then
x86_cpu_profile=v2
@@ -175,7 +178,7 @@ if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatc
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
toolchain_provenance="$native_build/toolchain-provenance.json"
toolchain_fingerprint=$(mkw_toolchain_identity "$workspace" clang clang++ "$cmake_bin" "$ninja_bin")
toolchain_fingerprint=$(printf '%s\n' "$(mkw_toolchain_identity "$workspace" "$cc_bin" "$cxx_bin" "$cmake_bin" "$ninja_bin")" "$cc_bin" "$cxx_bin" | mkw_sha256)
if [[ -d "$native_build" && "$(mkw_read_identity "$toolchain_provenance" ToolchainFingerprint)" != "$toolchain_fingerprint" ]]; then
printf 'MKWCBUILD: The toolchain or a pinned dependency changed; rebuilding the native build from scratch\n'
rm -rf "$native_build"
@@ -183,7 +186,7 @@ fi
step configure-native 'Configuring the native toolchain'
# Use Aurora's pinned SDL3 source on macOS. A system SDL3 can be older than
# Aurora's required API even when find_package() succeeds.
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" -DMKW_X86_CPU_PROFILE="$x86_cpu_profile" -DAURORA_SDL3_PROVIDER=vendor -DMKW_SETUP_VERSION="$setup_version"
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER="$cc_bin" -DCMAKE_CXX_COMPILER="$cxx_bin" -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" -DMKW_X86_CPU_PROFILE="$x86_cpu_profile" -DAURORA_SDL3_PROVIDER=vendor -DMKW_SETUP_VERSION="$setup_version"
mkw_write_identity "$toolchain_provenance" ToolchainFingerprint "$toolchain_fingerprint"
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
+6
View File
@@ -37,6 +37,10 @@ for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >
lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product"
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
icon="$script_dir/../../runtime/resources/$product.icns"
[[ -f "$icon" ]] || fail "missing app icon: $icon"
app="$output_dir/$product.app"
macos="$app/Contents/MacOS"
frameworks="$app/Contents/Frameworks"
@@ -51,6 +55,7 @@ cat > "$app/Contents/Info.plist" <<EOF
<key>CFBundleExecutable</key><string>$product</string>
<key>CFBundleIdentifier</key><string>org.wiicompiled.$product</string>
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
<key>CFBundleIconFile</key><string>$product.icns</string>
<key>CFBundleName</key><string>$product</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>0.1.0</string>
@@ -59,6 +64,7 @@ cat > "$app/Contents/Info.plist" <<EOF
<key>NSHighResolutionCapable</key><true/>
</dict></plist>
EOF
ditto "$icon" "$resources/$product.icns"
ditto "$build_dir/$product" "$macos/$product"
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do
ditto "$build_dir/$asset" "$resources/$asset"
+11 -1
View File
@@ -1,6 +1,6 @@
# NTSC-U (RMCE01) function map, ported from ../mkwii/MAP.txt (PAL RMCP01).
# Every entry was validated against the RMCE01 main.dol/StaticR.rel.
# Verdicts: PROVEN=12178, REFERENCED=12573, PLAUSIBLE=4500, IDENTITY=15, INTERPOLATED=0, ADDED=143 (binary-proven entries no PAL entry covers); entries=29409
# Verdicts: PROVEN=12178, REFERENCED=12583, PLAUSIBLE=4500, IDENTITY=15, INTERPOLATED=0, ADDED=143 (binary-proven entries no PAL entry covers); entries=29419
800018a8 0x800018a8
80001d50 0x80001d50
80001d6c 0x80001d6c
@@ -10702,6 +10702,14 @@
80243e80 nw4r::g3d::ScnRfl::GetTypeName
80243eb0 nw4r::g3d::ScnRfl::IsDerivedFrom
80243f18 0x80243f18
80243f68 0x80243f68
80243fa8 0x80243fa8
80244010 0x80244010
80244064 0x80244064
802440a0 0x802440a0
802440f8 0x802440f8
802441ac 0x802441ac
802441f0 0x802441f0
802441fc shadTex_802402E0
80244274 EGG::EGG__GlobalScreenCapture_Take
80244760 EGG::EGG__GlobalScreenCapture_Release
@@ -20501,6 +20509,7 @@
806c3c40 Objects::Heyho::vf_0xc
806c3c48 Objects::Heyho::UpdateCollision
806c3d28 Objects::Heyho::GetPropertiesBitfield
806c3d30 0x806c3d30
806c3d38 Objects::HighwayManager::__ct
806c4418 Objects::HighwayManager::__dt
806c44ec 0x806c44ec
@@ -21390,6 +21399,7 @@
806f1e54 Objects::HwanwanBall::LoadRoute
806f1e58 Objects::HwanwanBall::GetSubFileName
806f1e64 Objects::HwanwanBall::GetPropertiesBitfield
806f1e6c Objects::HwanwanBall::__dt(thunk)
806f1e74 0x806f1e74
806f2024 0x806f2024
806f2194 0x806f2194
+12 -7
View File
@@ -33,9 +33,14 @@
],
[
"0x80021BA8",
"0x80225F1C",
"0x80225F14",
"0x80021B08"
],
[
"0x80225F14",
"0x802261F8",
"0x80243F18"
],
[
"0x802261F8",
"0x802402E0",
@@ -608,13 +613,13 @@
],
[
"0x806CE828",
"0x806D02BC",
"0x806D02C4",
"0x806C229C"
],
[
"0x806D02BC",
"0x806D02C4",
"0x806D28F4",
"0x806EA19C"
"0x806EA1A4"
],
[
"0x806D2908",
@@ -673,13 +678,13 @@
],
[
"0x806E95B0",
"0x806EC7C0",
"0x806EC7C8",
"0x806EEC5C"
],
[
"0x806EC7C0",
"0x806EC7C8",
"0x806ED53C",
"0x806D8564"
"0x806D856C"
],
[
"0x806ED53C",
+1 -1
View File
@@ -37,7 +37,7 @@ endif()
function(mkw_apply_translated_compile_options target)
target_compile_options(${target} PRIVATE
-O2 ${MKW_TRANSLATED_PPC_FP_OPTIONS} -fno-slp-vectorize -w -pipe)
-O2 ${MKW_TRANSLATED_PPC_FP_OPTIONS} -fno-slp-vectorize -mllvm -hot-cold-split=true -w -pipe)
endfunction()
# Translated shard TUs are the memory-hungry compiles; everything else in the build is
+3
View File
@@ -22,6 +22,8 @@ public:
// Applied to the final host output, covering both AX and direct AI DMA.
void SetMasterVolume(float volume);
void SetMuted(bool muted);
// Mutes output without touching the user's persisted mute setting.
void SetSilenced(bool silenced);
private:
AudioBackend() = default;
@@ -43,6 +45,7 @@ private:
bool m_initialized = false;
float m_masterVolume = 1.0f;
bool m_muted = false;
bool m_silenced = false;
bool m_reportedDroppedBlock = false;
std::vector<int16_t> m_convertBuffer;
};
+7 -1
View File
@@ -4,6 +4,7 @@
#include "settings_overlay.h"
#include "runtime_config.h"
#include "gx_worker.h"
#include "perf_stats.h"
#include <aurora/aurora.h>
#include <aurora/event.h>
@@ -142,7 +143,12 @@ inline bool BeginAuroraFrame() {
if (GxWorker::ShouldDefer()) {
// The window half can touch the surface and render targets the worker draws into.
GxWorker::Sync();
if (!aurora_begin_frame_window()) {
bool begun = false;
{
PerfStats::WaitScope wait;
begun = aurora_begin_frame_window();
}
if (!begun) {
return false;
}
GxWorker::Submit([] { GxWorkerBeginFrame(); });
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <string_view>
// `--race[=<course id>]`: boot straight into a 1P VS race instead of the title screen.
// `--race-cpu`: the same, with the local racer driven by the CPU.
namespace BootRace {
// False when `arg` is not a boot-race option; throws on a malformed one.
bool ParseArgument(std::string_view arg);
bool Requested();
} // namespace BootRace
+44 -9
View File
@@ -291,6 +291,11 @@ struct KnownTypedNativeCpuCall {
static constexpr bool kAvailable = false;
};
template <typename T, typename = void>
struct IsPureTypedNativeCpuCall : std::false_type {};
template <typename T>
struct IsPureTypedNativeCpuCall<T, std::void_t<decltype(T::kPure)>> : std::bool_constant<T::kPure> {};
#include "guest_abi/native_cpu_calls.inc"
// Direct-mapped thread-local memo in front of the sorted registry lookup every bctrl performs;
@@ -554,7 +559,10 @@ inline void InvokeDirectCpu(CpuContext* ctx) {
return;
}
if constexpr (KnownTypedNativeCpuCall<Target>::kAvailable) {
if constexpr (IsPureTypedNativeCpuCall<KnownTypedNativeCpuCall<Target>>::value) {
KnownTypedNativeCpuCall<Target>::Invoke(cpu);
return;
} else if constexpr (KnownTypedNativeCpuCall<Target>::kAvailable) {
PpcNonvolatileGprGuard gprGuard(cpu);
if (TryGetCpuContext() != cpu) {
CpuContextScope scope(cpu);
@@ -581,12 +589,20 @@ inline void InvokeDirectCpu(CpuContext* ctx) {
// Cold tail of InvokeIndirectJump: a bctr target that is not a registered function.
[[noreturn]] MKW_PPC_NO_INLINE MKW_PPC_COLD void ReportMissingIndirectJumpTarget(uint32_t target, CpuContext* cpu);
// Indirect jump (bctr without link) - used for switch tables.
inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
// Use the caller's context, not the persistent global context!
// This is essential for tail calls via bctr where the 'this' pointer (r3)
// must be passed correctly to the target function.
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
// The common indirect case: an unguarded record called from its own CpuContext, so the full
// TryDispatchRawCpuTarget would only set the execution address and call.
MKW_PPC_FORCE_INLINE bool TryDispatchUnguardedRawCpuTarget(uint32_t target, CpuContext* cpu) {
const RawDispatchRecord* record = TranslatedFunctionRegistry::FindRawByAddressPtr(target);
if (record == nullptr || record->entry == nullptr || record->preserveNonvolatileGprs ||
record->nonvolatileFprWriteMask != 0 || TryGetCpuContext() != cpu) {
return false;
}
RecompMod::ScopedTranslatedExecutionAddress translatedExecution(record->address);
record->entry(cpu);
return true;
}
MKW_PPC_NO_INLINE inline void InvokeIndirectJumpSlow(uint32_t target, CpuContext* cpu) {
if (TryDispatchRawCpuTarget(TranslatedFunctionRegistry::FindRawByAddressPtr(target), cpu)) {
return;
}
@@ -601,12 +617,22 @@ inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
ReportMissingIndirectJumpTarget(target, cpu);
}
inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
// Indirect jump (bctr without link) - used for switch tables.
inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
// Use the caller's context, not the persistent global context!
// This is essential for tail calls via bctr where the 'this' pointer (r3)
// must be passed correctly to the target function.
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
if (TryDispatchUnguardedRawCpuTarget(target, cpu)) {
return;
}
InvokeIndirectJumpSlow(target, cpu);
}
MKW_PPC_NO_INLINE inline void InvokeIndirectCpuSlow(uint32_t target, CpuContext* cpu) {
if (target == 0) {
ReportMissingCpuTarget(target, cpu);
}
ApplyRuntimeCallOptions(target, cpu);
if (TryDispatchRawCpuTarget(TranslatedFunctionRegistry::FindRawByAddressPtr(target), cpu)) {
return;
}
@@ -615,3 +641,12 @@ inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
}
ReportMissingCpuTarget(target, cpu);
}
inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
ApplyRuntimeCallOptions(target, cpu);
if (TryDispatchUnguardedRawCpuTarget(target, cpu)) {
return;
}
InvokeIndirectCpuSlow(target, cpu);
}
+6 -5
View File
@@ -9,7 +9,8 @@ MKW_REGION_RANGE(NtscU, 8000808C, 8000ADBB, -0x40)
MKW_REGION_RANGE(NtscU, 8000AF24, 8000B6B3, -0xA0)
MKW_REGION_RANGE(NtscU, 8000B6B4, 8000C173, +0x15994)
MKW_REGION_RANGE(NtscU, 8000C174, 80021BA7, -0xB60)
MKW_REGION_RANGE(NtscU, 80021BA8, 80225F1B, -0xA0)
MKW_REGION_RANGE(NtscU, 80021BA8, 80225F13, -0xA0)
MKW_REGION_RANGE(NtscU, 80225F14, 802261F7, +0x1E004)
MKW_REGION_RANGE(NtscU, 802261F8, 802402DF, -0x384)
MKW_REGION_RANGE(NtscU, 802402E0, 80240E17, +0x3F1C)
MKW_REGION_RANGE(NtscU, 80240E18, 80244DD3, -0xEBC)
@@ -132,8 +133,8 @@ MKW_REGION_RANGE(NtscU, 806C4EF0, 806C63A7, +0x21CD0)
MKW_REGION_RANGE(NtscU, 806C63B0, 806C6B43, +0x21CC8)
MKW_REGION_RANGE(NtscU, 806C6B44, 806CCE8F, -0xABF4)
MKW_REGION_RANGE(NtscU, 806CCE90, 806CE81F, +0x1B97C)
MKW_REGION_RANGE(NtscU, 806CE828, 806D02BB, -0xC58C)
MKW_REGION_RANGE(NtscU, 806D02BC, 806D28F3, +0x19EE0)
MKW_REGION_RANGE(NtscU, 806CE828, 806D02C3, -0xC58C)
MKW_REGION_RANGE(NtscU, 806D02C4, 806D28F3, +0x19EE0)
MKW_REGION_RANGE(NtscU, 806D2908, 806D5C5B, -0xEBD0)
MKW_REGION_RANGE(NtscU, 806D5C60, 806D5ED7, +0x16B74)
MKW_REGION_RANGE(NtscU, 806D5ED8, 806DA913, -0xEE40)
@@ -145,8 +146,8 @@ MKW_REGION_RANGE(NtscU, 806DF7D0, 806DFD13, +0xEBB4)
MKW_REGION_RANGE(NtscU, 806DFD14, 806E3A8B, -0x10CB0)
MKW_REGION_RANGE(NtscU, 806E3A8C, 806E3E1F, +0xAE3C)
MKW_REGION_RANGE(NtscU, 806E3E20, 806E95AF, -0x11044)
MKW_REGION_RANGE(NtscU, 806E95B0, 806EC7BF, +0x56AC)
MKW_REGION_RANGE(NtscU, 806EC7C0, 806ED53B, -0x1425C)
MKW_REGION_RANGE(NtscU, 806E95B0, 806EC7C7, +0x56AC)
MKW_REGION_RANGE(NtscU, 806EC7C8, 806ED53B, -0x1425C)
MKW_REGION_RANGE(NtscU, 806ED53C, 806F62FB, -0x14254)
MKW_REGION_RANGE(NtscU, 806F62FC, 806F7697, -0x4488)
MKW_REGION_RANGE(NtscU, 806F77C4, 806F7A53, +0x1C2B4)
@@ -43,6 +43,9 @@ extern "C" void GX__SetNumTevStages_801722a8(uint32_t n);
extern "C" void GX__SetBlendMode_8017277c(uint32_t t, uint32_t s, uint32_t d, uint32_t op);
extern "C" void GX__LoadPosMtxImm_8017310c(uint32_t ma, uint32_t id);
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id);
extern "C" int32_t OS__DisableInterrupts_801a65ac();
extern "C" int32_t OS__EnableInterrupts_801a65c0();
extern "C" int32_t OS__RestoreInterrupts_801a65d4(int32_t level);
// Keyed by the translated region's addresses, so only translated code sees them;
// runtime calls resolve through the registry.
@@ -90,6 +93,20 @@ MKW_KNOWN_TYPED_NATIVE(80173214, GX__SetCurrentMtx_80173214, cpu->gpr[3]);
#undef MKW_KNOWN_TYPED_NATIVE
// Natives that neither touch CpuContext nor re-enter guest code: no register guard or context scope.
#define MKW_KNOWN_PURE_TYPED_NATIVE_R3(addr, fn, ...) \
template <> struct KnownTypedNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
static constexpr bool kPure = true; \
static void Invoke(CpuContext* cpu) { cpu->gpr[3] = static_cast<uint32_t>(fn(__VA_ARGS__)); } \
}
MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65AC, OS__DisableInterrupts_801a65ac);
MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65C0, OS__EnableInterrupts_801a65c0);
MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65D4, OS__RestoreInterrupts_801a65d4, static_cast<int32_t>(cpu->gpr[3]));
#undef MKW_KNOWN_PURE_TYPED_NATIVE_R3
#define MKW_KNOWN_NATIVE_CPU_CALL(addr, fn) \
template <> struct KnownNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <chrono>
// MKW_PERF_STATS=1: logs the game thread's busy time per presented frame, i.e. wall time minus the
// time it spends blocked on VI pacing, the GX worker or the audio mixer.
namespace PerfStats {
bool Enabled() noexcept;
void AddWait(std::chrono::steady_clock::duration wait) noexcept;
void OnFramePresented() noexcept;
class WaitScope {
public:
WaitScope() noexcept : m_start(Enabled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}) {}
~WaitScope() {
if (m_start != std::chrono::steady_clock::time_point{}) {
AddWait(std::chrono::steady_clock::now() - m_start);
}
}
WaitScope(const WaitScope&) = delete;
WaitScope& operator=(const WaitScope&) = delete;
private:
std::chrono::steady_clock::time_point m_start;
};
} // namespace PerfStats
+2 -1
View File
@@ -839,8 +839,9 @@ inline bool WiiRemotesEnabled(bool fallback = false) {
}
inline bool SetWiiRemotesEnabled(bool value) {
if (!WriteSetting("controller", "wii_remotes", value ? "true" : "false")) return false;
Mutable().wiiRemotes = value;
return WriteSetting("controller", "wii_remotes", value ? "true" : "false");
return true;
}
inline bool WiiAutoPairEnabled(bool fallback = true) {
Binary file not shown.
Binary file not shown.
+7 -1
View File
@@ -15,7 +15,7 @@ AudioBackend& AudioBackend::Instance() {
}
float AudioBackend::EffectiveGainLocked() const {
return m_muted ? 0.0f : m_masterVolume;
return m_muted || m_silenced ? 0.0f : m_masterVolume;
}
void AudioBackend::ApplyGainLocked() {
@@ -36,6 +36,12 @@ void AudioBackend::SetMuted(bool muted) {
ApplyGainLocked();
}
void AudioBackend::SetSilenced(bool silenced) {
std::lock_guard<std::mutex> lock(m_mutex);
m_silenced = silenced;
ApplyGainLocked();
}
bool AudioBackend::EnsureInitializedLocked(uint32_t sampleRate, uint32_t channels) {
if (m_initialized && m_sampleRate == sampleRate && m_channels == channels) {
return true;
+2
View File
@@ -3,6 +3,7 @@
#include "ax_internal.h"
#include "abi_bridge.h"
#include "perf_stats.h"
#include "audio_backend.h"
#include "ax_mix_kernels.h"
#include "isa/big_endian.h"
@@ -122,6 +123,7 @@ public:
void JoinMix() {
if (m_mixThread.joinable()) {
PerfStats::WaitScope wait;
std::unique_lock<std::mutex> lock(m_mixMutex);
m_mixIdle.wait(lock, [this] { return !m_mixPending && !m_mixBusy; });
}
+220
View File
@@ -0,0 +1,220 @@
#include "boot_race.h"
#include "abi_bridge.h"
#include "guest_abi/guest_region.h"
#include "guest_function_imports.h"
#include "memory.h"
#include "runtime_log.h"
#include <array>
#include <charconv>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
namespace {
constexpr std::string_view kOption = "--race";
constexpr uint32_t kLuigiCircuit = 0x08;
constexpr uint32_t kLastRaceCourse = 0x1F;
// Racedata (menu scenario, copied into the race scenario by Racedata::InitRace)
constexpr uint32_t kMenuScenarioOffset = 0xC10;
constexpr uint32_t kPlayersOffset = 0x8;
constexpr uint32_t kPlayerSize = 0xF0;
constexpr uint32_t kPlayerKartOffset = 0x8;
constexpr uint32_t kPlayerCharacterOffset = 0xC;
constexpr uint32_t kPlayerTypeOffset = 0x10;
constexpr uint32_t kPlayerControllerChannelOffset = 0x6;
constexpr uint32_t kPlayerCount = 12;
constexpr uint32_t kSettingsOffset = 0xB48;
constexpr uint32_t kSectionPadOffset = 0x34;
constexpr uint32_t kFirstGcnControllerId = 0x124;
constexpr uint32_t kSectionP1Vs = 0x20;
enum : uint32_t { kPlayerRealLocal = 0, kPlayerCpu = 1 };
enum : uint32_t { kGameModeVs = 1, kCc150 = 2, kCpuHard = 2, kItemsBalanced = 0 };
struct Racer {
uint32_t character;
uint32_t kart;
};
// Standard kart matching each character's weight class.
constexpr std::array<Racer, 12> kRacers{{
{0x00, 0x01}, // Mario
{0x07, 0x01}, // Luigi
{0x10, 0x01}, // Peach
{0x0A, 0x01}, // Yoshi
{0x0F, 0x01}, // Daisy
{0x08, 0x00}, // Toad
{0x06, 0x00}, // Baby Mario
{0x0E, 0x00}, // Koopa Troopa
{0x03, 0x02}, // Bowser
{0x09, 0x02}, // Donkey Kong
{0x0B, 0x02}, // Wario
{0x16, 0x02}, // Funky Kong
}};
std::optional<uint32_t> g_course;
bool g_cpuDriven = false;
uint32_t ParseCourse(std::string_view text) {
int base = 10;
if (text.starts_with("0x") || text.starts_with("0X")) {
text.remove_prefix(2);
base = 16;
}
uint32_t course = 0;
const auto [end, ec] = std::from_chars(text.data(), text.data() + text.size(), course, base);
if (text.empty() || ec != std::errc{} || end != text.data() + text.size() || course > kLastRaceCourse) {
throw std::invalid_argument("--race expects a race course id from 0 to 0x1F");
}
return course;
}
void SetUpMenuScenario(uint32_t course) {
const uint32_t racedata = Memory::Read32(MKW_GADDR(809BD728));
if (racedata == 0) {
throw std::runtime_error("--race: Racedata does not exist yet at SectionMgr::Init");
}
const uint32_t scenario = racedata + kMenuScenarioOffset;
for (uint32_t i = 0; i < kRacers.size(); ++i) {
const uint32_t player = scenario + kPlayersOffset + i * kPlayerSize;
Memory::Write32(player + kPlayerKartOffset, kRacers[i].kart);
Memory::Write32(player + kPlayerCharacterOffset, kRacers[i].character);
Memory::Write32(player + kPlayerTypeOffset, i == 0 ? kPlayerRealLocal : kPlayerCpu);
}
const uint32_t settings = scenario + kSettingsOffset;
Memory::Write32(settings + 0x00, course);
Memory::Write32(settings + 0x04, kCc150);
Memory::Write32(settings + 0x08, kGameModeVs);
Memory::Write32(settings + 0x0C, 0);
Memory::Write32(settings + 0x14, kCpuHard);
Memory::Write32(settings + 0x18, kItemsBalanced);
Memory::Write8(settings + 0x24, 0);
Memory::Write8(settings + 0x25, 3);
Memory::Write32(settings + 0x28, 0);
}
} // namespace
namespace BootRace {
bool ParseArgument(std::string_view arg) {
if (arg == "--race-cpu") {
g_cpuDriven = true;
if (!g_course) {
g_course = kLuigiCircuit;
}
return true;
}
if (!arg.starts_with(kOption)) {
return false;
}
const std::string_view rest = arg.substr(kOption.size());
if (rest.empty()) {
g_course = kLuigiCircuit;
} else if (rest.front() == '=') {
g_course = ParseCourse(rest.substr(1));
} else {
return false;
}
return true;
}
bool Requested() {
return g_course.has_value();
}
} // namespace BootRace
// ArgumentParser::parse, picks the boot section in SectionMgr::Init.
extern "C" void ArgumentParser_Parse_805b9344(CpuContext* ctx) {
static bool consumed = false;
if (!g_course || consumed) {
MKW_GUEST_FUNC(805B9344)(ctx);
return;
}
consumed = true;
SetUpMenuScenario(*g_course);
if (!g_cpuDriven) {
const uint32_t sectionMgr = Memory::Read32(MKW_GADDR(809C1E38));
if (sectionMgr == 0) {
throw std::runtime_error("--race: SectionMgr instance is not set at SectionMgr::Init");
}
ctx->gpr[3] = sectionMgr + kSectionPadOffset;
ctx->gpr[4] = 0;
ctx->gpr[5] = kFirstGcnControllerId;
MKW_GUEST_FUNC(8061B490)(ctx);
}
RT_LOGF("boot-race", "booting into 1P VS on course 0x%02X%s\n", *g_course, g_cpuDriven ? " (CPU driven)" : "");
ctx->gpr[3] = kSectionP1Vs;
}
REGISTER_NATIVE_FUNCTION_AS(0x805B9344, ArgumentParser_Parse_805b9344, "ArgumentParser_Parse_805b9344");
// --race-cpu hands the local racer to the CPU driver while it keeps its camera and HUD.
// RacedataScenario::InitScreens; local racers keep no real controller channel.
extern "C" void RacedataScenario_InitScreens_8052f4e8(CpuContext* ctx) {
if (!g_cpuDriven) {
MKW_GUEST_FUNC(8052F4E8)(ctx);
return;
}
const uint32_t players = ctx->gpr[3] + kPlayersOffset;
std::array<uint8_t, kPlayerCount> channels{};
for (uint32_t i = 0; i < kPlayerCount; ++i) {
channels[i] = Memory::Read8(players + i * kPlayerSize + kPlayerControllerChannelOffset);
}
MKW_GUEST_FUNC(8052F4E8)(ctx);
for (uint32_t i = 0; i < kPlayerCount; ++i) {
const uint32_t player = players + i * kPlayerSize;
if (Memory::Read32(player + kPlayerTypeOffset) == kPlayerRealLocal) {
Memory::Write8(player + kPlayerControllerChannelOffset, channels[i]);
}
}
}
REGISTER_NATIVE_FUNCTION_AS(0x8052F4E8, RacedataScenario_InitScreens_8052f4e8, "RacedataScenario_InitScreens_8052f4e8");
// KartAIController::IsCPU
extern "C" void KartAIController_IsCPU_8072624c(CpuContext* ctx) {
MKW_GUEST_FUNC(8072624C)(ctx);
if (g_cpuDriven) {
ctx->gpr[3] = 1;
}
}
REGISTER_NATIVE_FUNCTION_AS(0x8072624C, KartAIController_IsCPU_8072624c, "KartAIController_IsCPU_8072624c");
// Kart::Link::IsCPU; bit 0 marks a CPU racer, bit 1 the local one.
extern "C" void KartLink_IsCPU_80590664(CpuContext* ctx) {
MKW_GUEST_FUNC(80590664)(ctx);
if (g_cpuDriven) {
ctx->gpr[3] = ctx->gpr[0] & 3;
}
}
REGISTER_NATIVE_FUNCTION_AS(0x80590664, KartLink_IsCPU_80590664, "KartLink_IsCPU_80590664");
// RacedataScenario::InitRNG; fixed seeds keep --race-cpu runs repeatable for benchmarking.
extern "C" void RacedataScenario_InitRng_8052f924(CpuContext* ctx) {
const uint32_t scenario = ctx->gpr[3];
MKW_GUEST_FUNC(8052F924)(ctx);
if (g_cpuDriven) {
Memory::Write32(scenario + kSettingsOffset + 0x2C, 0x74A1B095u);
Memory::Write32(scenario + kSettingsOffset + 0x30, 0x1234567u);
}
}
REGISTER_NATIVE_FUNCTION_AS(0x8052F924, RacedataScenario_InitRng_8052f924, "RacedataScenario_InitRng_8052f924");
// SectionPad::GetFirstDeactivatedPad; a CPU driven racer needs no controller.
extern "C" void SectionPad_GetFirstDeactivatedPad_8061b9bc(CpuContext* ctx) {
MKW_GUEST_FUNC(8061B9BC)(ctx);
if (g_cpuDriven) {
ctx->gpr[3] = static_cast<uint32_t>(-1);
}
}
REGISTER_NATIVE_FUNCTION_AS(0x8061B9BC, SectionPad_GetFirstDeactivatedPad_8061b9bc,
"SectionPad_GetFirstDeactivatedPad_8061b9bc");
+43 -7
View File
@@ -1,5 +1,6 @@
#include "gx_worker.h"
#include "perf_stats.h"
#include "runtime_config.h"
#include "runtime_log.h"
@@ -84,12 +85,37 @@ void WaitTailBriefly(std::unique_lock<std::mutex>& lock) {
#endif
}
// Producer-only: the last write position handed to the worker. Publishing every record kept the
// head cache line bouncing between the game thread and the polling worker.
uint64_t g_published = 0;
constexpr uint64_t kPublishBytes = 2048;
void Publish() {
if (g_published == g_write) {
return;
}
g_published = g_write;
g_head.store(g_write, std::memory_order_seq_cst);
if (g_consumerWaiting.load(std::memory_order_seq_cst)) {
std::lock_guard lock{g_headMutex};
g_headCv.notify_one();
}
}
void WaitForTail(uint64_t minTail, bool service) {
for (int spin = 0; spin < 256 && g_tail.load(std::memory_order_acquire) < minTail; ++spin) {
CpuRelax();
if (g_tail.load(std::memory_order_acquire) >= minTail) {
return;
}
Publish();
{
PerfStats::WaitScope wait;
for (int spin = 0; spin < 256 && g_tail.load(std::memory_order_acquire) < minTail; ++spin) {
CpuRelax();
}
}
while (g_tail.load(std::memory_order_acquire) < minTail) {
{
PerfStats::WaitScope wait;
std::unique_lock lock{g_tailMutex};
g_producerWaiting.store(true, std::memory_order_seq_cst);
if (g_tail.load(std::memory_order_seq_cst) < minTail) {
@@ -103,9 +129,19 @@ void WaitForTail(uint64_t minTail, bool service) {
}
}
// Producer-only copy of a tail already observed; avoids reading the worker-written line per record.
uint64_t g_tailSeen = 0;
void WaitForSpace(size_t bytes) {
if (g_write + bytes > kCapacity) {
WaitForTail(g_write + bytes - kCapacity, false);
const uint64_t minTail = g_write + bytes - kCapacity;
if (g_tailSeen >= minTail) {
return;
}
g_tailSeen = g_tail.load(std::memory_order_acquire);
if (g_tailSeen < minTail) {
WaitForTail(minTail, false);
}
}
}
@@ -234,11 +270,10 @@ void* Reserve(size_t payloadSize, void (*run)(void*)) {
return g_ring + offset + kHeaderSize;
}
// Anything that waits on the worker publishes first (WaitForTail), so batching here only delays it.
void Commit() {
g_head.store(g_write, std::memory_order_seq_cst);
if (g_consumerWaiting.load(std::memory_order_seq_cst)) {
std::lock_guard lock{g_headMutex};
g_headCv.notify_one();
if (g_write - g_published >= kPublishBytes) {
Publish();
}
}
@@ -333,6 +368,7 @@ void Stop() {
detail::g_active.store(false, std::memory_order_release);
g_stop.store(true, std::memory_order_release);
Submit([] {});
Publish();
if (g_thread != nullptr) {
g_thread->join();
delete g_thread;
+3
View File
@@ -8,6 +8,7 @@
#include "fiber_manager.h"
#include "platform/host_platform.h"
#include "runtime_log.h"
#include "perf_stats.h"
#include <dolphin/vi.h>
@@ -161,6 +162,7 @@ constexpr std::chrono::microseconds kFinalSpinWindow{500};
void SleepPreciselyUntil(Clock::time_point deadline, bool finishWithSpin = false,
std::chrono::microseconds spinWindow = 750us) {
PerfStats::WaitScope wait;
const auto now = Clock::now();
if (now >= deadline) {
return;
@@ -584,6 +586,7 @@ void VI_HLE_PresentFrame(bool presentedXfb, bool paceToRetrace) {
s_lastPacedRetraceCount = g_vi.retraceCount;
}
settings_overlay::AdvancePresentedFrame();
PerfStats::OnFramePresented();
g_auroraFrameActive.store(false, std::memory_order_release);
g_auroraFrameHadWork.store(false, std::memory_order_release);
if (presentedXfb) {
+27 -5
View File
@@ -57,6 +57,9 @@
#endif
#include "abi_bridge.h"
#include "audio_backend.h"
#include "boot_race.h"
#include "guest_abi/guest_region.h"
#include "guest_flat_memory.h"
#include "gx_guest_write.h"
#include "memory.h"
@@ -165,6 +168,7 @@ std::atomic_bool g_auroraInitialized{false};
std::atomic_flag g_abortSignalHandled = ATOMIC_FLAG_INIT;
std::atomic_bool g_fatalErrorReported{false};
std::atomic_bool g_fatalPopupShown{false};
bool g_headless = false;
std::atomic<int> g_lastExitCode{0};
std::atomic_bool g_exitCodeSet{false};
@@ -696,6 +700,7 @@ void WriteFatalLogImpl(std::string_view reason, std::string_view extraDetails =
out << "[runtime] fatal log" << std::endl;
out << "[runtime] reason: " << reason << std::endl;
out << "[runtime] timestamp(seconds): " << nowSecs << std::endl;
out << "[runtime] game: " << GuestRegion::CurrentFacts().gameId << std::endl;
out << "[runtime] entry: " << (g_lastEntryLabel.empty() ? "<unknown>" : g_lastEntryLabel) << std::endl;
if (!extraDetails.empty()) {
out << "[runtime] details: " << extraDetails << std::endl;
@@ -770,7 +775,7 @@ void MarkFatalErrorReported() {
}
void ShowRuntimeFatalPopup(std::string_view category, std::string_view details) noexcept {
if (g_fatalPopupShown.exchange(true, std::memory_order_acq_rel)) {
if (g_headless || g_fatalPopupShown.exchange(true, std::memory_order_acq_rel)) {
return;
}
@@ -1342,12 +1347,25 @@ int RuntimeMain(int argc, char** argv) {
std::string currentEntryLabel;
// `--headless`: with a boot race, run without a window, sound or dialogs.
g_headless = std::any_of(argv + 1, argv + argc,
[](const char* arg) { return std::string_view(arg) == "--headless"; });
try {
if (argc != 1) {
throw std::invalid_argument("The game runtime does not accept command-line options; use Config.toml through the installed host.");
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) != "--headless" && !BootRace::ParseArgument(argv[i])) {
throw std::invalid_argument(std::string("Unknown command-line option '") + argv[i] +
"'; the game runtime only accepts --race[=<course id>], --race-cpu and --headless, use Config.toml through the installed host.");
}
}
if (g_headless && !BootRace::Requested()) {
throw std::invalid_argument("--headless needs --race[=<course id>] or --race-cpu");
}
if (g_headless) {
AudioBackend::Instance().SetSilenced(true);
}
RuntimeConfigFile::LogLoadedConfig();
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
if (!g_headless && RuntimeConfigFile::DiscordPresenceEnabled()) {
DiscordPresence::Initialize(RuntimeConfigFile::DiscordClientId(), "Mario Kart Wii");
}
SystemBridge::Initialize();
@@ -1381,7 +1399,8 @@ int RuntimeMain(int argc, char** argv) {
auroraConfig.windowHeight = RuntimeConfigFile::WindowHeight(auroraConfig.windowHeight);
auroraConfig.hasWindowPosition = RuntimeConfigFile::WindowPosition(
auroraConfig.windowPosX, auroraConfig.windowPosY);
auroraConfig.allowJoystickBackgroundEvents = true;
auroraConfig.allowJoystickBackgroundEvents = !g_headless;
auroraConfig.startHidden = g_headless;
auroraConfig.disableCopyFilter = RuntimeConfigFile::DisableCopyFilter(true);
// Dolphin-style custom textures. Aurora indexes <userPath>/texture_replacements
// once during aurora_initialize, so both knobs only take effect on the next launch.
@@ -1460,6 +1479,9 @@ int RuntimeMain(int argc, char** argv) {
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
auroraInfo.windowSize.native_fb_height);
settings_overlay::InitializeRuntimeSettings();
if (BootRace::Requested()) {
aurora_set_skip_unready_pipelines(false);
}
RT_LOG(RT_TAG_CONFIG) << "video.widescreen=" << (configWidescreen ? "true" : "false")
<< " force_16_9=" << (forceAspect169 ? "true" : "false")
<< " SCGetAspectRatio=" << (configWidescreen || forceAspect169 ? 1 : 0)
+67
View File
@@ -0,0 +1,67 @@
#include "perf_stats.h"
#include "runtime_log.h"
#include <algorithm>
#include <array>
#include <cstdlib>
#include <cstring>
namespace PerfStats {
namespace {
using Clock = std::chrono::steady_clock;
constexpr size_t kWindow = 240;
thread_local Clock::duration t_wait{};
struct FrameWindow {
Clock::time_point last{};
std::array<double, kWindow> busyMs{};
size_t count = 0;
};
FrameWindow g_window;
} // namespace
bool Enabled() noexcept {
static const bool enabled = [] {
const char* value = std::getenv("MKW_PERF_STATS");
return value != nullptr && std::strcmp(value, "0") != 0;
}();
return enabled;
}
void AddWait(Clock::duration wait) noexcept { t_wait += wait; }
void OnFramePresented() noexcept {
if (!Enabled()) {
return;
}
const auto now = Clock::now();
const Clock::duration wait = t_wait;
t_wait = {};
if (g_window.last == Clock::time_point{}) {
g_window.last = now;
return;
}
const auto busy = (now - g_window.last) - wait;
g_window.last = now;
g_window.busyMs[g_window.count++] = std::chrono::duration<double, std::milli>(busy).count();
if (g_window.count < kWindow) {
return;
}
std::array<double, kWindow> sorted = g_window.busyMs;
std::sort(sorted.begin(), sorted.end());
double sum = 0;
for (const double ms : sorted) {
sum += ms;
}
RT_LOGF("perf", "busy ms/frame over %zu frames: avg %.3f median %.3f p95 %.3f max %.3f\n", kWindow,
sum / kWindow, sorted[kWindow / 2], sorted[kWindow * 95 / 100], sorted[kWindow - 1]);
g_window.count = 0;
}
} // namespace PerfStats
+31 -4
View File
@@ -75,6 +75,7 @@ const char* GraphicsApiDisplayName() {
bool g_topBarVisible = false;
bool g_exitPromptOpen = false;
bool g_restartPromptOpen = false;
bool g_rumbleEnabled = RuntimeConfigFile::RumbleEnabled(true);
int g_controllerPort = 0;
float g_resolutionScale = RuntimeConfigFile::ResolutionMultiplier(1.0f);
@@ -299,6 +300,8 @@ void ApplyConfiguredMappings() {
}
bool g_wiiRemotesEnabled = RuntimeConfigFile::WiiRemotesEnabled();
const bool g_wiiRemotesAtLaunch = g_wiiRemotesEnabled;
bool g_wiiRemotesSaveFailed = false;
bool g_wiiAutoPair = RuntimeConfigFile::WiiAutoPairEnabled(true);
bool g_wiiSensorBarAbove = RuntimeConfigFile::WiiSensorBarAbove(false);
int g_wiiIrSensitivity = RuntimeConfigFile::WiiIrSensitivity(3);
@@ -379,10 +382,18 @@ void DrawWiiRemoteSetup() {
ImGui::SetTooltip("The sensor bar sensitivity from the Wii settings.\nTakes effect the next time the game starts.");
}
if (ImGui::Checkbox("Use Wii Remotes", &g_wiiRemotesEnabled)) {
RuntimeConfigFile::SetWiiRemotesEnabled(g_wiiRemotesEnabled);
g_wiiRemotesSaveFailed = !RuntimeConfigFile::SetWiiRemotesEnabled(g_wiiRemotesEnabled);
if (g_wiiRemotesSaveFailed) g_wiiRemotesEnabled = !g_wiiRemotesEnabled;
g_restartPromptOpen = g_wiiRemotesEnabled != g_wiiRemotesAtLaunch && !g_wiiRemotesSaveFailed;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Takes effect on the next launch. Turn this off to use a Wii U Pro Controller as a gamepad.");
ImGui::SetTooltip("Takes effect after restarting the game. Turn this off to use a Wii U Pro Controller as a gamepad.");
}
if (g_wiiRemotesSaveFailed) {
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "Could not save this setting to Config.toml.");
}
if (g_wiiRemotesEnabled != g_wiiRemotesAtLaunch) {
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f), "Restart the game to apply this change.");
}
ImGui::Unindent();
}
@@ -1357,6 +1368,19 @@ void DrawExitPrompt() {
ImGui::EndPopup();
}
void DrawRestartPrompt() {
constexpr const char* kTitle = "Restart required";
if (g_restartPromptOpen && !ImGui::IsPopupOpen(kTitle)) ImGui::OpenPopup(kTitle);
if (!ImGui::BeginPopupModal(kTitle, &g_restartPromptOpen, ImGuiWindowFlags_AlwaysAutoResize)) return;
ImGui::TextUnformatted(g_wiiRemotesEnabled ? "Wii Remotes will be used after you restart the game."
: "Wii Remotes will stop being used after you restart the game.");
if (ImGui::Button("Exit now", ImVec2(140.0f, 0.0f))) ExitForAuroraWindowClose();
ImGui::SameLine();
if (ImGui::Button("Restart later", ImVec2(140.0f, 0.0f))) g_restartPromptOpen = false;
if (!g_restartPromptOpen) ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
void DrawTopBar() {
if (!g_topBarVisible) {
return;
@@ -1495,7 +1519,7 @@ void PersistDisplayModeIfChanged() {
void ApplyInputBlockState() {
const bool blocked = controller_mapping_wizard::IsActive() || g_rebind.active ||
g_exitPromptOpen || g_topBarVisible || StartupScreenVisible();
g_exitPromptOpen || g_restartPromptOpen || g_topBarVisible || StartupScreenVisible();
PADBlockInput(blocked);
InputBindings::SetInputBlocked(blocked);
}
@@ -1561,7 +1585,9 @@ void HandleEvents(const AuroraEvent* events) noexcept {
RuntimeConfigFile::SetAudioMuted(g_audioMuted);
}
if (!g_rebind.active && IsToggleKey(ev->sdl, SDL_SCANCODE_ESCAPE)) {
if (g_exitPromptOpen) {
if (g_restartPromptOpen) {
g_restartPromptOpen = false;
} else if (g_exitPromptOpen) {
g_exitPromptOpen = false;
} else if (g_topBarVisible) {
SetTopBarVisible(false);
@@ -1610,6 +1636,7 @@ void Draw() noexcept {
DrawFpsOverlay();
DrawTopBar();
DrawExitPrompt();
DrawRestartPrompt();
controller_mapping_wizard::Draw();
ApplyInputBlockState();
DrawStartupScreen();
+3 -1
View File
@@ -1780,6 +1780,8 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
var pul = KamekPulFile.Load(codePul);
var selected = pul.SelectRegion(region);
// The server validates the hash of the unmodified chunk.
var sourceKamekCodeSha1 = SHA1.HashData(selected.CodeBlob);
if (string.Equals(profile?.Name, "retro-rewind", StringComparison.OrdinalIgnoreCase))
{
var corrected = pul.IsCombined && !string.Equals(region, "P", StringComparison.OrdinalIgnoreCase)
@@ -1988,7 +1990,7 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
baseManifest,
dvdOverlayRoots,
relocatedModuleImage,
SHA1.HashData(selected.CodeBlob),
sourceKamekCodeSha1,
retroWfcLoweringPlan?.StaticPointers,
selected.CodeSize,
selected.BssSize,