Compare commits

...

11 Commits

Author SHA1 Message Date
awesomeangotti
a5f72136b2 UI: Add random splashes to loading screen (#128)
This PR introduces splash text messages that change per startup on the loading screen after selecting a game. It also moves the "RYUBING" logo splash in logs to be inside its own class, which also handles loading screen splashes and titlebar splashes. Credits to VewDev, Lotp, Sh0inx, yell0wsuit, and Greemdev for pointers and assistance throughout this PR.

Co-authored-by: Awesomeangotti <awesomeangotti@noreply.git.ryujinx.app>
Co-authored-by: Awesomeangotti <143439211+Awesomeangotti@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/128
2026-06-26 02:42:41 +00:00
awesomeangotti
aa5d32a7b1 Change RPCData embedded resource to PlayReports (#147)
Change RPCData embedded resource to PlayReports

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/147
2026-06-26 00:23:20 +00:00
awesomeangotti
be5881f100 Discord Rich Presence: New Super Mario Bros U Deluxe (#130)
Add RPC support for NSMBUD play reports that generate on main menu and after finishing a course.

Tracked things:

Main menu
Last played course

Examples:

Main menu
![image](/attachments/9f1506bd-fc8c-4eca-930d-64f15a7c650d)

After finishing a course (By dying or by beating it)
![image](/attachments/a4af3f4f-230d-47ac-977a-20281a103cb6)

In the future should I be doing batch PRs for RPC related things? Yes.

Co-authored-by: Awesomeangotti <143439211+Awesomeangotti@users.noreply.github.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/130
2026-06-25 20:59:56 +00:00
_Neo_
8b1b015572 macOS: Fix Hypervisor BadArgument Launch Crash (#89)
Ayyyy, welcome to the **macOS: Fix Hypervisor BadArgument Launch Crash PR!**

We are so so back my friends.

This PR fixes the macOS Hypervisor `BadArgument` launch crash, while also sneaking in some improvements to help _potentially_ mitigate those lovely 0 FPS deadlock on macOS in certain games with Vulkan + Hypervisor enabled (there will be a separate PR in the future to address this).

The PR is slightly different in terms of the PR messages that I write, but I hope that it'll provide the necessary context to those unaware of the reasoning/causes of the issue.

Also, this is a bit of a read, so grab some snacks, pour a drink, get comfortable, and enjoy some…

### STORY TIME!!!

_Before we proceed forward, we must properly understand what Hypervisor is._

_Hypervisor refers to using Apple's built-in virtualization technology, the Hypervisor Framework, to accelerate CPU emulation._

_There are two main ways to do this:_

_1. **Interpreter** – executes instructions one by one (very slow)._
_2. **JIT (Just-In-Time)** recompilation – translates Switch CPU instructions into native code (much faster)._

_On Apple Silicon Macs (M1, M2, M3, etc.), the guest CPU and host CPU are both ARM64-based. As the Nintendo Switch itself is ARM64, The Hypervisor Framework can allow Ryujinx to execute large amounts of guest code more directly, reducing emulation overhead. The architectural similarity makes hardware-assisted execution practical. On Intel Macs, there isn't the same direct ARM-to-ARM advantage, so the benefits are much more limited or unavailable._

_When Hypervisor mode is available and working correctly, it can provide higher frames, reduced CPU bottlenecks, faster shader compilation in certain instances, smoother gameplay, and lower CPU usage._

_The improvement depends heavily on the game. CPU-heavy titles tend to benefit the most._

_There are downsides though. Sometimes, certain games may have compatibilities issues, certain feature may not work exactly the same, bugs in the actual Hypervisor implementation can cause crashes or issues, and performance gain vary significantly on a game-to-game basis._

_In terms of a simple analogy:_

_**Without Hypervisor:** Ryujinx acts like a translator who rewrites every sentence before speaking it._
_**With Hypervisor:** Ryujinx can let the Mac's CPU understand and execute much more of the Switch's code directly, so less translation work is needed._

_Anyway, now that we have the background, onto the story!_

Once upon a time, around ~December 2025 / January 2026, users — and yes, even devs (though let’s be honest, macOS devs are basically cryptids at this point)

_(Authors note: Cryptids - animals or other beings whose present existence is disputed or unsubstantiated by science)_

Started noticing inconsistent behaviour with the macOS Hypervisor. In other words, Hypervisor began showing inconsistent launch behaviour for users who've upgraded to the then current macOS versions (15.7.3 / 26.2). This behaviour continued as macOS got updated, and is still present on the latest version (Tahoe 26.5.1).

Initially, Hypervisor was mostly crashing with an `Unexpected result "Denied"`. The likely cause is two fold:
1. macOS security permissions changed during that update.
2. Hypervisor framework got updated, but nothing what communicated to the devs.

(It is likely to be the former, not the later. This is the main theory, at least.)

However, this crash later evolved into (because of course it did) into `Unexpected result "BadArgument"`.

We'll come back to this in a bit.

For users who wished to run games with Hypervisor enabled, they had a simple solution - build Ryujinx locally. Yes, this is an effective solution in its own right, as that does bypass certain macOS signing and certain .app bundle-related constraints, as well as certain security permissions.

However, this doesn't explain why:
* Official server builds were the only builds that were affected
* Users on _older macOS_ (below 15.7.3 / 26.2) could run games with Hypervisor enabled as if nothing had ever gone wrong in their lives.
* Users on _newer macOS_ versions _could still run certain older_ Ryujinx builds as intended — specifically, the last or close-to-last version they had lying around in their Downloads folder.
* Users on macOS above 15.7.3 / 26.2 could still run games with Hypervisor enabled, on builds downloaded from the server, for whatever reason.

So now we’re here:
* Same macOS versions → Different outcomes
* Different Ryujinx builds → Different behavior
* `Denied` → Evolved into `BadArgument`
* No consistent reproduction steps

And so, after attempting to fix this for quite some time, I've determined it to be unfixable. Ryujinx should drop macOS support because development has been held back far too long, and there is nothing we can possibly do. Every possible solution was tested and the results were null.

So, it is with great sadness that, here and now, I proclaim that Ryujinx will terminate further macOS support.

Bye-bye, Ryujinx on macOS! You had a great run, and you'll always be remembered. Thank you for bringing so much great memories to us, macOS users!

...

...

...

...

...

HAHA - NOT SO FAST!

(_Please tell me I got you there_)

Not all hope is lost - there is light at the end of the tunnel!

**The key insight is this:** `BadArgument` is not just “`Denied` but newer and worse”. It’s something else entirely.

* `Denied` → macOS refuses the request at the security boundary. Nothing even starts.
* `BadArgument` → macOS accepts the request, but the data being passed is now invalid under newer constraints.

So instead of:

_“You shall not pass.”_

We now have:

_“You may pass… but what is THAT?”_

And this brings us to this PR!

### The PR

Users on macOS were experiencing two major issues when Hypervisor was enabled:

**1. Hard Crash – "Unexpected result `BadArgument`"**

The most frequent stack trace pointed to:

```
System.Exception: Unexpected result "BadArgument".
   at Ryujinx.Cpu.AppleHv.HvResultExtensions.ThrowOnError(...)
   at Ryujinx.Cpu.AppleHv.HvExecutionContextVcpu.GetX(Int32 index)
   at Ryujinx.HLE.HOS.Kernel.Threading.KThread.GetCurrentContext()
   at Ryujinx.HLE.HOS.Kernel.Threading.KThread.GetThreadContext3(...)
```
This crash occurred very early during game initialization, most often inside supervisor calls such as `GetThreadContext3`.

**2. Intermittent Permanent 0 FPS Freezes with Vulkan (unrelated to `BadArgument`, but somewhat addressed in this PR)**
The game would run normally for a while, then drop to 0 FPS permanently. Disabling Hypervisor eliminated the freezes but performance was at times inconsistent.

### The Explanation

`HvResult.BadArgument` is returned by Apple's Hypervisor framework (`hv_vcpu_get_reg`, `hv_vcpu_get_sys_reg`, `hv_vcpu_get_simd_fp_reg`, etc.) when it determines that the requested operation cannot be performed in the current vCPU state.

**Common reasons why this happens:**
* Timing races - the guest tries to read registers before the vCPU has fully synchronized its internal state after context switches, exceptions, or vCPU pool reuse.
* Stricter validation in newer macOS versions.
* vCPU state inconsistencies during early boot or heavy syscall activity.

Because the original implementation called `.ThrowOnError()` unconditionally on every Hypervisor call, even a single transient `BadArgument` would immediately terminate execution. This was the direct cause of the crash.

The 0 FPS issue was a separate but related symptom: when Hypervisor is enabled, the guest CPU runs significantly faster than MoltenVK's presentation queue expects, leading to command buffer starvation and queue deadlock.

### The Changes

* **Implemented: Complete Shadow Register Cache (`HvExecutionContextVcpu.cs`)**
    * Added full in-memory shadow copies of every relevant register:
        * General-purpose registers: `private readonly ulong[] _x = new ulong[32]`;
        * Vector/SIMD registers: private readonly `V128[] _v = new V128[32]`;
        * System/special registers: `_pc`, `_elrEl1`, `_esrEl1`, `_tpidrEl0`, `_tpidrroEl0`, `_fpcr`, `_fpsr`, and `_pstateRaw` (stored as `ulong` internally to avoid type conversion issues).
    * **Reasoning:** Returning zero or garbage values on failure could corrupt game state. When the Hypervisor returns `BadArgument`, we now have a safe, previously-valid value to return instead of crashing. A shadow cache gives us that last known good value. The cache is updated on every successful read, so correctness is preserved in normal operation. This is the central fix for the `BadArgument` crash.

* **Defensive BadArgument Handling on Every Register Access Path**
    * Updated every read operation (`GetX`, `GetV`, `Pstate`, cached getters, etc.) to explicitly check the return value, with the same pattern applied across all accessors.
    * **Reasoning:** This directly eliminates the crash while preserving the original error-throwing behavior for genuine failures. Returning cached data is far safer than injecting zero or garbage values that could corrupt guest state:

```
HvResult res = HvApi.hv_vcpu_get_...(..., out value);
if (res == HvResult.BadArgument)
{
    _fallbackCount++;
    LogHvWarning("...");           // rate-limited
    return cachedValue;            // safe fallback
}
res.ThrowOnError();                // only throw on real errors
return cachedValue = value;        // success path + cache update
```

* **AggressiveMode Global Option** (NOT IMPLEMENTED IN THIS PR, BUT A FUTURE PR WILL ADD THIS FULLY)
    * Added:
`public static bool AggressiveMode { get; set; } = false;`
    * When true/enabled, all warning logs in LogHvWarning() are suppressed.
    * **Reasoning:** Diagnostic logging has overhead. In the future, users will be able to disable/enable this when required. This will also be useful for any dev working on macOS Hypevisor (aka - me).

* **Hot-Path Optimizations**
    * Changed index checks from `if (index < 0 || index > 30)` to `if ((uint)index > 30)` in `GetX`, `SetX`, `GetV`, and `SetV`.
    * Separated cache initialization into a dedicated `InitializeCacheDefaults()` method called by constructor and `Reset()`.
    * **Reasoning:** `GetX` is called millions of times per second. Reducing branches improves CPU branch prediction and overall throughput. The clean initialization pattern ensures reliable behavior when vCPUs are reused from the pool.

* **Improved Logging, Diagnostics & Monitoring**
    * Rate-limited warnings with a cooldown timer (`WarningCooldownTicks = 1_000_000_000 ≈ 1 second`).
    * Clear, descriptive messages (e.g. "PAC failure on SP_EL0", "PAC failure on X15").
    * Added `public long GetFallbackCount()` for runtime monitoring.
    * **Reasoning:** We need visibility into fallback frequency during development and testing, but we must not spam the log for end users during normal gameplay.

_Quick side note:_

_The logging mentions "PAC failure" rather than HV `BadArgument`, and this is a shorthand I've added right now for myself.
Technically, we are catching HvResult.BadArgument. It's called "PAC failure" in logs because PAC is a frequent trigger, especially with games like TOTK that use heavy mods (AKA TOTK Optimiser)._

_Right now, I'm calling it a PAC failure, but will later rename to `BadArgument` as I progress further on a certain issue that's been sitting in our GitHub for some time._

_Back to the main stuff:_

* **GPU Synchronization & _Potential_ 0 FPS Mitigation (`HvExecutionContext.cs`) (PR FOLLOW UP IN THE FUTURE)**
    * Added adaptive `TryGpuSync()` called periodically in the main `Execute()` loop (every 10–12 execution steps).
    * Added strategic `Thread.Yield()` after `SvcAarch64` handling and in sync points.
    * Tuned counters based on real gameplay testing (% 10 for light sync, % 6 for stronger sync).
    * **Reasoning:** Hypervisor accelerates the guest CPU significantly. Without periodic yielding/flushing, MoltenVK's presentation queue can starve, causing permanent 0 FPS. The adaptive approach gives aims to fix this by performing a "resync". This has been tested and is showing positive progress, but, as mentioned earlier, will continue in a future PR.

These are all of the changes in this PR.

It's been quite a ride getting Hypervisor back up and running, but I loved every second of it.

If there is anything that is unclear in this PR, please let me know and I'll provide more details and update the description above.

Here is a small FAQ section to answer some more questions. These answers come from my own personal testing and behaviour that I observed.

### FAQ
**Q:** Why can some users play games with Hypervisor enabled and others can't on newer macOS versions?
**A:** This needs a bit of clarification.
You _can_ still run games with Hypervisor enabled on latest macOS - just not all games. Games such as Pokémon Legends: Z-A, The Legend of Zelda: Tears of the Kingdom, The Legend of Zelda: Breath of the Wild, can all _run_ and will _boot_ with Hypervisor enabled.

However, some other games, like Beyblade X Xone, Beyblade X Evobattle, Tales of Xillia Remastered, Tales of Berserk Remastered, etc, will _crash_ on boot with `BadArgument` error.

So it's not that Hypervisor works for one user and doesn't work for the other.

_It's that the game doesn't cause Hypervisor to crash._

Games differ in how aggressively they read registers early in boot and how much they rely on modded/JIT code. Games with heavy early supervisor calls and/or mods that manipulate pointers/PAC (pointer authentication errors) are far more likely to hit BadArgument.

(PAC is the reason why TOTK crashes with UltraCam when Hypervisor is disabled on macOS)

Lightweight or less modded titles often avoid the problematic code paths.
––––––
**Q:** How stable is this fix?
**A:** Updating macOS versions (Tahoe -> Tahoe) didn't break this implementation. Current data and testing all provide positive results (i.e. - no crash, hooray!)
––––––
**Q:** What about the `Denied` error?
**A:** This has more so to do with macOS permissions. Currently, Ryujinx is not being shipped in a DMG (which, by Apple conventions, is a more proper way of handling macOS app installations outside of the App Store). There is a theory that packaging Ryujinx into a DMG file will alleviate any permission issues that we currently have, and may help fix this type of error, so this PR may not even be needed (but we'll have to see how it goes).

That said, this error has not appeared in any of the more recent crash logs that I've analysed or acquired myself (from testing), leading to a possible hypothesis that the permission issues have been resolved, but the Hypervisor implementation broke on server builds due to an update to the framework. This is, however, unlikely, because, again, only builds after a particular version (i.e. - the last working version on a users system) broke.

However, DMG packaging is still on a table and something that we want to implement as soon as possible.
––––––
**Q:** So why do local builds run then?
**A:** A lot of different factors - build environment, folder location, lack of permissions associated with running .exe files, different ways of handling pure .exe files and .app bundles, notarisation, and more. There was no "scientific testing" done on each of the factors and seeing which one affects what exactly. Pushing this PR and implementing the DMG will tell.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/89
2026-06-25 20:48:05 +00:00
ranidspace
c2e0cf2fd5 Remove unused windowing library package (#126)
The `OpenTK.Windowing.GraphicsLibraryFramework` package is included in the Ryujinx csproj, but is never used in the project.

Checking the source code, all source files included in the package are part of the `OpenTK.Windowing.GraphicsLibraryFramework` namespace, which is never included in the Ryujinx source code. Rider also confirms that it's an unused reference.

The library includes the entirety of libglfw into the program, it clears about a half a megabyte. Not much, but it's free.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/126
2026-06-25 03:49:14 +00:00
Renovate Bot
6eb6b1fc81 Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.4 (#145)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.Graphics.Vulkan.MoltenVK](https://github.com/Kenji-NX/MoltenVK) | `1.4.2-ryujinx.3` → `1.4.2-ryujinx.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.3/1.4.2-ryujinx.4?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/MoltenVK (Ryujinx.Graphics.Vulkan.MoltenVK)</summary>

### [`v1.4.2-ryujinx.4`](https://github.com/Kenji-NX/MoltenVK/releases/tag/v1.4.2-ryujinx.4)

##### Fork changes

- Revert "MVKCommandBuffer: Optimize management of command storage"

##### Upstream changes

- Add gl\_DrawID / DrawIndex support for MacOS
- Draw ID fix-ups for serialization and formatting
- Improve handling of draw ID buffer binding
- Update SPIRV-Cross to latest revision
- Do not enable blending when unsupported by format

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNzguMCIsInVwZGF0ZWRJblZlciI6IjQzLjE3OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/145
2026-06-19 02:03:32 +00:00
sh0inx
0ce1bf0b33 revert c698a10b23
revert Ryubing TR fixes (#143)

I've noticed some issues with the Turkish translation of Ryujinx. So I fixed and added certain translations.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/143
2026-06-19 02:02:56 +00:00
pikminluvv
c698a10b23 Ryubing TR fixes (#143)
I've noticed some issues with the Turkish translation of Ryujinx. So I fixed and added certain translations.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/143
2026-06-17 17:09:27 +00:00
Max
6d67a86efd 443 -libarmeillure-macos (#142)
Fixes [#443](https://github.com/Ryubing/Issues/issues/433)

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/142
2026-06-15 23:20:44 +00:00
LotP
e777e3f93b fix-tests (#140)
- downgrade unicorn to last working version
- update to new cp reg struct system
- remove nonexistent register (used to silently continue)
- fix partial unmap tests
    - InitializeSignalHandler() was moved out of the translator (almost 2.5 years ago), but the test code was never updated to manually call the function as it was changed to do in the real cpu context, so the tests just started failing.
    - by manually initializing the handler we no longer cause tests to fail.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/140
2026-06-15 02:25:18 +00:00
KeatonTheBot
7b19e041cb Linux: Fix remaining file/folder picker issues (#24)
I fixed the remaining Linux file picker issues after testing on Steam Deck. User profile images, mod manager, title manager, and DLC directory were still using the old file picker methods and not the helper methods. I could only apply the helper method to user profiles, but I came up with a workaround for the others.

The reason for the draft PR: I'd ideally like to fix the other three at the helper level, so maybe @greem can help with that since since he wrote the initial implementation.

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/24
2026-06-15 01:44:24 +00:00
25 changed files with 903 additions and 192 deletions

View File

@@ -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" />
@@ -63,6 +62,6 @@
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" />
</ItemGroup>
</Project>

View File

@@ -47,12 +47,14 @@ def get_new_name(
input_component = str(input_dylib_path).replace(str(input_directory), "")[1:]
return Path(os.path.join(output_directory, input_component))
def get_archs(dylib_path: Path) -> list[str]:
res = subprocess.check_output([LIPO, "-info", str(dylib_path)]).decode("utf-8")
if res.startswith("Non-fat file"):
return [res.split(":")[-1].strip()]
else:
return res.split("are:")[-1].strip().split()
def is_fat_file(dylib_path: Path) -> str:
res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode(
"utf-8"
)
return not res.split("\n")[0].startswith("Non-fat file")
def construct_universal_dylib(
arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path
@@ -67,12 +69,11 @@ def construct_universal_dylib(
os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path
)
else:
arm64_archs = get_archs(arm64_input_dylib_path)
x86_64_archs = get_archs(x86_64_input_dylib_path) if x86_64_input_dylib_path.exists() else []
if "arm64" in arm64_archs and "x86_64" in arm64_archs:
shutil.copy2(arm64_input_dylib_path, output_dylib_path)
elif x86_64_archs:
if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists():
with open(output_dylib_path, "wb") as dst:
with open(arm64_input_dylib_path, "rb") as src:
dst.write(src.read())
else:
subprocess.check_call(
[
LIPO,

View File

@@ -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() { }
}
}

View File

@@ -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)

View File

@@ -53,7 +53,7 @@ namespace Ryujinx.Cpu.Signal
public SignalHandlerRangeArray Ranges;
}
static class NativeSignalHandler
public static class NativeSignalHandler
{
private static readonly nint _handlerConfig;
private static nint _signalHandlerPtr;

View File

@@ -16,15 +16,15 @@ namespace Ryujinx.Graphics.Vulkan
{
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
bool isMoltenVk = gd.IsMoltenVk;
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
ResourceDescriptorCollection rdc = setDescriptors[setIndex];
ResourceStages activeStages = ResourceStages.None;
if (isMoltenVk)
{
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
@@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Vulkan
// causes invalid resource errors, allow the binding on all active stages as workaround.
// https://github.com/KhronosGroup/MoltenVK/issues/1870
stages = activeStages;
}
}
layoutBindings[descIndex] = new DescriptorSetLayoutBinding
{

View File

@@ -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" />

View File

@@ -439,7 +439,7 @@ namespace Ryujinx.Graphics.Vulkan
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk,
supportsPushDescriptors,
IsMoltenVk ? 16 : propertiesPushDescriptor.MaxPushDescriptors, // In case an old version of MoltenVK is used, apply a limit to prevent vertex explosions.
IsMoltenVk ? 16 : propertiesPushDescriptor.MaxPushDescriptors, // Prevents vertex explosions on MoltenVK.
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
supportsTransformFeedback,

View File

@@ -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;
}

View File

@@ -1,10 +1,23 @@
using System;
using System.Runtime.InteropServices;
using UnicornEngine.Const;
namespace Ryujinx.Tests.Unicorn
{
public class UnicornAArch32 : IDisposable
{
struct UcArmCpReg
{
public uint Cp;
public uint Is64;
public uint Sec;
public uint CRn;
public uint CRm;
public uint Opc1;
public uint Opc2;
public uint Val;
}
internal readonly UnicornEngine.Unicorn Uc;
private bool _isDisposed;
@@ -38,7 +51,7 @@ namespace Ryujinx.Tests.Unicorn
public int Fpscr
{
get => (int)GetRegister(Arm.UC_ARM_REG_FPSCR) | ((int)GetRegister(Arm.UC_ARM_REG_FPSCR_NZCV));
get => (int)GetRegister(Arm.UC_ARM_REG_FPSCR);
set => SetRegister(Arm.UC_ARM_REG_FPSCR, (uint)value);
}
@@ -85,8 +98,22 @@ namespace Ryujinx.Tests.Unicorn
public UnicornAArch32()
{
Uc = new UnicornEngine.Unicorn(Common.UC_ARCH_ARM, Common.UC_MODE_LITTLE_ENDIAN);
SetRegister(Arm.UC_ARM_REG_C1_C0_2, GetRegister(Arm.UC_ARM_REG_C1_C0_2) | 0xf00000);
UcArmCpReg reg = new()
{
Cp = 15,
Is64 = 0,
Sec = 0,
CRn = 13,
Opc1 = 0,
CRm = 0,
Opc2 = 2
};
GetRegister(Arm.UC_ARM_REG_CP_REG, ref reg);
reg.Val |= 0xf00000;
SetRegister(Arm.UC_ARM_REG_CP_REG, reg);
SetRegister(Arm.UC_ARM_REG_FPEXC, 0x40000000);
}
@@ -204,6 +231,17 @@ namespace Ryujinx.Tests.Unicorn
SetVector(Arm.UC_ARM_REG_D0 + index * 2, value);
}
public void GetRegister<T>(int register, ref T obj) where T : unmanaged
{
Span<T> span = new(ref obj);
Span<byte> dataSpan = MemoryMarshal.Cast<T, byte>(span);
byte[] data = dataSpan.ToArray();
Uc.RegRead(register, data);
data.AsSpan().CopyTo(dataSpan);
}
public uint GetRegister(int register)
{
byte[] data = new byte[4];
@@ -213,6 +251,13 @@ namespace Ryujinx.Tests.Unicorn
return BitConverter.ToUInt32(data, 0);
}
public void SetRegister<T>(int register, T obj) where T : unmanaged
{
byte[] data = MemoryMarshal.Cast<T, byte>(new Span<T>(ref obj)).ToArray();
Uc.RegWrite(register, data);
}
public void SetRegister(int register, uint value)
{
byte[] data = BitConverter.GetBytes(value);

View File

@@ -7,6 +7,7 @@ using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory.PartialUnmaps;
using Ryujinx.Cpu;
using Ryujinx.Cpu.Jit;
using Ryujinx.Cpu.Signal;
using Ryujinx.Memory;
using Ryujinx.Memory.Tracking;
using System;
@@ -60,6 +61,8 @@ namespace Ryujinx.Tests.Memory
new JitMemoryAllocator(),
new MockMemoryManager(),
AddressTable<ulong>.CreateForArm(true, MemoryManagerType.SoftwarePageTable));
NativeSignalHandler.InitializeSignalHandler();
}
[Test]

View 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"
}
}
}

View 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": []
}
}

View 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; }
}
}
}

View File

@@ -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();

View File

@@ -28,14 +28,14 @@
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
See:
https://github.com/amwx/FluentAvalonia/issues/481
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
-->
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
@@ -73,7 +73,7 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" />
@@ -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" />

View File

@@ -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 "";
}
}
}

View File

@@ -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}";

View File

@@ -13,6 +13,7 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Ryujinx.Ava.Utilities.StorageProviderExtensions;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -128,7 +129,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void Add()
{
IReadOnlyList<IStorageFile> result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
IReadOnlyList<IStorageFile> result = await CoreDumpable(() => _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true,
@@ -141,7 +142,7 @@ namespace Ryujinx.Ava.UI.ViewModels
MimeTypes = ["application/x-nx-nsp"],
},
},
});
}));
int totalDlcAdded = 0;
foreach (IStorageFile file in result)

View File

@@ -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:

View File

@@ -17,6 +17,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using static Ryujinx.Ava.Utilities.StorageProviderExtensions;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -288,11 +289,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void Add()
{
IReadOnlyList<IStorageFolder> result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
IReadOnlyList<IStorageFolder> result = await CoreDumpable(() => _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.SelectModDialogTitle],
AllowMultiple = true,
});
}));
foreach (IStorageFolder folder in result)
{

View File

@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Ryujinx.Ava.Utilities.StorageProviderExtensions;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -148,7 +149,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task Add()
{
IReadOnlyList<IStorageFile> result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
IReadOnlyList<IStorageFile> result = await CoreDumpable(() => _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = true,
FileTypeFilter = new List<FilePickerFileType>
@@ -160,7 +161,7 @@ namespace Ryujinx.Ava.UI.ViewModels
MimeTypes = ["application/x-nx-nsp"],
},
},
});
}));
int totalUpdatesAdded = 0;
foreach (IStorageFile file in result)

View File

@@ -4,10 +4,12 @@ using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using Ryujinx.HLE.FileSystem;
using SkiaSharp;
using System.Collections.Generic;
@@ -62,7 +64,7 @@ namespace Ryujinx.Ava.UI.Views.User
private async void Import_OnClick(object sender, RoutedEventArgs e)
{
IReadOnlyList<IStorageFile> result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
Optional<IStorageFile> result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
@@ -76,9 +78,9 @@ namespace Ryujinx.Ava.UI.Views.User
},
});
if (result.Count > 0)
if (result.HasValue)
{
_profile.Image = ProcessProfileImage(File.ReadAllBytes(result[0].Path.LocalPath));
_profile.Image = ProcessProfileImage(File.ReadAllBytes(result.Value.Path.LocalPath));
_parent.GoBack();
}
}

View File

@@ -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>

View File

@@ -29,7 +29,7 @@ namespace Ryujinx.Ava.Utilities
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
}
private static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
public static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
{
OsUtils.SetCoreDumpable(true);
try