mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-26 06:09:08 +00:00
Compare commits
8 Commits
Canary-1.3
...
Canary-1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5f72136b2 | ||
|
|
aa5d32a7b1 | ||
|
|
be5881f100 | ||
|
|
8b1b015572 | ||
|
|
c2e0cf2fd5 | ||
|
|
6eb6b1fc81 | ||
|
|
0ce1bf0b33 | ||
|
|
c698a10b23 |
@@ -39,13 +39,12 @@
|
||||
<!-- OpenTk.Audio.OpenAL has moved to OpenTk.Audio -->
|
||||
<!--<PackageVersion Include="OpenTK.Audio" Version="5.0.0-pre.15" />-->
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
|
||||
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
||||
<!-- Ryujinx.Audio.OpenAL.Dependencies is from the original project, last updated 12/30/20 -->
|
||||
<!--<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />-->
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.2" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.4-build6" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.3" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.4" />
|
||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
|
||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
|
||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu.AppleHv.Arm;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
@@ -17,9 +18,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
return _impl.ElrEl1;
|
||||
}
|
||||
return _impl.Pc;
|
||||
}
|
||||
}
|
||||
@@ -69,9 +68,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +79,13 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private readonly IHvExecutionContext _shadowContext;
|
||||
private IHvExecutionContext _impl;
|
||||
private int _shouldStep;
|
||||
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
private int _interruptRequested;
|
||||
|
||||
// GPU Sync control
|
||||
private int _syncCounter;
|
||||
private int _strongSyncCounter;
|
||||
|
||||
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
|
||||
{
|
||||
_counter = counter;
|
||||
@@ -108,38 +107,17 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
/// <inheritdoc/>
|
||||
public void SetV(int index, V128 value) => _impl.SetV(index, value);
|
||||
|
||||
private void InterruptHandler()
|
||||
{
|
||||
_exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void BreakHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler()
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void UndefinedHandler(ulong address, int opCode)
|
||||
{
|
||||
_exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||
}
|
||||
private void InterruptHandler() => _exceptionCallbacks.InterruptCallback?.Invoke(this);
|
||||
private void BreakHandler(ulong address, int imm) => _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
private void StepHandler() => _exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
private void SupervisorCallHandler(ulong address, int imm) => _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
private void UndefinedHandler(ulong address, int opCode) => _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
|
||||
{
|
||||
impl.RequestInterrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetAndClearInterruptRequested()
|
||||
@@ -161,13 +139,9 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
_impl.ElrEl1 = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_impl.Pc = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +155,11 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
|
||||
{
|
||||
HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
|
||||
|
||||
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
|
||||
|
||||
_syncCounter = 0;
|
||||
_strongSyncCounter = 0;
|
||||
|
||||
while (Running)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
|
||||
@@ -192,16 +168,23 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr |= (1 << 21);
|
||||
spsr |= (1U << 21);
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pstate |= (1 << 21);
|
||||
Pstate |= (1U << 21);
|
||||
}
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
|
||||
}
|
||||
|
||||
// Adaptive GPU synchronization to prevent 0 FPS
|
||||
if (++_syncCounter % 12 == 0)
|
||||
{
|
||||
TryGpuSync();
|
||||
_syncCounter = 0;
|
||||
}
|
||||
|
||||
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||
|
||||
HvExitReason reason = vcpu.ExitInfo->Reason;
|
||||
@@ -212,9 +195,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
|
||||
|
||||
if (hvEc != ExceptionClass.HvcAarch64)
|
||||
{
|
||||
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
|
||||
}
|
||||
|
||||
address = SynchronousException(memoryManager, ref vcpu);
|
||||
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
|
||||
@@ -245,10 +226,31 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
|
||||
}
|
||||
|
||||
// TryGpuSync() is called periodically in the main Execute() loop. The "syncing" value can be tuned based on gameplay results.
|
||||
// This feature it to be followed-up and further completed in a future PR.
|
||||
private void TryGpuSync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.Yield();
|
||||
|
||||
if (++_strongSyncCounter % 6 == 0)
|
||||
{
|
||||
Thread.Yield();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_strongSyncCounter % 100 == 0)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"[AppleHv] GPU sync issue: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
|
||||
{
|
||||
ulong vcpuHandle = vcpu.Handle;
|
||||
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
|
||||
|
||||
@@ -259,16 +261,20 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
case ExceptionClass.DataAbortLowerEl:
|
||||
DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
|
||||
break;
|
||||
|
||||
case ExceptionClass.TrappedMsrMrsSystem:
|
||||
InstructionTrap((uint)esr);
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.ELR_EL1, elr + 4UL).ThrowOnError();
|
||||
break;
|
||||
|
||||
case ExceptionClass.SvcAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
ushort id = (ushort)esr;
|
||||
SupervisorCallHandler(elr - 4UL, id);
|
||||
Thread.Yield(); // MoltenVK causes extremely frequent SVC exits, and HVF handles them in a busy loop. Hypervisor.Framework accelerates the guest CPU, and without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS deadlock.
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
case ExceptionClass.SoftwareStepLowerEl:
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
|
||||
spsr &= ~((ulong)(1 << 21));
|
||||
@@ -278,21 +284,23 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
StepHandler();
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
case ExceptionClass.BrkAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
BreakHandler(elr, (ushort)esr);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception($"Unhandled guest exception {ec}.");
|
||||
}
|
||||
|
||||
// Make sure we will continue running at EL0.
|
||||
if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
|
||||
{
|
||||
|
||||
// TODO: Invalidate only the range that was modified?
|
||||
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||
}
|
||||
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
|
||||
@@ -305,7 +313,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
if (farValid)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(vcpu, HvSysReg.FAR_EL1, out ulong far).ThrowOnError();
|
||||
|
||||
ulong size = 1UL << accessSizeLog2;
|
||||
|
||||
if (!tracking.VirtualMemoryEvent(far, size, write))
|
||||
@@ -349,9 +356,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private void WriteRt(uint rt, ulong value)
|
||||
{
|
||||
if (rt < 31)
|
||||
{
|
||||
SetX((int)rt, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool(HvVcpu vcpu)
|
||||
@@ -369,8 +374,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
_impl = newContext;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using ARMeilleure.State;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
@@ -14,8 +16,31 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
private static readonly SetSimdFpReg _setSimdFpReg;
|
||||
private static readonly nint _setSimdFpRegNativePtr;
|
||||
|
||||
public static bool AggressiveMode { get; set; } = false;
|
||||
private bool _earlyBootPhase = true;
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
private readonly ulong[] _x = new ulong[32];
|
||||
private readonly V128[] _v = new V128[32];
|
||||
|
||||
private ulong _pc;
|
||||
private ulong _elrEl1;
|
||||
private ulong _esrEl1;
|
||||
private ulong _tpidrEl0;
|
||||
private ulong _tpidrroEl0;
|
||||
private ulong _fpcr;
|
||||
private ulong _fpsr;
|
||||
private ulong _pstateRaw;
|
||||
|
||||
private long _fallbackCount;
|
||||
private long _lastWarningTicks;
|
||||
private const long WarningCooldownTicks = 500_000_000; // 0.5 seconds
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
private readonly object _registerLock = new object();
|
||||
|
||||
static HvExecutionContextVcpu()
|
||||
{
|
||||
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||
@@ -33,155 +58,293 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
}
|
||||
}
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
_vcpu = vcpu;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_registerLock)
|
||||
{
|
||||
_pstateRaw = 0x80000000UL;
|
||||
_pc = 0;
|
||||
_elrEl1 = 0;
|
||||
_esrEl1 = 0;
|
||||
_tpidrEl0 = 0;
|
||||
_tpidrroEl0 = 0;
|
||||
_fpcr = 0;
|
||||
_fpsr = 0;
|
||||
|
||||
Array.Clear(_x, 0, _x.Length);
|
||||
Array.Clear(_v, 0, _v.Length);
|
||||
|
||||
_fallbackCount = 0;
|
||||
_lastWarningTicks = 0;
|
||||
_interruptRequested = 0;
|
||||
_earlyBootPhase = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void LogHvWarning(string operation, string regName, string extra = "")
|
||||
{
|
||||
if (AggressiveMode) return;
|
||||
|
||||
long now = DateTime.UtcNow.Ticks;
|
||||
if (now - _lastWarningTicks <= WarningCooldownTicks) return;
|
||||
|
||||
string msg = $"[AppleHv] BadArgument on {operation} {regName} | PC=0x{_pc:X16}";
|
||||
if (!string.IsNullOrEmpty(extra)) msg += $" | {extra}";
|
||||
msg += $" | Total: {Interlocked.Read(ref _fallbackCount)}";
|
||||
|
||||
Logger.Warning?.Print(LogClass.Cpu, msg);
|
||||
_lastWarningTicks = now;
|
||||
}
|
||||
|
||||
public ulong Pc
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.PC, out ulong pc).ThrowOnError();
|
||||
return pc;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.PC, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetRegCached(HvReg.PC, ref _pc, "PC"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.PC, value, ref _pc, "PC"); }
|
||||
}
|
||||
|
||||
public ulong ElrEl1
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ELR_EL1, out ulong elr).ThrowOnError();
|
||||
return elr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ELR_EL1, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ELR_EL1, ref _elrEl1, "ELR_EL1"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.ELR_EL1, value, ref _elrEl1, "ELR_EL1"); }
|
||||
}
|
||||
|
||||
public ulong EsrEl1
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.ESR_EL1, out ulong esr).ThrowOnError();
|
||||
return esr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.ESR_EL1, value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return GetSysRegCached(HvSysReg.ESR_EL1, ref _esrEl1, "ESR_EL1"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.ESR_EL1, value, ref _esrEl1, "ESR_EL1"); }
|
||||
}
|
||||
|
||||
public long TpidrEl0
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, out ulong tpidrEl0).ThrowOnError();
|
||||
return (long)tpidrEl0;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDR_EL0, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDR_EL0, ref _tpidrEl0, "TPIDR_EL0"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDR_EL0, (ulong)value, ref _tpidrEl0, "TPIDR_EL0"); }
|
||||
}
|
||||
|
||||
public long TpidrroEl0
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, out ulong tpidrroEl0).ThrowOnError();
|
||||
return (long)tpidrroEl0;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.TPIDRRO_EL0, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (long)GetSysRegCached(HvSysReg.TPIDRRO_EL0, ref _tpidrroEl0, "TPIDRRO_EL0"); }
|
||||
set { lock (_registerLock) SetSysRegCached(HvSysReg.TPIDRRO_EL0, (ulong)value, ref _tpidrroEl0, "TPIDRRO_EL0"); }
|
||||
}
|
||||
|
||||
public uint Pstate
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong cpsr).ThrowOnError();
|
||||
return (uint)cpsr;
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.CPSR, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("Get", "CPSR (Pstate)");
|
||||
return (uint)_pstateRaw;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_pstateRaw = val;
|
||||
return (uint)val;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, (ulong)value).ThrowOnError();
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.CPSR, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("Set", "CPSR (Pstate)", $"value=0x{value:X}");
|
||||
}
|
||||
else res.ThrowOnError();
|
||||
_pstateRaw = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Fpcr
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPCR, out ulong fpcr).ThrowOnError();
|
||||
return (uint)fpcr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPCR, (ulong)value).ThrowOnError();
|
||||
}
|
||||
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPCR, ref _fpcr, "FPCR"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.FPCR, value, ref _fpcr, "FPCR"); }
|
||||
}
|
||||
|
||||
public uint Fpsr
|
||||
{
|
||||
get
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.FPSR, out ulong fpsr).ThrowOnError();
|
||||
return (uint)fpsr;
|
||||
}
|
||||
set
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.FPSR, (ulong)value).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
_vcpu = vcpu;
|
||||
get { lock (_registerLock) return (uint)GetRegCached(HvReg.FPSR, ref _fpsr, "FPSR"); }
|
||||
set { lock (_registerLock) SetRegCached(HvReg.FPSR, value, ref _fpsr, "FPSR"); }
|
||||
}
|
||||
|
||||
public ulong GetX(int index)
|
||||
{
|
||||
if (index == 31)
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out ulong value).ThrowOnError();
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out ulong value).ThrowOnError();
|
||||
return value;
|
||||
ulong value;
|
||||
string regName = index == 31 ? "SP_EL0" : $"X{index}";
|
||||
|
||||
if (index == 31)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, HvSysReg.SP_EL0, out value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetX", regName);
|
||||
return _x[31];
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return _x[31] = value;
|
||||
}
|
||||
|
||||
if ((uint)index > 30) return 0;
|
||||
|
||||
if (index == 0 && _earlyBootPhase && _pc == 0)
|
||||
{
|
||||
return _x[0];
|
||||
}
|
||||
|
||||
HvResult resX = HvApi.hv_vcpu_get_reg(_vcpu, HvReg.X0 + (uint)index, out value);
|
||||
if (resX == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetX", regName);
|
||||
return _x[index];
|
||||
}
|
||||
resX.ThrowOnError();
|
||||
return _x[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetX(int index, ulong value)
|
||||
{
|
||||
if (index == 31)
|
||||
lock (_registerLock)
|
||||
{
|
||||
HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value).ThrowOnError();
|
||||
}
|
||||
else
|
||||
{
|
||||
HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value).ThrowOnError();
|
||||
string regName = index == 31 ? "SP_EL0" : $"X{index}";
|
||||
|
||||
if (index == 31)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, HvSysReg.SP_EL0, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
|
||||
_x[31] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_x[31] = value;
|
||||
}
|
||||
else if ((uint)index <= 30)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, HvReg.X0 + (uint)index, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetX", regName, $"value=0x{value:X16}");
|
||||
_x[index] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_x[index] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public V128 GetV(int index)
|
||||
{
|
||||
HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 value).ThrowOnError();
|
||||
return new V128(value.Low, value.High);
|
||||
lock (_registerLock)
|
||||
{
|
||||
if ((uint)index > 31) return default;
|
||||
|
||||
HvResult res = HvApi.hv_vcpu_get_simd_fp_reg(_vcpu, HvSimdFPReg.Q0 + (uint)index, out HvSimdFPUchar16 val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetV", $"Q{index}");
|
||||
return _v[index];
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return _v[index] = new V128(val.Low, val.High);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetV(int index, V128 value)
|
||||
{
|
||||
_setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr).ThrowOnError();
|
||||
lock (_registerLock)
|
||||
{
|
||||
if ((uint)index > 31) return;
|
||||
|
||||
HvResult res = _setSimdFpReg(_vcpu, HvSimdFPReg.Q0 + (uint)index, value, _setSimdFpRegNativePtr);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetV", $"Q{index}");
|
||||
_v[index] = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
_v[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetRegCached(HvReg reg, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_reg(_vcpu, reg, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetReg", name);
|
||||
return cached;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return cached = val;
|
||||
}
|
||||
|
||||
private void SetRegCached(HvReg reg, ulong value, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_reg(_vcpu, reg, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetReg", name, $"value=0x{value:X16}");
|
||||
cached = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
cached = value;
|
||||
}
|
||||
|
||||
private ulong GetSysRegCached(HvSysReg reg, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_get_sys_reg(_vcpu, reg, out ulong val);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("GetSysReg", name);
|
||||
return cached;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
return cached = val;
|
||||
}
|
||||
|
||||
private void SetSysRegCached(HvSysReg reg, ulong value, ref ulong cached, string name)
|
||||
{
|
||||
HvResult res = HvApi.hv_vcpu_set_sys_reg(_vcpu, reg, value);
|
||||
if (res == HvResult.BadArgument)
|
||||
{
|
||||
Interlocked.Increment(ref _fallbackCount);
|
||||
LogHvWarning("SetSysReg", name, $"value=0x{value:X16}");
|
||||
cached = value;
|
||||
return;
|
||||
}
|
||||
res.ThrowOnError();
|
||||
cached = value;
|
||||
}
|
||||
|
||||
public long GetFallbackCount() => Interlocked.Read(ref _fallbackCount);
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
|
||||
<PackageReference Include="Silk.NET.Shaderc" ExcludeAssets="native" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
|
||||
@@ -689,13 +689,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
return context.Pstate & 0xFF0FFE20;
|
||||
}
|
||||
|
||||
private const long ContextCacheDurationTicks = 800_000; // ~0.8ms cache lifetime
|
||||
|
||||
private ThreadContext _cachedContext;
|
||||
private long _contextCacheTimestamp;
|
||||
private bool _hasValidContextCache;
|
||||
|
||||
private ThreadContext GetCurrentContext()
|
||||
{
|
||||
var now = DateTime.UtcNow.Ticks;
|
||||
|
||||
// Cache hit
|
||||
if (_hasValidContextCache && (now - _contextCacheTimestamp) < ContextCacheDurationTicks)
|
||||
{
|
||||
return _cachedContext;
|
||||
}
|
||||
|
||||
const int MaxRegistersAArch32 = 15;
|
||||
const int MaxFpuRegistersAArch32 = 16;
|
||||
|
||||
ThreadContext context = new();
|
||||
|
||||
Span<ulong> registersSpan = context.Registers.AsSpan();
|
||||
Span<V128> fpuRegistersSpan = context.FpuRegisters.AsSpan();
|
||||
|
||||
@@ -705,12 +718,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
registersSpan[i] = Context.GetX(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < fpuRegistersSpan.Length; i++)
|
||||
{
|
||||
fpuRegistersSpan[i] = Context.GetV(i);
|
||||
}
|
||||
|
||||
context.Fp = Context.GetX(29);
|
||||
context.Lr = Context.GetX(30);
|
||||
context.Sp = Context.GetX(31);
|
||||
@@ -724,12 +735,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
{
|
||||
registersSpan[i] = (uint)Context.GetX(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MaxFpuRegistersAArch32; i++)
|
||||
{
|
||||
fpuRegistersSpan[i] = Context.GetV(i);
|
||||
}
|
||||
|
||||
context.Pc = (uint)Context.Pc;
|
||||
context.Pstate = GetPsr(Context);
|
||||
context.Tpidr = (uint)Context.TpidrroEl0;
|
||||
@@ -738,6 +747,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
context.Fpcr = (uint)Context.Fpcr;
|
||||
context.Fpsr = (uint)Context.Fpsr;
|
||||
|
||||
// Update cache
|
||||
_cachedContext = context;
|
||||
_contextCacheTimestamp = now;
|
||||
_hasValidContextCache = true;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
216
src/Ryujinx/Assets/PlayReports/nsmbud.json
Normal file
216
src/Ryujinx/Assets/PlayReports/nsmbud.json
Normal file
@@ -0,0 +1,216 @@
|
||||
{
|
||||
"mario": {
|
||||
"1": {
|
||||
"1": "Acorn Plains Way",
|
||||
"2": "Tilted Tunnel",
|
||||
"21": "Crushing-Cogs Tower",
|
||||
"3": "Yoshi Hill",
|
||||
"4": "Mushroom Heights",
|
||||
"5": "Rise of the Piranha Plants",
|
||||
"23": "Lemmy's Swingback Castle",
|
||||
"13": "Blooper's Secret Lair"
|
||||
},
|
||||
"2": {
|
||||
"1": "Stone-Eye Zone",
|
||||
"2": "Perilous Pokey Cave",
|
||||
"3": "Fire Snake Cavern",
|
||||
"21": "Stoneslide Tower",
|
||||
"4": "Spike's Spouting Sands",
|
||||
"5": "Dry Desert Mushrooms",
|
||||
"6": "Blooming Lakitus",
|
||||
"23": "Morton's Compactor Castle",
|
||||
"14": "Piranha Plants on Ice"
|
||||
},
|
||||
"3": {
|
||||
"1": "Waterspout Beach",
|
||||
"2": "Tropical Refresher",
|
||||
"21": "Giant Skewer Tower",
|
||||
"20": "Haunted Shipwreck",
|
||||
"3": "Above the Cheep Cheep Seas",
|
||||
"4": "Urchin Shoals",
|
||||
"5": "Dragoneel's Undersea Grotto",
|
||||
"23": "Larry's Torpedo Castle",
|
||||
"15": "Skyward Stalk"
|
||||
},
|
||||
"4": {
|
||||
"1": "Spinning-Star Sky",
|
||||
"2": "Cooligan Fields",
|
||||
"21": "Freezing-Rain Tower",
|
||||
"3": "Prickly Goombas!",
|
||||
"4": "Scaling the Mountainside",
|
||||
"5": "Icicle Caverns",
|
||||
"20": "Swaying Ghost House",
|
||||
"23": "Wendy's Shifting Castle",
|
||||
"16": "Fliprus Lake"
|
||||
},
|
||||
"5": {
|
||||
"37": "The Mighty Cannonship",
|
||||
"1": "Jungle of the Giants",
|
||||
"2": "Bridge over Poisoned Waters",
|
||||
"3": "Bramball Woods",
|
||||
"21": "Snake Block Tower",
|
||||
"20": "Which-Way Labyrinth",
|
||||
"4": "Painted Swampland",
|
||||
"5": "Deepsea Ruins",
|
||||
"6": "Seesaw Bridge",
|
||||
"7": "Wiggler Stampede",
|
||||
"23": "Iggy's Volcanic Castle",
|
||||
"17": "Flight of the Para-Beetles"
|
||||
},
|
||||
"6": {
|
||||
"1": "Fuzzy Clifftop",
|
||||
"2": "Porcupuffer Falls",
|
||||
"21": "Grinding-Stone Tower",
|
||||
"3": "Wadlewing's Nest",
|
||||
"4": "Light Blocks, Dark Tower",
|
||||
"5": "Walking Piranha Plants!",
|
||||
"6": "Thrilling Spine Coaster",
|
||||
"22": "Screwtop Tower",
|
||||
"7": "Shifting-Floor Cave",
|
||||
"23": "Roy's Conveyor Castle"
|
||||
},
|
||||
"7": {
|
||||
"1": "Land of Flying Blocks",
|
||||
"2": "Seesaw Shrooms",
|
||||
"3": "Switchback Hill",
|
||||
"21": "Slide Lift Tower",
|
||||
"20": "Spinning Spirit House",
|
||||
"4": "Bouncy Cloud Boomerangs",
|
||||
"5": "A Quick Dip in the Sky",
|
||||
"6": "Snaking above Mist Valley",
|
||||
"23": "Ludwig's Clockwork Castle",
|
||||
"37": "Boarding the Airship"
|
||||
|
||||
},
|
||||
"8": {
|
||||
"1": "Meteor Moat",
|
||||
"2": "Magma-River Cruise",
|
||||
"3": "Rising Tides of Lava",
|
||||
"4": "firefall Cliffs",
|
||||
"42": "Red-Hot Elevator Ride",
|
||||
"43": "The Final Battle"
|
||||
},
|
||||
"9": {
|
||||
"1": "Spine-Tingling Spine Coaster",
|
||||
"2": "Run for It",
|
||||
"3": "Swim for Your Life!",
|
||||
"4": "Hammerswing Caverns",
|
||||
"5": "Spinning Platforms of Doom",
|
||||
"6": "Fire Bar Cliffs",
|
||||
"7": "Lakitu! Lakitu! Lakitu!",
|
||||
"8": "Pendulum Castle",
|
||||
"9": "Follow That Shell!"
|
||||
},
|
||||
"11": {
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": "",
|
||||
"7": "",
|
||||
"8": ""
|
||||
}
|
||||
},
|
||||
"luigi": {
|
||||
"1": {
|
||||
"1": "Waddlewing Warning!",
|
||||
"2": "Crooked Cavern",
|
||||
"21": "Flame-Gear Tower",
|
||||
"3": "Rolling Yoshi Hills",
|
||||
"4": "Piranha Heights",
|
||||
"5": "Piranha Gardens",
|
||||
"23": "Lemmy's Lights-Out Castle",
|
||||
"13": "Cheep Chomp Chase"
|
||||
},
|
||||
"2": {
|
||||
"1": "Spike's Tumbling Desert",
|
||||
"2": "Underground Grrrols",
|
||||
"3": "Piranhas in the Dark",
|
||||
"21": "Wind-Up Tower",
|
||||
"4": "The Walls Have Eyes",
|
||||
"5": "Stone Spike Conveyors",
|
||||
"6": "Spinning Sandstones",
|
||||
"23": "Morton's Lava-Block Castle",
|
||||
"14": "Slippery Rope Ladders"
|
||||
},
|
||||
"3": {
|
||||
"1": "Huckit Beach Resort",
|
||||
"2": "Urchin Reef Romp",
|
||||
"21": "Shish-Kebab Tower",
|
||||
"20": "Haunted Cargo Hold",
|
||||
"3": "Waterspout Sprint",
|
||||
"4": "The Great Geysers",
|
||||
"5": "Dragoneel Depths",
|
||||
"23": "Larry's Trigger-Happy Castle",
|
||||
"15": "Beanstalk Jungle"
|
||||
},
|
||||
"4": {
|
||||
"1": "Broozers and Barrels",
|
||||
"2": "Cooligan Shrooms",
|
||||
"21": "Icicle Tower",
|
||||
"3": "Fire and Ice",
|
||||
"4": "Weighty Waddlewings",
|
||||
"5": "Ice-Slide Expressway",
|
||||
"20": "Peek-a-Boo Ghost House",
|
||||
"23": "Wendy's Thwomp Castle",
|
||||
"16": "Fliprus Floes"
|
||||
},
|
||||
"5": {
|
||||
"1": "Giant Swing-Along",
|
||||
"2": "Dancing Blocks, Poison Swamp",
|
||||
"3": "Heart of Bramball Woods",
|
||||
"21": "Stone-Snake Tower",
|
||||
"20": "Boo's Favorite Haunt",
|
||||
"4": "Painted Pipeworks",
|
||||
"5": "Deepsea Stone-Eyes",
|
||||
"6": "Sumo Bro Bridge",
|
||||
"7": "Wiggler Floodlands",
|
||||
"23": "Iggy's Swinging-Chains Castle",
|
||||
"17": "Para-Beetle Parade"
|
||||
},
|
||||
"6": {
|
||||
"1": "Mount Fuzzy",
|
||||
"2": "Porcupuffer Cavern",
|
||||
"21": "Smashing-Stone Tower",
|
||||
"3": "Spike's Seesaws",
|
||||
"4": "Light-Up-Lift Tower",
|
||||
"5": "Rising Piranhas",
|
||||
"6": "Spine Coaster Stowaways",
|
||||
"22": "Sumo Bro's Spinning Tower",
|
||||
"7": "Switch-Lift Express",
|
||||
"23": "Roy's Ironclad Castle"
|
||||
},
|
||||
"7": {
|
||||
"1": "Frozen Fuzzies",
|
||||
"2": "Wiggler Rodeo",
|
||||
"3": "Rainbow Skywalk",
|
||||
"21": "Stonecrush Tower",
|
||||
"20": "Vanishing Ghost House",
|
||||
"4": "Above The Bouncy Clouds",
|
||||
"5": "Flame Chomp Ferris Wheel",
|
||||
"6": "Three-Headed Snake Block",
|
||||
"23": "Ludwig's Block-Press Castle",
|
||||
"37": "Bowser Jr. Showdown"
|
||||
},
|
||||
"8": {
|
||||
"1": "Magma Moat",
|
||||
"2": "Magmaw River Cruise",
|
||||
"3": "Hot Cogs",
|
||||
"4": "Firefall Rising",
|
||||
"42": "Current Event",
|
||||
"43": "The Final Battle"
|
||||
},
|
||||
"9": {
|
||||
"1": "Spine Coaster Connections",
|
||||
"2": "P Switch Peril",
|
||||
"3": "Star Coin Deep Dive",
|
||||
"4": "Hammerswing Hideout",
|
||||
"5": "Under Construction",
|
||||
"6": "Fire Bar Sprint",
|
||||
"7": "Cloudy Capers",
|
||||
"8": "Impossible Pendulums",
|
||||
"9": "Flying Squirrel Ovation"
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/Ryujinx/Assets/Splashes.json
Normal file
98
src/Ryujinx/Assets/Splashes.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"Locales": {
|
||||
"ar_SA": [],
|
||||
"de_DE": [],
|
||||
"el_GR": [],
|
||||
"en_US": [
|
||||
"Ryubing is my middle name.",
|
||||
"Giving it 110 percent!",
|
||||
"I don't think therefore I don't am!",
|
||||
"All hail Egg.",
|
||||
"Insert cringy joke here.",
|
||||
"ITS RYUBINGING TIME!",
|
||||
"I hate Mondays...",
|
||||
"Fantastical!",
|
||||
"Now with 100% more humor!",
|
||||
"'Not S&P approved' has been approved by S&P.",
|
||||
"ARE YOU NOT ENTERTAINED?",
|
||||
"It's an emulator!",
|
||||
"Now the real game begins...",
|
||||
"Cooked fresh since 2018!",
|
||||
"Must've been the wind...",
|
||||
"I used to be an adventurer like you before I took an arrow to the knee.",
|
||||
"Ryubing!",
|
||||
"May contain nuts!",
|
||||
"May include occasional pop culture references!",
|
||||
"100% organically grown!",
|
||||
"Have a nice day : )",
|
||||
"Spoats car!",
|
||||
"Bottom text",
|
||||
"Im sorry Dave. I'm afraid I can't do that.",
|
||||
"That's no moon...",
|
||||
"Sir, finishing this fight.",
|
||||
"I see how it is...",
|
||||
"Space! The final frontier!",
|
||||
"If you could not tell already, I love making bad jokes : )",
|
||||
"this.",
|
||||
"Probably contains no baked beans.",
|
||||
"Y'all ready for this?",
|
||||
"Removed Herobrine.",
|
||||
"Right to repair!",
|
||||
"Programmed in C#!",
|
||||
"Forgejo has dethroned Gitlab!",
|
||||
"Any ideas what to put here?",
|
||||
"Good morning!",
|
||||
"Good afternoon!",
|
||||
"Good evening!",
|
||||
"I hope you are having a great day!",
|
||||
"Please insert disc two!",
|
||||
"I... AM RYUBING!",
|
||||
"Ryubingin' it up",
|
||||
"bing bing wahoo.",
|
||||
"egg",
|
||||
"No, lossless scaling is NOT supported.",
|
||||
"How do you people do anything?",
|
||||
"One dollar.",
|
||||
"Somebody once told me!",
|
||||
"Its that time of the year again!",
|
||||
"Brewed from only the finest memes.",
|
||||
"Async shader compilation would destroy my soul : (",
|
||||
"Trans rights are human rights!",
|
||||
":3",
|
||||
"Patched ':3' splash replication glitch.",
|
||||
"Please connect a controller!",
|
||||
"Never gonna give you up!",
|
||||
"The game was rigged from the start.",
|
||||
"Ganon is watching you!",
|
||||
"Now with 100% more JSON in the splash code!",
|
||||
"Countless hours of fun!",
|
||||
"Sorry, Link. I can't give credit. Come back when you're a little... mmmmmm... richer!",
|
||||
"Do a barrel roll!",
|
||||
"You've met with a terrible fate, haven't you?",
|
||||
"Yahaha! You found me!",
|
||||
"I would've been in real trouble if you hadn't shown up when you did, goro.",
|
||||
"Stay fresh!",
|
||||
"Yellow, black. Yellow, black. Yellow, black. Yellow, black. Ooh, black and yellow! Let's shake it up a little.",
|
||||
"Whaaa? You came to see me again? That makes Beedle SO HAPPY!",
|
||||
"Don't get cooked, stay off the hook!",
|
||||
"Now with 100% more good vibes in the splash code!",
|
||||
"It is Wednesday my dudes!"
|
||||
],
|
||||
"es_ES": [],
|
||||
"fr_FR": [],
|
||||
"he_IL": [],
|
||||
"it_IT": [],
|
||||
"ja_JP": [],
|
||||
"ko_KR": [],
|
||||
"no_NO": [],
|
||||
"pl_PL": [],
|
||||
"pt_BR": [],
|
||||
"ru_RU": [],
|
||||
"sv_SE": [],
|
||||
"th_TH": [],
|
||||
"tr_TR": [],
|
||||
"uk_UA": [],
|
||||
"zh_CN": [],
|
||||
"zh_TW": []
|
||||
}
|
||||
}
|
||||
64
src/Ryujinx/Common/SplashTextHelper.cs
Normal file
64
src/Ryujinx/Common/SplashTextHelper.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Gommon;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class SplashTextHelper
|
||||
{
|
||||
public static void PrintSplash()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, " ___ __ _ ");
|
||||
Logger.Notice.Print(LogClass.Application, @" / _ \ __ __ __ __ / / (_) ___ ___ _");
|
||||
Logger.Notice.Print(LogClass.Application, @" / , _/ / // // // / / _ \ / / / _ \ / _ `/");
|
||||
Logger.Notice.Print(LogClass.Application, @"/_/|_| \_, / \_,_/ /_.__//_/ /_//_/ \_, / ");
|
||||
Logger.Notice.Print(LogClass.Application, " /___/ /___/ ");
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
Logger.Notice.Print(LogClass.Application, GetSplash());
|
||||
Logger.Notice.Print(LogClass.Application, "");
|
||||
}
|
||||
|
||||
private static string s_finalSplash = "";
|
||||
|
||||
public static string GetSplash()
|
||||
{
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
{
|
||||
s_finalSplash = GetLangJson();
|
||||
if (string.IsNullOrEmpty(s_finalSplash))
|
||||
{
|
||||
s_finalSplash = "Splash Text";
|
||||
}
|
||||
}
|
||||
|
||||
return $"{s_finalSplash}";
|
||||
}
|
||||
|
||||
private static SplashLocales s_splashJson;
|
||||
|
||||
private static string GetLangJson()
|
||||
{
|
||||
try
|
||||
{
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/Splashes.json");
|
||||
s_splashJson = JsonSerializer.Deserialize<SplashLocales>(data);
|
||||
return s_splashJson.Locales[ConfigurationState.Instance.UI.LanguageCode.Value].GetRandomElement();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private struct SplashLocales
|
||||
{
|
||||
public Dictionary<string, List<string>> Locales { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,13 +437,9 @@ namespace Ryujinx.Ava
|
||||
|
||||
internal static void PrintSystemInfo()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, " ___ __ _ ");
|
||||
Logger.Notice.Print(LogClass.Application, @" / _ \ __ __ __ __ / / (_) ___ ___ _");
|
||||
Logger.Notice.Print(LogClass.Application, @" / , _/ / // // // / / _ \ / / / _ \ / _ `/");
|
||||
Logger.Notice.Print(LogClass.Application, @"/_/|_| \_, / \_,_/ /_.__//_/ /_//_/ \_, / ");
|
||||
Logger.Notice.Print(LogClass.Application, " /___/ /___/ ");
|
||||
|
||||
|
||||
// Print the ryubing logo + joke splash
|
||||
SplashTextHelper.PrintSplash();
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}");
|
||||
Logger.Notice.Print(LogClass.Application, $".NET Runtime: {RuntimeInformation.FrameworkDescription}");
|
||||
SystemInfo.Gather().Print();
|
||||
|
||||
@@ -175,6 +175,8 @@
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Forgejo.png" />
|
||||
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
|
||||
<EmbeddedResource Include="Assets\PlayReports\*.json" />
|
||||
<EmbeddedResource Include="Assets\Splashes.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\assets\Locales\*.json" />
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Gommon;
|
||||
using Humanizer;
|
||||
using MsgPack;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ryujinx.Ava.Systems.PlayReport
|
||||
{
|
||||
@@ -1116,5 +1118,87 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
_ => "Wandering"
|
||||
};
|
||||
}
|
||||
|
||||
private static FormattedValue NsmbudRpc(SparseMultiValue values)
|
||||
{
|
||||
if (values.Matched.TryGetValue("WorldNo", out Value world) && values.Matched.TryGetValue("CourseNo", out Value course) | values.Matched.TryGetValue("GameModeType", out Value gamemode))
|
||||
{
|
||||
string worldstr = world.ToString();
|
||||
string coursestr = course.ToString();
|
||||
int courseint = Int32.Parse(coursestr);
|
||||
string gamemodestr = gamemode.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
Dictionary<string, Dictionary<string, Dictionary<string, string>>> output;
|
||||
string data;
|
||||
data = EmbeddedResources.ReadAllText("Ryujinx/Assets/PlayReports/nsmbud.json");
|
||||
output = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, Dictionary<string, string>>>>(data);
|
||||
if (SpecialMapNames(courseint) == "Hazard")
|
||||
{
|
||||
return $"Last Played: Course {worldstr}-Hazard";
|
||||
}
|
||||
string outputloc = output[MarioOrLuigiGamemode(gamemodestr)][worldstr][coursestr];
|
||||
return $"Last Played: Course {worldstr}-{SpecialMapNames(courseint)} | {outputloc}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return FormattedValue.ForceReset;
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Matched.TryGetValue("RlId", out Value RlId) | values.Matched.TryGetValue("TotalPlayTime", out Value TotalPlayTime))
|
||||
{
|
||||
return "At the main menu";
|
||||
}
|
||||
|
||||
static string MarioOrLuigiGamemode(string? gamemode) => gamemode switch
|
||||
{
|
||||
"0" => "mario",
|
||||
"1" => "luigi",
|
||||
"4" => "mario",
|
||||
"5" => "mario",
|
||||
_ => gamemode
|
||||
};
|
||||
|
||||
static string OtherGameMode(string? gamemode) => gamemode switch
|
||||
{
|
||||
"2" => "Boost Rush",
|
||||
"3" => "Challenges",
|
||||
"4" => "Coin Battle",
|
||||
"5" => "Coin Battle Editor",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
static string SpecialMapNames(int? course) => course switch
|
||||
{
|
||||
>= 1 and <= 9 => course.ToString(),
|
||||
13 => "Shortcut",
|
||||
14 => "Shortcut",
|
||||
15 => "Shortcut",
|
||||
16 => "Shortcut",
|
||||
17 => "Shortcut",
|
||||
20 => "Ghost",
|
||||
21 => "Tower",
|
||||
22 => "Tower",
|
||||
23 => "Castle",
|
||||
37 => "Airship",
|
||||
42 => "Castle",
|
||||
43 => "Castle",
|
||||
_ => "Hazard"
|
||||
};
|
||||
|
||||
// For future reference
|
||||
// Tower course = 21, Castle course = 23,Haunted Mansion/ship = 20
|
||||
// Tower course 2 (rock candy) = 22
|
||||
// Peach castle 1 = 42, Peach final battle = 43
|
||||
// airship = 37, jungle beetles = 17
|
||||
// Glacier seals = 16, water leaf = 15
|
||||
// desert ice = 14, acorn squid = 13
|
||||
// all other course numbers are to be considered a hazard
|
||||
|
||||
return "";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,12 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
||||
.WithDescription("based on gold count, report info only in the mii selector, and gamestage (progression)")
|
||||
.AddSparseMultiValueFormatter(["gold", "secret", "stage"], MiitopiaRPC)
|
||||
)
|
||||
.AddSpec(
|
||||
"0100ea80032ea000", // New Super Mario Bros U Deluxe
|
||||
spec => spec
|
||||
.WithDescription("based on world map return info.")
|
||||
.AddSparseMultiValueFormatter(["WorldNo", "CourseNo", "RlId", "TotalPlayTime", "GameModeType"], NsmbudRpc)
|
||||
)
|
||||
);
|
||||
|
||||
private static string Playing(string game) => $"Playing {game}";
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
[ObservableProperty] public partial string LoadHeading { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string CacheLoadStatus { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string Splash { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string DockedStatusText { get; set; }
|
||||
|
||||
@@ -1256,6 +1258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
break;
|
||||
case ShaderCacheLoadingState shaderCacheState:
|
||||
CacheLoadStatus = $"{current} / {total}";
|
||||
Splash = $"\"{SplashTextHelper.GetSplash()}\"";
|
||||
switch (shaderCacheState)
|
||||
{
|
||||
case ShaderCacheLoadingState.Start:
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}" RowDefinitions="Auto,Auto,Auto">
|
||||
IsVisible="{Binding ShowLoadProgress}" RowDefinitions="Auto,Auto,Auto,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
@@ -179,6 +179,16 @@
|
||||
Text="{Binding CacheLoadStatus}"
|
||||
TextAlignment="Start"
|
||||
MaxWidth="500" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="10"
|
||||
FontSize="14"
|
||||
FontStyle="Oblique"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding Splash}"
|
||||
Foreground="LightGray"
|
||||
TextAlignment="Start"
|
||||
MaxWidth="500" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user