mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-24 14:56:35 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4993b2226c | |||
| e15be3bf55 | |||
| 85992f85b0 | |||
| 5a930903fb | |||
| f8944f239a | |||
| 7678608943 | |||
| 4e0e1edb86 | |||
| 1d9cae387a | |||
| 0aba3cb3ea |
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+1
-1
Submodule aurora-main updated: dd399416be...3c80a1a9e8
Generated
+11
-1
@@ -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
|
||||
|
||||
Generated
+12
-7
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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; \
|
||||
|
||||
@@ -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
|
||||
@@ -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.
@@ -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;
|
||||
|
||||
@@ -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; });
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user