Author SHA1 Message Date
KeatonTheBot f904c82d76 Revert "use UnmanagedCallersOnly for delegates"
This reverts commit 7bf6c3f016.
2026-08-29 18:08:19 -05:00
KeatonTheBot 48b11b71fa RdXmlFile: Add, sort assemblies 2026-08-29 18:08:19 -05:00
KeatonTheBot 53bde335d9 Add solution configurations 2026-08-29 18:08:19 -05:00
KeatonTheBot ec0ec3a86a Tweak AOT MSBuild properties in Ryujinx.csproj 2026-08-29 18:08:19 -05:00
KeatonTheBot 02d0b480e3 Add Silk.NET.Vulkan.Extensions.EXT.ExtAttachmentFeedbackLoopDynamicState to rd.xml 2026-08-29 18:08:19 -05:00
Emmanuel Hansen 07de13a47b Add native AOT support
- Add native AOT solution config

- Simplify assembly access in application library class

- Make GetFunctionPointerForDelegate as explicit as possible
2026-08-29 18:08:19 -05:00
KeatonTheBot fc4c45ef1d UI: Fix XCI file trimmer selection crash 2026-08-29 17:33:11 -05:00
Neo 52c6a1890e Improve "Add raw copy dependencies for incompatible textures"
Initial PR was made by Avan for Ryubing.

This allows for Trails in the Sky 1st Chapter (and by extension Trails through Daybreak, Trails through Daybreak II, Trails Beyond the Horizon, and the future Trails in the Sky 2nd Chapter – tested via demo – to be fully playable.

Initial PR caused issues on macOS devices, specifically crashing in certain games (such as Mario Kart 8 Deluxe and the aforementioned Trails games). It was reverted on the original Ryubing project (at the time of writing this description) for causing small rendering issues on The Legend of Zelda: Breath of the Wild.

The rendering issues do not appear on macOS.

This commit improves the original PR by eliminating said macOS crash.

Trails in the Sky 1st writes its exposure/brightness value through an R32G32Float 1x1 texture and later reads the raw bits back through an R32Uint 2x1 texture mapped to the same guest memory. The two texture formats/dimensions are fully incompatible as texture views, so they end
up as separate host textures, and nothing kept their contents coherent - the reader saw stale zeroed data, making the exposure calculation (and the rendered image) too dark. (This is the initial fix for the game)

TextureGroup already had a raw-copy dependency mechanism intended to
handle this class of alias (TextureDependency, TextureGroupHandle
raw-copy plumbing), gated by CanCreateRawCopyDependency's strict same-guest-memory checks. This commit fixes three bugs that kept that mechanism from working correctly (and thus crashing Trails and other games, such as Mario Kart 8 Deluxe):

(1) TextureGroup.InitializeOverlaps() and TextureGroup.RegisterIncompatibleOverlap() only forwarded overlaps to CreateCopyDependency() when compatibility was LayoutIncompatible or better, silently excluding the fully Incompatible case the raw-copy path exists for. Widened both guards to let Incompatible overlaps through so CanCreateRawCopyDependency actually gets a chance to run.

(2) Once Incompatible overlaps were allowed through, CreateCopyDependency(TextureGroup, ...) could still fall back to a regular, non-raw textureCopy for such pairs, since ViewLayoutCompatible/CopySizeMatches only check byte size and were never meant to reason about fully incompatible pairs (e.g. a depth format aliasing a color format with the same byte size). On Vulkan running through MoltenVK on macOS, that non-raw copy path requires a pixel-format-reinterpreting texture view, and MoltenVK/Metal refuses to create any view onto a depth-format texture ("not castable"), aborting emulation. textureCopy is now forced false for Incompatible pairs, leaving raw copy (which already excludes depth/stencil formats) as the only route for that severity level.

(3) TextureGroupHandle.Inherit() copied a handle's pending DeferredCopy to the new handle when a view was recreated, but did not copy DeferredCopyRaw alongside it. A handle that inherited a pending raw copy would then execute it through the regular (non-raw) CopyTo path once acknowledged, reinterpreting the source bytes with the wrong row layout and corrupting the image - visible as vertical flickering stripes whenever a view happened to be recreated with a raw copy still pending. DeferredCopyRaw is now carried over together with DeferredCopy.

(As mentioned earlier, vertical stripes aren't present on macOS, so (3) doesn't negatively have an effect on macOS in any way).
2026-08-29 10:00:58 -05:00
MaxandMythrax 2288084b53 HLE: Implemented ILockAccessor and ICommonStateGetter commands and stubs for Virtual Boy – Nintendo Classics #3
- Moves emulation past the initial frame by providing the necessary inputs the game expects. Apparently games like to spin until they get what they want.

Co-authored-by: Mythrax <mythrax@mythrax-rs.org>
2026-08-29 09:49:39 -05:00
Babib3l dd519a32b6 Hotfix for the PTC version, it's adjacent comment and PTC writer logging 2026-08-29 09:49:39 -05:00
Babib3l 7be8829b0a Wire "Start games in fullscreen" option to use the new fullscreen behaviour
Final (hopefully) fix for https://github.com/Ryubing/Issues/issues/415
2026-08-25 14:51:02 -05:00
Renovate Bot 6225e142fc Update Svg.Controls to 12.0.0.15 (#22)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Svg.Controls.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) | `12.0.0.13` → `12.0.0.15` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Svg.Controls.Avalonia/12.0.0.15?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Svg.Controls.Avalonia/12.0.0.13/12.0.0.15?slim=true) |
| [Svg.Controls.Skia.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) | `12.0.0.13` → `12.0.0.15` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Svg.Controls.Skia.Avalonia/12.0.0.15?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Svg.Controls.Skia.Avalonia/12.0.0.13/12.0.0.15?slim=true) |

---

### 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 these updates 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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/22
2026-08-25 04:17:37 +00:00
Renovate Bot 9e3c03a606 Update dependency FluentAvaloniaUI to 3.1.0 (#21)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [FluentAvaloniaUI](https://github.com/amwx/FluentAvalonia) | `3.0.2` → `3.1.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/FluentAvaloniaUI/3.1.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/FluentAvaloniaUI/3.0.2/3.1.0?slim=true) |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/21
2026-08-25 02:46:07 +00:00
KeatonTheBot 8e9bcb2a8e Update System.IO.Hashing to 10.0.11 2026-08-24 15:32:05 -05:00
Babib3l 7101f52c01 River 2 : HLE: Use per-program ownership for PTC disk caches
This PR threads process/program identity into PTC disk cache initialization so cache ownership is selected from the launched process rather than global/shared application state.

Previously, the PTC initialization path only propagated loose title/version information into the CPU layer. That was mostly fine for a single launched application, but will introduce many problems once multiple programs can be launched during the same session, given later processes could inherit cache identity from the first loaded application.
To address this issue, this PR introduces a `PtcCacheInfo` payload and applies it through the process context, CPU context, translator, and PTC initialization paths. Cache ownership is now resolved once the kernel process PID is known and includes:

- PID
- Program ID / Title ID
- Application ID
- Program index
- Display version
- Process kind
- Cache selector

`PtcCacheInfo` now owns its default title/application/version values, and `Ptc` uses that cache info directly instead of mirroring title/version fields internally.

The persistent cache key itself does remains title/version/selector based rather than PID based, so caches remain reusable across launches while still being selected from the correct process context. PID is only used for diagnostics and ownership tracing in logs.

Additional PTC logging has also been added to report cache ownership and selected paths during PTC initialization, Profiling info load/save and translation cache load/save

Both the PTC and profiler internal versions were bumped (a bunch of times lol).
2026-08-24 13:45:08 -05:00
avan e80fc00462 Fix view compatibility for compressed textures with different logical sizes
It has been confirmed that Divinity: Original Sin 2 may describe the same or overlapping guest GPU memory using BCn-compressed textures with different logical dimensions, such as 104×104 and 102×102.

BCn formats store texture data in blocks, with each BC block covering 4×4 texels. After rounding the dimensions up to complete blocks, both 104×104 and 102×102 textures require a 26×26 grid of BC blocks. As a result, they have the same block footprint at the base mip level. Ryujinx originally determined size compatibility in TextureCompatibility.ViewSizeMatches() primarily from this block footprint, which could cause these textures to be classified as Full view-compatible and allowed to share the same Vulkan VkImage directly.

However, because the two textures have different logical dimensions, their mip chains are also different. For example, at mip level 3, a 104×104 texture is reduced to 13×13, while a 102×102 texture is reduced to 12×12. If a 104×104 child texture view shares the backing image of a 102×102 parent texture, a subsequent full mip upload may attempt to write 13×13 compressed data into a Vulkan image subresource whose actual dimensions are only 12×12.

This produces a compressed buffer-to-image copy that does not match the geometry of the backing image. After the invalid command is submitted to the GPU, the Vulkan driver may asynchronously report VK_ERROR_DEVICE_LOST during command buffer submission, waiting, or presentation. In Ryujinx, this eventually appears as: VulkanException: Unexpected API error "ErrorDeviceLost".

The fix adds a logical-dimension check to TextureCompatibility.ViewSizeMatches(). If either the parent or child uses a compressed texture format, and the actual width or height of the corresponding parent mip does not match the logical width or height of the child, the relationship is no longer classified as Full view-compatible. Instead, it is downgraded to CopyOnly.

CopyOnly indicates that the two textures may still describe the same or overlapping guest memory and that their contents must remain synchronized, but they cannot directly share a single fixed-size Vulkan image. Ryujinx instead allows each texture to retain a host texture matching its own logical dimensions and synchronizes their contents through the existing copy-dependency mechanism.

As a result, the 13×13 mip of the 104×104 texture is uploaded to an actual 13×13 destination mip, while the 12×12 mip of the 102×102 texture is uploaded to an actual 12×12 destination mip.
2026-08-22 18:51:41 -05:00
avan 33cbd29c23 Fix bindless elimination failures observed in OCTOPATH TRAVELER 0:
Failed to find handle source for bindless access of type "textureBuffer".
2026-08-22 18:51:41 -05:00
avan cfc7c6039f Fix OpenGL program relaunch
The Enhanced and Classic versions of FFT are different program indices within the same application. When the Classic version is launched, the game uses ExecuteProgram to stop the current program, trigger DisposeGpu, create a new renderer and OpenGL context, and then launch the requested program.

Previously, when AppHost executed DisposeGpu, it attempted to bind the old OpenGL context and then called Device.DisposeGpu to destroy GPU resources. During the program relaunch flow, however, the old RendererHost may already have been removed from the visual tree, causing its native window to become detached.

If the old OpenGL context can no longer be bound, a ContextException is thrown, preventing the subsequent AppExit and program relaunch flow from continuing.

The updated implementation handles this case by detecting a ContextException while binding the OpenGL context when ShouldRestart is true. It then skips the destruction of GPU resources that depend on the old OpenGL context, allowing the old context to be released together with the old window. The AppExit and program relaunch flow can then continue without being interrupted.
2026-08-22 18:51:41 -05:00
avan ac6db0fe76 Fix Vulkan/OpenGL attachmentless rendering
Some fragment passes used by FFT do not have any color or depth/stencil attachments. Instead, the fragment shader writes the results directly to storage images (on OpenGL: through imageStore).

### Vulkan
When SetImage is called and FramebufferParams has no attachments, its virtual size must be updated using the width, height, and layer count of the fragment storage image. Otherwise, the attachmentless framebuffer may retain an incorrect 1x1 extent, causing backgrounds, logos, UI elements, and other rendered content to be missing.

If the first draw occurs before the storage-image descriptor is rebound, SetImage cannot yet provide the correct storage-image dimensions. Therefore, when RecreateGraphicsPipelineIfNeeded finds that FramebufferParams has no attachments and still uses the default 1x1 extent, it initializes the framebuffer dimensions from the active viewport. Otherwise, the first Vulkan draw may be restricted to a 1x1 area and render incorrectly.

The storage-image extent is therefore used as the authoritative size for FramebufferParams, while the active viewport is used as a fallback when the storage-image extent is not yet available before the first draw.

### OpenGL
A storage image is not a framebuffer attachment, so OpenGL cannot derive the framebuffer width and height from it. When the framebuffer has no attachments but a viewport with valid dimensions has already been defined, Pipeline.PreDraw must set the default framebuffer width and height from the active viewport.

Without non-zero default width and height values, the attachmentless framebuffer remains incomplete. Drawing with that framebuffer results in InvalidFramebufferOperation, so rasterization and fragment shader execution do not occur even when the storage images are bound correctly.
2026-08-22 18:51:41 -05:00
KeatonTheBot bed294bb92 UI: Fix Restart Emulation menu item not being clickable 2026-08-21 12:31:39 -05:00
Babib3l ed70f7921e feature: Add GpuDriverVersion to GPU info logging
- The Vulkan backend now reports the driver name and version using VK_KHR_driver_properties when available, with fallbacks for raw Vulkan driverVersion parsing. Intel Windows drivers also get the full package-style version from the driver UUID when exposed by the driver.
  OpenGL now carries its existing OpenGL version string through HardwareInfo.GpuDriverVersion.
2026-08-21 12:05:42 -05:00
LotPandLotP1 1a1fe53535 Fix a crash when no controller is connected
Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-08-17 14:03:27 -05:00
KeatonTheBot 47e55acf21 UI: Update Avalonia to v12.1
- Avalonia to 12.1.1

- FluentAvalonia to 3.0.2

- Move DevTools Ctrl+F12 keybind to app initialization
2026-08-16 16:37:27 -05:00
avan 0e5fead0f9 Reject texture descriptors with unmapped addresses
Fix an "Invalid texture format 0x25A5A (sRGB: True)" error that occurs when running OCTOPATH TRAVELER 0.

The game may leave texture descriptor heap entries filled with 0x5A. Interpreting such uninitialized entries as valid texture descriptors produces an invalid format and causes texture creation to proceed with garbage descriptor data.

Validate the texture address before decoding the descriptor. Descriptors with a zero or unmapped address are marked as invalid and skipped. The invalid state is cleared when the corresponding texture pool entry is modified, allowing the descriptor to be evaluated again.
2026-08-10 00:05:44 -05:00
KeatonTheBotand_Neo_ d1fb89f746 UI: Fix sort timing
- Now sorts instantly when an option is selected

Co-authored-by: _Neo_ <ursamajorjanus2819@gmail.com>
2026-08-10 00:00:49 -05:00
KeatonTheBot cc1861f9bb UI: Fix Amiibo tag URL 2026-08-09 23:25:42 -05:00
avan 733bd0951b Fix shadow state handling for blend enable updates
Incorrect rendering was observed during loading transitions in Trails in the Sky 1st Chapter. Testing showed that routing the first render target's RT0 BlendEnable transition from disabled to enabled through the normal register write path prevented the issue.

Further investigation found that the normal register write was restoring Shadow RAM behavior that was missing from UpdateBlendEnable.

UpdateBlendEnable uses a fast bulk update path instead of issuing a normal register write for each render target. As a result, it bypasses the Shadow RAM handling provided by DeviceStateWithShadow.WriteWithRedundancyCheck.

A normal register write updates both State and ShadowState in MethodTrack and MethodTrackWithFilter modes. In MethodReplay mode, it ignores the incoming value and restores the value previously stored in ShadowState to State.

UpdateBlendEnable must reproduce the same behavior while retaining its bulk comparison and copy path. In track modes, copy the incoming enable values to shadowState. In replay mode, copy shadowState to the incoming enable span, then let the existing comparison and copy logic update state and mark BlendState dirty when necessary.

The original implementation incorrectly used state for both track and replay handling. This prevented Shadow RAM from correctly recording or replaying BlendEnable values, which could cause rendering state synchronization errors.

Correct the bulk update path to use ShadowState for tracking and replay. This preserves the optimized bulk operation while addressing the underlying issue without requiring an RT0-specific compatibility workaround.
2026-08-09 15:17:12 -05:00
mtmasantana 087e711e44 macOS: Fix NaN texture coordinates resolving toward max on Apple GPUs
- Fixes Animal Crossing: New Horizons backdrops being white instead of black

Patched applied from: https://github.com/Ryubing/Issues/issues/476
2026-08-05 14:04:43 -05:00
avan 9c46452388 Add raw copy dependencies for incompatible textures
Trails in the Sky 1st uses texture descriptors with different formats and dimensions that map to the same region of guest GPU memory. For example, the game interprets the same underlying data as both an R32Uint 2x1 texture and an R32G32Float 1x1 texture. Both textures have an 8-byte logical payload, but their texel formats and dimensions are different.

Ryubing cannot represent these two descriptors as normally compatible texture views, so it creates separate host texture objects for them. Since these host textures represent the same guest memory, a modification made to one of them should become visible to the other before the other texture is read. However, the existing synchronization mechanism does not fully handle this situation.

The game uses this pattern for its exposure or brightness history. A compute shader first writes the updated exposure data to the R32G32Float 1x1 host texture. A later operation then reads the raw bits of the same data through the R32Uint 2x1 host texture. The original Ryubing implementation did not synchronize the contents of these two separate host textures. As a result, the reading texture received stale zero values, which caused the shader to calculate an incorrectly low exposure value and made the rendered image appear too dark.

TextureGroup already has a guest-memory synchronization mechanism for incompatible overlaps. When _flushIncompatibleOverlaps is enabled, the most recent texture contents are written back to guest memory, after which the other texture reloads the data. However, this mechanism was primarily enabled through IsFormatHostIncompatible, which checks whether an individual texture format can be correctly supported by the host GPU.

In this case, both R32Uint and R32G32Float are individually supported by the host GPU. Therefore, IsFormatHostIncompatible does not enable this synchronization path. The issue is not that either format is unsupported. The issue is that two individually supported aliases, represented by separate host textures, do not maintain coherent contents.

In theory, the latest contents could be flushed to guest memory immediately after every GPU write to a host texture, allowing other textures that map to the same guest memory to reload the updated data. However, doing so could frequently trigger expensive operations such as GPU waits, data readbacks, guest texture layout conversions, and memory-tracking updates. This would be particularly costly for textures that are updated every frame or during every compute dispatch.

For this reason, the fix does not require an immediate flush after every write. Instead, it extends Ryubing's existing TextureDependency and TextureGroupHandle mechanisms. When two fully incompatible textures map to exactly the same guest memory and satisfy a strict set of raw-copy requirements, the program creates a raw-copy dependency between them.

When the source texture is modified, the target texture is only marked as requiring a raw copy. The latest logical raw bytes are copied from the source only when the target texture is actually about to be used. This keeps the contents of the separate host texture aliases coherent while avoiding unnecessary round trips through guest memory.
2026-08-04 23:17:36 -05:00
KeatonTheBot 0657b00622 SDL: Update game controller database on launch
- Downloads updated gamecontrollerdb.txt file on launch, only updates when a new file is available. This keeps the database updated between SDL releases.
2026-07-30 15:51:32 -05:00
KeatonTheBot 87cf0b0cbb Update System.IO.Hashing to 10.0.10 2026-07-27 21:55:14 -05:00
Renovate Bot 954332aa2a Update dependency Microsoft.NET.Test.Sdk to 18.8.1 (#20)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.7.0` → `18.8.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.8.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.7.0/18.8.1?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.8.1`](https://github.com/microsoft/vstest/releases/tag/v18.8.1)

#### What's Changed

- Fix protocol negotiation timeout when STJ reflection is disabled (18.8.1) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16281](https://github.com/microsoft/vstest/pull/16281)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.8.0...v18.8.1>

### [`v18.8.0`](https://github.com/microsoft/vstest/releases/tag/v18.8.0)

#### What's Changed

- Migrate from Newtonsoft.Json to System.Text.Json / Jsonite (merge to main) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15687](https://github.com/microsoft/vstest/pull/15687)
  - For more detail refer to <https://devblogs.microsoft.com/dotnet/vs-test-is-removing-its-newtonsoft-json-dependency/>
- Create source-only filter package by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15638](https://github.com/microsoft/vstest/pull/15638)
- Add ARM64 msdia140.dll support to test platform packages by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15692](https://github.com/microsoft/vstest/pull/15692)
- Fix mutex cleanup crash on macOS/Linux by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15684](https://github.com/microsoft/vstest/pull/15684)
- Restrict artifact temp directory permissions on Unix by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15729](https://github.com/microsoft/vstest/pull/15729)
- Add support for filtering uncategorized tests with TestCategory=None by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15727](https://github.com/microsoft/vstest/pull/15727)
- Fix SCI binding failure in DTA hosts (main) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15724](https://github.com/microsoft/vstest/pull/15724)
- Fix HTML logger parallel file collision by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15435](https://github.com/microsoft/vstest/pull/15435)
- Improve error message when testhost cannot be found by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16053](https://github.com/microsoft/vstest/pull/16053)
- Fix HTML logger exception on invalid XML chars in test display names by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;16051](https://github.com/microsoft/vstest/pull/16051)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.7.0...v18.8.0>

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/20
2026-07-28 02:43:28 +00:00
Renovate Bot e650d3fb04 Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.6 (#18)
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.5` → `1.4.2-ryujinx.6` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.5/1.4.2-ryujinx.6?slim=true) |

---

### Release Notes

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

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

##### Fork changes

- N/A

##### Upstream changes ([Changelog](https://github.com/KhronosGroup/MoltenVK/compare/67a91f3...db66022))

- Fix invalidation of cached Metal textures
- Add native sampler min/max filtering
- Move sampler reduction capability with format caps
- Move sampler reduction conversion to datatypes
- Raise minimum target to macOS 12.0 / iOS 15.0 / tvOS 15.0
- Do not enable subgroup emulation for reduction
- Fix descriptor pool alignment

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/18
2026-07-28 02:38:09 +00:00
Renovate Bot d578c5e2a9 Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.21.0 (#19)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) | `8.19.1` → `8.21.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.IdentityModel.JsonWebTokens/8.21.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.IdentityModel.JsonWebTokens/8.19.1/8.21.0?slim=true) |

---

### Release Notes

<details>
<summary>AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet (Microsoft.IdentityModel.JsonWebTokens)</summary>

### [`v8.21.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases/tag/8.21.0)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.20.0...8.21.0)

#### Performance Improvements

- Add JsonWebToken header-replacement path to avoid re-parsing payload. See [PR #&#8203;3553](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3553).
- Add claims dictionary preallocation. See [PR #&#8203;3550](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3550).

#### Bug Fixes

- Fix CI build failures: remove unused log message and add missing DPoP API entries. See [PR #&#8203;3551](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3551).

### [`v8.20.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.2...8.20.0)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.2...8.20.0)

### [`v8.19.2`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases/tag/8.19.2)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.1...8.19.2)

#### What's Changed

- Improve serializer and JWE key unwrap handling by [@&#8203;iNinja](https://github.com/iNinja) in [#&#8203;3544](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3544)

**Full Changelog**: <https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.1...8.19.2>

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/19
2026-07-28 02:37:01 +00:00
LotPandLotP1 521a18f5a7 mono-jit-v2
second attempt at single layer address tables (first attempt https://git.ryujinx.app/projects/Ryubing/pulls/167).
should work with all games now.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-17 16:47:59 -05:00
LotPandLotP1 4b255a98db Revert "mono-jit"
This reverts the mono jit PR.
It had several issues that I haven't had time to fix yet.
Expect mono jit v2 in the future with said fixes.

JitCache alignment fixes are still included/fixed.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-14 15:43:51 -05:00
Renovate Bot 8b34270a22 Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.5 (#17)
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.4` → `1.4.2-ryujinx.5` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Graphics.Vulkan.MoltenVK/1.4.2-ryujinx.4/1.4.2-ryujinx.5?slim=true) |

---

### Release Notes

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

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

##### Fork changes

- CI changes
  - Build with macOS 26
  - Remove `all` build
  - Add iOS Private API build

##### Upstream changes ([Changelog](https://github.com/KhronosGroup/MoltenVK/compare/29a0606...4b715bd))

- Fix incorrect placement heap check in MVKImagePlane::getMTLTexture()
- Fix AMD subgroup size to 32 for correct Metal simdgroup operations
- Fix SPIRV-Cross: symlink instead of submodule
- Fix regressions and crashes on Mac 1 (NVIDIA/Intel) GPUs
  - Work around combined sampler arg buffers on NVIDIA Mac1
  - `nativeTextureSwizzle = false` for NVIDIA Mac1
  - Restore shader-side image view swizzle fallback.
  - Avoid input attachment argument buffers on NVIDIA Mac1
  - Disable discard store checks on NVIDIA Mac1
  - Disable image descriptor argument buffers for shader swizzle
  - Advertise native texture swizzle only on supported GPU families
  - Simplify NVIDIA workaround checks
  - Disable discarded-fragment store checks on Mac1 GPUs
  - Avoid fragile Mac1 argument-buffer descriptor cases
- Do not rely on extension enable for vkGetPhysicalDeviceToolPropertiesEXT proc addr
- Emulate reversed-depth viewports on affected AMD Mac GPUs
  - Refine legacy AMD Mac2 workaround handling
  - Scope discarded-fragment store checks to Apple GPUs
- Disable non-working quad control flow
- Disable 64-bit float control properties

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/17
2026-07-13 23:33:38 +00:00
KeatonTheBot 9580f20caa Update System.IO.Hashing to 10.0.9 2026-07-12 00:26:41 -05:00
Renovate Bot 152460929d Update dependency Microsoft.NET.Test.Sdk to 18.7.0 (#15)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.6.0` → `18.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.6.0/18.7.0?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.7.0`](https://github.com/microsoft/vstest/releases/tag/v18.7.0)

#### What's Changed

- Add ARM64 msdia140.dll support to test platform packages by [@&#8203;jamesmcroft](https://github.com/jamesmcroft) in [#&#8203;15689](https://github.com/microsoft/vstest/pull/15689)
- Update System.Memory from 4.5.5 to 4.6.3 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15706](https://github.com/microsoft/vstest/pull/15706)

#### New Contributors

- [@&#8203;jamesmcroft](https://github.com/jamesmcroft) made their first contribution in [#&#8203;15689](https://github.com/microsoft/vstest/pull/15689)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.6.0...v18.7.0>

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/15
2026-07-10 22:20:04 +00:00
Renovate Bot 7a5554a17f Update dependency DynamicData to 9.4.33 (#14)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [DynamicData](https://github.com/reactiveui/DynamicData) | `9.4.31` → `9.4.33` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/DynamicData/9.4.33?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/DynamicData/9.4.31/9.4.33?slim=true) |

---

### Release Notes

<details>
<summary>reactiveui/DynamicData (DynamicData)</summary>

### [`v9.4.33`](https://github.com/reactivemarbles/DynamicData/releases/tag/9.4.33)

#### Fixes:

- [`9bc6b44`](https://github.com/reactiveui/DynamicData/commit/9bc6b443f4864a23977ddce1310edafc3f5449c3) Fix race in ExpireAfter when item is removed or updated before expiration fires ([#&#8203;1076](https://github.com/reactiveui/DynamicData/issues/1076)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`7b861d0`](https://github.com/reactiveui/DynamicData/commit/7b861d0e12d8ad02d288157c5dd10b0be5f78d6b) Fixed that the cache-land dynamic `.Filter()` operator relied on limited mutation of an internal `Dictionary<,>` during enumeration, a behavior that is not supported on target frameworks older than .NET Core 3.0. Additional logic to copy the keys to iterate, before mutation, should now be injected for builds targeting such older frameworks. ([#&#8203;1084](https://github.com/reactiveui/DynamicData/issues/1084)) [@&#8203;JakenVeina](https://github.com/JakenVeina)
- [`b93a6b9`](https://github.com/reactiveui/DynamicData/commit/b93a6b99333db814b9759b475a95d8a7ecec1c24) Fix docs and exception messages for Change ([#&#8203;1085](https://github.com/reactiveui/DynamicData/issues/1085)) [@&#8203;xackus](https://github.com/xackus)
- [`cc8b91e`](https://github.com/reactiveui/DynamicData/commit/cc8b91ea362097cd26339c3727da77e533e7802c) Fixed that the Build and Release action defined in release.yml was attempting to publish a bogus "empty.nupkg" file that exists in the normal build output of the DynamicData.Tests project, as a part of the EmptyFiles package, since the job was written to search for "\*.nupkg". ([#&#8203;1124](https://github.com/reactiveui/DynamicData/issues/1124)) [@&#8203;JakenVeina](https://github.com/JakenVeina)

#### Other:

- [`bf45445`](https://github.com/reactiveui/DynamicData/commit/bf45445e5e3ad68c8d421fce192653189799740f) Add AI Instruction Files for GitHub Copilot ([#&#8203;1078](https://github.com/reactiveui/DynamicData/issues/1078)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`12c1947`](https://github.com/reactiveui/DynamicData/commit/12c1947cd19e0149318827a42b6615c2313b6227) Overhaul XML documentation for ObservableCacheEx extension methods ([#&#8203;1080](https://github.com/reactiveui/DynamicData/issues/1080)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`b0d4c70`](https://github.com/reactiveui/DynamicData/commit/b0d4c701919e8fb9dabd291096fea2ec5f372fde) Update SwappableLock to support NET9+ Lock type ([#&#8203;1077](https://github.com/reactiveui/DynamicData/issues/1077)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`b966943`](https://github.com/reactiveui/DynamicData/commit/b966943fe78e51b88f2b426546f82610395d4ad5) Add DynamicAccessedMembers attributes to BindingListEx methods ([#&#8203;1087](https://github.com/reactiveui/DynamicData/issues/1087)) [@&#8203;daniel-lerch](https://github.com/daniel-lerch)
- [`28adb4f`](https://github.com/reactiveui/DynamicData/commit/28adb4fe1aef34ffa7d53f888af7a8f5bdefa690) Overhaul XML documentation for ObservableListEx extension methods ([#&#8203;1081](https://github.com/reactiveui/DynamicData/issues/1081)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`335ecbc`](https://github.com/reactiveui/DynamicData/commit/335ecbca2d300fdbfb9b2f40a1ffb95f00f46923) Add Preview workflows and enforce versioning rules ([#&#8203;1088](https://github.com/reactiveui/DynamicData/issues/1088)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`d59ed5c`](https://github.com/reactiveui/DynamicData/commit/d59ed5cc768f5df2859687ce23920ce5fb343509) Replace softprops/action-gh-release with gh CLI ([#&#8203;1092](https://github.com/reactiveui/DynamicData/issues/1092)) [@&#8203;dwcullop](https://github.com/dwcullop)
- [`767f273`](https://github.com/reactiveui/DynamicData/commit/767f2737c47c67990ba1876539e984598dcb851d) Restore stable 9.4 versioning on release/9.x ([#&#8203;1123](https://github.com/reactiveui/DynamicData/issues/1123)) [@&#8203;dwcullop](https://github.com/dwcullop)

#### Test:

- [`c1c312b`](https://github.com/reactiveui/DynamicData/commit/c1c312b3c044a5718ff8923d5a233f9e32f96b70) test: fix AsyncDisposeMany thread safety test timeout on CI ([#&#8203;1082](https://github.com/reactiveui/DynamicData/issues/1082)) [@&#8203;dwcullop](https://github.com/dwcullop)

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/14
2026-07-10 22:19:31 +00:00
Renovate Bot d5e4dfae56 Update dependency Ryujinx.SDL3-CS to 2026.707.0 (#16)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Ryujinx.SDL3-CS](https://github.com/Kenji-NX/SDL3-CS) | `2026.501.0` → `2026.707.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.SDL3-CS/2026.707.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.SDL3-CS/2026.501.0/2026.707.0?slim=true) |

---

### Release Notes

<details>
<summary>Kenji-NX/SDL3-CS (Ryujinx.SDL3-CS)</summary>

### [`v2026.707.0`](https://github.com/Kenji-NX/SDL3-CS/compare/2026.501.0...2026.707.0)

[Compare Source](https://github.com/Kenji-NX/SDL3-CS/compare/2026.501.0...2026.707.0)

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/16
2026-07-10 22:18:56 +00:00
Renovate Bot 28268ef7ef Update avalonia monorepo to 11.3.18 (#13)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Avalonia](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia/11.3.17/11.3.18?slim=true) |
| [Avalonia.Desktop](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Desktop/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Desktop/11.3.17/11.3.18?slim=true) |
| [Avalonia.Diagnostics](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Diagnostics/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Diagnostics/11.3.17/11.3.18?slim=true) |
| [Avalonia.Markup.Xaml.Loader](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.17` → `11.3.18` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Markup.Xaml.Loader/11.3.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Markup.Xaml.Loader/11.3.17/11.3.18?slim=true) |

---

### Release Notes

<details>
<summary>AvaloniaUI/Avalonia (Avalonia)</summary>

### [`v11.3.18`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.18)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.17...11.3.18)

##### What's Changed

##### Enhancements

- XAML – Enhance Roslyn-compiler visible metadata by [@&#8203;maxkatz6](https://github.com/maxkatz6) in [#&#8203;21546](https://github.com/AvaloniaUI/Avalonia/pull/21546)

##### Fixes

- Core – Fix StackOverflow when a `NaN` offset is set on `ScrollViewer` by [@&#8203;NicholasLachapelle](https://github.com/NicholasLachapelle) in [#&#8203;21558](https://github.com/AvaloniaUI/Avalonia/pull/21558)
- macOS – Handle `replacementRange` in `AvnView` by [@&#8203;MrJul](https://github.com/MrJul) in [#&#8203;21608](https://github.com/AvaloniaUI/Avalonia/pull/21608)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.17...11.3.18>

</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 these updates 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:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFzdGVyIiwibGFiZWxzIjpbXX0=-->

Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/13
2026-07-10 22:18:09 +00:00
LotPandLotP1 6c47892b94 mono-jit
- fix alignment issue with jitcache that could cause a crash.
- Reimplement sparse-jit to only use 1 layer.
    - the old 2 layer approach basically only ever used 1 path due to the bottom-layer size, so doing a double look-up seemed wasteful.
    - changed memory block allocation stategy to allocate based on requested code range instead of the possible address-space.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-10 10:03:48 -05:00
KeatonTheBot b362f837c5 Fix MemoryMap/MemoryUnmap/MemoryProtect in UnicornAArch32 and UnicornAArch64 2026-07-10 10:03:48 -05:00
LotPandLotP1 a82a721df6 instanced jit cache
- jit caches are no longer static.
- JitUnwindWindows now supports multiple jit caches.
- Jit caches should no longer cause crashes due to entry offset collisions.
- cpu tests now run concurrently.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-07-01 09:54:23 -05:00
ranidspace 297df259c3 Remove unused windowing library package
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.
2026-07-01 09:54:22 -05:00
LotP 3ace5deadf fix-tests
- 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.
2026-07-01 09:54:22 -05:00
_Neo_ 04aacb1385 macOS: Fix Hypervisor BadArgument Launch Crash
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.
2026-06-27 14:27:57 -05:00
Renovate Bot ea91841e9b Update dependency Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.4 (#12)
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/Kenji-NX/pulls/12
2026-06-19 04:17:44 +00:00
Babib3l 5734ad9090 Fix Windows console hide path targeting the foreground window
This PR addresses [Ryubing/Issues#345](https://github.com/Ryubing/Issues/issues/345) by fixing the Windows console hide/show path so it only acts on Ryujinx’s own console window instead of whatever window happens to be focused during startup. Previously, when Show Console was disabled, the helper could race with focus changes and end up affecting another app or shell window while leaving the console visible; this change removes that foreground-window dependency and keeps the startup behavior scoped to the Ryujinx console.
2026-06-14 23:18:49 -05:00
Renovate Bot 1486d2db40 Update dependency Microsoft.NET.Test.Sdk to 18.6.0 (#11)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.5.1` → `18.6.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.6.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.5.1/18.6.0?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.6.0`](https://github.com/microsoft/vstest/releases/tag/v18.6.0)

#### What's Changed

- Revert removal of Video Recorder by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15336](https://github.com/microsoft/vstest/pull/15336)
- Speed up blame by filtering non-.NET processes from dump collection by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15518](https://github.com/microsoft/vstest/pull/15518)
- Add README.md to NuGet packages by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15550](https://github.com/microsoft/vstest/pull/15550)
- Report child process info on connection timeout by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15603](https://github.com/microsoft/vstest/pull/15603)

##### Changes to tests and infra

- Brand as 18.6 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15423](https://github.com/microsoft/vstest/pull/15423)
- Upgrading code coverage version to 18.5.1, by [@&#8203;fhnaseer](https://github.com/fhnaseer) in [#&#8203;15422](https://github.com/microsoft/vstest/pull/15422)
- Updating System.Collections.Immutable to 9.0.11 by [@&#8203;MSLukeWest](https://github.com/MSLukeWest) in [#&#8203;15425](https://github.com/microsoft/vstest/pull/15425)
- Fix attachVS when used for debugging integration tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15451](https://github.com/microsoft/vstest/pull/15451)
- Replace dotnet.config, with global.json by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15449](https://github.com/microsoft/vstest/pull/15449)
- Document debugging integration tests with AttachVS by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;15452](https://github.com/microsoft/vstest/pull/15452)
- Fix stack overflow tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15461](https://github.com/microsoft/vstest/pull/15461)
- Make TestAssets.sln buildable locally by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15466](https://github.com/microsoft/vstest/pull/15466)
- Try filtering out tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15463](https://github.com/microsoft/vstest/pull/15463)
- Build just once when tfms run in parallel by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15465](https://github.com/microsoft/vstest/pull/15465)
- Review simplify compatibility sources, deduplicate tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15472](https://github.com/microsoft/vstest/pull/15472)
- Cleanup dead TRX code by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15474](https://github.com/microsoft/vstest/pull/15474)
- Update .NET runtimes to 8.0.25, 9.0.14, and 10.0.4 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15481](https://github.com/microsoft/vstest/pull/15481)
- Compat matrix checker by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15480](https://github.com/microsoft/vstest/pull/15480)
- Add trx analysis skill by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15486](https://github.com/microsoft/vstest/pull/15486)
- Split integration tests to single tfm and multi tfm project by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15484](https://github.com/microsoft/vstest/pull/15484)
- Update matrix by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15477](https://github.com/microsoft/vstest/pull/15477)
- Break infinite restore loop in VS by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15503](https://github.com/microsoft/vstest/pull/15503)
- Use global package cache for build, and local for running integration tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15500](https://github.com/microsoft/vstest/pull/15500)
- Update contributing by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15505](https://github.com/microsoft/vstest/pull/15505)
- Reduce test wall-clock time by increasing minThreads by [@&#8203;drognanar](https://github.com/drognanar) in [#&#8203;15502](https://github.com/microsoft/vstest/pull/15502)
- Indicator flakiness by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15513](https://github.com/microsoft/vstest/pull/15513)
- Fix ci build by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15515](https://github.com/microsoft/vstest/pull/15515)
- Fix thread safety issues by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15512](https://github.com/microsoft/vstest/pull/15512)
- Optimize DotnetSDKSimulation\_PostProcessing test (163s → 61s) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15516](https://github.com/microsoft/vstest/pull/15516)
- Build isolated test assets for single TFM instead of 7 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15517](https://github.com/microsoft/vstest/pull/15517)
- Remove unused dependencies from Library.IntegrationTests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15527](https://github.com/microsoft/vstest/pull/15527)
- Remove printing \_attachments content to console by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15520](https://github.com/microsoft/vstest/pull/15520)
- Add Linux/macOS test filtering guide to CONTRIBUTING.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15521](https://github.com/microsoft/vstest/pull/15521)
- Change integration test parallelization from ClassLevel to MethodLevel by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15526](https://github.com/microsoft/vstest/pull/15526)
- Unify target framework checks with IsNetFrameworkTarget/IsNetTarget by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15523](https://github.com/microsoft/vstest/pull/15523)
- Add unattended work instructions to copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15531](https://github.com/microsoft/vstest/pull/15531)
- Reduce code style rule severity from warning to suggestion by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15522](https://github.com/microsoft/vstest/pull/15522)
- Remove Debug/Release line number branching from tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15519](https://github.com/microsoft/vstest/pull/15519)
- Revise unattended work instructions in copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15532](https://github.com/microsoft/vstest/pull/15532)
- Improve CompatibilityRowsBuilder error message with diagnostic details by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15529](https://github.com/microsoft/vstest/pull/15529)
- docs: add git worktree and upstream sync workflow to copilot-instructions.md by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15538](https://github.com/microsoft/vstest/pull/15538)
- Add VSIX runner to smoke tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15541](https://github.com/microsoft/vstest/pull/15541)
- Remove deprecated WebTest and TMI test methods by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15525](https://github.com/microsoft/vstest/pull/15525)
- Fix compatibility test failures for legacy vstest.console and MSTest adapter by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15534](https://github.com/microsoft/vstest/pull/15534)
- Convert TestPlatform.sln to slnx format by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15551](https://github.com/microsoft/vstest/pull/15551)
- Convert test/TestAssets .sln files to .slnx format by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15557](https://github.com/microsoft/vstest/pull/15557)
- Enable parallelization for blame data collector tests by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15552](https://github.com/microsoft/vstest/pull/15552)
- Fix CI failure when GeneratedTestAssets directory doesn't exist by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15556](https://github.com/microsoft/vstest/pull/15556)
- Set DOTNET\_ROOT in test.sh for local Linux usage by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15559](https://github.com/microsoft/vstest/pull/15559)
- Use MSTest recommended analyzers by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15539](https://github.com/microsoft/vstest/pull/15539)
- Document semicolon handling in RunSettings test parameters by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15561](https://github.com/microsoft/vstest/pull/15561)
- Enable CA1067 analyzer and fix violations by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15560](https://github.com/microsoft/vstest/pull/15560)
- Fix HTML logger parallel file collision with atomic file creation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15562](https://github.com/microsoft/vstest/pull/15562)
- Deduplicate package extraction between verify-nupkgs and IntegrationTestBuild by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15554](https://github.com/microsoft/vstest/pull/15554)
- Fix MSTEST0046: use Assert.MatchesRegex instead of StringAssert.Matches by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15575](https://github.com/microsoft/vstest/pull/15575)
- Attach diagnostic logs to acceptance test runs by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15572](https://github.com/microsoft/vstest/pull/15572)
- Deprecate EnableShutdownAfterTestRun which is no-op by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15576](https://github.com/microsoft/vstest/pull/15576)
- Skip VideoRecorder test on CI due to access denied errors by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15587](https://github.com/microsoft/vstest/pull/15587)
- Fix integration test build collision with mutex + EventWaitHandle by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15568](https://github.com/microsoft/vstest/pull/15568)
- Reduce blame test flakiness: increase hang dump timeout to 10s by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15590](https://github.com/microsoft/vstest/pull/15590)
- Fix concurrent modification in MetricsCollection by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15581](https://github.com/microsoft/vstest/pull/15581)
- Fix PassingNoArguments test: disable --diag to preserve help output by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15583](https://github.com/microsoft/vstest/pull/15583)
- perf: fix dictionary double-lookups, Collection.Contains, and LINQ allocations by [@&#8203;Evangelink](https://github.com/Evangelink) in [#&#8203;15533](https://github.com/microsoft/vstest/pull/15533)
- Replace VSSDK-sourced DLLs with proper package references by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15567](https://github.com/microsoft/vstest/pull/15567)
- Add target framework to default TRX file name by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15565](https://github.com/microsoft/vstest/pull/15565)
- Update post-build template parameters by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15591](https://github.com/microsoft/vstest/pull/15591)
- Fix path for post-build template and adjust validation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15592](https://github.com/microsoft/vstest/pull/15592)
- Add azure-pipelines-official.yml to pipeline files by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15594](https://github.com/microsoft/vstest/pull/15594)
- Update comment formatting for signing validation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15597](https://github.com/microsoft/vstest/pull/15597)
- Fix enable-auto-merge for maestro by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15595](https://github.com/microsoft/vstest/pull/15595)
- Auto-approve maestro PRs by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15598](https://github.com/microsoft/vstest/pull/15598)
- Update enable-auto-merge to squash by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15602](https://github.com/microsoft/vstest/pull/15602)
- Update enable-auto-merge.yml for the correct permissions by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15606](https://github.com/microsoft/vstest/pull/15606)
- Add 365 regression tests for untested bug fixes by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15615](https://github.com/microsoft/vstest/pull/15615)
- Fix typos and add comments to empty catch blocks by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15609](https://github.com/microsoft/vstest/pull/15609)
- Fix flaky EventLogCollector test: ensure deterministic event log entries by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15607](https://github.com/microsoft/vstest/pull/15607)
- Mark PathConverter tests as Windows-only by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15617](https://github.com/microsoft/vstest/pull/15617)
- Fix HangDumpOnTimeout flakiness and ignore VideoRecorder test by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15616](https://github.com/microsoft/vstest/pull/15616)
- Add copilot-setup-steps.yml by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15604](https://github.com/microsoft/vstest/pull/15604)
- Add CreateNoNewWindow RunConfiguration setting by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15585](https://github.com/microsoft/vstest/pull/15585)
- Add preview packages documentation by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15628](https://github.com/microsoft/vstest/pull/15628)
- Cleanup filter implementation by [@&#8203;Youssef1313](https://github.com/Youssef1313) in [#&#8203;15629](https://github.com/microsoft/vstest/pull/15629)
- Fix SCI binding failure in DTA hosts (rel/18.6) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15722](https://github.com/microsoft/vstest/pull/15722)
- Remove DiagnosticSource binding redirect (rel/18.6) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15776](https://github.com/microsoft/vstest/pull/15776)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.5.1...v18.6.0>

</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/Kenji-NX/pulls/11
2026-06-13 05:10:20 +00:00
Renovate Bot c54ca28843 Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.19.1 (#10)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) | `8.18.0` → `8.19.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.IdentityModel.JsonWebTokens/8.19.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.IdentityModel.JsonWebTokens/8.18.0/8.19.1?slim=true) |

---

### Release Notes

<details>
<summary>AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet (Microsoft.IdentityModel.JsonWebTokens)</summary>

### [`v8.19.1`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/HEAD/CHANGELOG.md#8191)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.19.0...8.19.1)

\====

#### Bug Fixes

- Update `JwtSecurityTokenHandler` for `IssuerSigningKeyResolverUsingConfiguration` to take priority over `IssuerSigningKeyResolver`, matching the documented contract and the correct behavior already present in `JsonWebTokenHandler`. See [PR #&#8203;3519](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3519).

### [`v8.19.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/HEAD/CHANGELOG.md#8190)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.18.0...8.19.0)

\====

#### New Features

- Add ML-DSA (FIPS 204) post-quantum signature support. See [PR #&#8203;3479](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3479).
- Cache custom crypto providers in CryptoProviderFactory. See [PR #&#8203;3489](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3489).

#### Bug Fixes

- Disable automatic redirects on default HttpClient for JKU retrieval. See [PR #&#8203;3494](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3494).
- Adjust rented buffer handling in claim set parsing. See [PR #&#8203;3493](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3493).
- Tidy null handling in SAML conditions validation. See [PR #&#8203;3491](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3491).
- Improve validation of `jku` claim. See [PR #&#8203;3481](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3481).
- Limit telemetry algorithm dimension cardinality. See [PR #&#8203;3490](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3490).
- Add defensive copy of collections in ValidationParameters. See [PR #&#8203;3492](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3492).
- Update TokenValidationParameter copy constructor to make a deep copy. See [PR #&#8203;3488](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3488).
- Update to fail-closed when replay protection isn't configured and other DPoP hardening. See [PR #&#8203;3505](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3505).
- Apply RFC 3986 section 6.2.2 normalization to DPoP `htu` comparison. See [PR #&#8203;3509](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3509).

</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/Kenji-NX/pulls/10
2026-06-13 05:04:50 +00:00
Renovate Bot a53e691185 Update skiasharp monorepo to 3.119.4 (#9)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [SkiaSharp](https://go.microsoft.com/fwlink/?linkid=868515) | `3.119.2` → `3.119.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/SkiaSharp/3.119.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/SkiaSharp/3.119.2/3.119.4?slim=true) |
| [SkiaSharp.NativeAssets.Linux](https://go.microsoft.com/fwlink/?linkid=868515) | `3.119.2` → `3.119.4` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/SkiaSharp.NativeAssets.Linux/3.119.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/SkiaSharp.NativeAssets.Linux/3.119.2/3.119.4?slim=true) |

---

### 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 these updates 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/Kenji-NX/pulls/9
2026-06-13 05:04:29 +00:00
Renovate Bot bf8c902d13 Update dependency Ryujinx.Audio.OpenAL to 1.25.2 (#8)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| Ryujinx.Audio.OpenAL | `1.25.1` → `1.25.2` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Ryujinx.Audio.OpenAL/1.25.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Ryujinx.Audio.OpenAL/1.25.1/1.25.2?slim=true) |

---

### 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/Kenji-NX/pulls/8
2026-06-13 05:03:51 +00:00
Renovate Bot f4dc400a47 Update avalonia monorepo to 11.3.17 (#7)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Avalonia](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia/11.3.16/11.3.17?slim=true) |
| [Avalonia.Desktop](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Desktop/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Desktop/11.3.16/11.3.17?slim=true) |
| [Avalonia.Diagnostics](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Diagnostics/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Diagnostics/11.3.16/11.3.17?slim=true) |
| [Avalonia.Markup.Xaml.Loader](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.16` → `11.3.17` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Markup.Xaml.Loader/11.3.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Markup.Xaml.Loader/11.3.16/11.3.17?slim=true) |

---

### Release Notes

<details>
<summary>AvaloniaUI/Avalonia (Avalonia)</summary>

### [`v11.3.17`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.17)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.16...11.3.17)

##### What's Changed

##### Performance

- XAML – Improve type caching in the XAML compiler by [@&#8203;MrJul](https://github.com/MrJul) in [#&#8203;21408](https://github.com/AvaloniaUI/Avalonia/pull/21408)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.16...11.3.17>

</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 these updates 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/Kenji-NX/pulls/7
2026-06-13 05:03:21 +00:00
LotPandLotP1 a36882ab51 check for HandleDesc
HandleDesc can be null, make sure to check for that.

Co-authored-by: LotP1 <68976644+LotP1@users.noreply.github.com>
2026-05-30 21:27:05 -05:00
KeatonTheBot 76a123b81d misc: chore: Use explicit types & fix object creation 2026-05-30 16:26:50 -05:00
KeatonTheBot 9d3fecf334 Revert "Gpu: exclude RGBA16Float texture format for fast DMA copy on Apple silicon."
This reverts commit b16f74ffd9.
2026-05-27 17:41:51 -05:00
KeatonTheBot 31d0bc5915 Revert "Shader: skip initialization of all clip distances on macOS."
This reverts commit f764ae2d58.
2026-05-27 17:41:47 -05:00
KeatonTheBotandsunshineinabox 30dba1ed36 macOS: Fix black screen on Metal versions older than 3.1
Fixes a black screen on Metal versions older than 3.1 by disabling `VK_EXT_extended_dynamic_state` (and `VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE`). Fix imported from sunshineinabox's unpublished extended dynamic states branch.

Co-authored-by: sunshineinabox <aqemail@gmail.com>
2026-05-27 11:51:44 -05:00
Xam 0d6c0aeeab HLE: Applets: Software keyboard: update cursor position on SetInputText
fixes cursor being at 0 position even when text is present, especially annoying for handheld devices with kde virtual keyboard, which is pretty bad, with no way to move the cursor or delete text backward
2026-05-27 10:27:15 -05:00
Babib3l 74d6b310b8 Windows Fullscreen Fixes
This PR fixes two bugs introduced in #80 :
- Exiting fullscreen could exit the Ryujinx window  (https://github.com/Ryubing/Issues/issues/415)
- Toggling fullscreen on would move the window to the primary monitor

And two other bugs related to input view :
- Exiting fullscreen when window was previously in a non maximized state now conserves the window coordinates and size properly (https://github.com/Ryubing/Issues/issues/425)
- Opening the ryujinx app no makes the app grow slightly larger vertically on each launch (+31px on my 1080p monitor) (https://github.com/Ryubing/Issues/issues/425)
2026-05-25 11:59:44 -05:00
Babib3l 3289715b8c River : HLE: Make process identity explicit for service metadata resolution
This PR is the first in a batch of structural changes to Ryujinx.

**Changes**

- Added `ProcessIdentity` and `ProcessKind` to describe loaded programs by:
  - PID, program ID, application ID, program index, display version, process kind
- Stored identity metadata on `ProcessResult`.
- Added PID-based process lookup helpers to `ProcessLoader`.
- Updated HLE services to resolve application metadata through the caller PID instead of `Processes.ActiveApplication`.
- Added PTC/JIT disk cache initialization logging with PID, title ID, display version, selector, and enabled state.
- Added `ClientProcessId` property to ServiceCtx (/src/Ryujinx.HLE/HOS/ServiceCtx.cs) that uses the handle descriptor PId when available, falling back to `Process.Pid`.
- Updated 15 HLE service files to use `context.ClientProcessId` instead of `context.Process.Pid` for client process access, ensuring services correctly identify the calling process even when invoked via IPC with handle descriptors.

These changes make service metadata resolution more explicit and prepare the emulator for other structural changes later on.
2026-05-25 11:50:28 -05:00
Mabel 3a3129252a Fix Clipboard Copy Operation Crash
Fixes a COM exception crash related to clipboard copy events from changes in Avalonia 11.3

Solves [Ryubing/Issues#294](https://github.com/Ryubing/Issues/issues/294) caused by Avalonia 11.3's changes, see [AvaloniaUI/Avalonia#20007](https://github.com/AvaloniaUI/Avalonia/issues/20007) for more information

I've only tested this on Windows, no idea if this has issues in MacOS or Linux, or if it's even a problem there at all.
2026-05-24 14:56:27 -05:00
LotP ff47cb1d66 remap joy-cons 2026-05-22 14:31:00 -05:00
KeatonTheBot d440ff41d2 Update NuGet packages
- Ryujinx.Graphics.Vulkan.MoltenVK to 1.4.2-ryujinx.3

- System.IO.Hashing to 10.0.8
2026-05-20 14:30:24 -05:00
Renovate Bot 46308c32e7 Update dependency NUnit to 4.6.1 (#6)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [NUnit](https://nunit.org/) ([source](https://github.com/nunit/nunit)) | `4.5.1` → `4.6.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/NUnit/4.6.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/NUnit/4.5.1/4.6.1?slim=true) |

---

### Release Notes

<details>
<summary>nunit/nunit (NUnit)</summary>

### [`v4.6.1`](https://github.com/nunit/nunit/releases/tag/v4.6.1): NUnit version 4.6.1

See [release notes](https://docs.nunit.org/articles/nunit/release-notes/framework.html#nunit-461---may-18-2026) for details.

### [`v4.6.0`](https://github.com/nunit/nunit/releases/tag/v4.6.0): NUnit 4.6.0

[Compare Source](https://github.com/nunit/nunit/compare/4.5.1...4.6.0)

See [release notes](https://docs.nunit.org/articles/nunit/release-notes/framework.html#nunit-460---may-4-2026) for details.

</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/Kenji-NX/pulls/6
2026-05-20 18:43:04 +00:00
Renovate Bot bf09e153f1 Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.18.0 (#5)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) | `8.17.0` → `8.18.0` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.IdentityModel.JsonWebTokens/8.18.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.IdentityModel.JsonWebTokens/8.17.0/8.18.0?slim=true) |

---

### Release Notes

<details>
<summary>AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet (Microsoft.IdentityModel.JsonWebTokens)</summary>

### [`v8.18.0`](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/HEAD/CHANGELOG.md#8180)

[Compare Source](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.17.0...8.18.0)

\====

#### New Features

- Introduced a new interface `IConfigurationEventHandlerContextAware<T>` that provides context to the configuration event handler implementation, allowing it to optionally bypass a cache lookup. See [PR #&#8203;3444](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3444).
- Added Microsoft.IdentityModel.Dpop — a new package implementing DPoP (Demonstrating Proof-of-Possession) per RFC 9449. Provides both client-side and server-side proof validation with no System.Net.Http dependency. See [PR #&#8203;3443](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3443).

</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/Kenji-NX/pulls/5
2026-05-20 18:42:52 +00:00
Renovate Bot 0d1eebad31 Update dependency Microsoft.NET.Test.Sdk to 18.5.1 (#4)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) | `18.5.0` → `18.5.1` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Microsoft.NET.Test.Sdk/18.5.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Microsoft.NET.Test.Sdk/18.5.0/18.5.1?slim=true) |

---

### Release Notes

<details>
<summary>microsoft/vstest (Microsoft.NET.Test.Sdk)</summary>

### [`v18.5.1`](https://github.com/microsoft/vstest/releases/tag/v18.5.1)

#### What's Changed

- Fix System.Collections.Immutable binding mismatch in Common.dll (rel/18.5) by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15720](https://github.com/microsoft/vstest/pull/15720)
- Port verify-binding-redirects.ps1 to rel/18.5 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15719](https://github.com/microsoft/vstest/pull/15719)
- Bump to 18.5.1 by [@&#8203;nohwnd](https://github.com/nohwnd) in [#&#8203;15721](https://github.com/microsoft/vstest/pull/15721)

**Full Changelog**: <https://github.com/microsoft/vstest/compare/v18.5.0...v18.5.1>

</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/Kenji-NX/pulls/4
2026-05-20 18:28:36 +00:00
Renovate Bot b1b260ed17 Update avalonia monorepo to 11.3.16 (#3)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [Avalonia](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.14` → `11.3.16` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia/11.3.16?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia/11.3.14/11.3.16?slim=true) |
| [Avalonia.Desktop](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.14` → `11.3.16` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Desktop/11.3.16?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Desktop/11.3.14/11.3.16?slim=true) |
| [Avalonia.Diagnostics](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.14` → `11.3.16` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Diagnostics/11.3.16?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Diagnostics/11.3.14/11.3.16?slim=true) |
| [Avalonia.Markup.Xaml.Loader](https://avaloniaui.net/?utm_source=nuget&utm_medium=referral&utm_content=project_homepage_link) ([source](https://github.com/AvaloniaUI/Avalonia)) | `11.3.14` → `11.3.16` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/Avalonia.Markup.Xaml.Loader/11.3.16?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/Avalonia.Markup.Xaml.Loader/11.3.14/11.3.16?slim=true) |

---

### Release Notes

<details>
<summary>AvaloniaUI/Avalonia (Avalonia)</summary>

### [`v11.3.16`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.16)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.15...11.3.16)

##### What's Changed

- Core – Align Touch/Pen capture semantics with Mouse by [@&#8203;miloush](https://github.com/miloush) in [#&#8203;21363](https://github.com/AvaloniaUI/Avalonia/pull/21363)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.15...11.3.16>

### [`v11.3.15`](https://github.com/AvaloniaUI/Avalonia/releases/tag/11.3.15)

[Compare Source](https://github.com/AvaloniaUI/Avalonia/compare/11.3.14...11.3.15)

##### What's Changed

##### Security

- VNC – Bump SharpCompress from 0.24.0 to 0.48.0 by [@&#8203;kekekeks](https://github.com/kekekeks)

**Full Changelog**: <https://github.com/AvaloniaUI/Avalonia/compare/11.3.14...11.3.15>

</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 these updates 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/Kenji-NX/pulls/3
2026-05-20 18:27:08 +00:00
Renovate BotandKeatonTheBot a57756f447 Configure Renovate (#1)
Welcome to [Renovate](https://github.com/renovatebot/renovate)! This is an onboarding PR to help you understand and configure settings before regular Pull Requests begin.

🚦 To activate Renovate, merge this Pull Request. To disable Renovate, simply close this Pull Request unmerged.

📚 See our [Reading List](https://docs.renovatebot.com/reading-list/) for relevant documentation you may be interested in reading.

🔡 Do you want to change how Renovate upgrades your dependencies? Add your custom config to `.forgejo/renovate.json` in this branch. Renovate will update the Pull Request description the next time it runs.

---
### Detected Package Files

 * `Directory.Packages.props` (nuget)
 * `global.json` (nuget)
 * `src/ARMeilleure/ARMeilleure.csproj` (nuget)
 * `src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj` (nuget)
 * `src/Ryujinx.Common/Ryujinx.Common.csproj` (nuget)
 * `src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj` (nuget)
 * `src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj` (nuget)
 * `src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj` (nuget)
 * `src/Ryujinx.HLE/Ryujinx.HLE.csproj` (nuget)
 * `src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj` (nuget)
 * `src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj` (nuget)
 * `src/Ryujinx.Horizon/Ryujinx.Horizon.csproj` (nuget)
 * `src/Ryujinx.Input/Ryujinx.Input.csproj` (nuget)
 * `src/Ryujinx.SDL3.Common/Ryujinx.SDL3.Common.csproj` (nuget)
 * `src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj` (nuget)
 * `src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj` (nuget)
 * `src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj` (nuget)
 * `src/Ryujinx.Tests/Ryujinx.Tests.csproj` (nuget)
 * `src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj` (nuget)
 * `src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj` (nuget)
 * `src/Ryujinx/Ryujinx.csproj` (nuget)

### Configuration Summary

Based on the default config's presets, Renovate will:

  - Start dependency updates only once this onboarding PR is merged
  - Enable Renovate Dependency Dashboard creation.
  - Use semantic commit type `fix` for dependencies and `chore` for all others if semantic commits are in use.
  - Ignore `node_modules`, `bower_components`, `vendor` and various test/tests (except for nuget) directories.
  - Group known monorepo packages together.
  - Use curated list of recommended non-monorepo package groupings.
  - Show only the Age and Confidence Merge Confidence badges for pull requests.
  - Apply crowd-sourced package replacement rules.
  - Apply crowd-sourced workarounds for known problems with packages.
  - Ensure that every dependency pinned by digest and sourced from Forgejo contains a link to the commit-to-commit diff
  - Ensure that every dependency pinned by digest and sourced from GitHub.com and Github enterprise contains a link to the commit-to-commit diff
  - Enable Renovate configuration migration PRs when needed.
  - Require Dependency Dashboard approval for `major` updates.

---

### What to Expect

With your current configuration, Renovate will create 9 Pull Requests:

<details>
<summary>Update avalonia monorepo to 11.3.16</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/avalonia-monorepo`
  - Merge into: `master`
  - Upgrade [Avalonia](https://github.com/AvaloniaUI/Avalonia) to `11.3.16`
  - Upgrade [Avalonia.Desktop](https://github.com/AvaloniaUI/Avalonia) to `11.3.16`
  - Upgrade [Avalonia.Diagnostics](https://github.com/AvaloniaUI/Avalonia) to `11.3.16`
  - Upgrade [Avalonia.Markup.Xaml.Loader](https://github.com/AvaloniaUI/Avalonia) to `11.3.16`

</details>

<details>
<summary>Update dependency Microsoft.NET.Test.Sdk to 18.5.1</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/vstest-monorepo`
  - Merge into: `master`
  - Upgrade [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) to `18.5.1`

</details>

<details>
<summary>Update dependency Microsoft.IdentityModel.JsonWebTokens to 8.18.0</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/dotnet-azure-ad-identitymodel-extensions-monorepo`
  - Merge into: `master`
  - Upgrade [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) to `8.18.0`

</details>

<details>
<summary>Update dependency NUnit to 4.6.1</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/nunit-4.x`
  - Merge into: `master`
  - Upgrade [NUnit](https://github.com/nunit/nunit) to `4.6.1`

</details>

<details>
<summary>Update dotnet monorepo</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/dotnet-monorepo`
  - Merge into: `master`
  - Upgrade [Microsoft.CodeAnalysis.Analyzers](https://github.com/dotnet/roslyn) to `3.11.0`
  - Upgrade [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) to `4.14.0`
  - Upgrade [System.IO.Hashing](https://github.com/dotnet/dotnet) to `10.0.8`
  - Upgrade [dotnet-sdk](https://github.com/dotnet/sdk) to `10.0.300`

</details>

<details>
<summary>Update avalonia monorepo to v12</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/major-avalonia-monorepo`
  - Merge into: `master`
  - Upgrade [Avalonia](https://github.com/AvaloniaUI/Avalonia) to `12.0.3`
  - Upgrade [Avalonia.Desktop](https://github.com/AvaloniaUI/Avalonia) to `12.0.3`
  - Upgrade [Avalonia.Markup.Xaml.Loader](https://github.com/AvaloniaUI/Avalonia) to `12.0.3`

</details>

<details>
<summary>Update dependency Avalonia.Controls.DataGrid to v12</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/avalonia.controls.datagrid-12.x`
  - Merge into: `master`
  - Upgrade [Avalonia.Controls.DataGrid](https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid) to `12.0.0`

</details>

<details>
<summary>Update dotnet monorepo to v5</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/major-dotnet-monorepo`
  - Merge into: `master`
  - Upgrade [Microsoft.CodeAnalysis.Analyzers](https://github.com/dotnet/roslyn) to `5.3.0`
  - Upgrade [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) to `5.3.0`

</details>

<details>
<summary>Update Svg.Controls to v12</summary>

  - Schedule: ["at any time"]
  - Branch name: `renovate/major-svg.controls`
  - Merge into: `master`
  - Upgrade [Svg.Controls.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) to `12.0.0.9`
  - Upgrade [Svg.Controls.Skia.Avalonia](https://github.com/wieslawsoltes/Svg.Skia) to `12.0.0.9`

</details>

🚸 PR creation will be limited to maximum 2 per hour, so it doesn't swamp any CI resources or overwhelm the project. See [docs for `prHourlyLimit`](https://docs.renovatebot.com/configuration-options/#prhourlylimit) for details.

---

❓ Got questions? Check out Renovate's [Docs](https://docs.renovatebot.com/), particularly the Getting Started section.
If you need any further assistance then you can also [request help here](https://github.com/renovatebot/renovate/discussions).

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).

<!--renovate-config-hash:b90d3633c498120cf097b76d90e40f530a1210d84c00ad2dc3491f01a81cbdd8-->

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
Reviewed-on: https://git.ryujinx.app/projects/Kenji-NX/pulls/1
2026-05-20 16:58:32 +00:00
be405de436 Fix crash on Mac and Android caused by a buffer validation error fix
Fixes a bug introduced in e88fd327. The problematic commit did its work directly on the updater's own arrays, calling Auto.Get() on each entry as it went, and nulling entries out along the way. The problem is that Get() can call back into Commit (via ClearMirrors -> Rebind), and when it did, that reentrant Commit would read from the same arrays the outer call was still in the middle of processing, hit one of the entries the outer had already nulled, and throw an NullReferenceException. The fix is to have Commit start by copying everything it needs into local variables and resetting _count to zero, so a reentrant call sees a clean updater and operates on its own data. The outer call then writes its snapshot back into the native arrays just before recording the Vulkan bind.

Co-authored-by: AsperTheDog <guillerman0000@gmail.com>
Co-authored-by: Max <randomgirlisweird@gmail.com>
Co-authored-by: Babib3l <gab.chevanne@gmail.com>
2026-05-19 12:18:56 -05:00
AsperTheDogandAsperTheDog e88fd3273d Fix Vulkan validation errors
This PR fixes several validation errors caused by invalid Vulkan usage. These validation errors often end up invoking Undefined Behavior on the driver side, which can lead to artifacts or crashes which are driver specific and otherwise incredibly hard to track. I don't think it should have any impact on performance, but it would be good to test it with as many games as possible (maybe a bug in a game was fixed?).

Each commit fixes an error. I added to each a description with the validation error that was fixed and a small explanation on what was causing it and how I fixed it.

Co-authored-by: AsperTheDog <guillerman0000@gmail.com>
2026-05-15 17:22:21 -05:00
AsperTheDogandAsperTheDog 79f55b12bf Change non-uniform shader extension to be more conservative
The previous fix for Tomodachi Life (#91) included the extension to all shaders, independently on if it was needed or not. This PR fixes that by lazily adding the extension only when it is actually needed.

This change should not be noticed by anyone, but it avoids having to modify shaders that do not perform any type of dynamic indexing, which apparently is something some modders care about.

Co-authored-by: AsperTheDog <guillerman0000@gmail.com>
2026-05-15 17:22:21 -05:00
Max 0bc515e4ff [HLE] Stub ILibrarySelfAccessor:ExitAndReturn (10) 2026-05-15 17:22:21 -05:00
AsperTheDogandAsperTheDog 541fa63330 Add shader non-uniform indexing support
This PR marks ALL texture indexes as nonuniform to fix an issue with the paths in Tomodachi Life: Living the Dream on AMD cards. It should have a negligible impact on performance (and it should not have an impact at all on NVIDIA cards!)

It's caused by what is called 'implicit non-uniform sampler array indexing'. The idea is basically that some GPUs optimize texture lookups from indexed texture arrays, by assuming that you are never going to index different textures within a single workgroup. What this causes is that visual glitch where a subgroup is tasked with rendering a block of the screen, and in the boundaries some cores are indexing the wrong texture.

Co-authored-by: AsperTheDog <guillerman0000@gmail.com>
2026-05-14 08:52:04 -05:00
cookieso 55aded3a05 Input: Implement HD Rumble for compatible Nin devices
This PR addresses [this issue](https://github.com/Ryubing/Issues/issues/231) and implements HD Rumble for compatible Nin devices.
## New Features
- Add the option for Gamepads to implement HD Rumble
- Add the HD rumble capability to SDL3Gamepad and SDL3JoyCon when they meet certain requirements
2026-05-14 08:52:04 -05:00
Max 61e0aba325 Check if the Device is rendering before waiting on it
Fixes an issue where there were missed references and an ``OperationCancelled`` exception when exiting an application.
2026-05-14 08:49:51 -05:00
yell0wsuitandyell0wsuit 23fb4924e3 [HLE] Match hardware screenshot buffer size behavior for captures
## Description

~~Fixes a fatal CLR crash when `caps` screenshot saving receives an input buffer larger than `0x384000`.~~

~~Resolves a crash in Tomodachi Life: Living the Dream where saving the pictures to the system's album crashes with 0x80131506.~~

Follow up to #18. This PR adjusts the validation and copy behavior to better match real hardware, and adds logging to make invalid screenshot buffer cases easier to diagnose.

Real hardware accepts screenshot buffers with a size greater than or equal to `0x384000`, but only `0x384000` bytes are needed for the 1280x720 RGBA image.

This changes screenshot saving to:

- reject buffers smaller than `0x384000`
- accept buffers equal to or larger than `0x384000`
- copy only the first `0x384000` bytes into the 1280x720 bitmap

## Testing

Tested with a real Switch NRO using `capssuSaveScreenShotEx0`, `capssuSaveScreenShotEx1`, and `capssuSaveScreenShotEx2`.

Observed hardware behavior:

```text
0x384000     => OK
0x384000 - 1 => NullInputBuffer
0x384000 + 1 => OK
0x3C0000     => OK // Tomo life picture size
```

Co-authored-by: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com>
2026-05-11 14:30:04 -05:00
Babib3l 8fcc539ed9 Fix Windows fullscreen gap when toggling from maximized
This PR aims to Fix the Windows fullscreen gap when toggling from maximized state by using canonical Win32 fullscreen approach. When entering fullscreen, the window style is saved and replaced with WS_POPUP | WS_VISIBLE to remove all window chrome (title bar, borders), then SetWindowPos sizes the window to cover the full screen with SWP_FRAMECHANGED to force Win32 to recalculate the frame. On exit, the original window style is restored. This eliminates the few-pixel gap at the top that occurred because Avalonia's WindowState = FullScreen transition from a maximized state retained the title bar non-client area, leaving visible space at the top of the screen (Fullscreen resolution would be 1920 x 1072p on a 1080p monitor, it now correctly renders at 1920 x 1080p)
2026-05-11 14:30:04 -05:00
yell0wsuitandyell0wsuit 51e4323a73 [HLE] Fix StoreData layout and implement IDatabaseService.Append
- Fixes `StoreData` layout/update handling so `UpdateLatest` returns the stored Mii data correctly.

- Implements `IDatabaseService.Append` (https://switchbrew.org/wiki/Shared_Database_services#IDatabaseService)

Also adds regression tests for `UpdateLatest` and `Append`.

(Might) fix Mario Kart 8 Deluxe crashing on first boot due to failed Mii verification check (due to custom Mii from emulator), and potentially Tomodachi Life: Living the Dream for the "Import Mii from system" option.

Co-authored-by: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com>
2026-05-11 11:09:52 -05:00
Max fcd55edf93 [HLE] Renamed INotificationServicesForSystem and implemented a few commands and stubs
Should allow Ring Fit and other games that rely heavily on the notification system to get in-game (?), needs testing.
2026-05-11 11:09:02 -05:00
Frosch 44872f45e0 fix: gamepads have the same name
When connecting multiple controllers of the same model, the first device's name ends with (0), the second with (1), the third with (1), the fourth with (1), and so on. To ensure these names are truly unique, GetUniqueGamepadName is now called recursively.
2026-05-11 10:45:54 -05:00
Max 1950b34e39 [HLE] Implemented ILibraryAppletSelfAccessor:1
Needed for Tomodachi Life: Living the Dream (?)

based on [this](https://www.reddit.com/r/Ryubing/comments/1t4lfc9/comment/ok4e7tu/)
2026-05-11 10:45:19 -05:00
LotP 4aa8e249f1 fix nacp language buffer 2026-05-04 17:13:31 -05:00
KeatonTheBot 3c13bdb4af Revert "added skia native assets for windows, macOS and switched to no-depend for linux"
This reverts commit af6dd5b64c.
2026-05-04 02:27:43 -05:00
KeatonTheBot 7dec9a2f09 UI: RPC: Asset images 2026-05-04 01:50:21 -05:00
Max af6dd5b64c added skia native assets for windows, macOS and switched to no-depend for linux
im not really sure why these were missing, but theyre here now :woopernod:
2026-05-04 01:50:00 -05:00
Max c6fba938bb CPU: Increased base JIT cache size
@MaxLastBreath found that The Legend of Zelda: Tears of the Kingdom experiences inconsistent crashing with a limited JIT cache size. Increasing the cache size seems to help with this, and increasing it shouldn't degrade performance for other titles.

@LotP plans to look this over later, but for now, this should suffice as a fix for affected users.
2026-05-04 01:50:00 -05:00
Max 4b4fd560ed UI: LoadGuestApplication asynchronous cancellation
Fixed LoadGuestApplication hanging when cancelled.
Since startup procedure has technically changed, we should consider testing this with a variety of game formats to ensure regressions do not occur.
2026-05-04 01:48:31 -05:00
KeatonTheBot 7f99e32a0a Update Ryujinx.SDL3-CS to 2026.501.0
- Fixes upstream SDL issue: Crash on Windows when a controller is connected
2026-05-01 19:52:38 -05:00
KeatonTheBot 4b8c74be10 Update Ryujinx.SDL3-CS to 2026.430.0
- Should work better with Switch 2 Pro Controllers
2026-04-30 00:58:07 -05:00
KeatonTheBot 2af1bc1808 Update NuGet packages
- Avalonia to 11.3.14

- FluentAvalonia to 2.5.1

- Gommon to 2.8.1.2

- Microsoft.NET.Test.Sdk to 1.8.5.0

- SDL3-CS to 2026.426.0

- System.IO.Hashing to 10.0.7
2026-04-27 00:49:28 -05:00
KeatonTheBot 1d19b318c1 infra: Remove PDB files from Release builds
- Use DebugType=none in Directory.Build.props

- Remove files included in NuGet packages
2026-04-22 10:04:19 -05:00
KeatonTheBot 36767d7fce misc: Replace IntPtr/UIntPtr with nint/nuint 2026-04-21 16:26:47 -05:00
Xam 20a9ecaebf HLE: CaptureManager: SaveScreenShot: properly handle screenshot image data
no way this was working before, and if it did, just pure luck, unsafe blind copy of bytes as is and zero checks.

i only tested tomodachi, but should fix all games that were crashing on saving screenshots

the crash was happening because the screenshot buffer was bigger than the bitmap buffer, so marshall.copy() was raising an unhandhled expection crashing the emu.

on top of this, because the data was just copied as is, the result image was garbled.
2026-04-21 16:25:33 -05:00
LotP 77c947eb88 Fix Dual Joy-Con driver and InputView 2026-04-21 16:25:33 -05:00
KeatonTheBotandMaki 251b65771a Update SDL2 to SDL3
- Fix: Crash when connecting a JoyCon

- Fix: Make controller GUIDs match old SDL2 GUIDs

- Fix: Detect face button layout for gamepads

Co-authored-by: Maki <77-maki@users.noreply.git.ryujinx.app>
2026-04-14 22:13:05 -05:00
KeatonTheBot 7aa27b2ca5 Use Forgejo badges 2026-04-14 00:18:57 -05:00
KeatonTheBot 3a479a437d Update GitLab references to Forgejo 2026-04-13 23:49:54 -05:00
sh0inx 5359ab77e1 Update BiquadFilterEffectParameterTests.cs 2026-04-13 23:47:56 -05:00
KeatonTheBot 3d5682375e Add Color Space Passthrough to Headless config and MainWindow 2026-04-10 02:11:36 -05:00
KeatonTheBot bc777e1968 Avalonia: Restore Color Space Passthrough for Windows/Linux
Limiting to macOS is no longer needed. Windows and Linux can also utilize this.

This reverts commit f61b7818c3.
2026-04-09 17:25:04 -05:00
LotP e729014cfa Accurate Service Names
- Services now use their actual names when creating processes instead of the generic "Service".

- Services now use their names encapsulated in curly brackets for threads, e.g. {HID}, instead of the default "HLE.OsThread", making it easier to see what is actually happening behind the scenes. Client threads should use <> and host threads should use {}.

- Fixed a bug where initiating services would create extra unneeded servers.

This is part 1 of a series of service and Horizon changes i'm working on. More will follow at a later date.
2026-04-05 16:53:25 -05:00
KeatonTheBot 39b7242ef7 Update NuGet packages
* Avalonia to 11.3.13

* CommunityToolkit.Mvvm to 8.4.2

* Microsoft.IdentityModel.JsonWebTokens to 8.17.0
2026-04-01 16:24:15 -05:00
KeatonTheBot d55c0a702b Vulkan: Enable VK_EXT_memory_priority for Pageable Memory to work properly 2026-03-31 22:47:20 -05:00
KeatonTheBot f39b53982a VulkanInitialization: Move declarations closer to usage 2026-03-31 22:44:33 -05:00
KeatonTheBot 46a57538c6 Restore Ryujinx.LibHac to 0.21.0-alpha.128 2026-03-24 16:21:38 -05:00
KeatonTheBot 9b93b6fe18 Update NuGet packages
* Svg.Controls.Avalonia group to 11.3.9.5

* DynamicData to 9.4.31

* Humanizer to 3.0.10

* NUnit to 4.5.1

* NUnit3TestAdapter to 6.2.0

* Ryujinx.LibHac to 0.21.0-alpha.133

* System.IO.Hashing to 10.0.5
2026-03-24 15:21:36 -05:00
KeatonTheBot d207d43768 Optimize texture cache code 2026-03-24 15:21:33 -05:00
KeatonTheBot d7f90f9f1f Tweak feedback loop restriction logic 2026-03-18 18:23:37 -05:00
sh0inx 1b714ab1aa [HLE] Added "null" check for isAtRest
If an npad device was checking for if the device was at rest, we called `GetSixAxisSensorLifo()` -- if the npad was of type `None`, it triggered the `NotImplementedException` in that method because `None` doesn't have a gyroscope, and therefore no data attached to it.
2026-03-15 16:16:14 -05:00
Coxxs a6af70baa1 HLE: Implement CreateContextForSystem
Implement nn::ssl::sf::ISslServiceForSystem -> CreateContextForSystem (100)

Ryujinx implements both ISslService and ISslServiceForSystem in one class so we can just return new ISslContext.

Also fixed the incorrect parameter reading order. (CreateContextForSystem basically has same params as CreateContext)
2026-03-14 18:25:36 -05:00
KeatonTheBot 8c9cdfe9ce Clean up texture cache code for low-VRAM GPUs 2026-03-14 18:06:09 -05:00
KeatonTheBotandStossy11 5b3a620bb0 Vulkan: Add A8B8G8R8 texture format
Co-authored-by: Stossy11 <stossy11@stossy11.com>
2026-03-04 16:43:46 -06:00
KeatonTheBot d977208b19 Update NuGet packages
* Avalonia to 11.3.12

* Gommon to 2.8.1.1

* Microsoft.IdentityModel.JsonWebTokens to 8.16.0

* Microsoft.NET.Test.Sdk to 18.3.0

* NUnit to 4.5.0
2026-02-26 17:42:27 -06:00
KeatonTheBot 477be704bb Update SkiaSharp to 3.119.2
* Update deprecated SkiaSharp code
2026-02-26 17:42:27 -06:00
KeatonTheBot 5c8be1b927 Optimize application pool sizes for DRAM selections 2026-02-26 17:42:27 -06:00
BowedCascade adc8ed594a Add restart emulation command
- Add RestartEmulation() method to MainWindowViewModel

- Add 'Restart Emulation' menu item with Ctrl+R keybinding

- Add locale translations for all supported languages

- No confirmation dialog (faster restart experience)
2026-02-26 17:42:27 -06:00
Digote 9104a75a48 HLE: Return LastUrl for Web/Offline BrowserApplet exit reason
Fixes infinite loop on title screen for Ni no Kuni when no save file exists.
2026-02-26 17:42:27 -06:00
sunshineinabox c0ba4e64cf Vulkan: Buffer alignment 2026-02-26 17:42:27 -06:00
sunshineinabox 6cf0a413a0 Vulkan: Enable pageable device-local memory 2026-02-26 17:39:07 -06:00
sunshineinabox caed40d99a Vulkan: StorageImageExtendedFormats support 2026-02-26 17:39:07 -06:00
V380-Ori f764ae2d58 Shader: skip initialization of all clip distances on macOS. 2026-02-25 17:15:47 -06:00
V380-Ori b16f74ffd9 Gpu: exclude RGBA16Float texture format for fast DMA copy on Apple silicon.
* Store a boolean for if Fast DMA texture copy is supported instead of checking vendor every time
2026-02-25 17:15:47 -06:00
V380-Ori bd96f2f51f Avalonia: fix HiDPI/Retina scaling for embedded window on macOS. 2026-02-25 17:15:46 -06:00
KeatonTheBot 3bf8a17f01 Tweak GetCpuName code 2026-02-21 14:02:34 -06:00
KeatonTheBot d5bd8871a2 Windows ARM (win-arm64) build now launches with full trimming 2026-02-20 15:16:32 -06:00
BowedCascade dcafe32d5c Fix backslash key not mappable in controller settings
Fixes keyboard mapping for the backslash key (\) which was previously unmappable in controller button assignment.
2026-02-19 09:51:24 -06:00
KeatonTheBot 18bd84c463 Update NuGet packages
Silk.NET to official NuGet 2.23.0

System group to 10.0.3
2026-02-10 17:09:56 -06:00
Coxxs 9a684ff066 audio: Fix crash due to invalid Splitter size
Fix crash caused by reading incorrect size of Splitter data.

In most games the crash doesnt happen if you have nn::audio::AudioRendererParameter VoiceCount aligned to 2, as that causes splitter data to be aligned by 0x10, but otherwise, the alignment by 0x10 done in SplitterContext->Update may exceed SplitterSize (which was previously labeled as Unknown24), causing a crash.

(Crash can be replicated by doing AudioRendererParameter.voiceCount++; in any SDK 20.X game that works, which doesn't cause crash on actual hardware)

(This patch is from a friend who makes mods.)
2026-02-01 16:11:46 -06:00
GreemDev 60c867622e audio backend projects code cleanup 2026-01-31 02:25:45 -06:00
Stossy11andGreemDev 3cdae775b1 Add macOS native Audio Backend
Adds Ryujinx.Audio.Backends.Apple, which is a AudioToolbox based audio backend for macOS (and by extension iOS). Also added in MeloNX.

* Expose AudioToolkit in UI

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-01-31 02:25:41 -06:00
sh0inx 061bfb4074 HLE: Stubbed IUserLocalCommuniationService SetProtocol (106)
Should fix Animal Crossing: New Horizons crashing on LDN connection and potentially other titles with Switch 2 updates.
2026-01-31 01:28:06 -06:00
KeatonTheBot 2b15dfc473 Update FluentAvalonia to 2.5.0 2026-01-30 12:16:22 -06:00
Coxxs a948308a2f HLE: Implement 10106 and 10107 in IPrepoService
In some games using newer SDK (e.g. Splatoon 3 11.0.0), when `nn::prepo::PlayReport::SetOptInCheckEnabled` is set to False, `nn::prepo::PlayReport::Save` will call `IPrepoService:10106` (SaveReport) and `IPrepoService:10107` (SaveReportWithUser) instead of 10104 (SaveReportOld) and 10105 (SaveReportWithUserOld).

A new param (optInCheckEnabled) is added in 10106/10107 compare to 10104/10105.

This should fix missing service error for Splatoon 3 11.0.0 and other games using newer SDK and has set `nn::prepo::PlayReport::SetOptInCheckEnabled` to False.
2026-01-29 20:56:00 -06:00
sh0inx a91f60b6ad HLE: Implement IHidServer IsSixAxisSensorAtRest
Fixes the actually insane amount of log spam in games that check for this, such as Luigi's Mansion 3.

Values in HidDevices.NpadDevices.isAtRest may need to be tuned to a better range for resting detection. I originally set them to 0, and my controller rests at definitely NOT 0.

Will need to be revisited when implementing functionality for the global SixAxisActive bool, IHidServer.StartSixAxisTracking, and IHidServer.StopSixAxisTracking.
2026-01-24 12:44:57 -06:00
KeatonTheBot 56eef239a4 Update OpenAL to 1.25.1 2026-01-20 17:02:52 -06:00
KeatonTheBot bef07155dc Update NuGet packages
* Avalonia to 11.3.11, Svg.Controls.Avalonia group to 11.3.9.2

* Gommon to 2.8.0.4

* NUnit3TestAdapter to 6.1.0

* Ryujinx.LibHac to 0.21.0-alpha.128

* System group to 10.0.2
2026-01-15 21:07:01 -06:00
KeatonTheBot 253f47c971 Linux: Fix file picker not launching from disabling core dumps
Core dumps are disabled by default on Linux, but this prevents access to the file picker due to security hardening. To work around this, core dumps are selectively enabled and disabled around the file picker tasks.
2026-01-14 17:05:32 -06:00
KeatonTheBotandGreemDev 7d0947cac6 Add CommunityToolkit.Mvvm
Use [ObservableProperty] attribute, which greatly reduces or eliminates boilerplate code needed to generate observable properties

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-01-14 16:58:45 -06:00
KeatonTheBot 0d29b77a14 Fix MainWindow height calculation, now launches in Steam Deck's Gaming Mode 2026-01-09 22:03:39 -06:00
KeatonTheBotandGreemDev 2aecce018c chore: CI sh/py script changes
Co-authored-by: GreemDev <greemdev@ryujinx.app>
2026-01-09 16:13:44 -06:00
KeatonTheBot 8ac2b1e497 UI: Update Svg.Controls.Avalonia group to 11.3.9.1 2026-01-06 10:19:34 -06:00
KeatonTheBot 9a1d33dde5 UI: Restore FluentAvaloniaUI package, disable animations on app initialization
* Avalonia's built-in color picker is now used when selecting a firmware avatar
2026-01-04 02:40:05 -06:00
KeatonTheBot 899c99740d Linux: Fix file picker not launching 2026-01-03 23:33:28 -06:00
KeatonTheBot 1390fd02d7 Vulkan: Add ROG Xbox Ally device ID to feedback loop restriction 2026-01-02 22:54:46 -06:00
KeatonTheBot 5ecdd197cd Fix ComboBox alignment on texture format dropdown 2025-12-27 23:08:43 -06:00
KeatonTheBot 546082128f Update NuGet packages
* Avalonia to 11.3.10

* Gommon to 2.8.0.3

* Silk.NET.Shaderc to 2.23.0

* System group to 10.0.1
2025-12-27 20:41:12 -06:00
KeatonTheBot 2aae771ab3 Clean up heap adjustment code 2025-12-27 18:53:20 -06:00
V380-Ori a2c53ed6cf Use Ryujinx.MoltenVK 1.4.1
Vulkan: remove InvalidResource workaround, as it is no longer needed since macOS Sonoma.

Vulkan: enable textureGatherOffsets on MoltenVK.
2025-12-26 15:19:19 -06:00
V380-Ori fba786cd38 Replace shaderc.net with Silk.NET.Shaderc 2025-12-26 15:09:50 -06:00
Francesco Saltori 292d42d394 Update Italian translation 2025-12-26 15:04:56 -06:00
KeatonTheBot afa4782493 Heap adjustments/fixes
* Restore 6 GiB heap for 4 GiB DRAM selection

* Limit heap to 8 GiB for games that crash or won't load with extended heaps
2025-12-25 02:20:45 -06:00
GreemDev b6cd6770ff Removed TypedStringEnumConverter; it exists in .NET now.
As per the remark XMLdoc on the type: Get rid of this converter if dotnet supports similar functionality out of the box.
2025-12-23 08:18:10 -06:00
KeatonTheBot fecb02647d Update FFmpeg to 6.1.4 for Windows/Linux 2025-12-23 01:40:39 -06:00
KeatonTheBot bf12b601b0 Update OpenAL to 1.25.0 2025-12-22 18:02:32 -06:00
GreemDev 4a5729cc01 feature: macOS Liquid Glass dynamic icon
No more squircle jail! Thanks @transistor.exe in the Ryubing discord for dropping the asset files for this to happen.
2025-12-22 11:54:22 -06:00
Coxxs 5a16e24450 gdb: add monitor get mapping
This add the following commands to Gdb Stub:

monitor get mappings
monitor get mappings <address>
monitor get mapping <address>

This is the first time arguments are used in Rcmd, so FindRcmdDelegate is changed to CallRcmdDelegate, to allow it to handle arguments cleanly.

The reply format mimics the same command on Atmosphere.

The forced pagination of monitor get mappings in Atmosphere is not added, as I think it's not needed here.
2025-12-21 23:03:56 -06:00
KeatonTheBot 2dfa040d92 Attempt #1: Fix win-arm64 build not launching with full trimming 2025-12-21 15:17:16 -06:00
KeatonTheBot da6b58b9fa Update Silk.NET.Vulkan to 2.23.0
* Add GitLab package registry to nuget.config
2025-12-19 17:49:03 -06:00
KeatonTheBotandAaron Robinson ad516777d9 Enable full trimming
Fix trim errors by excluding full assemblies from trimming that use reflection

Co-authored-by: Aaron Robinson <aaronrobin1234@gmail.com>
2025-12-19 17:49:03 -06:00
gdkchanandKeatonTheBot 812cecd713 Initial texture replacement implementation
Unmerged PR #5632, originally by @gdkchan

* Add mod manager support - @KeatonTheBot

* Remove redundant code - @KeatonTheBot

* Optimize, modernize code to newer C# standards where appropriate - @KeatonTheBot

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
2025-12-19 17:49:02 -06:00
KeatonTheBot d00bda7e53 Migrate SLN to SLNX 2025-12-13 13:32:54 -06:00
KeatonTheBot c70f692324 misc: chore: Fix possible NullReferenceExceptions, suppress warnings 2025-12-13 13:32:49 -06:00
LotP 9920619ff6 fix pre-action crash
if a buffer is inherited, ignore it and remove it from the list.

fixes a crash when a buffer is inherited during a sync and the containing ranges have been modified to not fit in the old buffer.
2025-12-13 12:09:14 -06:00
LotP 2ba33ec2ef Fix kaddressarbiter crash
Fixes a crash when trying to access the thread count on a not (yet) existing list of threads.
2025-12-06 20:29:22 -06:00
LotP e404954f88 Memory Changes 3.2
Fixes a few crashes:
- fixes a crash related to waking threads (priorities were wrong).
- fixes a crash from reusing the SetRenderTargets texture array (left-over data causing issues).
- fixes a mistake and an oversight in the buffer system.
  - buffers were getting updated wrong causing bad data to be stored or some times cut.
  - modified ranges would extend past their old buffers, crashing on syncs. Old buffers are now skipped as the new buffers already sync instead.

Introduces pooling in a few more places to increase memory efficiency.

simplified RangeList item logic.
- removed RangeItem by making all the range objects use the I(NonOverlapping)Range interface.
- BufferCache class no longer locks its RangeList, as the list is only ever accessed synchronously.

Small change to how keyboard snapshots are stored.

Increase ThreadedRenderer SpanPool size to fit slightly more data (4MB -> 8MB).
2025-12-06 19:41:14 -06:00
LotP 6b383873e8 Update BiquadFilterEffectParameter2.cs
Fixes audio issues in Metroid Prime 4 and other games
2025-12-05 08:13:33 -06:00
KeatonTheBot 32d1653af9 Fix heap size for kernel check and DRAM selections 2025-12-04 20:17:52 -06:00
Princess Piplup 298e2cdefd Fix SaveCurrentScreenshot
This fixes SaveCurrentScreenshot so it correctly saves the screenshot into the screenshot folder, it's no longer a stub

I was going to add the capture button so all games worked but tbh I only care for spongebob

games tested
Pokemon Z-A: https://files.raychu.xyz/u/1FaUGV.png
Pokemon Violet: https://files.raychu.xyz/u/6swfVS.png
Pokemon Violet: https://files.raychu.xyz/u/JaBBX2.png
Spongebob The Cosmic Shake: https://files.raychu.xyz/u/8z5X2e.png
2025-12-04 20:17:51 -06:00
Alula 5a53d36992 Disable coredumps by default on Linux 2025-12-04 20:04:34 -06:00
KeatonTheBot 8c99b90926 nuget: bump packages
* Humanizer to 3.0.1

* Microsoft.IdentityModel.JsonWebTokensto 8.15.0

* Microsoft.NET.Test.Sdk to 18.0.1
2025-11-21 11:48:38 -06:00
KeatonTheBot e287c9fc85 Null-conditional assignments, continued 2025-11-19 20:33:06 -06:00
GreemDev e349ef72fb Use the new C# 14 null propagation setter 2025-11-19 20:09:08 -06:00
GreemDev b7c13715f1 Add .NET Runtime version in About window under Ryujinx version. 2025-11-17 09:14:04 -06:00
KeatonTheBot 139e18a8e1 feature: .NET 10 2025-11-17 09:14:04 -06:00
Coxxs 58a76a0b9f Stub IWriterForApplication: 0 (CreateContextRegistrar)
Fix games that uses ContextRegistrar (e.g. After the socket blocking issue is fixed, Splatoon 3 and other ModuleSystem games will call this when booting, to create a context for the error, after a connection attempt to the server failed.)
2025-11-17 08:42:37 -06:00
KeatonTheBot 6292192cc0 misc: chore: Remove unnecessary usings 2025-11-11 17:40:33 -06:00
LotP a926a31133 Memory changes 3.1
Fixes audio bug causing static noise to play in certain games.

Fixes inputs being dropped after a certain amount of playtime.
2025-11-11 16:05:45 -06:00
GreemDev 17b7eadb91 UI: App Library: automatically remove nonexistent autoload/game dirs from the configuration upon load. 2025-11-10 20:34:39 -06:00
KeatonTheBot 61a783e10a Add 43:18 aspect ratio (for 3440x1440 [WQHD], 6880×2880 [UW6K+] users) 2025-11-08 16:49:46 -06:00
GreemDevandcomex 9b8517caf3 Fix socket closing on shutdown
Previously, sockets were only ever closed when the game specifically requested it.

Thanks @comex on GitHub for the patch submitted via the [Ryubing] issues page.

Co-Authored-By: comex <47517+comex@users.noreply.github.com>
2025-11-05 08:21:54 -06:00
KeatonTheBot 457f62d773 nuget: bump packages
* Gommon to 2.8.0.1

* Microsoft.NET.Test.Sdk to 18.0.0

* Newtonsoft.Json to 13.0.4 (reduces trim warnings by 56)

* Ryujinx.LibHac to 0.21.0-alpha.126

* System group to 9.0.10

* UnicornEngine.Unicorn to 2.1.4-a40db6c
2025-11-02 20:42:23 -06:00
KeatonTheBot a6c0eabf49 misc: Tweak NullReferenceException fixes 2025-10-31 01:04:00 -05:00
LotP e3ee28605d Memory changes 3
General memory improvements to decrease GC pressure and frequency.

Pool big arrays and objects that are created and deleted often.

Skip data copies when they aren't needed.

Inline flag checks to skip unneeded allocations.

From my testing the performance is about the same, but the GC frequency is much lower and collection is faster causing less and smaller spikes.
2025-10-30 23:25:51 -05:00
KeatonTheBot 2c50fbcc1f misc: Use nint/nuint instead of IntPtr/UIntPtr 2025-10-29 14:54:07 -05:00
GreemDev 90cb9d42db hle: Throw a ServiceNotImplementedException instead of ArgumentException if any number arguments provided to ILibraryAppletAccessor are nonzero 2025-10-29 14:52:15 -05:00
LotP 1a3b407839 ILibraryAppletAccessor:90 tweak
Minor update to logic to make our work easier in the future.
2025-10-29 14:52:13 -05:00
sh0inx 46a0783293 HLE: Stub ILibraryAppletAccessor Unknown90
This lets games such as The Legend of Zelda: Tears of the Kingdom (v1.4.2) and other SDK 20+ games successfully circumvent a crash when calling an applet.

Example: No controller connected on boot -> calls Controller Applet -> no stub = crash.
2025-10-27 13:11:33 -05:00
KeatonTheBot 82ae055fe5 Code cleanup: Audio effects fix and audio object pooling
Commit cdbe5686
2025-10-26 17:33:51 -05:00
LotP f98af9e175 gpu allocation optimizations
ObjectPool now uses ConcurrentBag instead if ConcurrentStack, as it has a smaller memory footprint.

Fix compiler warnings related to Audio Command Pools.

Switch gpu command initialization to use pointers, that way skipping the allocation of the command which is unnecessary.

Skip byte array allocation in Ioctl2/3 if it isn't needed (if the source data is all continuous we don't need to copy it to make it continuous).
2025-10-26 16:46:55 -05:00
LotP cdbe568620 audio effects fix and audio object pooling
Revert and reimplement Float BiquadFilterEffect support, fixes infinite load issues in a few games like Splatoon 3.

Fix incorrect string check with the new thread naming system.

Implement object pooling for all Audio Commands and a few other audio related objects and use a growing error list for updating wave buffers instead of always allocating space for 8 errors.
2025-10-25 23:49:52 -05:00
KeatonTheBot 86f47b8333 Revert cleanup change in KScheduler.cs
From commit a7af26bc.
2025-10-25 17:57:31 -05:00
Coxxs 2993dba583 Fix application list loads slowly when RyuLDN is enabled
Currently, application list will not show until ApplicationLibrary_LdnGameDataReceived calls ViewModel.RefreshView();, forcing a refresh. This makes application list load slowly when RyuLDN is enabled.
2025-10-24 14:30:38 -05:00
GreemDev e720e4a87b gdb: YACC (yet another cleanup commit) 2025-10-24 09:47:08 -05:00
Coxxs f30d832847 Skip directories (and do not RecurseSubdirectories) when finding the icon fallback
Newer applications have a folder for ounce in the Control nca. This fixes Ryujinx trying to open a folder as a file, causing another exception, when trying to find the icon fallback.
2025-10-24 09:47:08 -05:00
Coxxs afa1ab95c2 Move ProcessInfo and Minidump to HleProcessDebugger
This allows developers to call them in the code for debugging.
2025-10-22 16:35:09 -05:00
GreemDev 52b6fcf1a4 gdb: some more cleanups 2025-10-22 15:25:33 -05:00
GreemDev 35449846d9 gdb: Make waiting for a process to start more forgiving (200ms per poll 10x -> 500ms) 2025-10-22 08:10:25 -05:00
Coxxs 5d68312b54 gdb: Abort if unable to start GDB server
When users enable GDB stub, we assume they intend to use it (it slows down the game a lot). If the server cannot start, we abort to save users' time troubleshooting why their GDB client can not connect.
2025-10-22 08:10:24 -05:00
GreemDev b8aaadac5f gdb: Catch SocketException from TcpListener#Start 2025-10-22 08:09:48 -05:00
KeatonTheBot a369b246a3 Fix Ori 2 not launching (revert Possible NullReferenceException change) 2025-10-21 16:43:03 -05:00
KeatonTheBot d96e93ef0d Missed application pool for 4 GiB DRAM 2025-10-21 13:29:57 -05:00
GreemDev 0bfd2ad3ef gpu: tweak: Do not log missing Votevtg implementation. 2025-10-21 13:25:32 -05:00
Coxxs df36c23652 gdb: fix IsProcess32Bit throws exception if called too early
For some reason, LLDB will call commands that use IsProcess32Bit earlier than GDB.
2025-10-20 21:53:26 -05:00
GreemDev 843a7113d1 chore: fix in-code typos 2025-10-20 21:53:26 -05:00
GreemDev 2d420ee561 gdb: dynamic rcmd system & more cleanups 2025-10-20 21:53:26 -05:00
Coxxs b958044e4e gdb: Support qAttached; Add missing ReplyOK when detach 2025-10-20 21:34:19 -05:00
Coxxs d659bec27a Implement IUserServiceCreator: 1 (CreateClientProcessMonitor)
This should fix nn::ldn::Initialize in games that use SDK 18 or higher.
2025-10-20 20:57:30 -05:00
Coxxs 107df58666 Update LoadIdTokenCache for 19.0.0+
This should stub nn::account::LoadNetworkServiceAccountIdTokenCache for games that use SDK 19 or higher.
2025-10-20 20:57:06 -05:00
KeatonTheBot 280461dc78 Update NUnit to 4.4.0
* NUnit3TestAdapter to 5.2.0

* Rename Assert class to ClassicAssert to align with NUnit 4.x changes
2025-10-19 23:01:06 -05:00
KeatonTheBot 1724149443 misc: chore: Remove unnecessary usings 2025-10-19 17:48:37 -05:00
Xam b44e2a87fd Fix duplicate volume and mode change events in AppHost 2025-10-19 17:47:12 -05:00
Xam b1f767b2d5 Input: AvaloniaMouseDriver: fix native touch inputs 2025-10-19 17:46:22 -05:00
Xam ec9713b111 Horizon: Audio: HwopusIpcServer: fix random crashes regression in Pokemon Quest 2025-10-19 17:45:43 -05:00
GreemDev a7af26bc1f gdb: More cleanup changes
- Move the message handler into its debugger class part,
- Move all message types into one file and collapse 3 of the ones with no data into a generic, stateless message with a single property being its type,
- Add an Fpscr helper property on IExecutionContext along with a comment about what Fpscr is (similar to the other registers in there)
- Moved the Rcmd helpers (such as GetRegisters, GetMinidump, etc) into a dedicated Debugger class part,
- Fixed the double-collection (ToArray being called twice) in GetThreadUids & GetThread in KProcess
2025-10-19 09:56:08 -05:00
GreemDev 91a5f69805 gdb: more cleanups
- convert GdbRegisters utilities into extensions on IExecutionContext

- add a Write/Read Register helper on Debugger that handles 32/64 bit instead of doing that for every usage of register reading/writing
2025-10-18 09:50:01 -05:00
GreemDev f908bfe150 gdb: Code cleanup pass #2
Moved the reply functionality into the command processor, move the main debugger thread into a dedicated class part, and more
2025-10-17 08:27:13 -05:00
Coxxs a2c494e216 gdb: Cleanup
Remove unused function and fix a bug.
2025-10-17 08:26:32 -05:00
GreemDev e489e0d19f gdb: Cleanup Debugger.cs
by moving the GDB command handlers and command processor out of the class and into their own
2025-10-17 08:23:54 -05:00
KeatonTheBot f362e33293 12 GiB heap crash workaround
Using resolution mods, heaps past 8 GiB work in some games (like LoZ: TotK) and not in others (like SMP Jamboree). Setting the heap to a hard limit of 8 GiB on the 10 & 12 GiB DRAM options seems to be the safe bet right now until a better solution is found.
2025-10-16 15:38:34 -05:00
GreemDevandCoxxs 1e2fd6f4c2 misc: Update Ryujinx.LibHac
Match the behavior with AMS: https://github.com/Atmosphere-NX/Atmosphere/blob/c8e39a54d257bf9875ac852fc1521f046c968339/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp#L93

This should fix the error ResultFs.InvalidArgument (2002-6001) in some nca.

Co-authored-by: Coxxs <58-coxxs@users.noreply.git.ryujinx.app>
2025-10-16 13:40:52 -05:00
Coxxs 35a8116ae8 gdb: Do not skip CheckInterrupt when gdb stub is enabled
When GDB stub is enabled, CheckSynchronization is called too frequently because it skips _nativeContext.SetCounter(MinCountForCheck) (called in CheckInterrupt()), causing CheckSynchronization to be constantly called in JIT. This could cause performance issue.

It also skips the call to the KProcess.InterruptHandler, which causes some games to deadlock when GDB stub is enabled.

I'm not sure if this change will cause any side effects, but it seems GDB stub is still working correctly after this change.

This change will not affect regular users.
2025-10-16 11:07:05 -05:00
KeatonTheBot 41b8b6961c Restore original application pool sizes for DRAM selections 2025-10-15 21:52:24 -05:00
LotPandKeatonTheBot 42727fca07 12 GiB heap support
The heap was limited to 6 GiB no matter the memory setting, causing memory configurations above 8 GiB to not actually affect the heap size.

Now when the memory config is set to [10 or] 12 GiB the heap also allocates 12 GiB.

The SetHeapSize SysCall will now allow heap sizes up to 12 GiB (technically slightly less).

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
2025-10-15 21:49:11 -05:00
Coxxs f5822c16af Flush the error log before exit
Currently, some logs can be missing when a fatal error occurs (especially GuestBrokeExecutionException).

This MR attempts to flush logs to console and file before process exit.
2025-10-13 21:59:20 -05:00
GreemDev 66a5719e10 UI: Move IgnoreControllerApplet to the System config section object 2025-10-12 21:48:26 -05:00
KeatonTheBot cc2fb8f0b6 UI: Fix line breaks in Turbo Mode tooltips 2025-10-12 21:48:26 -05:00
Goodfeat c9c78841b9 feature: add the ability to skip profile select dialog when opening games that use it
the skip behavior is done by passing the user id of the profile you have selected in Options > Manage User Profiles
2025-10-12 21:48:25 -05:00
KeatonTheBot f26bd3d321 UI: Update FluentAvalonia.NoAnim to 2.4.0-build3 2025-10-12 18:31:34 -05:00
Coxxs b28f52387a gdb: Fix the crash that occurs when GDB is connected early
* "Suspend Application on Start" is on
2025-10-11 21:22:40 -05:00
Coxxs 83412689b7 gdb: Add monitor minidump command
This will dump the process information, stack trace, and registers from all threads to both the Ryujinx log and gdb.
2025-10-11 10:44:00 -05:00
LotP 0b5f312b20 Sync thread name on Schedule
After commit 51ca73cb, using a non-blocking socket (e.g. poll(..., timeout=0)) will still result in a blocking socket.

It appears the above commit inverted the behavior.
This merge request fixes the blocking flag: blocking will be set to false when the flag is 0x800.
2025-10-11 10:42:56 -05:00
LotP c0b7452cb5 SDK20 and REV15 support
* Fixed an issue where games would boot loop because of an incorrect HID state.

  * Turns out the SamplingNumber of the atomic input storage doesn't match the SamplingNumber of the input state held by the atomic storage, instead it is exactly double the value in the input state.

* Added new Condition struct to the HID Shared memory and populate it with dummy data to fix the no-controller crash (already merged).

* The audio renderer has been mostly updated to rev15, allowing rev15 games to launch.

  * Biquad filters now use floats.

  * Several structures have been renamed to match the SDK names, making it easier to compare functionality. A few names are still missing and will be changed at a later date.

  * The new commands from rev15 have been added to the CommandType enum, but they are still missing from the code itself.

    * Due to changes in the SDK layout, the time estimation functions are either missing or very well hidden (or Ghidra search functionality is useless). We can't fully implement the new commands until the timing data has been located.

  * A few minor tweaks to the code have been made to more accurately match the SDK.
2025-10-11 10:39:50 -05:00
Judas Drekonym 6ba8ae53f2 Add TitleID sort method
Adds an additional application list sorting method for the TitleID. A
bit of a niche choice for sorting but I think the TID is a relevant
enough piece of metadata that it should be there. (And I personally
would be using it)

- Using existing TitleId constant in ApplicationSort, implying this was
meant to be in the sorting options at some point?
- Reuses the "DlcManagerTableHeadingTitleIdLabel" locale for fulfilling
the need already, might be better to make a unique one for this in the
long run but this codebase is new to me so I wanted to make the changes
as unobtrusive as possible
- Using app.Id for the comparer seems to work fine, not sure if using
something else like IdString would be better?
2025-10-10 12:14:44 -05:00
Evan Husted d3bce3f361 UI: Only show DLC RomFS button under Extract Data when DLCs are available.
Also convert the constructor of DlcSelectViewModel to expect a normal title id and not one already converted to the base ID.
2025-10-09 17:18:50 -05:00
Evan Husted 2471bb8ede UI: Move DLC RomFS dumping under normal RomFS dumping.
Also removed it from DLC manager.
2025-10-09 17:18:50 -05:00
KeatonTheBot c3bfce3378 Update Kenji-NX to 2.0.5 2025-10-06 08:57:12 -05:00
KeatonTheBot 88e8d1309c UI: RPC: Asset images
* Final Fantasy Tactics: The Ivalice Chronicles

* Hades II

* Sonic Racing: CrossWorlds

* Super Mario Galaxy 1 & 2
2025-10-03 10:06:02 -05:00
KeatonTheBot ebeb532099 Fix build error: Replace StartsWithIgnoreCase with standard .NET equivalent 2025-10-02 15:18:35 -05:00
KeatonTheBot c501fe264b Fix push descriptors bugfix logic for Intel Arc on Linux 2025-10-01 15:30:02 -05:00
GreemDev 61d0430006 vulkan: Intel Arc on Linux also has the push descriptors bug. 2025-10-01 14:20:02 -05:00
KeatonTheBot cd477cf744 Ryujinx.csproj: Move PublishTrimmed setting for win-arm64 into existing PropertyGroup 2025-10-01 08:49:24 -05:00
KeatonTheBot 0487b781dc Revert NullReferenceException change in IntervalTree.cs
* Not technically needed because newNode.Parent is the same value as parent
2025-09-30 17:53:09 -05:00
KeatonTheBot 51b32981b6 misc: chore: Fix possible NullReferenceExceptions, InvalidOperationExceptions 2025-09-30 15:05:14 -05:00
KeatonTheBot fa682d406e misc: chore: Merge into pattern 2025-09-24 13:51:15 -05:00
KeatonTheBot 503dea74c2 misc: chore: Remove unnecessary usings 2025-09-24 13:48:36 -05:00
KeatonTheBot 1f979e5c6f misc: chore: Fix object creation 2025-09-24 13:44:00 -05:00
KeatonTheBot 378dc33cca misc: chore: Discard unused parameters 2025-09-24 13:26:50 -05:00
KeatonTheBot 9c37a557dd misc: chore: Merge duplicated 'if' branches 2025-09-24 13:26:50 -05:00
Mcost45 6e5bd0c9f2 Include SL/SR default bindings for single joycons
Single L/R Joycons default to unbound for the SL/SR inputs - so by default you can't progress past 'press L + R to continue' type screens.
But

* ConfigGamepadInputId.SingleLeftTrigger0(L)

* ConfigGamepadInputId.SingleRightTrigger0(L)

* ConfigGamepadInputId.SingleLeftTrigger1(R)

* ConfigGamepadInputId.SingleRightTrigger1(R)

already exist (and I verified these are the inputs triggered by the SL/SR buttons), so my change would default to these instead.
2025-09-23 09:27:59 -05:00
Alula 0f2b11d4d5 feat: resolve real module names in HLE debugger 2025-09-20 11:32:15 -05:00
KeatonTheBot 1abf5b82c5 Fix: Configuration migration for Turbo Mode 2025-09-19 16:28:59 -05:00
KeatonTheBot d751cfa81a UI: Update Svg.Controls.Avalonia group to 11.3.6.2 2025-09-19 08:20:24 -05:00
KeatonTheBot af14fadb2f UI: Update Avalonia to 11.3.6 2025-09-17 09:15:01 -05:00
KeatonTheBot 4fd4e3a4ff Increase # of maximum log files from 4 to 5 2025-09-15 16:21:11 -05:00
KeatonTheBot a1834900be Revert stream loader changes
"add stream based loaders" - Commit c16559f2

"Fix some DLCs crashing due to stream loader change" - Commit a62deb8c
2025-09-14 14:51:46 -05:00
KeatonTheBot fe7a30c747 Revert Android changes
* "Android: NCE support" - Commit cd3221ab

* "Android: Numerous fixes" - Commit 19dd23c2

* "Android: Memory specific switches" - Commit 98b4ff33

* "Android: Remove unmanaged code" - Commit 846b5b6e
2025-09-14 14:51:44 -05:00
KeatonTheBot 3a387309b4 Memory changes part 2 crash fix
* This was an oversight: Two lines were left out when initially cherry-picking from Ryubing
2025-09-12 17:58:00 -05:00
KeatonTheBot 867a92a8bc nuget: bump System group to 9.0.9 2025-09-10 15:41:20 -05:00
Coxxs 45bf443c01 Fix headless mode
Fix the error

`Error setting value to option 'gdb-stub-port': Check if Option or Value attribute values are set properly for the given type.`

https://github.com/commandlineparser/commandline/issues/612
2025-09-10 13:14:07 -05:00
LotP a09568d1b7 Memory changes 2.2.1
Cleans up some leftover comments i forgot to remove.

Potentially fixes 1 more crash.
2025-09-07 13:11:57 -05:00
LotP dae7ae7eaf Memory changes 2.2
A few more internal changes to the RangeList systems.

* No longer using a QuickAccess dictionary.

  * The performance of the dictionary wasn't much faster than just doing binary searches.

  * Using just binary searches allows us to take advantage of span and array returns as they're are faster than linked lists when iterating or copying the overlaps.

Small code optimizations.

Fixes a few leftover crashes.
2025-09-06 12:26:08 -05:00
KeatonTheBot c2f54d0a5c UI: RPC: Hollow Knight Silksong asset image 2025-09-05 08:59:38 -05:00
LotP f4dfdd35b2 hle: Basic event handle implementation for IApplicationFunctions 210
Lets Hollow Knight: Silksong boot.

* remove stub

* Add version comment
2025-09-05 08:59:26 -05:00
GreemDevandKeatonTheBot c56d04a9a9 feature: Turbo Mode
Adds an elapsed tick multiplier feature which speeds up games which are built upon delta time.

More information: https://web.archive.org/web/20240713135029/https://github.com/Ryujinx/Ryujinx/pull/6456

Co-authored-by: KeatonTheBot <keaton@ryujinx.app>
2025-09-04 23:49:32 -05:00
KeatonTheBot 5c6175cebf UI: Fix Match System Time setting not appropriately disabling/enabling System Time options 2025-09-04 23:49:26 -05:00
KeatonTheBot 9addccb50f misc: chore: Replace Gommon functions with standard .NET equivalents (part 3) 2025-09-04 14:28:54 -05:00
KeatonTheBot 17946c78e6 UI: Update Avalonia to 11.3.5 2025-09-04 12:31:51 -05:00
KeatonTheBot 29647b6351 Update SDL to 2.32.10 2025-09-02 10:33:51 -05:00
KeatonTheBot 6e6983799e Update FFmpeg runtimes to 6.1.3 for Windows/Linux 2025-09-01 12:44:24 -05:00
LotP 61da23cb9e Memory changes 2.1
* Fixes a few crashes

* Simplifies a few functions

* Changes a few calls to use faster methods
2025-08-31 20:42:31 -05:00
KeatonTheBot a62deb8cfd Fix some DLCs crashing due to stream loader change 2025-08-31 20:42:28 -05:00
KeatonTheBot 8ea79ae5d5 UI: Update Svg.Controls.Avalonia group to 11.3.0.4 2025-08-28 16:26:13 -05:00
GreemDev 09f3bf4b55 [ci skip] chore: Change LDN server URL (it's the same server, just a more official URL) 2025-08-28 09:50:29 -05:00
LotP 171624e7f0 Memory Changes part 2
* Slightly refactors RangeLists from the last Memory Changes MR, which fixes issue 61.

* Convert as many const size array iterators to span iterators as possible. When iterating over a const size array, every iteration created a Span, now only the first iteration does in most places.

* Now using object pooling for a few object types that were rapidly deleted and recreated.

* Converted a few flag checks to binary operations to save memory allocations.
2025-08-26 15:30:12 -05:00
LotP 01cb33f658 Memory Changes
* Refactors the RangeList and derivative classes used for handling lists of regions

* The Binary searches are now more performant, relying on edge searches instead of just returning the first matching hit and manually iterating until the edge is found

* Most look-ups now return a RangeItem, which acts as a linked list node now, instead where possible, moving away from Array copies. This should help with some specific lag spikes.

* Made IntrusiveRedBlackTreeNodes act like linked list nodes too to improve the lookup time of minimums, maximums and successors.

* Changed a few cases of HasFlag() into binary operations to save on memory allocations.

In general, [these changes] should increase frame time stability and lag spikes, but at the cost of some overhead to memory look-ups, the result being a very slightly better average fps from my testing (~1-2%).
2025-08-26 15:28:43 -05:00
KeatonTheBot 121bb5dc76 UI: Update Avalonia to 11.3.4
* Fix text and ComboBox alignment in Save Manager
2025-08-21 11:36:23 -05:00
KeatonTheBot d73ef0e3fb nuget: bump packages
* Microsoft.CodeAnalysis.CSharp to 4.12.0

* Microsoft.IdentityModel.JsonWebTokens to 8.14.0

* UnicornEngine.Unicorn to 2.1.3
2025-08-20 16:51:15 -05:00
MaxLastBreathandGreemDev 46b2da2435 Fix Avalonia Native MouseWheel-Support
* Rename timer interval constant

* Also cut the delay after which scrolling is considered ended in half

Co-authored-by: GreemDev <greemdev@ryujinx.app>
2025-08-20 11:51:46 -05:00
gdkchan aca897c70c Protect against stack overflow caused by deep recursive calls
* PPTC version bump

* Also reset call depth when not using the unmanaged dispatch loop

* Increment call depth on function start rather than before call
2025-08-20 11:50:15 -05:00
gdkchan e7d423cd07 Avoid lookup of invalid textures if pool did not change 2025-08-17 00:41:30 -05:00
KeatonTheBot 48c809a80d Revert "Memory Changes"
This reverts commit d5c9bc662c.

Solves crash in Kirby Star Allies after a few mins of gameplay, possibly other titles.
2025-08-17 00:19:01 -05:00
Coxxs 7fd01214cf gdb: Improve stepping 2025-08-11 15:06:46 -05:00
Coxxs ef72e9b790 Add GDB Stub 2025-08-11 15:06:41 -05:00
LotP d5c9bc662c Memory Changes 2025-08-07 11:59:44 -05:00
KeatonTheBot 3cf90e808d nuget: bump System group to 9.0.8 2025-08-06 08:49:45 -05:00
KeatonTheBot 326f744093 nuget: bump DiscordRichPresence to 1.6.1.70 2025-08-05 14:04:33 -05:00
KeatonTheBot 6cecbaa625 UI: Update FluentAvalonia.NoAnim to 2.4.0-build2, revert ColorPicker changes 2025-07-30 21:19:15 -05:00
KeatonTheBot 1443e1a2ff UI: Update Avalonia to 11.3.2, FluentAvalonia to 2.4.0
* FluentAvalonia: Disabled NavigationView selection indicator animations due to bugged implementation in 2.1.0+, restoring previous behavior

* Avalonia: Fixed text on certain buttons being larger than normal

* Avalonia: Fixed ComboBox code inserting extra space to the left of selected items
2025-07-29 18:01:11 -05:00
KeatonTheBot 12d7f95468 nuget: bump DiscordRichPresence to 1.4.1.37, Microsoft.IdentityModel.JsonWebTokens to 8.13.0 2025-07-29 17:48:55 -05:00
KeatonTheBot b59a833467 UI: RPC: Pokémon Friends asset image 2025-07-28 20:10:33 -05:00
GreemDev 50a2caf799 Update Ryujinx.LibHac
This should fix crashes with mods that worked on Ryubing 1.3.1.

Thanks @cyphix!

https://git.ryujinx.app/ryubing/libhac/-/commit/e39169ab5053ff179b7fef38f624dfc608d2596c
2025-07-21 22:43:51 -05:00
KeatonTheBot 7d1fd99d3d nuget: bump System group to 9.0.7 2025-07-12 16:22:42 -05:00
Emmanuel Hansen c16559f20e add stream based loaders
* extend stream loading support

* fix content manager rebase

* fix update searching
2025-06-28 13:41:52 -05:00
Coxxs 6ecd6111d5 fix: UI deadlock when launching a game with "Trace Logs" enabled
This fixes https://github.com/Ryubing/Issues/issues/30

* Switch to "Release" build config (PerformanceCheck(); will only be called in Release build config)

* Enable "Trace Logs" in Ryujinx settings

* Double-click a game to launch

* Ryujinx will attempt to open a confirmation dialog box that never opens, causing UI deadlock
2025-06-20 10:40:29 -05:00
KeatonTheBot 0ee71fa064 misc: chore: Replace Gommon functions with standard .NET equivalents (part 2) 2025-06-20 10:36:56 -05:00
KeatonTheBot 6e8f7e7fcb misc: chore: Replace additional instances of Gommon ForEach with 'foreach' statements 2025-06-18 16:19:17 -05:00
KeatonTheBot f9780b33bb misc: chore: Use 'foreach' statement in place of Gommon ForEach in "delete all" mod manager crash fix
* Simplify statement for valid mod folder checking
2025-06-18 02:41:02 -05:00
KeatonTheBot d2a532f971 misc: chore: Fix numerous NullReferenceExceptions, InvalidOperationExceptions 2025-06-18 02:25:06 -05:00
Coxxs a90db3464d fix: socket blocking flag is inverted when setting it 2025-06-13 17:00:28 -05:00
KeatonTheBot c61d171e52 nuget: bump packages
* DynamicData to 9.4.1

* Microsoft.IdentityModel.JsonWebTokens to 8.12.0
2025-06-12 19:16:04 -05:00
KeatonTheBot 8927a78c4d nuget: bump DiscordRichPresence to 1.3.0.28 2025-06-12 17:05:53 -05:00
KeatonTheBot aa836dd924 nuget: bump System group to 9.0.6 2025-06-11 14:07:59 -05:00
mqudsi 878ea70aa9 Work around Escape hotkey race with exit confirmation dialog 2025-06-11 10:47:53 -05:00
rockingdice 9f0a35ee9b fix: crash caused by cursor overflow
* This fixes a crash that occurred when opening the soft keyboard for the second time
2025-06-10 18:17:48 -05:00
rockingdice ed0ebdec5b fix: use the correct font family for CJK characters 2025-06-10 18:16:56 -05:00
KeatonTheBot b5a1272141 Update SDL2 to 2.32.8 2025-06-09 03:08:12 -05:00
GreemDev 6768e1fa71 infra: Update to Ryujinx.LibHac 0.20.0
This is identical to the previous version, it's just on NuGet.org so we can comment out the LibHacAlpha source in nuget.config.
2025-06-08 23:20:47 -05:00
GreemDev 5c2d710fbb misc: Update LibHac
* _lastFileOffset now correctly stores the offset for the last file in the chain

  * Fixes an issue where a RomFS mod with both overridden and new files in the same folder would load incorrectly

* Renamed a variable with the wrong name

* Now behaves correctly when the same file is added twice in a row compared to V1 MR
2025-06-06 17:06:35 -05:00
LotP d3f3eacc41 fix: use accurate length for enumerating
See merge request ryubing/ryujinx!49
2025-06-04 11:42:21 -05:00
LotP 5983cb9e5f Update LibHac
* Fixes Avalonia timeout

* Cuts RomFS rebuilding times by up to a factor of 1000 in games with big RomFS like TotK
2025-06-01 02:26:43 -05:00
KeatonTheBot e09f16838e Suppress CA1416 warnings, correct argument kind in IFileSystem 2025-06-01 02:26:15 -05:00
KeatonTheBot 18f302c0b0 Update OpenTK to 4.9.4, OpenAL to 1.24.3 2025-06-01 02:26:01 -05:00
KeatonTheBot 8f77076117 Change stick visualizer color to system accent color 2025-06-01 02:25:39 -05:00
KeatonTheBot 39544e323c Update Kenji-NX to 2.0.4 2025-05-29 22:01:40 -05:00
KeatonTheBot 24932b7e1b infra: Remove Ryujinx.Common references from project files that indirectly reference the same file 2025-05-29 11:10:57 -05:00
KeatonTheBot 2aaf34937d infra: Fix missing libarmeilleure-jitsupport.dylib on macOS (arm64) builds 2025-05-29 10:28:00 -05:00
KeatonTheBot bce3426d26 Linux: Fix games not launching (from Bionic code) 2025-05-28 03:37:23 -05:00
MrKev 51ca73cb10 Fix JWT Claims and Socket Flag Handling to Improve Just Dance® Server Connection 2025-05-27 18:28:15 -05:00
KeatonTheBot cd3221abdc Android: NCE support 2025-05-26 16:02:20 -05:00
KeatonTheBot 19dd23c288 Android: Numerous fixes
* GetSerialNumber in ISystemSettingsServer

* Bionic qualifiers

* Revert application pool size to 3285 MB for 4 GB DRAM

* Fix PPTC recompilation if mod or patch is applied
2025-05-26 16:01:23 -05:00
GreemDev 20f1cef9b2 fix: Super Mario Party Jamboree audio renderer crashing 2025-05-26 10:22:13 -05:00
KeatonTheBot 2f400491d5 ffmpeg: Fix green screen issues on Linux 2025-05-25 22:34:06 -05:00
ChromJ f6351e3fff Add Ctrl+Comma shortcut for settings
See merge request kenji-nx/ryujinx!2
2025-05-24 23:47:13 -05:00
KeatonTheBot f15ddcf9c4 UI: Fix GitLab logos not showing on git server in Ryujinx.UI.Common\Resources folder due to capitalization error 2025-05-24 22:23:44 -05:00
Evan Husted 88d904acde Headless in Avalonia v2
Launch the Ryujinx.exe, first argument --no-gui or nogui, and the rest of the arguments should be your normal headless script. You can include the new option --use-main-config which will provide any arguments that you don't, filled in from your main config made by the UI.
2025-05-24 19:32:45 -05:00
KeatonTheBot fbcf57bef5 UI: RPC: Add LUNAR Remastered Collection 2025-05-22 15:29:39 -05:00
KeatonTheBot 8bfb670e65 infra: Update SDL2 to 2.32.6 2025-05-21 15:50:27 -05:00
KeatonTheBot e00bece65a nuget: Remove GtkSharp packages 2025-05-20 02:26:44 -05:00
GreemDev 2a03f7b1d4 infra: Update to Ryujinx.LibHac 0.20.0.
This time it's pulled in via GitLab package registry.
2025-05-20 00:28:49 -05:00
KeatonTheBot 6c7b7d6fc4 UI: Change the GitHub button in the About window to GitLab 2025-05-19 23:03:37 -05:00
KeatonTheBot c43db7d5f6 Update README.md and other documents 2025-05-17 13:35:37 -05:00
KeatonTheBot cc92af12a4 infra: Fix clean installation crash 2025-05-16 17:23:10 -05:00
650 changed files with 13442 additions and 37939 deletions
+35
View File
@@ -0,0 +1,35 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"renovate/config"
],
"enabledManagers": ["nuget"],
"packageRules": [
{
// require approval for *all* NuGet package updates, not just major versions.
"matchDepTypes": "nuget",
"dependencyDashboardApproval": true
},
{
// Ignore Gommon for automatic updates. I make breaking changes on minor updates not infrequently.
"matchDepNames": "Gommon",
"matchDepTypes": "nuget",
"enabled": false
},
{
"description": "group Silk.NET packages",
"extends": ["renovate/config//groups/silkdotnet.json"],
"groupName": "Silk.NET"
},
{
"description": "group OpenTK packages",
"extends": ["renovate/config//groups/opentk.json"],
"groupName": "OpenTK"
},
{
"description": "group Svg.Controls packages",
"extends": ["renovate/config//groups/svgcontrols.json"],
"groupName": "Svg.Controls"
}
]
}
+4 -3
View File
@@ -16,6 +16,10 @@ x64/
build/ build/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
AppDir/
publish_appimage/
publish_ava/
publish_tmp_ava/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/ !packages/*/build/
@@ -173,6 +177,3 @@ PublishProfiles/
# Glade backup files # Glade backup files
*.glade~ *.glade~
# Log files
/logs/
-9
View File
@@ -1,9 +0,0 @@
[submodule "src/KenjinxAndroid/app/src/main/cpp/libraries/adrenotools"]
path = src/KenjinxAndroid/app/src/main/cpp/libraries/adrenotools
url = https://git.ryujinx.app/kenji-nx/libadrenotools.git
[submodule "src/KenjinxAndroid/app/src/main/cpp/libraries/openal"]
path = src/KenjinxAndroid/app/src/main/cpp/libraries/openal
url = https://git.ryujinx.app/kenji-nx/openal-soft.git
[submodule "src/KenjinxAndroid/app/src/main/cpp/libraries/adrenotools/lib/linkernsbypass"]
path = src/KenjinxAndroid/app/src/main/cpp/libraries/adrenotools/lib/linkernsbypass
url = https://git.ryujinx.app/kenji-nx/liblinkernsbypass.git
+2 -1
View File
@@ -1,6 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<DebugType Condition="'$(Configuration)'=='Release'">none</DebugType>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+21 -23
View File
@@ -3,41 +3,41 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.13" /> <PackageVersion Include="Avalonia" Version="12.1.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="12.1.2" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.13" /> <PackageVersion Include="Avalonia.Desktop" Version="12.1.1" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.13" /> <PackageVersion Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.13" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="12.1.1" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" /> <PackageVersion Include="Svg.Controls.Avalonia" Version="12.0.0.15" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" /> <PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="12.0.0.15" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="Concentus" Version="2.2.2" /> <PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" /> <PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.31" /> <PackageVersion Include="DynamicData" Version="9.4.33" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.4.1" /> <PackageVersion Include="FluentAvaloniaUI" Version="3.1.0" />
<PackageVersion Include="Gommon" Version="2.8.1.1" /> <PackageVersion Include="Gommon" Version="2.8.1.2" />
<PackageVersion Include="Humanizer" Version="3.0.10" /> <PackageVersion Include="Humanizer" Version="3.0.10" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.17.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.21.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" /> <PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="NUnit" Version="4.5.1" /> <PackageVersion Include="NUnit" Version="4.6.1" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.2.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageVersion Include="OpenTK.Core" Version="4.9.4" /> <PackageVersion Include="OpenTK.Core" Version="4.9.4" />
<PackageVersion Include="OpenTK.Graphics" Version="4.9.4" /> <PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" /> <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" /> <PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.2" /> <PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.2" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Linux" Version="6.1.3-build5" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Linux" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.macOS" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.macOS" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.3-build5" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.6" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.128" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.707.0" /> <PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.707.0" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
@@ -45,12 +45,10 @@
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" /> <PackageVersion Include="SkiaSharp" Version="3.119.4" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.119.4" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.12" /> <PackageVersion Include="System.IO.Hashing" Version="10.0.11" />
<PackageVersion Include="System.Management" Version="9.0.12" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.0" />
<PackageVersion Include="Rxmxnx.PInvoke.Extensions" Version="2.9.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
-261
View File
@@ -1,261 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibKenjinx", "src\LibKenjinx\LibKenjinx.csproj", "{AF58C1D5-DE16-429B-B155-7CE83B4E8FA6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "src\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "src\Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "src\Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "src\Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "src\ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "src\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj", "{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "src\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "src\Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
nuget.config = nuget.config
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Cpu", "src\Ryujinx.Cpu\Ryujinx.Cpu.csproj", "{3DF35E3D-D844-4399-A9A1-A9E923264C17}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Device", "src\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj", "{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Host1x", "src\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj", "{C35F1536-7DE5-4F9D-9604-B5B4E1561947}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.Vp9", "src\Ryujinx.Graphics.Nvdec.Vp9\Ryujinx.Graphics.Nvdec.Vp9.csproj", "{B9AECA11-E248-4886-A10B-81B631CAAF29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src\Ryujinx.Graphics.Vic\Ryujinx.Graphics.Vic.csproj", "{81BB2C11-9408-4EA3-822E-42987AF54429}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "src\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "src\Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "src\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "src\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj", "{D4D09B08-D580-4D69-B886-C35D2853F6C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryujinx.Horizon\Ryujinx.Horizon.csproj", "{AF34127A-3A92-43E5-8496-14960A50B1F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}.Release|Any CPU.Build.0 = Release|Any CPU
{AF58C1D5-DE16-429B-B155-7CE83B4E8FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF58C1D5-DE16-429B-B155-7CE83B4E8FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF58C1D5-DE16-429B-B155-7CE83B4E8FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF58C1D5-DE16-429B-B155-7CE83B4E8FA6}.Release|Any CPU.Build.0 = Release|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}.Release|Any CPU.Build.0 = Release|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.Build.0 = Release|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Release|Any CPU.Build.0 = Release|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ADA7EA87-0D63-4D97-9433-922A2124401F}.Release|Any CPU.Build.0 = Release|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}.Release|Any CPU.Build.0 = Release|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9558FB96-075D-4219-8FFF-401979DC0B69}.Release|Any CPU.Build.0 = Release|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1B1AD28-289D-47B7-A106-326972240207}.Release|Any CPU.Build.0 = Release|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}.Release|Any CPU.Build.0 = Release|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Debug|Any CPU.Build.0 = Debug|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.ActiveCfg = Release|Any CPU
{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}.Release|Any CPU.Build.0 = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5E6C691-9E22-4263-8F40-42F002CE66BE}.Release|Any CPU.Build.0 = Release|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1CC5322-7325-4F6B-9625-194B30BE1296}.Release|Any CPU.Build.0 = Release|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DF35E3D-D844-4399-A9A1-A9E923264C17}.Release|Any CPU.Build.0 = Release|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3002C3C-7B09-4FE7-894A-372EDA22FC6E}.Release|Any CPU.Build.0 = Release|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C35F1536-7DE5-4F9D-9604-B5B4E1561947}.Release|Any CPU.Build.0 = Release|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9AECA11-E248-4886-A10B-81B631CAAF29}.Release|Any CPU.Build.0 = Release|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81BB2C11-9408-4EA3-822E-42987AF54429}.Release|Any CPU.Build.0 = Release|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.Build.0 = Release|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.Build.0 = Release|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.Build.0 = Release|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C16F112F-38C3-40BC-9F5F-4791112063D6}.Release|Any CPU.Build.0 = Release|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.Build.0 = Release|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.Build.0 = Release|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4D09B08-D580-4D69-B886-C35D2853F6C8}.Release|Any CPU.Build.0 = Release|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}.Release|Any CPU.Build.0 = Release|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77D01AD9-2C98-478E-AE1D-8F7100738FB4}.Release|Any CPU.Build.0 = Release|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77F96ECE-4952-42DB-A528-DED25572A573}.Release|Any CPU.Build.0 = Release|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF34127A-3A92-43E5-8496-14960A50B1F1}.Release|Any CPU.Build.0 = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A}
EndGlobalSection
EndGlobal
+7 -1
View File
@@ -1,4 +1,10 @@
<Solution> <Solution>
<Configurations>
<BuildType Name="Debug" />
<BuildType Name="Debug AOT" />
<BuildType Name="Release" />
<BuildType Name="Release AOT" />
</Configurations>
<Folder Name="/Solution Items/"> <Folder Name="/Solution Items/">
<File Path=".editorconfig" /> <File Path=".editorconfig" />
<File Path="Directory.Build.props" /> <File Path="Directory.Build.props" />
@@ -6,7 +12,7 @@
<File Path="nuget.config" /> <File Path="nuget.config" />
</Folder> </Folder>
<Project Path="src/ARMeilleure/ARMeilleure.csproj" /> <Project Path="src/ARMeilleure/ARMeilleure.csproj" />
<Project Path="src/LibKenjinx/LibKenjinx.csproj" /> <Project Path="src/Ryujinx.Audio.Backends.Apple/Ryujinx.Audio.Backends.Apple.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj" /> <Project Path="src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj" /> <Project Path="src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj" /> <Project Path="src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj" />
-4
View File
@@ -2,10 +2,6 @@
SCRIPT_DIR=$(dirname "$(realpath "$0")") SCRIPT_DIR=$(dirname "$(realpath "$0")")
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx" RYUJINX_BIN="Ryujinx"
fi fi
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
CURRENTDIR="$(readlink -f "$(dirname "$0")")"
exec "$CURRENTDIR"/usr/bin/Ryujinx.sh "$@"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
set -eu
ROOTDIR="$(readlink -f "$(dirname "$0")")"/../../../
cd "$ROOTDIR"
BUILDDIR=${BUILDDIR:-publish}
OUTDIR=${OUTDIR:-publish_appimage}
rm -rf AppDir
mkdir -p AppDir/usr/bin
cp distribution/linux/Ryujinx.desktop AppDir/Ryujinx.desktop
cp distribution/linux/appimage/AppRun AppDir/AppRun
cp src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png AppDir/Ryujinx.svg
cp -r "$BUILDDIR"/* AppDir/usr/bin/
# Ensure necessary bins are set as executable
chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR"
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage
Binary file not shown.
+5 -3
View File
@@ -10,6 +10,8 @@
<string>Ryujinx</string> <string>Ryujinx</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>Ryujinx.icns</string> <string>Ryujinx.icns</string>
<key>CFBundleIconName</key>
<string>Ryujinx</string>
<key>CFBundleDocumentTypes</key> <key>CFBundleDocumentTypes</key>
<array> <array>
<dict> <dict>
@@ -40,17 +42,17 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.1</string> <string>2.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.1.0</string> <string>2.0.0</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<key>CSResourcesFileMapped</key> <key>CSResourcesFileMapped</key>
<true/> <true/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 - 2023 Ryujinx Team and Contributors.</string> <string>Copyright © 2018 - 2024 Ryujinx Team and Contributors. Copyright © 2024 - 2025 Kenji-NX and Contributors.</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.games</string> <string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
Binary file not shown.
+4 -1
View File
@@ -19,7 +19,7 @@ if platform.system() == "Darwin":
else: else:
OTOOL = shutil.which("llvm-otool") OTOOL = shutil.which("llvm-otool")
if OTOOL is None: if OTOOL is None:
for llvm_ver in [15, 14, 13]: for llvm_ver in [17, 16, 15, 14, 13]:
otool_path = shutil.which(f"llvm-otool-{llvm_ver}") otool_path = shutil.which(f"llvm-otool-{llvm_ver}")
if otool_path is not None: if otool_path is not None:
OTOOL = otool_path OTOOL = otool_path
@@ -562,9 +562,11 @@ search_path = [
for path in content_directory.rglob("**/*.dylib"): for path in content_directory.rglob("**/*.dylib"):
if not path.name.startswith("._"):
current_search_path = [path.parent] current_search_path = [path.parent]
current_search_path.extend(search_path) current_search_path.extend(search_path)
print(f"Fixing path '{path}' using search path of '{current_search_path}' for context of '{content_directory}'.")
fixup_dylib( fixup_dylib(
path, path,
get_path_related_to_target_exec(content_directory, path), get_path_related_to_target_exec(content_directory, path),
@@ -573,6 +575,7 @@ for path in content_directory.rglob("**/*.dylib"):
) )
for path in content_directory.rglob("**/*.so"): for path in content_directory.rglob("**/*.so"):
if not path.name.startswith("._"):
current_search_path = [path.parent] current_search_path = [path.parent]
current_search_path.extend(search_path) current_search_path.extend(search_path)
+13 -14
View File
@@ -26,7 +26,7 @@ else:
LIPO = shutil.which("llvm-lipo") LIPO = shutil.which("llvm-lipo")
if LIPO is None: if LIPO is None:
for llvm_ver in [15, 14, 13]: for llvm_ver in [17, 16, 15, 14, 13]:
lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}") lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}")
if lipo_path is not None: if lipo_path is not None:
LIPO = lipo_path LIPO = lipo_path
@@ -47,14 +47,12 @@ def get_new_name(
input_component = str(input_dylib_path).replace(str(input_directory), "")[1:] input_component = str(input_dylib_path).replace(str(input_directory), "")[1:]
return Path(os.path.join(output_directory, input_component)) return Path(os.path.join(output_directory, input_component))
def get_archs(dylib_path: Path) -> list[str]:
def is_fat_file(dylib_path: Path) -> str: res = subprocess.check_output([LIPO, "-info", str(dylib_path)]).decode("utf-8")
res = subprocess.check_output([LIPO, "-info", str(dylib_path.absolute())]).decode( if res.startswith("Non-fat file"):
"utf-8" return [res.split(":")[-1].strip()]
) else:
return res.split("are:")[-1].strip().split()
return not res.split("\n")[0].startswith("Non-fat file")
def construct_universal_dylib( def construct_universal_dylib(
arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path
@@ -69,11 +67,12 @@ def construct_universal_dylib(
os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path
) )
else: else:
if is_fat_file(arm64_input_dylib_path) or not x86_64_input_dylib_path.exists(): arm64_archs = get_archs(arm64_input_dylib_path)
with open(output_dylib_path, "wb") as dst: x86_64_archs = get_archs(x86_64_input_dylib_path) if x86_64_input_dylib_path.exists() else []
with open(arm64_input_dylib_path, "rb") as src:
dst.write(src.read()) if "arm64" in arm64_archs and "x86_64" in arm64_archs:
else: shutil.copy2(arm64_input_dylib_path, output_dylib_path)
elif x86_64_archs:
subprocess.check_call( subprocess.check_call(
[ [
LIPO, LIPO,
+19 -4
View File
@@ -25,26 +25,41 @@ cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents" cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns" cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh" cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
cp Assets.car "$APP_BUNDLE_DIRECTORY/Contents/Resources/Assets.car"
cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources" cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo" echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
# Fixup libraries and executable # Fixup libraries and executable
echo "Running bundle fix up python script"
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
# Resign all dyplib files as ad-hoc after changing them
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
# Now sign it # Now sign it
echo "Starting signing process"
if ! [ -x "$(command -v codesign)" ]; if ! [ -x "$(command -v codesign)" ];
then then
if ! [ -x "$(command -v rcodesign)" ]; if ! [ -x "$(command -v rcodesign)" ];
then then
echo "Cannot find rcodesign on your system, please install rcodesign." echo "Cannot find rcodesign on your system, please install rcodesign and ensure it is in your search path."
exit 1 exit 1
fi fi
# cargo install apple-codesign echo "Using rcodesign for ad-hoc signing"
echo "Usign rcodesign for ad-hoc signing"
echo "Resigning all frameworks dylib files as ad-hoc"
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec rcodesign sign {} \;
echo "Signing app bundle as ad-hoc"
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY" rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
else else
echo "Usign codesign for ad-hoc signing" echo "Using codesign for ad-hoc signing"
echo "Resigning all frameworks dylib files as ad-hoc"
find "$APP_BUNDLE_DIRECTORY/Contents/Frameworks" -type f -name "*.dylib" -exec codesign --force --sign - {} \;
echo "Signing app bundle as ad-hoc"
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY" codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY"
fi fi
+15 -9
View File
@@ -20,7 +20,19 @@ SOURCE_REVISION_ID=$6
CONFIGURATION=$7 CONFIGURATION=$7
EXTRA_ARGS=$8 EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ]; if [[ "$(uname)" == "Darwin" ]]; then
echo "Clearing xattr on all dot underscore files"
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
for f; do
dir=$(dirname "$f")
base=$(basename "$f")
orig="$dir/${base#._}"
[ -f "$orig" ] && xattr -c "$orig" || true
done
' sh {} +
fi
if [ "$VERSION" == "2.0.0" ];
then then
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else else
@@ -66,11 +78,11 @@ python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_
if ! [ -x "$(command -v lipo)" ]; if ! [ -x "$(command -v lipo)" ];
then then
if ! [ -x "$(command -v llvm-lipo-14)" ]; if ! [ -x "$(command -v llvm-lipo-17)" ];
then then
LIPO=llvm-lipo LIPO=llvm-lipo
else else
LIPO=llvm-lipo-14 LIPO=llvm-lipo-17
fi fi
else else
LIPO=lipo LIPO=lipo
@@ -109,12 +121,6 @@ python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME" rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd popd
echo "Done" echo "Done"
@@ -1,111 +0,0 @@
#!/bin/bash
set -e
if [ "$#" -lt 7 ]; then
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
exit 1
fi
mkdir -p "$1"
mkdir -p "$2"
mkdir -p "$3"
BASE_DIR=$(readlink -f "$1")
TEMP_DIRECTORY=$(readlink -f "$2")
OUTPUT_DIRECTORY=$(readlink -f "$3")
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
VERSION=$5
SOURCE_REVISION_ID=$6
CONFIGURATION=$7
EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ];
then
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
else
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
fi
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
X64_OUTPUT="$TEMP_DIRECTORY/publish_x64"
UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish"
EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2
rm -rf "$TEMP_DIRECTORY"
mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
rm -rf "$OUTPUT_DIRECTORY"
mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different outputs and remove the executable
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
# Make it libraries universal
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-14)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-14
fi
else
LIPO=lipo
fi
# Make the executable universal
$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create
# Now sign it
if ! [ -x "$(command -v codesign)" ];
then
if ! [ -x "$(command -v rcodesign)" ];
then
echo "Cannot find rcodesign on your system, please install rcodesign."
exit 1
fi
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Using rcodesign for ad-hoc signing"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE"
fi
done
else
echo "Using codesign for ad-hoc signing"
for FILE in "$UNIVERSAL_OUTPUT"/*; do
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE"
fi
done
fi
echo "Creating archive"
pushd "$OUTPUT_DIRECTORY"
tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
popd
echo "Done"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "9.0.100", "version": "10.0.100",
"rollForward": "latestFeature" "rollForward": "latestFeature"
} }
} }
+2
View File
@@ -4,6 +4,8 @@
<RuntimeIdentifiers>osx-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>osx-arm64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
<Configurations>Debug;Release;Debug AOT;Release AOT</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+7 -5
View File
@@ -8,7 +8,7 @@ namespace ARMeilleure.CodeGen
/// <summary> /// <summary>
/// Represents a compiled function. /// Represents a compiled function.
/// </summary> /// </summary>
readonly struct CompiledFunction public readonly struct CompiledFunction
{ {
/// <summary> /// <summary>
/// Gets the machine code of the <see cref="CompiledFunction"/>. /// Gets the machine code of the <see cref="CompiledFunction"/>.
@@ -44,10 +44,11 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function. /// <typeparamref name="T"/> pointing to the mapped function.
/// </summary> /// </summary>
/// <typeparam name="T">Type of delegate</typeparam> /// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns> /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T Map<T>() public T Map<T>(JitCache jitCache)
{ {
return MapWithPointer<T>(out _); return MapWithPointer<T>(jitCache, out _);
} }
/// <summary> /// <summary>
@@ -55,11 +56,12 @@ namespace ARMeilleure.CodeGen
/// <typeparamref name="T"/> pointing to the mapped function. /// <typeparamref name="T"/> pointing to the mapped function.
/// </summary> /// </summary>
/// <typeparam name="T">Type of delegate</typeparam> /// <typeparam name="T">Type of delegate</typeparam>
/// <param name="jitCache">The jit cache to map the function into</param>
/// <param name="codePointer">Pointer to the function code in memory</param> /// <param name="codePointer">Pointer to the function code in memory</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns> /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T MapWithPointer<T>(out nint codePointer) public T MapWithPointer<T>(JitCache jitCache, out nint codePointer)
{ {
codePointer = JitCache.Map(this); codePointer = jitCache.Map(this);
return Marshal.GetDelegateForFunctionPointer<T>(codePointer); return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
} }
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents a relocation. /// Represents a relocation.
/// </summary> /// </summary>
readonly struct RelocEntry public readonly struct RelocEntry
{ {
public const int Stride = 13; // Bytes. public const int Stride = 13; // Bytes.
+1 -1
View File
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents relocation information about a <see cref="CompiledFunction"/>. /// Represents relocation information about a <see cref="CompiledFunction"/>.
/// </summary> /// </summary>
readonly struct RelocInfo public readonly struct RelocInfo
{ {
/// <summary> /// <summary>
/// Gets an empty <see cref="RelocInfo"/>. /// Gets an empty <see cref="RelocInfo"/>.
+1 -1
View File
@@ -5,7 +5,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Represents a symbol. /// Represents a symbol.
/// </summary> /// </summary>
readonly struct Symbol public readonly struct Symbol
{ {
private readonly ulong _value; private readonly ulong _value;
@@ -3,7 +3,7 @@ namespace ARMeilleure.CodeGen.Linking
/// <summary> /// <summary>
/// Types of <see cref="Symbol"/>. /// Types of <see cref="Symbol"/>.
/// </summary> /// </summary>
enum SymbolType : byte public enum SymbolType : byte
{ {
/// <summary> /// <summary>
/// Refers to nothing, i.e no symbol. /// Refers to nothing, i.e no symbol.
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
struct UnwindInfo public struct UnwindInfo
{ {
public const int Stride = 4; // Bytes. public const int Stride = 4; // Bytes.
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
enum UnwindPseudoOp public enum UnwindPseudoOp
{ {
PushReg = 0, PushReg = 0,
SetFrame = 1, SetFrame = 1,
@@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.Unwinding namespace ARMeilleure.CodeGen.Unwinding
{ {
struct UnwindPushEntry public struct UnwindPushEntry
{ {
public const int Stride = 16; // Bytes. public const int Stride = 16; // Bytes.
+7 -31
View File
@@ -20,45 +20,21 @@ namespace ARMeilleure.Common
new( 1, 6) new( 1, 6)
]; ];
private static readonly AddressTableLevel[] _levels64BitSparseTiny = private static readonly AddressTableLevel[] _levels64BitMono =
[ [
new( 11, 28), new( 2, 37)
new( 2, 9)
]; ];
private static readonly AddressTableLevel[] _levels32BitSparseTiny = private static readonly AddressTableLevel[] _levels32BitMono =
[ [
new( 10, 22), new( 1, 31)
new( 1, 9)
]; ];
private static readonly AddressTableLevel[] _levels64BitSparseGiant = public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool mono)
[
new( 38, 1),
new( 2, 36)
];
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
[
new( 31, 1),
new( 1, 30)
];
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
//low power will never run worse than non-sparse, but for most systems it won't be necessary
//high power is always used, but I've left low power in here for future reference
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
{ {
if (sparse) if (mono)
{ {
if (lowPower) return for64Bits ? _levels64BitMono : _levels32BitMono;
{
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
}
else
{
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
}
} }
else else
{ {
@@ -0,0 +1,8 @@
namespace ARMeilleure.Common
{
public enum AddressTableType
{
Default,
Sparse
}
}
+9
View File
@@ -53,5 +53,14 @@ namespace ARMeilleure.Common
{ {
return (bits >> shift) | (bits << (size - shift)); return (bits >> shift) | (bits << (size - shift));
} }
public static T AlignUp<T>(T value, T size) where T : IBinaryInteger<T>
=> (value + (size - T.One)) & -size;
public static T AlignDown<T>(T value, T size) where T : IBinaryInteger<T>
=> value & -size;
public static T DivRoundUp<T>(T value, T dividend) where T : IBinaryInteger<T>
=> (value + (dividend - T.One)) / dividend;
} }
} }
+9 -3
View File
@@ -5,10 +5,9 @@ namespace ARMeilleure.Common
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
{ {
/// <summary> /// <summary>
/// True if the address table's bottom level is sparsely mapped. /// Gets the <see cref="AddressTableType"/> of the <see cref="IAddressTable{TEntry}"/> instance.
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
/// </summary> /// </summary>
bool Sparse { get; } AddressTableType TableType { get; }
/// <summary> /// <summary>
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance. /// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
@@ -31,6 +30,13 @@ namespace ARMeilleure.Common
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception> /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
nint Base { get; } nint Base { get; }
/// <summary>
/// Signal that the given code range exists.
/// </summary>
/// <param name="address">Guest code range address</param>
/// <param name="size">Guest code range size</param>
void SignalCodeRange(ulong address, ulong size);
/// <summary> /// <summary>
/// Determines if the specified <paramref name="address"/> is in the range of the /// Determines if the specified <paramref name="address"/> is in the range of the
/// <see cref="IAddressTable{TEntry}"/>. /// <see cref="IAddressTable{TEntry}"/>.
@@ -5,7 +5,7 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using ARMeilleure.Translation.PTC; using ARMeilleure.Translation.PTC;
using System.Linq;
using static ARMeilleure.Instructions.InstEmitHelper; using static ARMeilleure.Instructions.InstEmitHelper;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory; using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -147,6 +147,12 @@ namespace ARMeilleure.Instructions
public static void EmitCall(ArmEmitterContext context, ulong immediate) public static void EmitCall(ArmEmitterContext context, ulong immediate)
{ {
if (context.IsSingleStep)
{
context.Return(Const(immediate));
return;
}
bool isRecursive = immediate == context.EntryAddress; bool isRecursive = immediate == context.EntryAddress;
if (isRecursive) if (isRecursive)
@@ -160,13 +166,25 @@ namespace ARMeilleure.Instructions
} }
public static void EmitVirtualCall(ArmEmitterContext context, Operand target) public static void EmitVirtualCall(ArmEmitterContext context, Operand target)
{
if (context.IsSingleStep)
{
if (target.Type == OperandType.I32)
{
target = context.ZeroExtend32(OperandType.I64, target);
}
context.Return(target);
}
else
{ {
EmitTableBranch(context, target, isJump: false); EmitTableBranch(context, target, isJump: false);
} }
}
public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn)
{ {
if (isReturn) if (isReturn || context.IsSingleStep)
{ {
EmitReturn(context, target); EmitReturn(context, target);
} }
@@ -218,9 +236,9 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, hostAddressAddr); hostAddress = context.Load(OperandType.I64, hostAddressAddr);
} }
else if (table.Sparse) else if (table.TableType == AddressTableType.Sparse)
{ {
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels. // Inline table lookup. Only enabled when the sparse function table is enabled with 1 level.
// Deliberately attempts to avoid branches. // Deliberately attempts to avoid branches.
Operand tableBase = !context.HasPtc ? Operand tableBase = !context.HasPtc ?
@@ -229,9 +247,7 @@ namespace ARMeilleure.Instructions
hostAddress = tableBase; hostAddress = tableBase;
for (int i = 0; i < table.Levels.Length; i++) AddressTableLevel level = table.Levels.Last();
{
AddressTableLevel level = table.Levels[i];
int clearBits = 64 - (level.Index + level.Length); int clearBits = 64 - (level.Index + level.Length);
Operand index = context.ShiftLeft( Operand index = context.ShiftLeft(
@@ -241,12 +257,11 @@ namespace ARMeilleure.Instructions
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index)); hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
} }
}
else else
{ {
hostAddress = !context.HasPtc ? hostAddress = !context.HasPtc ?
Const((long)context.Stubs.DispatchStub) : Const(context.Stubs.DispatchStub) :
Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol); Const(context.Stubs.DispatchStub, Ptc.DispatchStubSymbol);
} }
if (isJump) if (isJump)
@@ -406,7 +406,7 @@ namespace ARMeilleure.Instructions
{ {
Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2);
return EmitUnaryMathCall(context, nameof(MathHelper.Abs), res); return EmitUnaryMathCall(context, nameof(Math.Abs), res);
}); });
} }
} }
@@ -451,7 +451,7 @@ namespace ARMeilleure.Instructions
{ {
Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2);
return EmitUnaryMathCall(context, nameof(MathHelper.Abs), res); return EmitUnaryMathCall(context, nameof(Math.Abs), res);
}); });
} }
} }
@@ -483,7 +483,7 @@ namespace ARMeilleure.Instructions
{ {
EmitScalarUnaryOpF(context, (op1) => EmitScalarUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Abs), op1); return EmitUnaryMathCall(context, nameof(Math.Abs), op1);
}); });
} }
} }
@@ -522,7 +522,7 @@ namespace ARMeilleure.Instructions
{ {
EmitVectorUnaryOpF(context, (op1) => EmitVectorUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Abs), op1); return EmitUnaryMathCall(context, nameof(Math.Abs), op1);
}); });
} }
} }
@@ -2246,7 +2246,7 @@ namespace ARMeilleure.Instructions
{ {
EmitScalarUnaryOpF(context, (op1) => EmitScalarUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Floor), op1); return EmitUnaryMathCall(context, nameof(Math.Floor), op1);
}); });
} }
} }
@@ -2265,7 +2265,7 @@ namespace ARMeilleure.Instructions
{ {
EmitVectorUnaryOpF(context, (op1) => EmitVectorUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Floor), op1); return EmitUnaryMathCall(context, nameof(Math.Floor), op1);
}); });
} }
} }
@@ -2322,7 +2322,7 @@ namespace ARMeilleure.Instructions
{ {
EmitScalarUnaryOpF(context, (op1) => EmitScalarUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), op1); return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1);
}); });
} }
} }
@@ -2341,7 +2341,7 @@ namespace ARMeilleure.Instructions
{ {
EmitVectorUnaryOpF(context, (op1) => EmitVectorUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), op1); return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1);
}); });
} }
} }
@@ -2390,7 +2390,7 @@ namespace ARMeilleure.Instructions
{ {
EmitScalarUnaryOpF(context, (op1) => EmitScalarUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Truncate), op1); return EmitUnaryMathCall(context, nameof(Math.Truncate), op1);
}); });
} }
} }
@@ -2409,7 +2409,7 @@ namespace ARMeilleure.Instructions
{ {
EmitVectorUnaryOpF(context, (op1) => EmitVectorUnaryOpF(context, (op1) =>
{ {
return EmitUnaryMathCall(context, nameof(MathHelper.Truncate), op1); return EmitUnaryMathCall(context, nameof(Math.Truncate), op1);
}); });
} }
} }
@@ -43,7 +43,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Abs), op1)); EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1));
} }
} }
@@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitVectorUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Abs), op1)); EmitVectorUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1));
} }
} }
else else
@@ -726,8 +726,8 @@ namespace ARMeilleure.Instructions
if (absolute) if (absolute)
{ {
ne = EmitUnaryMathCall(context, nameof(MathHelper.Abs), ne); ne = EmitUnaryMathCall(context, nameof(Math.Abs), ne);
me = EmitUnaryMathCall(context, nameof(MathHelper.Abs), me); me = EmitUnaryMathCall(context, nameof(Math.Abs), me);
} }
Operand e = EmitSoftFloatCall(context, name, ne, me); Operand e = EmitSoftFloatCall(context, name, ne, me);
@@ -333,7 +333,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Floor), op1)); EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1));
} }
} }
@@ -349,7 +349,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitFcvt(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Floor), op1), signed: true, scalar: false); EmitFcvt(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1), signed: true, scalar: false);
} }
} }
@@ -365,7 +365,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Floor), op1)); EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1));
} }
} }
@@ -538,7 +538,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), op1)); EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1));
} }
} }
@@ -554,7 +554,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), op1)); EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1));
} }
} }
@@ -357,10 +357,10 @@ namespace ARMeilleure.Instructions
toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert);
break; break;
case 0b10: // Towards positive infinity case 0b10: // Towards positive infinity
toConvert = EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), toConvert); toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert);
break; break;
case 0b11: // Towards negative infinity case 0b11: // Towards negative infinity
toConvert = EmitUnaryMathCall(context, nameof(MathHelper.Floor), toConvert); toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert);
break; break;
} }
@@ -494,10 +494,10 @@ namespace ARMeilleure.Instructions
toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert);
break; break;
case 0b10: // Towards positive infinity case 0b10: // Towards positive infinity
toConvert = EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), toConvert); toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert);
break; break;
case 0b11: // Towards negative infinity case 0b11: // Towards negative infinity
toConvert = EmitUnaryMathCall(context, nameof(MathHelper.Floor), toConvert); toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert);
break; break;
} }
@@ -534,7 +534,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(MathHelper.Floor), m)); EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Floor), m));
} }
} }
@@ -574,7 +574,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), m)); EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Ceiling), m));
} }
} }
@@ -613,7 +613,7 @@ namespace ARMeilleure.Instructions
} }
else else
{ {
EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(MathHelper.Truncate), op1)); EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Truncate), op1));
} }
} }
@@ -460,8 +460,8 @@ namespace ARMeilleure.Instructions
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
MethodInfo info = (op.Size & 1) == 0 MethodInfo info = (op.Size & 1) == 0
? typeof(MathHelperF).GetMethod(name, [typeof(float)]) ? typeof(MathF).GetMethod(name, [typeof(float)])
: typeof(MathHelper).GetMethod(name, [typeof(double)]); : typeof(Math).GetMethod(name, [typeof(double)]);
return context.Call(info, n); return context.Call(info, n);
} }
@@ -470,11 +470,11 @@ namespace ARMeilleure.Instructions
{ {
IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; IOpCodeSimd op = (IOpCodeSimd)context.CurrOp;
string name = nameof(MathHelper.Round); string name = nameof(Math.Round);
MethodInfo info = (op.Size & 1) == 0 MethodInfo info = (op.Size & 1) == 0
? typeof(MathHelperF).GetMethod(name, [typeof(float), typeof(int)]) ? typeof(MathF).GetMethod(name, [typeof(float), typeof(MidpointRounding)])
: typeof(MathHelper).GetMethod(name, [typeof(double), typeof(int)]); : typeof(Math).GetMethod(name, [typeof(double), typeof(MidpointRounding)]);
return context.Call(info, n, Const((int)roundMode)); return context.Call(info, n, Const((int)roundMode));
} }
@@ -510,16 +510,16 @@ namespace ARMeilleure.Instructions
context.MarkLabel(lbl1); context.MarkLabel(lbl1);
context.BranchIf(lbl2, rMode, rP, Comparison.NotEqual); context.BranchIf(lbl2, rMode, rP, Comparison.NotEqual);
context.Copy(res, EmitUnaryMathCall(context, nameof(MathHelper.Ceiling), op)); context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Ceiling), op));
context.Branch(lblEnd); context.Branch(lblEnd);
context.MarkLabel(lbl2); context.MarkLabel(lbl2);
context.BranchIf(lbl3, rMode, rM, Comparison.NotEqual); context.BranchIf(lbl3, rMode, rM, Comparison.NotEqual);
context.Copy(res, EmitUnaryMathCall(context, nameof(MathHelper.Floor), op)); context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Floor), op));
context.Branch(lblEnd); context.Branch(lblEnd);
context.MarkLabel(lbl3); context.MarkLabel(lbl3);
context.Copy(res, EmitUnaryMathCall(context, nameof(MathHelper.Truncate), op)); context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Truncate), op));
context.Branch(lblEnd); context.Branch(lblEnd);
context.MarkLabel(lblEnd); context.MarkLabel(lblEnd);
-115
View File
@@ -1,115 +0,0 @@
using System;
#if ANDROID
using System.Runtime.CompilerServices;
#else
using System.Runtime.InteropServices;
#endif
namespace ARMeilleure.Instructions
{
static class MathHelper
{
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static double Abs(double value)
{
return Math.Abs(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static double Ceiling(double value)
{
return Math.Ceiling(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static double Floor(double value)
{
return Math.Floor(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static double Round(double value, int mode)
{
return Math.Round(value, (MidpointRounding)mode);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static double Truncate(double value)
{
return Math.Truncate(value);
}
}
static class MathHelperF
{
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static float Abs(float value)
{
return MathF.Abs(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static float Ceiling(float value)
{
return MathF.Ceiling(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static float Floor(float value)
{
return MathF.Floor(value);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static float Round(float value, int mode)
{
return MathF.Round(value, (MidpointRounding)mode);
}
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static float Truncate(float value)
{
return MathF.Truncate(value);
}
}
}
+4 -129
View File
@@ -2,11 +2,6 @@ using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using System; using System;
#if ANDROID
using System.Runtime.CompilerServices;
#else
using System.Runtime.InteropServices;
#endif
namespace ARMeilleure.Instructions namespace ARMeilleure.Instructions
{ {
@@ -39,11 +34,6 @@ namespace ARMeilleure.Instructions
Context = null; Context = null;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void Break(ulong address, int imm) public static void Break(ulong address, int imm)
{ {
Statistics.PauseTimer(); Statistics.PauseTimer();
@@ -53,11 +43,6 @@ namespace ARMeilleure.Instructions
Statistics.ResumeTimer(); Statistics.ResumeTimer();
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void SupervisorCall(ulong address, int imm) public static void SupervisorCall(ulong address, int imm)
{ {
Statistics.PauseTimer(); Statistics.PauseTimer();
@@ -67,11 +52,6 @@ namespace ARMeilleure.Instructions
Statistics.ResumeTimer(); Statistics.ResumeTimer();
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void Undefined(ulong address, int opCode) public static void Undefined(ulong address, int opCode)
{ {
Statistics.PauseTimer(); Statistics.PauseTimer();
@@ -82,51 +62,26 @@ namespace ARMeilleure.Instructions
} }
#region "System registers" #region "System registers"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetCtrEl0() public static ulong GetCtrEl0()
{ {
return GetContext().CtrEl0; return GetContext().CtrEl0;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetDczidEl0() public static ulong GetDczidEl0()
{ {
return GetContext().DczidEl0; return GetContext().DczidEl0;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetCntfrqEl0() public static ulong GetCntfrqEl0()
{ {
return GetContext().CntfrqEl0; return GetContext().CntfrqEl0;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetCntpctEl0() public static ulong GetCntpctEl0()
{ {
return GetContext().CntpctEl0; return GetContext().CntpctEl0;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetCntvctEl0() public static ulong GetCntvctEl0()
{ {
return GetContext().CntvctEl0; return GetContext().CntvctEl0;
@@ -134,51 +89,26 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Read" #region "Read"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static byte ReadByte(ulong address) public static byte ReadByte(ulong address)
{ {
return GetMemoryManager().ReadGuest<byte>(address); return GetMemoryManager().ReadGuest<byte>(address);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ushort ReadUInt16(ulong address) public static ushort ReadUInt16(ulong address)
{ {
return GetMemoryManager().ReadGuest<ushort>(address); return GetMemoryManager().ReadGuest<ushort>(address);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint ReadUInt32(ulong address) public static uint ReadUInt32(ulong address)
{ {
return GetMemoryManager().ReadGuest<uint>(address); return GetMemoryManager().ReadGuest<uint>(address);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong ReadUInt64(ulong address) public static ulong ReadUInt64(ulong address)
{ {
return GetMemoryManager().ReadGuest<ulong>(address); return GetMemoryManager().ReadGuest<ulong>(address);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 ReadVector128(ulong address) public static V128 ReadVector128(ulong address)
{ {
return GetMemoryManager().ReadGuest<V128>(address); return GetMemoryManager().ReadGuest<V128>(address);
@@ -186,92 +116,47 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Write" #region "Write"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void WriteByte(ulong address, byte value) public static void WriteByte(ulong address, byte value)
{ {
GetMemoryManager().WriteGuest(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void WriteUInt16(ulong address, ushort value) public static void WriteUInt16(ulong address, ushort value)
{ {
GetMemoryManager().WriteGuest(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void WriteUInt32(ulong address, uint value) public static void WriteUInt32(ulong address, uint value)
{ {
GetMemoryManager().WriteGuest(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void WriteUInt64(ulong address, ulong value) public static void WriteUInt64(ulong address, ulong value)
{ {
GetMemoryManager().WriteGuest(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void WriteVector128(ulong address, V128 value) public static void WriteVector128(ulong address, V128 value)
{ {
GetMemoryManager().WriteGuest(address, value); GetMemoryManager().WriteGuest(address, value);
} }
#endregion #endregion
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void EnqueueForRejit(ulong address) public static void EnqueueForRejit(ulong address)
{ {
Context.Translator.EnqueueForRejit(address, GetContext().ExecutionMode); Context.Translator.EnqueueForRejit(address, GetContext().ExecutionMode);
} }
#if ANDROID public static void SignalMemoryTracking(ulong address, ulong size, bool write)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void SignalMemoryTracking(ulong address, ulong size, byte write)
{ {
GetMemoryManager().SignalMemoryTracking(address, size, write == 1); GetMemoryManager().SignalMemoryTracking(address, size, write);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void ThrowInvalidMemoryAccess(ulong address) public static void ThrowInvalidMemoryAccess(ulong address)
{ {
throw new InvalidAccessException(address); throw new InvalidAccessException(address);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong GetFunctionAddress(ulong address) public static ulong GetFunctionAddress(ulong address)
{ {
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
@@ -279,22 +164,12 @@ namespace ARMeilleure.Instructions
return (ulong)function.FuncPointer.ToInt64(); return (ulong)function.FuncPointer.ToInt64();
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static void InvalidateCacheLine(ulong address) public static void InvalidateCacheLine(ulong address)
{ {
Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes); Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes);
} }
#if ANDROID public static bool CheckSynchronization()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static byte CheckSynchronization()
{ {
Statistics.PauseTimer(); Statistics.PauseTimer();
@@ -304,7 +179,7 @@ namespace ARMeilleure.Instructions
Statistics.ResumeTimer(); Statistics.ResumeTimer();
return (byte)(context.Running ? 1 : 0); return context.Running;
} }
public static ExecutionContext GetContext() public static ExecutionContext GetContext()
@@ -1,21 +1,11 @@
using ARMeilleure.State; using ARMeilleure.State;
using System; using System;
#if ANDROID
using System.Runtime.CompilerServices;
#else
using System.Runtime.InteropServices;
#endif
namespace ARMeilleure.Instructions namespace ARMeilleure.Instructions
{ {
static class SoftFallback static class SoftFallback
{ {
#region "ShrImm64" #region "ShrImm64"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static long SignedShrImm64(long value, long roundConst, int shift) public static long SignedShrImm64(long value, long roundConst, int shift)
{ {
if (roundConst == 0L) if (roundConst == 0L)
@@ -58,11 +48,6 @@ namespace ARMeilleure.Instructions
} }
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift) public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift)
{ {
if (roundConst == 0L) if (roundConst == 0L)
@@ -107,11 +92,6 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Saturation" #region "Saturation"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static int SatF32ToS32(float value) public static int SatF32ToS32(float value)
{ {
if (float.IsNaN(value)) if (float.IsNaN(value))
@@ -123,11 +103,6 @@ namespace ARMeilleure.Instructions
value <= int.MinValue ? int.MinValue : (int)value; value <= int.MinValue ? int.MinValue : (int)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static long SatF32ToS64(float value) public static long SatF32ToS64(float value)
{ {
if (float.IsNaN(value)) if (float.IsNaN(value))
@@ -139,11 +114,6 @@ namespace ARMeilleure.Instructions
value <= long.MinValue ? long.MinValue : (long)value; value <= long.MinValue ? long.MinValue : (long)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint SatF32ToU32(float value) public static uint SatF32ToU32(float value)
{ {
if (float.IsNaN(value)) if (float.IsNaN(value))
@@ -155,11 +125,6 @@ namespace ARMeilleure.Instructions
value <= uint.MinValue ? uint.MinValue : (uint)value; value <= uint.MinValue ? uint.MinValue : (uint)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong SatF32ToU64(float value) public static ulong SatF32ToU64(float value)
{ {
if (float.IsNaN(value)) if (float.IsNaN(value))
@@ -171,11 +136,6 @@ namespace ARMeilleure.Instructions
value <= ulong.MinValue ? ulong.MinValue : (ulong)value; value <= ulong.MinValue ? ulong.MinValue : (ulong)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static int SatF64ToS32(double value) public static int SatF64ToS32(double value)
{ {
if (double.IsNaN(value)) if (double.IsNaN(value))
@@ -187,11 +147,6 @@ namespace ARMeilleure.Instructions
value <= int.MinValue ? int.MinValue : (int)value; value <= int.MinValue ? int.MinValue : (int)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static long SatF64ToS64(double value) public static long SatF64ToS64(double value)
{ {
if (double.IsNaN(value)) if (double.IsNaN(value))
@@ -203,11 +158,6 @@ namespace ARMeilleure.Instructions
value <= long.MinValue ? long.MinValue : (long)value; value <= long.MinValue ? long.MinValue : (long)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint SatF64ToU32(double value) public static uint SatF64ToU32(double value)
{ {
if (double.IsNaN(value)) if (double.IsNaN(value))
@@ -219,11 +169,6 @@ namespace ARMeilleure.Instructions
value <= uint.MinValue ? uint.MinValue : (uint)value; value <= uint.MinValue ? uint.MinValue : (uint)value;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong SatF64ToU64(double value) public static ulong SatF64ToU64(double value)
{ {
if (double.IsNaN(value)) if (double.IsNaN(value))
@@ -237,11 +182,6 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Count" #region "Count"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
{ {
value ^= value >> 1; value ^= value >> 1;
@@ -261,11 +201,6 @@ namespace ARMeilleure.Instructions
private static ReadOnlySpan<byte> ClzNibbleTbl => [4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]; private static ReadOnlySpan<byte> ClzNibbleTbl => [4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0];
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.).
{ {
if (value == 0ul) if (value == 0ul)
@@ -289,81 +224,41 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Table" #region "Table"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbl1(V128 vector, int bytes, V128 tb0) public static V128 Tbl1(V128 vector, int bytes, V128 tb0)
{ {
return TblOrTbx(default, vector, bytes, tb0); return TblOrTbx(default, vector, bytes, tb0);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1) public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1)
{ {
return TblOrTbx(default, vector, bytes, tb0, tb1); return TblOrTbx(default, vector, bytes, tb0, tb1);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
{ {
return TblOrTbx(default, vector, bytes, tb0, tb1, tb2); return TblOrTbx(default, vector, bytes, tb0, tb1, tb2);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
{ {
return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3); return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0) public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0)
{ {
return TblOrTbx(dest, vector, bytes, tb0); return TblOrTbx(dest, vector, bytes, tb0);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1) public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1)
{ {
return TblOrTbx(dest, vector, bytes, tb0, tb1); return TblOrTbx(dest, vector, bytes, tb0, tb1);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2)
{ {
return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2); return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3)
{ {
return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3); return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3);
@@ -405,54 +300,14 @@ namespace ARMeilleure.Instructions
private const uint Crc32RevPoly = 0xedb88320; private const uint Crc32RevPoly = 0xedb88320;
private const uint Crc32cRevPoly = 0x82f63b78; private const uint Crc32cRevPoly = 0x82f63b78;
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value); public static uint Crc32b(uint crc, byte value) => Crc32(crc, Crc32RevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value); public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value); public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value); public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value); public static uint Crc32cb(uint crc, byte value) => Crc32(crc, Crc32cRevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value); public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value); public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value);
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value); public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value);
private static uint Crc32h(uint crc, uint poly, ushort val) private static uint Crc32h(uint crc, uint poly, ushort val)
@@ -503,41 +358,21 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Aes" #region "Aes"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Decrypt(V128 value, V128 roundKey) public static V128 Decrypt(V128 value, V128 roundKey)
{ {
return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey)); return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey));
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Encrypt(V128 value, V128 roundKey) public static V128 Encrypt(V128 value, V128 roundKey)
{ {
return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey)); return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey));
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 InverseMixColumns(V128 value) public static V128 InverseMixColumns(V128 value)
{ {
return CryptoHelper.AesInvMixColumns(value); return CryptoHelper.AesInvMixColumns(value);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 MixColumns(V128 value) public static V128 MixColumns(V128 value)
{ {
return CryptoHelper.AesMixColumns(value); return CryptoHelper.AesMixColumns(value);
@@ -545,11 +380,6 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Sha1" #region "Sha1"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk) public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk)
{ {
for (int e = 0; e <= 3; e++) for (int e = 0; e <= 3; e++)
@@ -570,21 +400,11 @@ namespace ARMeilleure.Instructions
return hash_abcd; return hash_abcd;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static uint FixedRotate(uint hash_e) public static uint FixedRotate(uint hash_e)
{ {
return hash_e.Rol(30); return hash_e.Rol(30);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk) public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk)
{ {
for (int e = 0; e <= 3; e++) for (int e = 0; e <= 3; e++)
@@ -605,11 +425,6 @@ namespace ARMeilleure.Instructions
return hash_abcd; return hash_abcd;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk) public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk)
{ {
for (int e = 0; e <= 3; e++) for (int e = 0; e <= 3; e++)
@@ -630,11 +445,6 @@ namespace ARMeilleure.Instructions
return hash_abcd; return hash_abcd;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11) public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11)
{ {
ulong t2 = w4_7.Extract<ulong>(0); ulong t2 = w4_7.Extract<ulong>(0);
@@ -645,11 +455,6 @@ namespace ARMeilleure.Instructions
return result ^ (w0_3 ^ w8_11); return result ^ (w0_3 ^ w8_11);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15) public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15)
{ {
V128 t = tw0_3 ^ (w12_15 >> 32); V128 t = tw0_3 ^ (w12_15 >> 32);
@@ -694,31 +499,16 @@ namespace ARMeilleure.Instructions
#endregion #endregion
#region "Sha256" #region "Sha256"
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk) public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk)
{ {
return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true); return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk) public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk)
{ {
return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false); return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false);
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7) public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7)
{ {
V128 result = new(); V128 result = new();
@@ -737,11 +527,6 @@ namespace ARMeilleure.Instructions
return result; return result;
} }
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15) public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15)
{ {
V128 result = new(); V128 result = new();
@@ -843,11 +628,6 @@ namespace ARMeilleure.Instructions
} }
#endregion #endregion
#if ANDROID
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#else
[UnmanagedCallersOnly]
#endif
public static V128 PolynomialMult64_128(ulong op1, ulong op2) public static V128 PolynomialMult64_128(ulong op1, ulong op2)
{ {
V128 result = V128.Zero; V128 result = V128.Zero;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.Memory namespace ARMeilleure.Memory
{ {
public class ReservedRegion public class ReservedRegion : IDisposable
{ {
public const int DefaultGranularity = 65536; // Mapping granularity in Windows. public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
+1 -3
View File
@@ -8,13 +8,11 @@ namespace ARMeilleure
// low-core count PPTC // low-core count PPTC
public static bool EcoFriendly { get; set; } = false; public static bool EcoFriendly { get; set; } = false;
// Jit cache eviction
public static bool CacheEviction { get; set; } = false;
public static bool FastFP { get; set; } = true; public static bool FastFP { get; set; } = true;
public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool AllowLcqInFunctionTable { get; set; } = true;
public static bool UseUnmanagedDispatchLoop { get; set; } = true; public static bool UseUnmanagedDispatchLoop { get; set; } = true;
public static bool EnableDebugging { get; set; } = false;
public static bool EnableDeepCallRecursionProtection { get; set; } = true; public static bool EnableDeepCallRecursionProtection { get; set; } = true;
public static bool UseAdvSimdIfAvailable { get; set; } = true; public static bool UseAdvSimdIfAvailable { get; set; } = true;
@@ -8,7 +8,7 @@ namespace ARMeilleure.Signal
{ {
public static class NativeSignalHandlerGenerator public static class NativeSignalHandlerGenerator
{ {
public const int MaxTrackedRanges = 8; public const int MaxTrackedRanges = 16;
private const int StructAddressOffset = 0; private const int StructAddressOffset = 0;
private const int StructWriteOffset = 4; private const int StructWriteOffset = 4;
+7 -6
View File
@@ -1,5 +1,6 @@
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation; using ARMeilleure.Translation;
using ARMeilleure.Translation.Cache;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory; using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -17,7 +18,7 @@ namespace ARMeilleure.Signal
public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState); public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState);
public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr); public delegate void DebugNativeWriteLoop(nint nativeWriteLoopPtr, nint writePtr);
public static DebugPartialUnmap GenerateDebugPartialUnmap() public static DebugPartialUnmap GenerateDebugPartialUnmap(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -31,10 +32,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugPartialUnmap>(jitCache);
} }
public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(nint structPtr) public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(JitCache jitCache, nint structPtr)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -48,10 +49,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugThreadLocalMapGetOrReserve>(jitCache);
} }
public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop() public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -77,7 +78,7 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(); return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DebugNativeWriteLoop>(jitCache);
} }
} }
} }
+34 -4
View File
@@ -1,4 +1,5 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System.Threading;
namespace ARMeilleure.State namespace ARMeilleure.State
{ {
@@ -10,7 +11,7 @@ namespace ARMeilleure.State
internal nint NativeContextPtr => _nativeContext.BasePtr; internal nint NativeContextPtr => _nativeContext.BasePtr;
private bool _interrupted; internal bool Interrupted { get; private set; }
private readonly ICounter _counter; private readonly ICounter _counter;
@@ -67,6 +68,8 @@ namespace ARMeilleure.State
public bool IsAarch32 { get; set; } public bool IsAarch32 { get; set; }
public ulong ThreadUid { get; set; }
internal ExecutionMode ExecutionMode internal ExecutionMode ExecutionMode
{ {
get get
@@ -92,14 +95,19 @@ namespace ARMeilleure.State
private readonly ExceptionCallbackNoArgs _interruptCallback; private readonly ExceptionCallbackNoArgs _interruptCallback;
private readonly ExceptionCallback _breakCallback; private readonly ExceptionCallback _breakCallback;
private readonly ExceptionCallbackNoArgs _stepCallback;
private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback; private readonly ExceptionCallback _undefinedCallback;
internal int ShouldStep;
public ulong DebugPc { get; set; }
public ExecutionContext( public ExecutionContext(
IJitMemoryAllocator allocator, IJitMemoryAllocator allocator,
ICounter counter, ICounter counter,
ExceptionCallbackNoArgs interruptCallback = null, ExceptionCallbackNoArgs interruptCallback = null,
ExceptionCallback breakCallback = null, ExceptionCallback breakCallback = null,
ExceptionCallbackNoArgs stepCallback = null,
ExceptionCallback supervisorCallback = null, ExceptionCallback supervisorCallback = null,
ExceptionCallback undefinedCallback = null) ExceptionCallback undefinedCallback = null)
{ {
@@ -107,6 +115,7 @@ namespace ARMeilleure.State
_counter = counter; _counter = counter;
_interruptCallback = interruptCallback; _interruptCallback = interruptCallback;
_breakCallback = breakCallback; _breakCallback = breakCallback;
_stepCallback = stepCallback;
_supervisorCallback = supervisorCallback; _supervisorCallback = supervisorCallback;
_undefinedCallback = undefinedCallback; _undefinedCallback = undefinedCallback;
@@ -134,9 +143,9 @@ namespace ARMeilleure.State
internal void CheckInterrupt() internal void CheckInterrupt()
{ {
if (_interrupted) if (Interrupted)
{ {
_interrupted = false; Interrupted = false;
_interruptCallback?.Invoke(this); _interruptCallback?.Invoke(this);
} }
@@ -146,16 +155,37 @@ namespace ARMeilleure.State
public void RequestInterrupt() public void RequestInterrupt()
{ {
_interrupted = true; Interrupted = true;
}
public void StepHandler()
{
_stepCallback?.Invoke(this);
}
public void RequestDebugStep()
{
Interlocked.Exchange(ref ShouldStep, 1);
RequestInterrupt();
} }
internal void OnBreak(ulong address, int imm) internal void OnBreak(ulong address, int imm)
{ {
if (Optimizations.EnableDebugging)
{
DebugPc = Pc;
}
_breakCallback?.Invoke(this, address, imm); _breakCallback?.Invoke(this, address, imm);
} }
internal void OnSupervisorCall(ulong address, int imm) internal void OnSupervisorCall(ulong address, int imm)
{ {
if (Optimizations.EnableDebugging)
{
DebugPc = Pc;
}
_supervisorCallback?.Invoke(this, address, imm); _supervisorCallback?.Invoke(this, address, imm);
} }
+16
View File
@@ -23,6 +23,12 @@ namespace ARMeilleure.State
public int Running; public int Running;
public int CallDepth; public int CallDepth;
public long Tpidr2El0; public long Tpidr2El0;
/// <summary>
/// Precise PC value used for debugging.
/// This will only be set when Optimizations.EnableDebugging is true.
/// </summary>
public ulong DebugPrecisePc;
} }
private static NativeCtxStorage _dummyStorage = new(); private static NativeCtxStorage _dummyStorage = new();
@@ -40,6 +46,11 @@ namespace ARMeilleure.State
public ulong GetPc() public ulong GetPc()
{ {
if (Optimizations.EnableDebugging)
{
return GetStorage().DebugPrecisePc;
}
// TODO: More precise tracking of PC value. // TODO: More precise tracking of PC value.
return GetStorage().DispatchAddress; return GetStorage().DispatchAddress;
} }
@@ -269,6 +280,11 @@ namespace ARMeilleure.State
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running);
} }
public static int GetDebugPrecisePcOffset()
{
return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc);
}
public static int GetCallDepthOffset() public static int GetCallDepthOffset()
{ {
return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth); return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth);
@@ -52,6 +52,7 @@ namespace ARMeilleure.Translation
public bool HighCq { get; } public bool HighCq { get; }
public bool HasPtc { get; } public bool HasPtc { get; }
public Aarch32Mode Mode { get; } public Aarch32Mode Mode { get; }
public bool IsSingleStep { get; }
private int _ifThenBlockStateIndex = 0; private int _ifThenBlockStateIndex = 0;
private Condition[] _ifThenBlockState = []; private Condition[] _ifThenBlockState = [];
@@ -66,7 +67,8 @@ namespace ARMeilleure.Translation
ulong entryAddress, ulong entryAddress,
bool highCq, bool highCq,
bool hasPtc, bool hasPtc,
Aarch32Mode mode) Aarch32Mode mode,
bool isSingleStep)
{ {
Memory = memory; Memory = memory;
CountTable = countTable; CountTable = countTable;
@@ -76,6 +78,7 @@ namespace ARMeilleure.Translation
HighCq = highCq; HighCq = highCq;
HasPtc = hasPtc; HasPtc = hasPtc;
Mode = mode; Mode = mode;
IsSingleStep = isSingleStep;
_labels = new Dictionary<ulong, Operand>(); _labels = new Dictionary<ulong, Operand>();
} }
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ARMeilleure.Translation.Cache namespace ARMeilleure.Translation.Cache
{ {
readonly struct CacheEntry : IComparable<CacheEntry> public readonly struct CacheEntry : IComparable<CacheEntry>
{ {
public int Offset { get; } public int Offset { get; }
public int Size { get; } public int Size { get; }
@@ -23,11 +23,30 @@ namespace ARMeilleure.Translation.Cache
} }
} }
private readonly int _regionSize;
private int _regionCount;
private readonly List<MemoryBlock> _blocks = []; private readonly List<MemoryBlock> _blocks = [];
public CacheMemoryAllocator(int capacity) public CacheMemoryAllocator(int regionSize, int initialRegionCount = 1)
{ {
_blocks.Add(new MemoryBlock(0, capacity)); _regionCount = 0;
_regionSize = regionSize;
for (; initialRegionCount > 0; initialRegionCount--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
}
public void AddNewBlocks(int count)
{
for (; count > 0; count--)
{
_blocks.Add(new MemoryBlock(_regionSize * _regionCount, _regionSize));
_regionCount++;
}
} }
public int Allocate(int size) public int Allocate(int size)
@@ -66,12 +85,13 @@ namespace ARMeilleure.Translation.Cache
index = ~index; index = ~index;
} }
if (index < _blocks.Count) int endOffs = block.Offset + block.Size;
// Don't merge blocks from different allocations
if (index < _blocks.Count && endOffs % _regionSize != 0)
{ {
MemoryBlock next = _blocks[index]; MemoryBlock next = _blocks[index];
int endOffs = block.Offset + block.Size;
if (next.Offset == endOffs) if (next.Offset == endOffs)
{ {
block = new MemoryBlock(block.Offset, block.Size + next.Size); block = new MemoryBlock(block.Offset, block.Size + next.Size);
@@ -79,7 +99,8 @@ namespace ARMeilleure.Translation.Cache
} }
} }
if (index > 0) // Don't merge blocks from different allocations
if (index > 0 && block.Offset % _regionSize != 0)
{ {
MemoryBlock prev = _blocks[index - 1]; MemoryBlock prev = _blocks[index - 1];
+79 -200
View File
@@ -2,140 +2,70 @@ using ARMeilleure.CodeGen;
using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.Native; using ARMeilleure.Native;
using Humanizer;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace ARMeilleure.Translation.Cache namespace ARMeilleure.Translation.Cache
{ {
static partial class JitCache public partial class JitCache : IDisposable
{ {
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize(); private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 1; private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; private const int CodeAlignment = 4; // Bytes.
private const int FullCacheSize = 2047 * 1024 * 1024; private const uint CacheSize = 256 * 1024 * 1024;
private const int ReducedCacheSize = FullCacheSize / 8;
private const float EvictionTargetPercentage = 0.20f; private readonly JitCacheInvalidation _jitCacheInvalidator;
private const int MaxEntriesToEvictAtOnce = 100;
// Simple logging configuration private readonly CacheMemoryAllocator _cacheAllocator;
private const int LogInterval = 5000; // Log every 5000 allocations
private static ReservedRegion _jitRegion; private readonly List<CacheEntry> _cacheEntries = [];
private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator; private readonly Lock _lock = new();
private static readonly List<CacheEntry> _cacheEntries = []; private readonly List<ReservedRegion> _jitRegions = [];
private static readonly Dictionary<int, EntryUsageStats> _entryUsageStats = [];
private static readonly Lock _lock = new();
private static bool _initialized;
private static int _cacheSize;
// Basic statistics
private static int _totalAllocations = 0;
private static int _totalEvictions = 0;
private class EntryUsageStats
{
public long LastAccessTime { get; private set; }
public int UsageCount { get; private set; }
public EntryUsageStats()
{
LastAccessTime = DateTime.UtcNow.Ticks;
UsageCount = 1;
}
public void UpdateUsage()
{
LastAccessTime = DateTime.UtcNow.Ticks;
UsageCount++;
}
}
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)] [LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize); private static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
public static void Initialize(IJitMemoryAllocator allocator) public JitCache(IJitMemoryAllocator allocator)
{ {
if (_initialized)
{
return;
}
lock (_lock) lock (_lock)
{ {
if (_initialized) _jitRegions.Add(new(allocator, CacheSize));
{
return;
}
_cacheSize = Optimizations.CacheEviction ? ReducedCacheSize : FullCacheSize; _cacheAllocator = new((int)CacheSize);
_jitRegion = new ReservedRegion(allocator, (ulong)_cacheSize);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS()) if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{ {
_jitCacheInvalidator = new JitCacheInvalidation(allocator); _jitCacheInvalidator = new JitCacheInvalidation(allocator);
} }
_cacheAllocator = new CacheMemoryAllocator(_cacheSize);
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, (uint)_cacheSize, _jitRegion.Pointer + Allocate(_pageSize)); JitUnwindWindows.InstallFunctionTableHandler(
this, _jitRegions[0].Pointer, CacheSize, _jitRegions[0].Pointer + Allocate(_pageSize)
);
} }
Logger.Info?.Print(LogClass.Cpu, $"JIT Cache initialized: Size={_cacheSize / (1024 * 1024)} MB, Eviction={Optimizations.CacheEviction}");
_initialized = true;
} }
} }
public static nint Map(CompiledFunction func) public nint Map(CompiledFunction func)
{ {
byte[] code = func.Code; byte[] code = func.Code;
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized); int funcOffset = Allocate(code.Length);
_totalAllocations++; nint funcPtr = GetFunctionPtr(funcOffset);
int funcOffset;
if (Optimizations.CacheEviction)
{
int codeSize = AlignCodeSize(code.Length);
funcOffset = _cacheAllocator.Allocate(codeSize);
if (funcOffset < 0)
{
EvictEntries(codeSize);
funcOffset = _cacheAllocator.Allocate(codeSize);
if (funcOffset < 0)
{
throw new OutOfMemoryException("JIT Cache exhausted even after eviction.");
}
}
_jitRegion.ExpandIfNeeded((ulong)funcOffset + (ulong)codeSize);
}
else
{
funcOffset = Allocate(code.Length);
}
nint funcPtr = _jitRegion.Pointer + funcOffset;
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{ {
@@ -165,79 +95,104 @@ namespace ARMeilleure.Translation.Cache
Add(funcOffset, code.Length, func.UnwindInfo); Add(funcOffset, code.Length, func.UnwindInfo);
// Simple periodic logging
if (_totalAllocations % LogInterval == 0)
{
LogCacheStatus();
}
return funcPtr; return funcPtr;
} }
} }
public static void Unmap(nint pointer) public void Unmap(nint pointer)
{ {
lock (_lock) lock (_lock)
{ {
Debug.Assert(_initialized); for (int i = 0; i < _jitRegions.Count; i++)
{
ReservedRegion jitRegion = _jitRegions[i];
if (pointer.ToInt64() < jitRegion.Pointer.ToInt64() ||
pointer.ToInt64() >= (jitRegion.Pointer + (nint)CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); int funcOffset = (int)(pointer.ToInt64() - jitRegion.Pointer.ToInt64() + i * CacheSize);
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset) if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{ {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex); _cacheEntries.RemoveAt(entryIndex);
if (Optimizations.CacheEviction)
{
_entryUsageStats.Remove(funcOffset);
} }
return;
} }
} }
} }
private static void ReprotectAsWritable(int offset, int size) private void ReprotectAsWritable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionStart = offset & ~_pageMask; int regionEnd = endOffs % (int)CacheSize == 0
int regionEnd = (endOffs + _pageMask) & ~_pageMask; ? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); GetRegion(offset).Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static void ReprotectAsExecutable(int offset, int size) private void ReprotectAsExecutable(int offset, int size)
{ {
int endOffs = offset + size; int endOffs = offset + size;
int regionStart = (offset % (int)CacheSize) & ~_pageMask;
int regionEnd = endOffs % (int)CacheSize == 0
? (((int)CacheSize) + _pageMask) & ~_pageMask
: ((endOffs % (int)CacheSize) + _pageMask) & ~_pageMask;
int regionStart = offset & ~_pageMask; GetRegion(offset).Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
} }
private static int Allocate(int codeSize) private int Allocate(int codeSize)
{ {
codeSize = AlignCodeSize(codeSize); codeSize = AlignCodeSize(codeSize);
int allocOffset = _cacheAllocator.Allocate(codeSize); int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0) if (allocOffset >= 0)
{ {
throw new OutOfMemoryException("JIT Cache exhausted."); GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset;
} }
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); _cacheAllocator.AddNewBlocks(1);
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache of size {(_jitRegions.Count * CacheSize).Bytes()} exhausted, creating new Cache Region ({((_jitRegions.Count + 1) * CacheSize).Bytes()} Total Allocation).");
_jitRegions.Add(newRegion);
allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
GetRegion(allocOffset).ExpandIfNeeded((ulong)(allocOffset % (int)CacheSize) + (ulong)codeSize);
return allocOffset; return allocOffset;
} }
private nint GetFunctionPtr(int offset)
{
return GetRegion(offset).Pointer + (offset % (int)CacheSize);
}
private ReservedRegion GetRegion(int offset)
{
int index = offset / (int)CacheSize;
return _jitRegions[index];
}
private static int AlignCodeSize(int codeSize) private static int AlignCodeSize(int codeSize)
{ {
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
} }
private static void Add(int offset, int size, UnwindInfo unwindInfo) private void Add(int offset, int size, UnwindInfo unwindInfo)
{ {
CacheEntry entry = new(offset, size, unwindInfo); CacheEntry entry = new(offset, size, unwindInfo);
@@ -249,14 +204,9 @@ namespace ARMeilleure.Translation.Cache
} }
_cacheEntries.Insert(index, entry); _cacheEntries.Insert(index, entry);
if (Optimizations.CacheEviction)
{
_entryUsageStats[offset] = new EntryUsageStats();
}
} }
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex) public bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{ {
lock (_lock) lock (_lock)
{ {
@@ -270,12 +220,6 @@ namespace ARMeilleure.Translation.Cache
if (index >= 0) if (index >= 0)
{ {
entry = _cacheEntries[index]; entry = _cacheEntries[index];
if (Optimizations.CacheEviction && _entryUsageStats.TryGetValue(offset, out EntryUsageStats stats))
{
stats.UpdateUsage();
}
entryIndex = index; entryIndex = index;
return true; return true;
} }
@@ -286,82 +230,17 @@ namespace ARMeilleure.Translation.Cache
return false; return false;
} }
private static void EvictEntries(int requiredSize) public void Dispose()
{ {
if (!Optimizations.CacheEviction) if (OperatingSystem.IsWindows())
{ {
return; JitUnwindWindows.RemoveFunctionTableHandler(_jitRegions[0].Pointer);
} }
lock (_lock) foreach (ReservedRegion jitRegion in _jitRegions)
{ {
int targetSpace = Math.Max(requiredSize, (int)(_cacheSize * EvictionTargetPercentage)); jitRegion.Dispose();
int freedSpace = 0;
int evictedCount = 0;
var entriesWithStats = _cacheEntries
.Where(e => _entryUsageStats.ContainsKey(e.Offset))
.Select(e => new {
Entry = e,
Stats = _entryUsageStats[e.Offset],
Score = CalculateEvictionScore(_entryUsageStats[e.Offset])
})
.OrderBy(x => x.Score)
.Take(MaxEntriesToEvictAtOnce)
.ToList();
foreach (var item in entriesWithStats)
{
int entrySize = AlignCodeSize(item.Entry.Size);
int entryIndex = _cacheEntries.BinarySearch(item.Entry);
if (entryIndex >= 0)
{
_cacheAllocator.Free(item.Entry.Offset, entrySize);
_cacheEntries.RemoveAt(entryIndex);
_entryUsageStats.Remove(item.Entry.Offset);
freedSpace += entrySize;
evictedCount++;
if (freedSpace >= targetSpace)
{
break;
} }
} }
} }
_totalEvictions += evictedCount;
Logger.Info?.Print(LogClass.Cpu, $"JIT Cache: Evicted {evictedCount} entries, freed {freedSpace / (1024 * 1024.0):F2} MB");
}
}
private static double CalculateEvictionScore(EntryUsageStats stats)
{
long currentTime = DateTime.UtcNow.Ticks;
long ageInTicks = currentTime - stats.LastAccessTime;
double ageInSeconds = ageInTicks / 10_000_000.0;
const double usageWeight = 1.0;
const double ageWeight = 2.0;
double usageScore = Math.Log10(stats.UsageCount + 1) * usageWeight;
double ageScore = (10.0 / (ageInSeconds + 1.0)) * ageWeight;
return usageScore + ageScore;
}
private static void LogCacheStatus()
{
int estimatedUsedSize = _cacheEntries.Sum(e => AlignCodeSize(e.Size));
double usagePercentage = 100.0 * estimatedUsedSize / _cacheSize;
Logger.Info?.Print(LogClass.Cpu,
$"JIT Cache status: entries={_cacheEntries.Count}, " +
$"est. used={estimatedUsedSize / (1024 * 1024.0):F2} MB ({usagePercentage:F1}%), " +
$"evictions={_totalEvictions}, allocations={_totalAllocations}");
}
}
} }
@@ -2,6 +2,8 @@
using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.Unwinding;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -27,6 +29,29 @@ namespace ARMeilleure.Translation.Cache
public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize]; public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize];
} }
private unsafe struct InternalFunctionHandler
{
public InternalFunctionHandler(JitCache jitCache, nint workBufferPtr)
{
_jitCache = jitCache;
_runtimeFunction = (RuntimeFunction*)workBufferPtr;
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction);
}
readonly JitCache _jitCache;
readonly RuntimeFunction* _runtimeFunction;
readonly UnwindInfo* _unwindInfo;
public RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{
return JitUnwindWindows.FunctionTableHandler(_jitCache, _runtimeFunction, _unwindInfo, controlPc, context);
}
}
private enum UnwindOp private enum UnwindOp
{ {
PushNonvol = 0, PushNonvol = 0,
@@ -59,27 +84,28 @@ namespace ARMeilleure.Translation.Cache
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction; private static readonly int _sizeOfRuntimeFunction;
private unsafe static RuntimeFunction* _runtimeFunction; private static readonly ConcurrentDictionary<ulong, InternalFunctionHandler> _functionTableHandlers = new();
private unsafe static UnwindInfo* _unwindInfo; static JitUnwindWindows()
{
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
}
public static void InstallFunctionTableHandler(nint codeCachePointer, uint codeCacheLength, nint workBufferPtr) public static void InstallFunctionTableHandler(JitCache jitCache, nint codeCachePointer, uint codeCacheLength, nint workBufferPtr)
{ {
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
_sizeOfRuntimeFunction = Marshal.SizeOf<RuntimeFunction>();
bool result; bool result;
InternalFunctionHandler handler;
unsafe unsafe
{ {
_runtimeFunction = (RuntimeFunction*)workBufferPtr; handler = new InternalFunctionHandler(jitCache, workBufferPtr);
_unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction); _getRuntimeFunctionCallback = handler.FunctionTableHandler;
_getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler);
result = RtlInstallFunctionTableCallback( result = RtlInstallFunctionTableCallback(
codeCachePtr | 3, codeCachePtr | 3,
@@ -94,6 +120,8 @@ namespace ARMeilleure.Translation.Cache
{ {
throw new InvalidOperationException("Failure installing function table callback."); throw new InvalidOperationException("Failure installing function table callback.");
} }
_functionTableHandlers.TryAdd(codeCachePtr, handler);
} }
public static void RemoveFunctionTableHandler(nint codeCachePointer) public static void RemoveFunctionTableHandler(nint codeCachePointer)
@@ -111,24 +139,26 @@ namespace ARMeilleure.Translation.Cache
{ {
throw new InvalidOperationException("Failure removing function table callback."); throw new InvalidOperationException("Failure removing function table callback.");
} }
_functionTableHandlers.Remove(codeCachePtr, out _);
} }
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context) private static unsafe RuntimeFunction* FunctionTableHandler(JitCache jitCache, RuntimeFunction* runtimeFunction, UnwindInfo* unwindInfo, ulong controlPc, nint context)
{ {
int offset = (int)((long)controlPc - context.ToInt64()); int offset = (int)((long)controlPc - context.ToInt64());
if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _)) if (!jitCache.TryFind(offset, out CacheEntry funcEntry, out _))
{ {
return null; // Not found. return null; // Not found.
} }
CodeGen.Unwinding.UnwindInfo unwindInfo = funcEntry.UnwindInfo; CodeGen.Unwinding.UnwindInfo funcUnwindInfo = funcEntry.UnwindInfo;
int codeIndex = 0; int codeIndex = 0;
for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) for (int index = funcUnwindInfo.PushEntries.Length - 1; index >= 0; index--)
{ {
UnwindPushEntry entry = unwindInfo.PushEntries[index]; UnwindPushEntry entry = funcUnwindInfo.PushEntries[index];
switch (entry.PseudoOp) switch (entry.PseudoOp)
{ {
@@ -140,14 +170,14 @@ namespace ARMeilleure.Translation.Cache
if (stackOffset <= 0xFFFF0) if (stackOffset <= 0xFFFF0)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
} }
else else
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
} }
break; break;
@@ -161,18 +191,18 @@ namespace ARMeilleure.Translation.Cache
if (allocSize <= 128) if (allocSize <= 128)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
} }
else if (allocSize <= 0x7FFF8) else if (allocSize <= 0x7FFF8)
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
} }
else else
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16); unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
} }
break; break;
@@ -180,7 +210,7 @@ namespace ARMeilleure.Translation.Cache
case UnwindPseudoOp.PushReg: case UnwindPseudoOp.PushReg:
{ {
_unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex); unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
break; break;
} }
@@ -192,16 +222,16 @@ namespace ARMeilleure.Translation.Cache
Debug.Assert(codeIndex <= MaxUnwindCodesArraySize); Debug.Assert(codeIndex <= MaxUnwindCodesArraySize);
_unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler. unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler.
_unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize; unwindInfo->SizeOfProlog = (byte)funcUnwindInfo.PrologSize;
_unwindInfo->CountOfUnwindCodes = (byte)codeIndex; unwindInfo->CountOfUnwindCodes = (byte)codeIndex;
_unwindInfo->FrameRegister = 0; unwindInfo->FrameRegister = 0;
_runtimeFunction->BeginAddress = (uint)funcEntry.Offset; runtimeFunction->BeginAddress = (uint)funcEntry.Offset;
_runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size); runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size);
_runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction; runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction;
return _runtimeFunction; return runtimeFunction;
} }
private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo) private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo)
+9 -2
View File
@@ -1,11 +1,18 @@
using System;
namespace ARMeilleure.Translation namespace ARMeilleure.Translation
{ {
public class DelegateInfo class DelegateInfo
{ {
#pragma warning disable IDE0052 // Remove unread private member
private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected.
#pragma warning restore IDE0052
public nint FuncPtr { get; } public nint FuncPtr { get; }
public DelegateInfo(nint funcPtr) public DelegateInfo(Delegate dlg, nint funcPtr)
{ {
_dlg = dlg;
FuncPtr = funcPtr; FuncPtr = funcPtr;
} }
} }
File diff suppressed because it is too large Load Diff
@@ -97,14 +97,7 @@ namespace ARMeilleure.Translation
public virtual Operand Call(MethodInfo info, params Operand[] callArgs) public virtual Operand Call(MethodInfo info, params Operand[] callArgs)
{ {
#if ANDROID nint funcPtr = Delegates.GetDelegateFuncPtr(info);
// For Android, use the Delegates class to get the function pointer
int index = Delegates.GetDelegateIndex(info);
nint funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
#else
// For other platforms, use direct method handle approach
nint funcPtr = info.MethodHandle.GetFunctionPointer();
#endif
OperandType returnType = GetOperandType(info.ReturnType); OperandType returnType = GetOperandType(info.ReturnType);
+4 -16
View File
@@ -359,10 +359,7 @@ namespace ARMeilleure.Translation
IntervalTreeNode<TK, TV> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); IntervalTreeNode<TK, TV> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
if (tmp != null) tmp?.Parent = ParentOf(replacementNode);
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null) if (ParentOf(replacementNode) == null)
{ {
@@ -570,10 +567,7 @@ namespace ARMeilleure.Translation
{ {
IntervalTreeNode<TK, TV> right = RightOf(node); IntervalTreeNode<TK, TV> right = RightOf(node);
node.Right = LeftOf(right); node.Right = LeftOf(right);
if (node.Right != null) node.Right?.Parent = node;
{
node.Right.Parent = node;
}
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node); IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
right.Parent = nodeParent; right.Parent = nodeParent;
if (nodeParent == null) if (nodeParent == null)
@@ -601,10 +595,7 @@ namespace ARMeilleure.Translation
{ {
IntervalTreeNode<TK, TV> left = LeftOf(node); IntervalTreeNode<TK, TV> left = LeftOf(node);
node.Left = RightOf(left); node.Left = RightOf(left);
if (node.Left != null) node.Left?.Parent = node;
{
node.Left.Parent = node;
}
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node); IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
left.Parent = nodeParent; left.Parent = nodeParent;
if (nodeParent == null) if (nodeParent == null)
@@ -651,10 +642,7 @@ namespace ARMeilleure.Translation
/// <param name="color">Color (Boolean)</param> /// <param name="color">Color (Boolean)</param>
private static void SetColor(IntervalTreeNode<TK, TV> node, bool color) private static void SetColor(IntervalTreeNode<TK, TV> node, bool color)
{ {
if (node != null) node?.Color = color;
{
node.Color = color;
}
} }
/// <summary> /// <summary>
+13 -5
View File
@@ -4,6 +4,7 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common; using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -46,6 +47,8 @@ namespace ARMeilleure.Translation.PTC
public PtcProfiler Profiler { get; } public PtcProfiler Profiler { get; }
private readonly JitCache _jitCache;
// Carriers. // Carriers.
private MemoryStream _infosStream; private MemoryStream _infosStream;
private List<byte[]> _codesList; private List<byte[]> _codesList;
@@ -75,7 +78,7 @@ namespace ARMeilleure.Translation.PTC
private volatile int _translateTotalCount; private volatile int _translateTotalCount;
public event Action<PtcLoadingState, int, int> PtcStateChanged; public event Action<PtcLoadingState, int, int> PtcStateChanged;
public Ptc() public Ptc(JitCache jitCache)
{ {
Profiler = new PtcProfiler(this); Profiler = new PtcProfiler(this);
@@ -86,6 +89,8 @@ namespace ARMeilleure.Translation.PTC
_waitEvent = new ManualResetEvent(true); _waitEvent = new ManualResetEvent(true);
_jitCache = jitCache;
_disposed = false; _disposed = false;
CacheInfo = new PtcCacheInfo(0, null, null, 0, null, "Unknown", "default"); CacheInfo = new PtcCacheInfo(0, null, null, 0, null, "Unknown", "default");
@@ -253,7 +258,8 @@ namespace ARMeilleure.Translation.PTC
outerHeader.FeatureInfo != GetFeatureInfo() || outerHeader.FeatureInfo != GetFeatureInfo() ||
outerHeader.MemoryManagerMode != GetMemoryManagerMode() || outerHeader.MemoryManagerMode != GetMemoryManagerMode() ||
outerHeader.OSPlatform != GetOSPlatform() || outerHeader.OSPlatform != GetOSPlatform() ||
outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture) outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture ||
outerHeader.DebuggerMode != Optimizations.EnableDebugging)
{ {
InvalidateCompressedStream(compressedStream); InvalidateCompressedStream(compressedStream);
@@ -434,6 +440,7 @@ namespace ARMeilleure.Translation.PTC
MemoryManagerMode = GetMemoryManagerMode(), MemoryManagerMode = GetMemoryManagerMode(),
OSPlatform = GetOSPlatform(), OSPlatform = GetOSPlatform(),
Architecture = (uint)RuntimeInformation.ProcessArchitecture, Architecture = (uint)RuntimeInformation.ProcessArchitecture,
DebuggerMode = Optimizations.EnableDebugging,
UncompressedStreamSize = UncompressedStreamSize =
(long)Unsafe.SizeOf<InnerHeader>() + (long)Unsafe.SizeOf<InnerHeader>() +
@@ -731,7 +738,7 @@ namespace ARMeilleure.Translation.PTC
return new UnwindInfo(pushEntries, prologueSize); return new UnwindInfo(pushEntries, prologueSize);
} }
private static TranslatedFunction FastTranslate( private TranslatedFunction FastTranslate(
byte[] code, byte[] code,
Counter<uint> callCounter, Counter<uint> callCounter,
ulong guestSize, ulong guestSize,
@@ -739,7 +746,7 @@ namespace ARMeilleure.Translation.PTC
bool highCq) bool highCq)
{ {
CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty); CompiledFunction cFunc = new(code, unwindInfo, RelocInfo.Empty);
GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(out nint gFuncPointer); GuestFunction gFunc = cFunc.MapWithPointer<GuestFunction>(_jitCache, out nint gFuncPointer);
return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq); return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
} }
@@ -1030,7 +1037,7 @@ namespace ARMeilleure.Translation.PTC
return osPlatform; return osPlatform;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)] [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 87*/)]
private struct OuterHeader private struct OuterHeader
{ {
public ulong Magic; public ulong Magic;
@@ -1042,6 +1049,7 @@ namespace ARMeilleure.Translation.PTC
public byte MemoryManagerMode; public byte MemoryManagerMode;
public uint OSPlatform; public uint OSPlatform;
public uint Architecture; public uint Architecture;
public bool DebuggerMode;
public long UncompressedStreamSize; public long UncompressedStreamSize;
@@ -9,13 +9,13 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Timers; using System.Timers;
using static ARMeilleure.Translation.PTC.PtcFormatter; using static ARMeilleure.Translation.PTC.PtcFormatter;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
using System.Linq;
namespace ARMeilleure.Translation.PTC namespace ARMeilleure.Translation.PTC
{ {
+54 -11
View File
@@ -24,10 +24,11 @@ namespace ARMeilleure.Translation
private readonly IJitMemoryAllocator _allocator; private readonly IJitMemoryAllocator _allocator;
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs; private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
public readonly JitCache JitCache;
private readonly Ptc _ptc; private readonly Ptc _ptc;
internal TranslatorCache<TranslatedFunction> Functions { get; } internal TranslatorCache<TranslatedFunction> Functions { get; }
internal IAddressTable<ulong> FunctionTable { get; } public IAddressTable<ulong> FunctionTable { get; }
internal EntryTable<uint> CountTable { get; } internal EntryTable<uint> CountTable { get; }
internal TranslatorStubs Stubs { get; } internal TranslatorStubs Stubs { get; }
internal TranslatorQueue Queue { get; } internal TranslatorQueue Queue { get; }
@@ -43,16 +44,19 @@ namespace ARMeilleure.Translation
_oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>(); _oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
_ptc = new Ptc(); JitCache = new JitCache(allocator);
_ptc = new Ptc(JitCache);
Queue = new TranslatorQueue(); Queue = new TranslatorQueue();
JitCache.Initialize(allocator);
CountTable = new EntryTable<uint>(); CountTable = new EntryTable<uint>();
Functions = new TranslatorCache<TranslatedFunction>(); Functions = new TranslatorCache<TranslatedFunction>();
FunctionTable = functionTable; FunctionTable = functionTable;
Stubs = new TranslatorStubs(FunctionTable);
Stubs = new TranslatorStubs(JitCache, FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
} }
@@ -119,7 +123,25 @@ namespace ARMeilleure.Translation
NativeInterface.RegisterThread(context, Memory, this); NativeInterface.RegisterThread(context, Memory, this);
if (Optimizations.UseUnmanagedDispatchLoop) if (Optimizations.EnableDebugging)
{
context.DebugPc = address;
do
{
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
{
context.DebugPc = Step(context, context.DebugPc);
context.StepHandler();
}
else
{
context.DebugPc = ExecuteSingle(context, context.DebugPc);
}
context.CheckInterrupt();
}
while (context.Running && context.DebugPc != 0);
}
else if (Optimizations.UseUnmanagedDispatchLoop)
{ {
Stubs.DispatchLoop(context.NativeContextPtr, address); Stubs.DispatchLoop(context.NativeContextPtr, address);
} }
@@ -149,6 +171,7 @@ namespace ARMeilleure.Translation
} }
ClearJitCache(); ClearJitCache();
JitCache.Dispose();
Stubs.Dispose(); Stubs.Dispose();
FunctionTable.Dispose(); FunctionTable.Dispose();
@@ -176,7 +199,7 @@ namespace ARMeilleure.Translation
return nextAddr; return nextAddr;
} }
public ulong Step(State.ExecutionContext context, ulong address) private ulong Step(State.ExecutionContext context, ulong address)
{ {
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);
@@ -187,6 +210,8 @@ namespace ARMeilleure.Translation
return address; return address;
} }
internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode)
{ {
if (!Functions.TryGetValue(address, out TranslatedFunction func)) if (!Functions.TryGetValue(address, out TranslatedFunction func))
@@ -230,7 +255,8 @@ namespace ARMeilleure.Translation
address, address,
highCq, highCq,
_ptc.State != PtcState.Disabled, _ptc.State != PtcState.Disabled,
mode: Aarch32Mode.User); mode: Aarch32Mode.User,
isSingleStep: singleStep);
Logger.StartPass(PassName.Decoding); Logger.StartPass(PassName.Decoding);
@@ -284,7 +310,7 @@ namespace ARMeilleure.Translation
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
} }
GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out nint funcPointer); GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(JitCache, out nint funcPointer);
Allocators.ResetAll(); Allocators.ResetAll();
@@ -369,9 +395,13 @@ namespace ARMeilleure.Translation
if (block.Exit) if (block.Exit)
{ {
// Left option here as it may be useful if we need to return to managed rather than tail call in // Return to managed rather than tail call.
// future. (eg. for debug) bool useReturns = Optimizations.EnableDebugging;
bool useReturns = false;
if (Optimizations.EnableDebugging)
{
EmitDebugPrecisePcUpdate(context, block.Address);
}
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
} }
@@ -395,6 +425,11 @@ namespace ARMeilleure.Translation
} }
} }
if (Optimizations.EnableDebugging)
{
EmitDebugPrecisePcUpdate(context, opCode.Address);
}
Operand lblPredicateSkip = default; Operand lblPredicateSkip = default;
if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al) if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al)
@@ -491,6 +526,14 @@ namespace ARMeilleure.Translation
context.MarkLabel(lblExit); context.MarkLabel(lblExit);
} }
internal static void EmitDebugPrecisePcUpdate(EmitterContext context, ulong address)
{
long debugPrecisePcOffs = NativeContext.GetDebugPrecisePcOffset();
Operand debugPrecisePcAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(debugPrecisePcOffs));
context.Store(debugPrecisePcAddr, Const(address));
}
public void InvalidateJitCacheRegion(ulong address, ulong size) public void InvalidateJitCacheRegion(ulong address, ulong size)
{ {
ulong[] overlapAddresses = []; ulong[] overlapAddresses = [];
+12 -7
View File
@@ -14,6 +14,8 @@ namespace ARMeilleure.Translation
/// </summary> /// </summary>
class TranslatorStubs : IDisposable class TranslatorStubs : IDisposable
{ {
private readonly JitCache _jitCache;
private readonly Lazy<nint> _slowDispatchStub; private readonly Lazy<nint> _slowDispatchStub;
private bool _disposed; private bool _disposed;
@@ -83,12 +85,15 @@ namespace ARMeilleure.Translation
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified /// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
/// <see cref="Translator"/> instance. /// <see cref="Translator"/> instance.
/// </summary> /// </summary>
/// <param name="jitCache">Jit cache to map functions in</param>
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param> /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
public TranslatorStubs(IAddressTable<ulong> functionTable) public TranslatorStubs(JitCache jitCache, IAddressTable<ulong> functionTable)
{ {
ArgumentNullException.ThrowIfNull(functionTable); ArgumentNullException.ThrowIfNull(functionTable);
_jitCache = jitCache;
_functionTable = functionTable; _functionTable = functionTable;
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
@@ -115,12 +120,12 @@ namespace ARMeilleure.Translation
{ {
if (_dispatchStub.IsValueCreated) if (_dispatchStub.IsValueCreated)
{ {
JitCache.Unmap(_dispatchStub.Value); _jitCache.Unmap(_dispatchStub.Value);
} }
if (_dispatchLoop.IsValueCreated) if (_dispatchLoop.IsValueCreated)
{ {
JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); _jitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
} }
_disposed = true; _disposed = true;
@@ -188,7 +193,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64 }; OperandType[] argTypes = new[] { OperandType.I64 };
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(); GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func); return Marshal.GetFunctionPointerForDelegate(func);
} }
@@ -213,7 +218,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64 }; OperandType[] argTypes = new[] { OperandType.I64 };
GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(); GuestFunction func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>(_jitCache);
return Marshal.GetFunctionPointerForDelegate(func); return Marshal.GetFunctionPointerForDelegate(func);
} }
@@ -290,7 +295,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.None; OperandType retType = OperandType.None;
OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 }; OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(); return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>(_jitCache);
} }
/// <summary> /// <summary>
@@ -314,7 +319,7 @@ namespace ARMeilleure.Translation
OperandType retType = OperandType.I64; OperandType retType = OperandType.I64;
OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 }; OperandType[] argTypes = new[] { OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(); return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>(_jitCache);
} }
} }
} }
@@ -1,6 +1,7 @@
using ARMeilleure.CodeGen.X86; using ARMeilleure.CodeGen.X86;
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State; using ARMeilleure.State;
using ARMeilleure.Translation.Cache;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory; using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
@@ -59,7 +60,7 @@ namespace ARMeilleure.Translation
return context.VectorExtract(OperandType.I32, vec, 0); return context.VectorExtract(OperandType.I32, vec, 0);
} }
public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest() public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest(JitCache jitCache)
{ {
EmitterContext context = new(); EmitterContext context = new();
@@ -141,7 +142,7 @@ namespace ARMeilleure.Translation
OperandType[] argTypes = [OperandType.I64]; OperandType[] argTypes = [OperandType.I64];
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(); return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<FpFlagsPInvokeTest>(jitCache);
} }
} }
} }
-12
View File
@@ -1,12 +0,0 @@
.idea/
*.iml
.gradle
local.properties
.DS_Store
build/
captures
.externalNativeBuild
.cxx/
app/src/main/jniLibs/arm64-v8a/**
!app/src/main/jniLibs/arm64-v8a/.gitkeep
-175
View File
@@ -1,175 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.plugin.compose'
}
android {
namespace = 'org.kenjinx.android'
compileSdk 37
defaultConfig {
applicationId "org.kenjinx.android"
minSdk 29
targetSdk 37
versionCode 20100
versionName '2.1.0-pr.2'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
//noinspection ChromeOsAbiSupport
abiFilters 'arm64-v8a'
}
externalNativeBuild {
cmake {
cppFlags "-std=c++20 -O3",
"-fno-math-errno -fno-trapping-math -fno-signed-zeros -ffinite-math-only"
arguments "-DANDROID_STL=c++_shared",
"-DCMAKE_BUILD_TYPE=Release",
"-DANDROID_ARM_NEON=TRUE",
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
}
buildTypes {
release {
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.debug
debuggable false
jniDebuggable false
}
}
def deviceFlavors = [
armv8 : "-march=armv8-a",
armv82 : "-march=armv8.2-a",
armv87 : "-march=armv8.7-a",
sd8eg5 : "-march=armv8.7-a+sve+sve2",
]
flavorDimensions "appid", "device"
productFlavors {
mainline {
dimension "appid"
applicationId "org.kenjinx.android"
manifestPlaceholders = [applicationLabel: "@string/app_name"]
}
optimized {
dimension "appid"
applicationId "com.garena.game.codm"
manifestPlaceholders = [applicationLabel: "@string/app_name_optimized"]
}
}
deviceFlavors.each { name, flags ->
productFlavors.create(name) {
dimension "device"
externalNativeBuild {
cmake {
cppFlags flags
}
}
}
}
androidComponents {
onVariants(selector().withBuildType("debug")) {
packaging.dex.useLegacyPackaging.set(false)
}
beforeVariants(selector().all()) { variant ->
def flavors = variant.productFlavors.collect { it.second }.toSet()
def invalidCombos = [
["optimized", "armv8"],
["optimized", "armv87"],
["optimized", "sd8eg5"]
]
if (invalidCombos.any { combo -> flavors.containsAll(combo) }) {
enable = false
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
buildFeatures {
compose = true
prefab = true
buildConfig = true
}
packagingOptions {
jniLibs {
keepDebugSymbols += '**/libkenjinx.so'
useLegacyPackaging true
}
dex {
useLegacyPackaging true
}
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version = '4.1.2'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/extLibs', 'src/main/jniLibs']
}
}
}
tasks.named("preBuild") {
dependsOn ':libkenjinx:assemble'
}
dependencies {
runtimeOnly project(":libkenjinx")
implementation 'androidx.activity:activity-compose:1.13.0'
implementation 'androidx.appcompat:appcompat:1.8.0'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.constraintlayout:constraintlayout:2.2.2'
implementation 'androidx.core:core-ktx:1.19.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.11.0'
implementation 'androidx.navigation:navigation-compose:2.9.8'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'br.com.devsrsouza.compose.icons:css-gg:1.1.1'
implementation 'com.anggrayudi:storage:2.2.0'
implementation 'com.github.swordfish90:radialgamepad:2.0.0'
implementation 'com.google.android.material:material:1.14.0'
implementation 'com.google.code.gson:gson:2.14.0'
implementation 'com.halilibo.compose-richtext:richtext-commonmark:0.20.0'
implementation 'com.halilibo.compose-richtext:richtext-ui-material3:0.20.0'
implementation 'com.halilibo.compose-richtext:richtext-ui:0.20.0'
implementation 'io.coil-kt:coil-compose:2.7.0'
implementation 'net.java.dev.jna:jna:5.19.1@aar'
implementation 'net.lingala.zip4j:zip4j:2.11.6'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0'
implementation platform('androidx.compose:compose-bom:2026.08.00')
implementation platform('org.jetbrains.kotlin:kotlin-bom:2.3.10')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation platform('androidx.compose:compose-bom:2026.08.00')
testImplementation 'junit:junit:4.13.2'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
debugImplementation 'androidx.compose.ui:ui-tooling'
}
-37
View File
@@ -1,37 +0,0 @@
-optimizationpasses 5
-optimizations !code/simplification/arithmetic
-optimizations !code/simplification/cast
-optimizations !field/*
-optimizations !class/merging/*
-optimizations !method/inlining/short
-optimizations !method/inlining/unique
-optimizations method/inlining/tailrecursion
-optimizations method/removal/parameter
-optimizations code/merging
-optimizations code/simplification/variable
-allowaccessmodification
-repackageclasses ''
-keepattributes Exceptions,InnerClasses,Signature,*Annotation*
-dontpreverify
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends androidx.fragment.app.Fragment
-dontwarn java.awt.Component
-dontwarn java.awt.GraphicsEnvironment
-dontwarn java.awt.HeadlessException
-dontwarn java.awt.Window
-dontwarn javax.lang.model.element.Modifier
-assumenosideeffects class java.lang.Math {
public static double random();
public static double sin(...);
public static double cos(...);
public static double sqrt(...);
}
-assumenosideeffects public class ** {
public boolean is*();
public boolean get*();
public boolean has*();
}
@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Android 11+ Package Visibility: What this app is allowed to see/access -->
<queries>
<!-- If other flavors/starters are involved, make them visible here -->
<package android:name="org.kenjinx.android"/>
<!-- General: Make LAUNCHER activities and ACTION_VIEW files resolvable -->
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent>
</queries>
<uses-feature
android:name="android.hardware.audio.output"
android:required="true" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Notifications + special-use Foreground Service (Android 14+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Required for startForeground() -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<application
android:name=".KenjinxApplication"
android:allowBackup="true"
android:appCategory="game"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:isGame="true"
android:label="${applicationLabel}"
android:supportsRtl="true"
android:theme="@style/Theme.KenjinxAndroid"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:exported="true"
android:screenOrientation="unspecified"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
android:hardwareAccelerated="true"
android:theme="@style/Theme.KenjinxAndroid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="org.kenjinx.android.LAUNCH_GAME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!--
<intent-filter>
<action android:name="org.kenji.android.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
-->
<!-- Shortcut Wizard Activity (transparent, freely rotatable) -->
<activity
android:name=".ShortcutWizardActivity"
android:exported="false"
android:theme="@style/Theme.KenjinxAndroid.Transparent"
android:screenOrientation="unspecified"
android:configChanges="orientation|screenSize|keyboardHidden|uiMode" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name=".providers.DocumentProvider"
android:authorities="${applicationId}.providers"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<service
android:name=".service.EmulationService"
android:exported="false"
android:stopWithTask="true"
android:foregroundServiceType="mediaPlayback" />
</application>
</manifest>
@@ -1,80 +0,0 @@
include(FetchContent)
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.31.5)
# Declares and names the project.
project("kenjinxjni")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
FetchContent_Declare(
adrenotools
GIT_REPOSITORY https://github.com/bylaws/libadrenotools.git
GIT_TAG 8fae8ce254dfc1344527e05301e43f37dea2df80
)
FetchContent_MakeAvailable(adrenotools)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
kenjinxjni
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
vulkan_wrapper.cpp
kenjinx.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
kenjinxjni
# Links the target library to the log library
# included in the NDK.
${log-lib}
-lvulkan
-landroid
adrenotools
)
# Build external libraries if prebuilt files don't exist
set(JNI_PATH ../jniLibs/${CMAKE_ANDROID_ARCH_ABI})
cmake_path(ABSOLUTE_PATH JNI_PATH NORMALIZE)
cmake_path(APPEND JNI_PATH libcrypto.so OUTPUT_VARIABLE LIBCRYPTO_JNI_PATH)
cmake_path(APPEND JNI_PATH libssl.so OUTPUT_VARIABLE LIBSSL_JNI_PATH)
# Add OpenAL
add_subdirectory(libraries/openal)
set_target_properties(OpenAL PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${JNI_PATH}
ARCHIVE_OUTPUT_DIRECTORY ${JNI_PATH}
RUNTIME_OUTPUT_DIRECTORY ${JNI_PATH}
)
@@ -1,254 +0,0 @@
// Write C++ code here.
//
// Do not forget to dynamically load the C++ library into your application.
//
// For instance,
//
// In MainActivity.java:
// static {
// System.loadLibrary("kenjinxjni");
// }
//
// Or, in MainActivity.kt:
// companion object {
// init {
// System.loadLibrary("kenjinxjni")
// }
// }
#include "kenjinx.h"
#include "pthread.h"
#include <chrono>
#include <csignal>
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint;
extern "C"
{
JNIEXPORT jlong JNICALL
Java_org_kenjinx_android_NativeHelpers_getNativeWindow(
JNIEnv *env,
jobject instance,
jobject surface) {
auto nativeWindow = ANativeWindow_fromSurface(env, surface);
return nativeWindow == nullptr ? -1 : (jlong) nativeWindow;
}
JNIEXPORT void JNICALL
Java_org_kenjinx_android_NativeHelpers_releaseNativeWindow(
JNIEnv *env,
jobject instance,
jlong window) {
auto nativeWindow = (ANativeWindow *) window;
if (nativeWindow != nullptr)
ANativeWindow_release(nativeWindow);
}
long createSurface(long native_surface, long instance) {
auto nativeWindow = (ANativeWindow *) native_surface;
VkSurfaceKHR surface;
auto vkInstance = (VkInstance) instance;
auto fpCreateAndroidSurfaceKHR =
reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(vkInstance,
"vkCreateAndroidSurfaceKHR"));
if (fpCreateAndroidSurfaceKHR == nullptr)
LOGE("Could not get function pointer to CreateAndroidSurfaceKHR");
VkAndroidSurfaceCreateInfoKHR info;
info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
info.pNext = nullptr;
info.flags = 0;
info.window = nativeWindow;
VK_CHECK(fpCreateAndroidSurfaceKHR(vkInstance, &info, nullptr, &surface));
return (long) surface;
}
JNIEXPORT jlong JNICALL
Java_org_kenjinx_android_NativeHelpers_getCreateSurfacePtr(
JNIEnv *env,
jobject instance) {
return (jlong) createSurface;
}
char *getStringPointer(
JNIEnv *env,
jstring jS) {
const char *cparam = env->GetStringUTFChars(jS, nullptr);
auto len = env->GetStringUTFLength(jS);
char *s = new char[len + 1]; //null terminator
strcpy(s, cparam);
env->ReleaseStringUTFChars(jS, cparam);
return s;
}
jstring createString(
JNIEnv *env,
char *ch) {
auto str = env->NewStringUTF(ch);
return str;
}
jstring createStringFromStdString(
JNIEnv *env,
std::string s) {
auto str = env->NewStringUTF(s.c_str());
return str;
}
}
extern "C"
void setRenderingThread() {
auto currentId = pthread_self();
_renderingThreadId = currentId;
_currentTimePoint = std::chrono::high_resolution_clock::now();
}
extern "C"
JNIEXPORT void JNICALL
Java_org_kenjinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) {
JavaVM *vm = nullptr;
env->GetJavaVM(&vm);
_vm = vm;
_mainActivity = thiz;
_mainActivityClass = env->GetObjectClass(thiz);
}
bool isInitialOrientationFlipped = true;
extern "C"
void setCurrentTransform(long native_window, int transform) {
if (native_window == 0 || native_window == -1)
return;
auto nativeWindow = (ANativeWindow *) native_window;
auto nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
transform = transform >> 1;
// transform is a valid VkSurfaceTransformFlagBitsKHR
switch (transform) {
case 0x1:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
break;
case 0x2:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90;
break;
case 0x4:
nativeTransform = isInitialOrientationFlipped
? ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY
: ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180;
break;
case 0x8:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270;
break;
case 0x10:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL;
break;
case 0x20:
nativeTransform = static_cast<ANativeWindowTransform>(
ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL |
ANATIVEWINDOW_TRANSFORM_ROTATE_90);
break;
case 0x40:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL;
break;
case 0x80:
nativeTransform = static_cast<ANativeWindowTransform>(
ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL |
ANATIVEWINDOW_TRANSFORM_ROTATE_90);
break;
case 0x100:
nativeTransform = ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY;
break;
}
nativeWindow->perform(nativeWindow, NATIVE_WINDOW_SET_BUFFERS_TRANSFORM,
static_cast<int32_t>(nativeTransform));
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_kenjinx_android_NativeHelpers_loadDriver(JNIEnv *env, jobject thiz,
jstring native_lib_path,
jstring private_apps_path,
jstring driver_name) {
auto libPath = getStringPointer(env, native_lib_path);
auto privateAppsPath = getStringPointer(env, private_apps_path);
auto driverName = getStringPointer(env, driver_name);
auto handle = adrenotools_open_libvulkan(
RTLD_NOW,
ADRENOTOOLS_DRIVER_CUSTOM,
nullptr,
libPath,
privateAppsPath,
driverName,
nullptr,
nullptr
);
delete libPath;
delete privateAppsPath;
delete driverName;
return (jlong) handle;
}
extern "C"
void debug_break(int code) {
if (code >= 3)
int r = 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_org_kenjinx_android_NativeHelpers_setTurboMode(JNIEnv *env, jobject thiz, jboolean enable) {
adrenotools_set_turbo(enable);
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_kenjinx_android_NativeHelpers_getMaxSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->maxSwapInterval;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_kenjinx_android_NativeHelpers_getMinSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->minSwapInterval;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_kenjinx_android_NativeHelpers_setSwapInterval(JNIEnv *env, jobject thiz,
jlong native_window, jint swap_interval) {
auto nativeWindow = (ANativeWindow *) native_window;
return nativeWindow->setSwapInterval(nativeWindow, swap_interval);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_org_kenjinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong ptr) {
return createString(env, (char*)ptr);
}
extern "C"
JNIEXPORT void JNICALL
Java_org_kenjinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *env, jobject thiz,
jboolean is_flipped) {
isInitialOrientationFlipped = is_flipped;
}
@@ -1,60 +0,0 @@
//
// Created by Emmanuel Hansen on 6/19/2023.
//
#ifndef KENJINXNATIVE_KENJINX_H
#define KENJINXNATIVE_KENJINX_H
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <exception>
#include <fcntl.h>
#include <jni.h>
#include <string>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include "vulkan_wrapper.h"
#include <vulkan/vulkan_android.h>
#include "adrenotools/driver.h"
#include "native_window.h"
// Android log function wrappers
static const char* TAG = "Kenjinx";
#define LOGI(...) \
((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGW(...) \
((void)__android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__))
#define LOGE(...) \
((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
// A macro to pass call to Vulkan and check for return value for success
#define CALL_VK(func) \
if (VK_SUCCESS != (func)) { \
__android_log_print(ANDROID_LOG_ERROR, "Tutorial ", \
"Vulkan error. File[%s], line[%d]", __FILE__, \
__LINE__); \
assert(false); \
}
// A macro to check value is VK_SUCCESS
// Used also for non-vulkan functions but return VK_SUCCESS
#define VK_CHECK(x) CALL_VK(x)
#define LoadLib(a) dlopen(a, RTLD_NOW)
void *_kenjinxNative = nullptr;
// Kenjinx imported functions
bool (*initialize)(char *) = nullptr;
long _renderingThreadId = 0;
JavaVM *_vm = nullptr;
jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr;
#endif //KENJINXNATIVE_KENJINX_H
@@ -1,305 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2021 The Android Open Source Project
#pragma once
/* A collection of various types from AOSP that allow us to access private APIs for Native Window which we utilize for emulating the guest SF more accurately */
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=29;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_UNSIGNED_CAST(x) static_cast<unsigned int>(x)
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=34-38;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
#define ANDROID_NATIVE_MAKE_CONSTANT(a, b, c, d) \
((ANDROID_NATIVE_UNSIGNED_CAST(a) << 24) | \
(ANDROID_NATIVE_UNSIGNED_CAST(b) << 16) | \
(ANDROID_NATIVE_UNSIGNED_CAST(c) << 8) | \
(ANDROID_NATIVE_UNSIGNED_CAST(d)))
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=60;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
#define ANDROID_NATIVE_WINDOW_MAGIC ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d')
constexpr int AndroidNativeWindowMagic{ANDROID_NATIVE_WINDOW_MAGIC};
#undef ANDROID_NATIVE_WINDOW_MAGIC
#undef ANDROID_NATIVE_MAKE_CONSTANT
#undef ANDROID_NATIVE_UNSIGNED_CAST
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=325-331;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
constexpr int64_t NativeWindowTimestampAuto{-9223372036854775807LL - 1};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=198-259;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
enum {
NATIVE_WINDOW_CONNECT = 1, /* deprecated */
NATIVE_WINDOW_DISCONNECT = 2, /* deprecated */
NATIVE_WINDOW_SET_CROP = 3, /* private */
NATIVE_WINDOW_SET_BUFFER_COUNT = 4,
NATIVE_WINDOW_SET_BUFFERS_TRANSFORM = 6,
NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP = 7,
NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS = 8,
NATIVE_WINDOW_SET_SCALING_MODE = 10, /* private */
NATIVE_WINDOW_LOCK = 11, /* private */
NATIVE_WINDOW_UNLOCK_AND_POST = 12, /* private */
NATIVE_WINDOW_API_CONNECT = 13, /* private */
NATIVE_WINDOW_API_DISCONNECT = 14, /* private */
NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS = 15, /* private */
NATIVE_WINDOW_SET_POST_TRANSFORM_CROP = 16, /* deprecated, unimplemented */
NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM = 17, /* private */
NATIVE_WINDOW_SET_SIDEBAND_STREAM = 18,
NATIVE_WINDOW_SET_BUFFERS_DATASPACE = 19,
NATIVE_WINDOW_SET_SURFACE_DAMAGE = 20, /* private */
NATIVE_WINDOW_SET_SHARED_BUFFER_MODE = 21,
NATIVE_WINDOW_SET_AUTO_REFRESH = 22,
NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION = 23,
NATIVE_WINDOW_GET_NEXT_FRAME_ID = 24,
NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25,
NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26,
NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27,
NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28,
NATIVE_WINDOW_GET_HDR_SUPPORT = 29,
NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31,
NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32,
NATIVE_WINDOW_SET_BUFFERS_CTA861_3_METADATA = 33,
NATIVE_WINDOW_SET_BUFFERS_HDR10_PLUS_METADATA = 34,
NATIVE_WINDOW_SET_AUTO_PREROTATION = 35,
NATIVE_WINDOW_GET_LAST_DEQUEUE_START = 36, /* private */
NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT = 37, /* private */
NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION = 38, /* private */
NATIVE_WINDOW_GET_LAST_QUEUE_DURATION = 39, /* private */
NATIVE_WINDOW_SET_FRAME_RATE = 40,
NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR = 41, /* private */
NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR = 42, /* private */
NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR = 43, /* private */
NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44, /* private */
NATIVE_WINDOW_ALLOCATE_BUFFERS = 45, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46, /* private */
NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47, /* private */
NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2 = 50, /* private */
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativebase/include/nativebase/nativebase.h;l=43-56;drc=cb496acbe593326e8d5d563847067d02b2df40ec
*/
struct android_native_base_t {
int magic;
int version;
void *reserved[4];
void (*incRef)(android_native_base_t *);
void (*decRef)(android_native_base_t *);
};
/**
* @url https://cs.android.com/android/platform/superproject/+/android11-release:frameworks/native/libs/nativewindow/include/system/window.h;l=341-560;drc=401cda638e7d17f6697b5a65c9a5ad79d056202d
*/
struct ANativeWindow {
struct android_native_base_t common;
/* flags describing some attributes of this surface or its updater */
const uint32_t flags;
/* min swap interval supported by this updated */
const int minSwapInterval;
/* max swap interval supported by this updated */
const int maxSwapInterval;
/* horizontal and vertical resolution in DPI */
const float xdpi;
const float ydpi;
/* Some storage reserved for the OEM's driver. */
intptr_t oem[4];
/*
* Set the swap interval for this surface.
*
* Returns 0 on success or -errno on error.
*/
int (*setSwapInterval)(struct ANativeWindow *window,
int interval);
/*
* Hook called by EGL to acquire a buffer. After this call, the buffer
* is not locked, so its content cannot be modified. This call may block if
* no buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new dequeueBuffer function that
* outputs a fence file descriptor should be used in its place.
*/
int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer);
/*
* hook called by EGL to lock a buffer. This MUST be called before modifying
* the content of a buffer. The buffer must have been acquired with
* dequeueBuffer first.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but it is essentially a no-op, and calls
* to it should be removed.
*/
int (*lockBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* Buffers MUST be queued in the same order than they were dequeued.
*
* Returns 0 on success or -errno on error.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new queueBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*queueBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* hook used to retrieve information about the native window.
*
* Returns 0 on success or -errno on error.
*/
int (*query)(const struct ANativeWindow *window,
int what, int *value);
/*
* hook used to perform various operations on the surface.
* (*perform)() is a generic mechanism to add functionality to
* ANativeWindow while keeping backward binary compatibility.
*
* DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions
* defined below.
*
* (*perform)() returns -ENOENT if the 'what' parameter is not supported
* by the surface's implementation.
*
* See above for a list of valid operations, such as
* NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT
*/
int (*perform)(struct ANativeWindow *window,
int operation, ...);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* XXX: This function is deprecated. It will continue to work for some
* time for binary compatibility, but the new cancelBuffer function that
* takes a fence file descriptor should be used in its place (pass a value
* of -1 for the fence file descriptor if there is no valid one to pass).
*/
int (*cancelBuffer_DEPRECATED)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer);
/*
* Hook called by EGL to acquire a buffer. This call may block if no
* buffers are available.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The libsync fence file descriptor returned in the int pointed to by the
* fenceFd argument will refer to the fence that must signal before the
* dequeued buffer may be written to. A value of -1 indicates that the
* caller may access the buffer immediately without waiting on a fence. If
* a valid file descriptor is returned (i.e. any value except -1) then the
* caller is responsible for closing the file descriptor.
*
* Returns 0 on success or -errno on error.
*/
int (*dequeueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer **buffer, int *fenceFd);
/*
* Hook called by EGL when modifications to the render buffer are done.
* This unlocks and post the buffer.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file descriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used. The
* caller must not use the file descriptor after it is passed to
* queueBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*queueBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
/*
* Hook used to cancel a buffer that has been dequeued.
* No synchronization is performed between dequeue() and cancel(), so
* either external synchronization is needed, or these functions must be
* called from the same thread.
*
* The window holds a reference to the buffer between dequeueBuffer and
* either queueBuffer or cancelBuffer, so clients only need their own
* reference if they might use the buffer after queueing or canceling it.
* Holding a reference to a buffer after queueing or canceling it is only
* allowed if a specific buffer count has been set.
*
* The fenceFd argument specifies a libsync fence file decsriptor for a
* fence that must signal before the buffer can be accessed. If the buffer
* can be accessed immediately then a value of -1 should be used.
*
* Note that if the client has not waited on the fence that was returned
* from dequeueBuffer, that same fence should be passed to cancelBuffer to
* ensure that future uses of the buffer are preceded by a wait on that
* fence. The caller must not use the file descriptor after it is passed
* to cancelBuffer, and the ANativeWindow implementation is responsible for
* closing it.
*
* Returns 0 on success or -errno on error.
*/
int (*cancelBuffer)(struct ANativeWindow *window,
struct ANativeWindowBuffer *buffer, int fenceFd);
};
@@ -1,404 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is generated.
#include "vulkan_wrapper.h"
#include <dlfcn.h>
int InitVulkan(void) {
void* libvulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!libvulkan)
return 0;
// Vulkan supported, set function addresses
vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(dlsym(libvulkan, "vkCreateInstance"));
vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(dlsym(libvulkan, "vkDestroyInstance"));
vkEnumeratePhysicalDevices = reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(dlsym(libvulkan, "vkEnumeratePhysicalDevices"));
vkGetPhysicalDeviceFeatures = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures>(dlsym(libvulkan, "vkGetPhysicalDeviceFeatures"));
vkGetPhysicalDeviceFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceFormatProperties"));
vkGetPhysicalDeviceImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceImageFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceImageFormatProperties"));
vkGetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceProperties"));
vkGetPhysicalDeviceQueueFamilyProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceQueueFamilyProperties"));
vkGetPhysicalDeviceMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceMemoryProperties"));
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(libvulkan, "vkGetInstanceProcAddr"));
vkGetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(dlsym(libvulkan, "vkGetDeviceProcAddr"));
vkCreateDevice = reinterpret_cast<PFN_vkCreateDevice>(dlsym(libvulkan, "vkCreateDevice"));
vkDestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>(dlsym(libvulkan, "vkDestroyDevice"));
vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(dlsym(libvulkan, "vkEnumerateInstanceExtensionProperties"));
vkEnumerateDeviceExtensionProperties = reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(dlsym(libvulkan, "vkEnumerateDeviceExtensionProperties"));
vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(dlsym(libvulkan, "vkEnumerateInstanceLayerProperties"));
vkEnumerateDeviceLayerProperties = reinterpret_cast<PFN_vkEnumerateDeviceLayerProperties>(dlsym(libvulkan, "vkEnumerateDeviceLayerProperties"));
vkGetDeviceQueue = reinterpret_cast<PFN_vkGetDeviceQueue>(dlsym(libvulkan, "vkGetDeviceQueue"));
vkQueueSubmit = reinterpret_cast<PFN_vkQueueSubmit>(dlsym(libvulkan, "vkQueueSubmit"));
vkQueueWaitIdle = reinterpret_cast<PFN_vkQueueWaitIdle>(dlsym(libvulkan, "vkQueueWaitIdle"));
vkDeviceWaitIdle = reinterpret_cast<PFN_vkDeviceWaitIdle>(dlsym(libvulkan, "vkDeviceWaitIdle"));
vkAllocateMemory = reinterpret_cast<PFN_vkAllocateMemory>(dlsym(libvulkan, "vkAllocateMemory"));
vkFreeMemory = reinterpret_cast<PFN_vkFreeMemory>(dlsym(libvulkan, "vkFreeMemory"));
vkMapMemory = reinterpret_cast<PFN_vkMapMemory>(dlsym(libvulkan, "vkMapMemory"));
vkUnmapMemory = reinterpret_cast<PFN_vkUnmapMemory>(dlsym(libvulkan, "vkUnmapMemory"));
vkFlushMappedMemoryRanges = reinterpret_cast<PFN_vkFlushMappedMemoryRanges>(dlsym(libvulkan, "vkFlushMappedMemoryRanges"));
vkInvalidateMappedMemoryRanges = reinterpret_cast<PFN_vkInvalidateMappedMemoryRanges>(dlsym(libvulkan, "vkInvalidateMappedMemoryRanges"));
vkGetDeviceMemoryCommitment = reinterpret_cast<PFN_vkGetDeviceMemoryCommitment>(dlsym(libvulkan, "vkGetDeviceMemoryCommitment"));
vkBindBufferMemory = reinterpret_cast<PFN_vkBindBufferMemory>(dlsym(libvulkan, "vkBindBufferMemory"));
vkBindImageMemory = reinterpret_cast<PFN_vkBindImageMemory>(dlsym(libvulkan, "vkBindImageMemory"));
vkGetBufferMemoryRequirements = reinterpret_cast<PFN_vkGetBufferMemoryRequirements>(dlsym(libvulkan, "vkGetBufferMemoryRequirements"));
vkGetImageMemoryRequirements = reinterpret_cast<PFN_vkGetImageMemoryRequirements>(dlsym(libvulkan, "vkGetImageMemoryRequirements"));
vkGetImageSparseMemoryRequirements = reinterpret_cast<PFN_vkGetImageSparseMemoryRequirements>(dlsym(libvulkan, "vkGetImageSparseMemoryRequirements"));
vkGetPhysicalDeviceSparseImageFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties>(dlsym(libvulkan, "vkGetPhysicalDeviceSparseImageFormatProperties"));
vkQueueBindSparse = reinterpret_cast<PFN_vkQueueBindSparse>(dlsym(libvulkan, "vkQueueBindSparse"));
vkCreateFence = reinterpret_cast<PFN_vkCreateFence>(dlsym(libvulkan, "vkCreateFence"));
vkDestroyFence = reinterpret_cast<PFN_vkDestroyFence>(dlsym(libvulkan, "vkDestroyFence"));
vkResetFences = reinterpret_cast<PFN_vkResetFences>(dlsym(libvulkan, "vkResetFences"));
vkGetFenceStatus = reinterpret_cast<PFN_vkGetFenceStatus>(dlsym(libvulkan, "vkGetFenceStatus"));
vkWaitForFences = reinterpret_cast<PFN_vkWaitForFences>(dlsym(libvulkan, "vkWaitForFences"));
vkCreateSemaphore = reinterpret_cast<PFN_vkCreateSemaphore>(dlsym(libvulkan, "vkCreateSemaphore"));
vkDestroySemaphore = reinterpret_cast<PFN_vkDestroySemaphore>(dlsym(libvulkan, "vkDestroySemaphore"));
vkCreateEvent = reinterpret_cast<PFN_vkCreateEvent>(dlsym(libvulkan, "vkCreateEvent"));
vkDestroyEvent = reinterpret_cast<PFN_vkDestroyEvent>(dlsym(libvulkan, "vkDestroyEvent"));
vkGetEventStatus = reinterpret_cast<PFN_vkGetEventStatus>(dlsym(libvulkan, "vkGetEventStatus"));
vkSetEvent = reinterpret_cast<PFN_vkSetEvent>(dlsym(libvulkan, "vkSetEvent"));
vkResetEvent = reinterpret_cast<PFN_vkResetEvent>(dlsym(libvulkan, "vkResetEvent"));
vkCreateQueryPool = reinterpret_cast<PFN_vkCreateQueryPool>(dlsym(libvulkan, "vkCreateQueryPool"));
vkDestroyQueryPool = reinterpret_cast<PFN_vkDestroyQueryPool>(dlsym(libvulkan, "vkDestroyQueryPool"));
vkGetQueryPoolResults = reinterpret_cast<PFN_vkGetQueryPoolResults>(dlsym(libvulkan, "vkGetQueryPoolResults"));
vkCreateBuffer = reinterpret_cast<PFN_vkCreateBuffer>(dlsym(libvulkan, "vkCreateBuffer"));
vkDestroyBuffer = reinterpret_cast<PFN_vkDestroyBuffer>(dlsym(libvulkan, "vkDestroyBuffer"));
vkCreateBufferView = reinterpret_cast<PFN_vkCreateBufferView>(dlsym(libvulkan, "vkCreateBufferView"));
vkDestroyBufferView = reinterpret_cast<PFN_vkDestroyBufferView>(dlsym(libvulkan, "vkDestroyBufferView"));
vkCreateImage = reinterpret_cast<PFN_vkCreateImage>(dlsym(libvulkan, "vkCreateImage"));
vkDestroyImage = reinterpret_cast<PFN_vkDestroyImage>(dlsym(libvulkan, "vkDestroyImage"));
vkGetImageSubresourceLayout = reinterpret_cast<PFN_vkGetImageSubresourceLayout>(dlsym(libvulkan, "vkGetImageSubresourceLayout"));
vkCreateImageView = reinterpret_cast<PFN_vkCreateImageView>(dlsym(libvulkan, "vkCreateImageView"));
vkDestroyImageView = reinterpret_cast<PFN_vkDestroyImageView>(dlsym(libvulkan, "vkDestroyImageView"));
vkCreateShaderModule = reinterpret_cast<PFN_vkCreateShaderModule>(dlsym(libvulkan, "vkCreateShaderModule"));
vkDestroyShaderModule = reinterpret_cast<PFN_vkDestroyShaderModule>(dlsym(libvulkan, "vkDestroyShaderModule"));
vkCreatePipelineCache = reinterpret_cast<PFN_vkCreatePipelineCache>(dlsym(libvulkan, "vkCreatePipelineCache"));
vkDestroyPipelineCache = reinterpret_cast<PFN_vkDestroyPipelineCache>(dlsym(libvulkan, "vkDestroyPipelineCache"));
vkGetPipelineCacheData = reinterpret_cast<PFN_vkGetPipelineCacheData>(dlsym(libvulkan, "vkGetPipelineCacheData"));
vkMergePipelineCaches = reinterpret_cast<PFN_vkMergePipelineCaches>(dlsym(libvulkan, "vkMergePipelineCaches"));
vkCreateGraphicsPipelines = reinterpret_cast<PFN_vkCreateGraphicsPipelines>(dlsym(libvulkan, "vkCreateGraphicsPipelines"));
vkCreateComputePipelines = reinterpret_cast<PFN_vkCreateComputePipelines>(dlsym(libvulkan, "vkCreateComputePipelines"));
vkDestroyPipeline = reinterpret_cast<PFN_vkDestroyPipeline>(dlsym(libvulkan, "vkDestroyPipeline"));
vkCreatePipelineLayout = reinterpret_cast<PFN_vkCreatePipelineLayout>(dlsym(libvulkan, "vkCreatePipelineLayout"));
vkDestroyPipelineLayout = reinterpret_cast<PFN_vkDestroyPipelineLayout>(dlsym(libvulkan, "vkDestroyPipelineLayout"));
vkCreateSampler = reinterpret_cast<PFN_vkCreateSampler>(dlsym(libvulkan, "vkCreateSampler"));
vkDestroySampler = reinterpret_cast<PFN_vkDestroySampler>(dlsym(libvulkan, "vkDestroySampler"));
vkCreateDescriptorSetLayout = reinterpret_cast<PFN_vkCreateDescriptorSetLayout>(dlsym(libvulkan, "vkCreateDescriptorSetLayout"));
vkDestroyDescriptorSetLayout = reinterpret_cast<PFN_vkDestroyDescriptorSetLayout>(dlsym(libvulkan, "vkDestroyDescriptorSetLayout"));
vkCreateDescriptorPool = reinterpret_cast<PFN_vkCreateDescriptorPool>(dlsym(libvulkan, "vkCreateDescriptorPool"));
vkDestroyDescriptorPool = reinterpret_cast<PFN_vkDestroyDescriptorPool>(dlsym(libvulkan, "vkDestroyDescriptorPool"));
vkResetDescriptorPool = reinterpret_cast<PFN_vkResetDescriptorPool>(dlsym(libvulkan, "vkResetDescriptorPool"));
vkAllocateDescriptorSets = reinterpret_cast<PFN_vkAllocateDescriptorSets>(dlsym(libvulkan, "vkAllocateDescriptorSets"));
vkFreeDescriptorSets = reinterpret_cast<PFN_vkFreeDescriptorSets>(dlsym(libvulkan, "vkFreeDescriptorSets"));
vkUpdateDescriptorSets = reinterpret_cast<PFN_vkUpdateDescriptorSets>(dlsym(libvulkan, "vkUpdateDescriptorSets"));
vkCreateFramebuffer = reinterpret_cast<PFN_vkCreateFramebuffer>(dlsym(libvulkan, "vkCreateFramebuffer"));
vkDestroyFramebuffer = reinterpret_cast<PFN_vkDestroyFramebuffer>(dlsym(libvulkan, "vkDestroyFramebuffer"));
vkCreateRenderPass = reinterpret_cast<PFN_vkCreateRenderPass>(dlsym(libvulkan, "vkCreateRenderPass"));
vkDestroyRenderPass = reinterpret_cast<PFN_vkDestroyRenderPass>(dlsym(libvulkan, "vkDestroyRenderPass"));
vkGetRenderAreaGranularity = reinterpret_cast<PFN_vkGetRenderAreaGranularity>(dlsym(libvulkan, "vkGetRenderAreaGranularity"));
vkCreateCommandPool = reinterpret_cast<PFN_vkCreateCommandPool>(dlsym(libvulkan, "vkCreateCommandPool"));
vkDestroyCommandPool = reinterpret_cast<PFN_vkDestroyCommandPool>(dlsym(libvulkan, "vkDestroyCommandPool"));
vkResetCommandPool = reinterpret_cast<PFN_vkResetCommandPool>(dlsym(libvulkan, "vkResetCommandPool"));
vkAllocateCommandBuffers = reinterpret_cast<PFN_vkAllocateCommandBuffers>(dlsym(libvulkan, "vkAllocateCommandBuffers"));
vkFreeCommandBuffers = reinterpret_cast<PFN_vkFreeCommandBuffers>(dlsym(libvulkan, "vkFreeCommandBuffers"));
vkBeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(dlsym(libvulkan, "vkBeginCommandBuffer"));
vkEndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>(dlsym(libvulkan, "vkEndCommandBuffer"));
vkResetCommandBuffer = reinterpret_cast<PFN_vkResetCommandBuffer>(dlsym(libvulkan, "vkResetCommandBuffer"));
vkCmdBindPipeline = reinterpret_cast<PFN_vkCmdBindPipeline>(dlsym(libvulkan, "vkCmdBindPipeline"));
vkCmdSetViewport = reinterpret_cast<PFN_vkCmdSetViewport>(dlsym(libvulkan, "vkCmdSetViewport"));
vkCmdSetScissor = reinterpret_cast<PFN_vkCmdSetScissor>(dlsym(libvulkan, "vkCmdSetScissor"));
vkCmdSetLineWidth = reinterpret_cast<PFN_vkCmdSetLineWidth>(dlsym(libvulkan, "vkCmdSetLineWidth"));
vkCmdSetDepthBias = reinterpret_cast<PFN_vkCmdSetDepthBias>(dlsym(libvulkan, "vkCmdSetDepthBias"));
vkCmdSetBlendConstants = reinterpret_cast<PFN_vkCmdSetBlendConstants>(dlsym(libvulkan, "vkCmdSetBlendConstants"));
vkCmdSetDepthBounds = reinterpret_cast<PFN_vkCmdSetDepthBounds>(dlsym(libvulkan, "vkCmdSetDepthBounds"));
vkCmdSetStencilCompareMask = reinterpret_cast<PFN_vkCmdSetStencilCompareMask>(dlsym(libvulkan, "vkCmdSetStencilCompareMask"));
vkCmdSetStencilWriteMask = reinterpret_cast<PFN_vkCmdSetStencilWriteMask>(dlsym(libvulkan, "vkCmdSetStencilWriteMask"));
vkCmdSetStencilReference = reinterpret_cast<PFN_vkCmdSetStencilReference>(dlsym(libvulkan, "vkCmdSetStencilReference"));
vkCmdBindDescriptorSets = reinterpret_cast<PFN_vkCmdBindDescriptorSets>(dlsym(libvulkan, "vkCmdBindDescriptorSets"));
vkCmdBindIndexBuffer = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(dlsym(libvulkan, "vkCmdBindIndexBuffer"));
vkCmdBindVertexBuffers = reinterpret_cast<PFN_vkCmdBindVertexBuffers>(dlsym(libvulkan, "vkCmdBindVertexBuffers"));
vkCmdDraw = reinterpret_cast<PFN_vkCmdDraw>(dlsym(libvulkan, "vkCmdDraw"));
vkCmdDrawIndexed = reinterpret_cast<PFN_vkCmdDrawIndexed>(dlsym(libvulkan, "vkCmdDrawIndexed"));
vkCmdDrawIndirect = reinterpret_cast<PFN_vkCmdDrawIndirect>(dlsym(libvulkan, "vkCmdDrawIndirect"));
vkCmdDrawIndexedIndirect = reinterpret_cast<PFN_vkCmdDrawIndexedIndirect>(dlsym(libvulkan, "vkCmdDrawIndexedIndirect"));
vkCmdDispatch = reinterpret_cast<PFN_vkCmdDispatch>(dlsym(libvulkan, "vkCmdDispatch"));
vkCmdDispatchIndirect = reinterpret_cast<PFN_vkCmdDispatchIndirect>(dlsym(libvulkan, "vkCmdDispatchIndirect"));
vkCmdCopyBuffer = reinterpret_cast<PFN_vkCmdCopyBuffer>(dlsym(libvulkan, "vkCmdCopyBuffer"));
vkCmdCopyImage = reinterpret_cast<PFN_vkCmdCopyImage>(dlsym(libvulkan, "vkCmdCopyImage"));
vkCmdBlitImage = reinterpret_cast<PFN_vkCmdBlitImage>(dlsym(libvulkan, "vkCmdBlitImage"));
vkCmdCopyBufferToImage = reinterpret_cast<PFN_vkCmdCopyBufferToImage>(dlsym(libvulkan, "vkCmdCopyBufferToImage"));
vkCmdCopyImageToBuffer = reinterpret_cast<PFN_vkCmdCopyImageToBuffer>(dlsym(libvulkan, "vkCmdCopyImageToBuffer"));
vkCmdUpdateBuffer = reinterpret_cast<PFN_vkCmdUpdateBuffer>(dlsym(libvulkan, "vkCmdUpdateBuffer"));
vkCmdFillBuffer = reinterpret_cast<PFN_vkCmdFillBuffer>(dlsym(libvulkan, "vkCmdFillBuffer"));
vkCmdClearColorImage = reinterpret_cast<PFN_vkCmdClearColorImage>(dlsym(libvulkan, "vkCmdClearColorImage"));
vkCmdClearDepthStencilImage = reinterpret_cast<PFN_vkCmdClearDepthStencilImage>(dlsym(libvulkan, "vkCmdClearDepthStencilImage"));
vkCmdClearAttachments = reinterpret_cast<PFN_vkCmdClearAttachments>(dlsym(libvulkan, "vkCmdClearAttachments"));
vkCmdResolveImage = reinterpret_cast<PFN_vkCmdResolveImage>(dlsym(libvulkan, "vkCmdResolveImage"));
vkCmdSetEvent = reinterpret_cast<PFN_vkCmdSetEvent>(dlsym(libvulkan, "vkCmdSetEvent"));
vkCmdResetEvent = reinterpret_cast<PFN_vkCmdResetEvent>(dlsym(libvulkan, "vkCmdResetEvent"));
vkCmdWaitEvents = reinterpret_cast<PFN_vkCmdWaitEvents>(dlsym(libvulkan, "vkCmdWaitEvents"));
vkCmdPipelineBarrier = reinterpret_cast<PFN_vkCmdPipelineBarrier>(dlsym(libvulkan, "vkCmdPipelineBarrier"));
vkCmdBeginQuery = reinterpret_cast<PFN_vkCmdBeginQuery>(dlsym(libvulkan, "vkCmdBeginQuery"));
vkCmdEndQuery = reinterpret_cast<PFN_vkCmdEndQuery>(dlsym(libvulkan, "vkCmdEndQuery"));
vkCmdResetQueryPool = reinterpret_cast<PFN_vkCmdResetQueryPool>(dlsym(libvulkan, "vkCmdResetQueryPool"));
vkCmdWriteTimestamp = reinterpret_cast<PFN_vkCmdWriteTimestamp>(dlsym(libvulkan, "vkCmdWriteTimestamp"));
vkCmdCopyQueryPoolResults = reinterpret_cast<PFN_vkCmdCopyQueryPoolResults>(dlsym(libvulkan, "vkCmdCopyQueryPoolResults"));
vkCmdPushConstants = reinterpret_cast<PFN_vkCmdPushConstants>(dlsym(libvulkan, "vkCmdPushConstants"));
vkCmdBeginRenderPass = reinterpret_cast<PFN_vkCmdBeginRenderPass>(dlsym(libvulkan, "vkCmdBeginRenderPass"));
vkCmdNextSubpass = reinterpret_cast<PFN_vkCmdNextSubpass>(dlsym(libvulkan, "vkCmdNextSubpass"));
vkCmdEndRenderPass = reinterpret_cast<PFN_vkCmdEndRenderPass>(dlsym(libvulkan, "vkCmdEndRenderPass"));
vkCmdExecuteCommands = reinterpret_cast<PFN_vkCmdExecuteCommands>(dlsym(libvulkan, "vkCmdExecuteCommands"));
vkDestroySurfaceKHR = reinterpret_cast<PFN_vkDestroySurfaceKHR>(dlsym(libvulkan, "vkDestroySurfaceKHR"));
vkGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceSupportKHR"));
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfaceFormatsKHR"));
vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceSurfacePresentModesKHR"));
vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(dlsym(libvulkan, "vkCreateSwapchainKHR"));
vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(dlsym(libvulkan, "vkDestroySwapchainKHR"));
vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(dlsym(libvulkan, "vkGetSwapchainImagesKHR"));
vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(dlsym(libvulkan, "vkAcquireNextImageKHR"));
vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(dlsym(libvulkan, "vkQueuePresentKHR"));
vkGetPhysicalDeviceDisplayPropertiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceDisplayPropertiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPropertiesKHR"));
vkGetPhysicalDeviceDisplayPlanePropertiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"));
vkGetDisplayPlaneSupportedDisplaysKHR = reinterpret_cast<PFN_vkGetDisplayPlaneSupportedDisplaysKHR>(dlsym(libvulkan, "vkGetDisplayPlaneSupportedDisplaysKHR"));
vkGetDisplayModePropertiesKHR = reinterpret_cast<PFN_vkGetDisplayModePropertiesKHR>(dlsym(libvulkan, "vkGetDisplayModePropertiesKHR"));
vkCreateDisplayModeKHR = reinterpret_cast<PFN_vkCreateDisplayModeKHR>(dlsym(libvulkan, "vkCreateDisplayModeKHR"));
vkGetDisplayPlaneCapabilitiesKHR = reinterpret_cast<PFN_vkGetDisplayPlaneCapabilitiesKHR>(dlsym(libvulkan, "vkGetDisplayPlaneCapabilitiesKHR"));
vkCreateDisplayPlaneSurfaceKHR = reinterpret_cast<PFN_vkCreateDisplayPlaneSurfaceKHR>(dlsym(libvulkan, "vkCreateDisplayPlaneSurfaceKHR"));
vkCreateSharedSwapchainsKHR = reinterpret_cast<PFN_vkCreateSharedSwapchainsKHR>(dlsym(libvulkan, "vkCreateSharedSwapchainsKHR"));
#ifdef VK_USE_PLATFORM_XLIB_KHR
vkCreateXlibSurfaceKHR = reinterpret_cast<PFN_vkCreateXlibSurfaceKHR>(dlsym(libvulkan, "vkCreateXlibSurfaceKHR"));
vkGetPhysicalDeviceXlibPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceXlibPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
vkCreateXcbSurfaceKHR = reinterpret_cast<PFN_vkCreateXcbSurfaceKHR>(dlsym(libvulkan, "vkCreateXcbSurfaceKHR"));
vkGetPhysicalDeviceXcbPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceXcbPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
vkCreateWaylandSurfaceKHR = reinterpret_cast<PFN_vkCreateWaylandSurfaceKHR>(dlsym(libvulkan, "vkCreateWaylandSurfaceKHR"));
vkGetPhysicalDeviceWaylandPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceWaylandPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
vkCreateMirSurfaceKHR = reinterpret_cast<PFN_vkCreateMirSurfaceKHR>(dlsym(libvulkan, "vkCreateMirSurfaceKHR"));
vkGetPhysicalDeviceMirPresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceMirPresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceMirPresentationSupportKHR"));
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
vkCreateAndroidSurfaceKHR = reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(dlsym(libvulkan, "vkCreateAndroidSurfaceKHR"));
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
vkCreateWin32SurfaceKHR = reinterpret_cast<PFN_vkCreateWin32SurfaceKHR>(dlsym(libvulkan, "vkCreateWin32SurfaceKHR"));
vkGetPhysicalDeviceWin32PresentationSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR>(dlsym(libvulkan, "vkGetPhysicalDeviceWin32PresentationSupportKHR"));
#endif
#ifdef USE_DEBUG_EXTENTIONS
vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(dlsym(libvulkan, "vkCreateDebugReportCallbackEXT"));
vkDestroyDebugReportCallbackEXT = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(dlsym(libvulkan, "vkDestroyDebugReportCallbackEXT"));
vkDebugReportMessageEXT = reinterpret_cast<PFN_vkDebugReportMessageEXT>(dlsym(libvulkan, "vkDebugReportMessageEXT"));
#endif
return 1;
}
// No Vulkan support, do not set function addresses
PFN_vkCreateInstance vkCreateInstance;
PFN_vkDestroyInstance vkDestroyInstance;
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties;
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
PFN_vkCreateDevice vkCreateDevice;
PFN_vkDestroyDevice vkDestroyDevice;
PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
PFN_vkGetDeviceQueue vkGetDeviceQueue;
PFN_vkQueueSubmit vkQueueSubmit;
PFN_vkQueueWaitIdle vkQueueWaitIdle;
PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
PFN_vkAllocateMemory vkAllocateMemory;
PFN_vkFreeMemory vkFreeMemory;
PFN_vkMapMemory vkMapMemory;
PFN_vkUnmapMemory vkUnmapMemory;
PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
PFN_vkBindBufferMemory vkBindBufferMemory;
PFN_vkBindImageMemory vkBindImageMemory;
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties;
PFN_vkQueueBindSparse vkQueueBindSparse;
PFN_vkCreateFence vkCreateFence;
PFN_vkDestroyFence vkDestroyFence;
PFN_vkResetFences vkResetFences;
PFN_vkGetFenceStatus vkGetFenceStatus;
PFN_vkWaitForFences vkWaitForFences;
PFN_vkCreateSemaphore vkCreateSemaphore;
PFN_vkDestroySemaphore vkDestroySemaphore;
PFN_vkCreateEvent vkCreateEvent;
PFN_vkDestroyEvent vkDestroyEvent;
PFN_vkGetEventStatus vkGetEventStatus;
PFN_vkSetEvent vkSetEvent;
PFN_vkResetEvent vkResetEvent;
PFN_vkCreateQueryPool vkCreateQueryPool;
PFN_vkDestroyQueryPool vkDestroyQueryPool;
PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
PFN_vkCreateBuffer vkCreateBuffer;
PFN_vkDestroyBuffer vkDestroyBuffer;
PFN_vkCreateBufferView vkCreateBufferView;
PFN_vkDestroyBufferView vkDestroyBufferView;
PFN_vkCreateImage vkCreateImage;
PFN_vkDestroyImage vkDestroyImage;
PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
PFN_vkCreateImageView vkCreateImageView;
PFN_vkDestroyImageView vkDestroyImageView;
PFN_vkCreateShaderModule vkCreateShaderModule;
PFN_vkDestroyShaderModule vkDestroyShaderModule;
PFN_vkCreatePipelineCache vkCreatePipelineCache;
PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
PFN_vkMergePipelineCaches vkMergePipelineCaches;
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
PFN_vkCreateComputePipelines vkCreateComputePipelines;
PFN_vkDestroyPipeline vkDestroyPipeline;
PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
PFN_vkCreateSampler vkCreateSampler;
PFN_vkDestroySampler vkDestroySampler;
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
PFN_vkResetDescriptorPool vkResetDescriptorPool;
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
PFN_vkCreateFramebuffer vkCreateFramebuffer;
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
PFN_vkCreateRenderPass vkCreateRenderPass;
PFN_vkDestroyRenderPass vkDestroyRenderPass;
PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
PFN_vkCreateCommandPool vkCreateCommandPool;
PFN_vkDestroyCommandPool vkDestroyCommandPool;
PFN_vkResetCommandPool vkResetCommandPool;
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
PFN_vkEndCommandBuffer vkEndCommandBuffer;
PFN_vkResetCommandBuffer vkResetCommandBuffer;
PFN_vkCmdBindPipeline vkCmdBindPipeline;
PFN_vkCmdSetViewport vkCmdSetViewport;
PFN_vkCmdSetScissor vkCmdSetScissor;
PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
PFN_vkCmdDraw vkCmdDraw;
PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
PFN_vkCmdDispatch vkCmdDispatch;
PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
PFN_vkCmdCopyImage vkCmdCopyImage;
PFN_vkCmdBlitImage vkCmdBlitImage;
PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
PFN_vkCmdFillBuffer vkCmdFillBuffer;
PFN_vkCmdClearColorImage vkCmdClearColorImage;
PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
PFN_vkCmdClearAttachments vkCmdClearAttachments;
PFN_vkCmdResolveImage vkCmdResolveImage;
PFN_vkCmdSetEvent vkCmdSetEvent;
PFN_vkCmdResetEvent vkCmdResetEvent;
PFN_vkCmdWaitEvents vkCmdWaitEvents;
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
PFN_vkCmdBeginQuery vkCmdBeginQuery;
PFN_vkCmdEndQuery vkCmdEndQuery;
PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
PFN_vkCmdPushConstants vkCmdPushConstants;
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
PFN_vkCmdNextSubpass vkCmdNextSubpass;
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
PFN_vkQueuePresentKHR vkQueuePresentKHR;
PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR;
PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR;
PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR;
PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR;
PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR;
PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR;
PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR;
PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#ifdef VK_USE_PLATFORM_XLIB_KHR
PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR;
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
PFN_vkCreateMirSurfaceKHR vkCreateMirSurfaceKHR;
PFN_vkGetPhysicalDeviceMirPresentationSupportKHR vkGetPhysicalDeviceMirPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR;
#endif
PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;
@@ -1,236 +0,0 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is generated.
#ifndef VULKAN_WRAPPER_H
#define VULKAN_WRAPPER_H
#define VK_NO_PROTOTYPES 1
#include <vulkan/vulkan.h>
/* Initialize the Vulkan function pointer variables declared in this header.
* Returns 0 if vulkan is not available, non-zero if it is available.
*/
int InitVulkan(void);
// VK_core
extern PFN_vkCreateInstance vkCreateInstance;
extern PFN_vkDestroyInstance vkDestroyInstance;
extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
extern PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
extern PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
extern PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties;
extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
extern PFN_vkCreateDevice vkCreateDevice;
extern PFN_vkDestroyDevice vkDestroyDevice;
extern PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
extern PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
extern PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
extern PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
extern PFN_vkGetDeviceQueue vkGetDeviceQueue;
extern PFN_vkQueueSubmit vkQueueSubmit;
extern PFN_vkQueueWaitIdle vkQueueWaitIdle;
extern PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
extern PFN_vkAllocateMemory vkAllocateMemory;
extern PFN_vkFreeMemory vkFreeMemory;
extern PFN_vkMapMemory vkMapMemory;
extern PFN_vkUnmapMemory vkUnmapMemory;
extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
extern PFN_vkGetDeviceMemoryCommitment vkGetDeviceMemoryCommitment;
extern PFN_vkBindBufferMemory vkBindBufferMemory;
extern PFN_vkBindImageMemory vkBindImageMemory;
extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
extern PFN_vkGetImageSparseMemoryRequirements vkGetImageSparseMemoryRequirements;
extern PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties;
extern PFN_vkQueueBindSparse vkQueueBindSparse;
extern PFN_vkCreateFence vkCreateFence;
extern PFN_vkDestroyFence vkDestroyFence;
extern PFN_vkResetFences vkResetFences;
extern PFN_vkGetFenceStatus vkGetFenceStatus;
extern PFN_vkWaitForFences vkWaitForFences;
extern PFN_vkCreateSemaphore vkCreateSemaphore;
extern PFN_vkDestroySemaphore vkDestroySemaphore;
extern PFN_vkCreateEvent vkCreateEvent;
extern PFN_vkDestroyEvent vkDestroyEvent;
extern PFN_vkGetEventStatus vkGetEventStatus;
extern PFN_vkSetEvent vkSetEvent;
extern PFN_vkResetEvent vkResetEvent;
extern PFN_vkCreateQueryPool vkCreateQueryPool;
extern PFN_vkDestroyQueryPool vkDestroyQueryPool;
extern PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
extern PFN_vkCreateBuffer vkCreateBuffer;
extern PFN_vkDestroyBuffer vkDestroyBuffer;
extern PFN_vkCreateBufferView vkCreateBufferView;
extern PFN_vkDestroyBufferView vkDestroyBufferView;
extern PFN_vkCreateImage vkCreateImage;
extern PFN_vkDestroyImage vkDestroyImage;
extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
extern PFN_vkCreateImageView vkCreateImageView;
extern PFN_vkDestroyImageView vkDestroyImageView;
extern PFN_vkCreateShaderModule vkCreateShaderModule;
extern PFN_vkDestroyShaderModule vkDestroyShaderModule;
extern PFN_vkCreatePipelineCache vkCreatePipelineCache;
extern PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
extern PFN_vkGetPipelineCacheData vkGetPipelineCacheData;
extern PFN_vkMergePipelineCaches vkMergePipelineCaches;
extern PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
extern PFN_vkCreateComputePipelines vkCreateComputePipelines;
extern PFN_vkDestroyPipeline vkDestroyPipeline;
extern PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
extern PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
extern PFN_vkCreateSampler vkCreateSampler;
extern PFN_vkDestroySampler vkDestroySampler;
extern PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
extern PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
extern PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
extern PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
extern PFN_vkResetDescriptorPool vkResetDescriptorPool;
extern PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
extern PFN_vkFreeDescriptorSets vkFreeDescriptorSets;
extern PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
extern PFN_vkCreateFramebuffer vkCreateFramebuffer;
extern PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
extern PFN_vkCreateRenderPass vkCreateRenderPass;
extern PFN_vkDestroyRenderPass vkDestroyRenderPass;
extern PFN_vkGetRenderAreaGranularity vkGetRenderAreaGranularity;
extern PFN_vkCreateCommandPool vkCreateCommandPool;
extern PFN_vkDestroyCommandPool vkDestroyCommandPool;
extern PFN_vkResetCommandPool vkResetCommandPool;
extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
extern PFN_vkEndCommandBuffer vkEndCommandBuffer;
extern PFN_vkResetCommandBuffer vkResetCommandBuffer;
extern PFN_vkCmdBindPipeline vkCmdBindPipeline;
extern PFN_vkCmdSetViewport vkCmdSetViewport;
extern PFN_vkCmdSetScissor vkCmdSetScissor;
extern PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
extern PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
extern PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants;
extern PFN_vkCmdSetDepthBounds vkCmdSetDepthBounds;
extern PFN_vkCmdSetStencilCompareMask vkCmdSetStencilCompareMask;
extern PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
extern PFN_vkCmdSetStencilReference vkCmdSetStencilReference;
extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
extern PFN_vkCmdDraw vkCmdDraw;
extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
extern PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
extern PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
extern PFN_vkCmdDispatch vkCmdDispatch;
extern PFN_vkCmdDispatchIndirect vkCmdDispatchIndirect;
extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
extern PFN_vkCmdCopyImage vkCmdCopyImage;
extern PFN_vkCmdBlitImage vkCmdBlitImage;
extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
extern PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
extern PFN_vkCmdUpdateBuffer vkCmdUpdateBuffer;
extern PFN_vkCmdFillBuffer vkCmdFillBuffer;
extern PFN_vkCmdClearColorImage vkCmdClearColorImage;
extern PFN_vkCmdClearDepthStencilImage vkCmdClearDepthStencilImage;
extern PFN_vkCmdClearAttachments vkCmdClearAttachments;
extern PFN_vkCmdResolveImage vkCmdResolveImage;
extern PFN_vkCmdSetEvent vkCmdSetEvent;
extern PFN_vkCmdResetEvent vkCmdResetEvent;
extern PFN_vkCmdWaitEvents vkCmdWaitEvents;
extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
extern PFN_vkCmdBeginQuery vkCmdBeginQuery;
extern PFN_vkCmdEndQuery vkCmdEndQuery;
extern PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
extern PFN_vkCmdWriteTimestamp vkCmdWriteTimestamp;
extern PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
extern PFN_vkCmdPushConstants vkCmdPushConstants;
extern PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
extern PFN_vkCmdNextSubpass vkCmdNextSubpass;
extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
// VK_KHR_surface
extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR;
extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
extern PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
// VK_KHR_swapchain
extern PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
extern PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
extern PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
extern PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
extern PFN_vkQueuePresentKHR vkQueuePresentKHR;
// VK_KHR_display
extern PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR;
extern PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR;
extern PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR;
extern PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR;
extern PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR;
extern PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR;
extern PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR;
// VK_KHR_display_swapchain
extern PFN_vkCreateSharedSwapchainsKHR vkCreateSharedSwapchainsKHR;
#ifdef VK_USE_PLATFORM_XLIB_KHR
// VK_KHR_xlib_surface
extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
// VK_KHR_xcb_surface
extern PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR;
extern PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
// VK_KHR_wayland_surface
extern PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
extern PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_MIR_KHR
// VK_KHR_mir_surface
extern PFN_vkCreateMirSurfaceKHR vkCreateMirSurfaceKHR;
extern PFN_vkGetPhysicalDeviceMirPresentationSupportKHR vkGetPhysicalDeviceMirPresentationSupportKHR;
#endif
#ifdef VK_USE_PLATFORM_ANDROID_KHR
// VK_KHR_android_surface
extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
#endif
#ifdef VK_USE_PLATFORM_WIN32_KHR
// VK_KHR_win32_surface
extern PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
extern PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR;
#endif
#ifdef USE_DEBUG_EXTENTIONS
#include <vulkan/vk_sdk_platform.h>
// VK_EXT_debug_report
extern PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
extern PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
extern PFN_vkDebugReportMessageEXT vkDebugReportMessageEXT;
#endif
#endif // VULKAN_WRAPPER_H
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

@@ -1,7 +0,0 @@
package org.kenjinx.android
enum class BackendThreading {
Auto,
Off,
On
}
@@ -1,9 +0,0 @@
package org.kenjinx.android
import androidx.activity.ComponentActivity
abstract class BaseActivity : ComponentActivity() {
companion object {
val crashHandler = CrashHandler()
}
}
@@ -1,95 +0,0 @@
package org.kenjinx.android
import android.os.Build
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.lang.Thread.UncaughtExceptionHandler
/**
* Handles uncaught exceptions in the application and logs them to a file
* Location: src/main/java/org/kenjinx/android/CrashHandler.kt
*/
class CrashHandler : UncaughtExceptionHandler {
private var crashLog: StringBuilder = StringBuilder()
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
companion object {
private const val LOG_FOLDER = "Logs"
private const val CRASH_LOG_FILE = "crash.log"
private const val MAX_LOG_SIZE = 5 * 1024 * 1024 // 5MB
}
override fun uncaughtException(t: Thread, e: Throwable) {
try {
generateCrashLog(t, e)
saveCrashLog()
} catch (writeError: Exception) {
// If we can't write to the crash log, print to system log as fallback
writeError.printStackTrace()
}
}
private fun generateCrashLog(thread: Thread, throwable: Throwable) {
crashLog.apply {
append("\n=== Crash Report ===\n")
append("Date: ${dateFormat.format(Date())}\n")
// Device Information
append("\n=== Device Info ===\n")
append("Device: ${Build.MANUFACTURER} ${Build.MODEL}\n")
append("Android Version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})\n")
append("Device Fingerprint: ${Build.FINGERPRINT}\n")
// Thread Information
append("\n=== Thread Info ===\n")
append("Thread Name: ${thread.name}\n")
append("Thread ID: ${thread.id}\n")
append("Thread Priority: ${thread.priority}\n")
append("Thread State: ${thread.state}\n")
// Exception Details
append("\n=== Exception Info ===\n")
append("Exception Class: ${throwable.javaClass.name}\n")
append("Exception Message: ${throwable.message}\n")
// Stack Trace
append("\n=== Stack Trace ===\n")
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
append(sw.toString())
// Cause (if any)
throwable.cause?.let { cause ->
append("\n=== Cause ===\n")
val causeSw = StringWriter()
cause.printStackTrace(PrintWriter(causeSw))
append(causeSw.toString())
}
append("\n=== End of Crash Report ===\n")
append("----------------------------------------\n")
}
}
private fun saveCrashLog() {
val logDir = File(MainActivity.AppPath, LOG_FOLDER).apply {
if (!exists()) mkdirs()
}
val crashLogFile = File(logDir, CRASH_LOG_FILE)
// Rotate log if it's too large
if (crashLogFile.exists() && crashLogFile.length() > MAX_LOG_SIZE) {
val backupFile = File(logDir, "${CRASH_LOG_FILE}.old")
if (backupFile.exists()) backupFile.delete()
crashLogFile.renameTo(backupFile)
}
// Write the crash log
crashLogFile.appendText(crashLog.toString())
}
}
@@ -1,436 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.RadialGamePadConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
typealias GamePad = RadialGamePad
typealias GamePadConfig = RadialGamePadConfig
// --- Dummy IDs to disconnect legacy L3/R3 (stick double tap/tap) ---
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.apply {
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
).shareIn(viewModel.activity.lifecycleScope, SharingStarted.Lazily)
events.safeCollect {
controller.handleEvent(it)
}
}
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: GamePad
var rightGamePad: GamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = GamePad(generateConfig(true, useSwitchLayout), 16f, activity)
rightGamePad = GamePad(generateConfig(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
// Ignore legacy L3/R3 via stick press (double tap)
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) {
return
}
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
// Horizontal
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
// Vertical
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
suspend fun <T> Flow<T>.safeCollect(block: suspend (T) -> Unit) {
this.catch { }
.collect { block(it) }
}
private fun generateConfig(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (Stick) */
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
setOf(),
"LeftStick",
null
),
listOf(
// D-Pad
SecondaryDialConfig.Cross(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.5f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 1,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
distance,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 1,
buttonScale,
1.5f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox Style: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick (unchanged)
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 7,
/* size */ 2,
/* gap */ 2f,
distance,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 6,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
distance,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 5,
buttonScale,
1.5f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,420 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
// --- Dummy IDs to disconnect legacy L3/R3 (stick double tap/tap) ---
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController2(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController2): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController2(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
events.safeCollect { controller.handleEvent(it) }
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: RadialGamePad
var rightGamePad: RadialGamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = RadialGamePad(generateConfig2(true, useSwitchLayout), 16f, activity)
rightGamePad = RadialGamePad(generateConfig2(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
// Ignore legacy L3/R3 via stick press (double tap)
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) {
return
}
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
// Horizontal
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
// Vertical
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
// --- Local copy of the config generation (name = generateConfig2, to avoid conflicts) ---
private fun generateConfig2(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (Stick) */
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
setOf(),
"LeftStick",
null
),
listOf(
// D-Pad
SecondaryDialConfig.Cross(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.1f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
0.2f,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
1f,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 1,
buttonScale,
1.0f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox Style: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 6,
/* size */ 3,
/* gap */ 2.7f,
distance,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
0.2f,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
1f,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 5,
buttonScale,
1.0f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,413 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
// --- Dummy IDs to disconnect legacy L3/R3 (stick double tap/tap) ---
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController3(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController3): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController3(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
events.safeCollect { controller.handleEvent(it) }
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: RadialGamePad
var rightGamePad: RadialGamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = RadialGamePad(generateConfig3(true, useSwitchLayout), 16f, activity)
rightGamePad = RadialGamePad(generateConfig3(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
// Ignore legacy L3/R3 via stick press (double tap)
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
// Horizontal
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
// Vertical
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
private fun generateConfig3(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (D-Pad) */
PrimaryDialConfig.Cross(
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
),
listOf(
// Left stick
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
SecondaryDialConfig.Stick(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.7f,
distance,
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
null,
setOf(),
"LeftStick",
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
0.2f,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
1f,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 1,
buttonScale,
1.0f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox Style: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 6,
/* size */ 3,
/* gap */ 2.7f,
distance,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
0.2f,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
1f,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 5,
buttonScale,
1.0f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,410 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController4(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController4): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController4(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
events.safeCollect { controller.handleEvent(it) }
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: RadialGamePad
var rightGamePad: RadialGamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = RadialGamePad(generateConfig4(true, useSwitchLayout), 16f, activity)
rightGamePad = RadialGamePad(generateConfig4(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
private fun generateConfig4(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (Stick) */
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
setOf(),
"LeftStick",
null
),
listOf(
// D-Pad
SecondaryDialConfig.Cross(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.1f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L-Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
1.2f,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL-Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
2f,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 6,
/* size */ 3,
/* gap */ 2.7f,
distance,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R-Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
1.2f,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR-Trigger
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
2f,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 separat
SecondaryDialConfig.SingleButton(
/* sector */ 3,
buttonScale,
0f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 separat
SecondaryDialConfig.SingleButton(
/* sector */ 4,
buttonScale,
0f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,414 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController5(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController5): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController5(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
events.safeCollect { controller.handleEvent(it) }
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: RadialGamePad
var rightGamePad: RadialGamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = RadialGamePad(generateConfig5(true, useSwitchLayout), 16f, activity)
rightGamePad = RadialGamePad(generateConfig5(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
private fun generateConfig5(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (Stick) */
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
setOf(),
"LeftStick",
null
),
listOf(
// D-Pad
SecondaryDialConfig.Cross(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.1f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L Bumper
SecondaryDialConfig.SingleButton(
/* sector */ 3,
1.5f,
1.0f,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 2,
1.5f,
1.1f,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox-Stil: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 6,
/* size */ 3,
/* gap */ 2.7f,
distance,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
0.3f,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R Bumper
SecondaryDialConfig.SingleButton(
/* sector */ 3,
1.5f,
1.0f,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 4,
1.5f,
1.1f,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 3,
1.1f,
0f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 4,
1.1f,
0f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,411 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.content.Context
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.math.MathUtils
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.swordfish.radialgamepad.library.RadialGamePad
import com.swordfish.radialgamepad.library.config.ButtonConfig
import com.swordfish.radialgamepad.library.config.CrossConfig
import com.swordfish.radialgamepad.library.config.CrossContentDescription
import com.swordfish.radialgamepad.library.config.PrimaryDialConfig
import com.swordfish.radialgamepad.library.config.SecondaryDialConfig
import com.swordfish.radialgamepad.library.event.Event
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
private const val DUMMY_LEFT_STICK_PRESS_ID = 10001
private const val DUMMY_RIGHT_STICK_PRESS_ID = 10002
class GameController6(var activity: Activity) : IGameController {
companion object {
private fun init(context: Context, controller: GameController6): View {
val inflater = LayoutInflater.from(context)
val parent = FrameLayout(context)
val view = inflater.inflate(R.layout.game_layout, parent, false)
val leftContainer = view.findViewById<FrameLayout>(R.id.leftcontainer)!!
val rightContainer = view.findViewById<FrameLayout>(R.id.rightcontainer)!!
leftContainer.addView(controller.leftGamePad)
rightContainer.addView(controller.rightGamePad)
leftContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = 0f
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
rightContainer.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
val scale = QuickSettings(controller.activity).controllerScale.coerceIn(0.5f, 1.5f)
v.pivotX = v.width.toFloat()
v.pivotY = v.height.toFloat()
v.scaleX = scale
v.scaleY = scale
}
leftContainer.post { leftContainer.requestLayout() }
rightContainer.post { rightContainer.requestLayout() }
return view
}
@Composable
fun Compose(viewModel: MainViewModel) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val controller = GameController6(viewModel.activity)
val c = init(context, controller)
viewModel.activity.lifecycleScope.launch {
val events = merge(
controller.leftGamePad.events(),
controller.rightGamePad.events()
)
events.safeCollect { controller.handleEvent(it) }
}
controller.controllerView = c
viewModel.setGameController(controller)
controller.setVisible(QuickSettings(viewModel.activity).useVirtualController)
c
}
)
}
}
private var controllerView: View? = null
var leftGamePad: RadialGamePad
var rightGamePad: RadialGamePad
var controllerId: Int = -1
override val isVisible: Boolean
get() = controllerView?.isVisible ?: false
init {
val useSwitchLayout = QuickSettings(activity).useSwitchLayout
leftGamePad = RadialGamePad(generateConfig6(true, useSwitchLayout), 16f, activity)
rightGamePad = RadialGamePad(generateConfig6(false, useSwitchLayout), 16f, activity)
leftGamePad.primaryDialMaxSizeDp = 200f
rightGamePad.primaryDialMaxSizeDp = 200f
leftGamePad.gravityX = -1f
leftGamePad.gravityY = 1f
rightGamePad.gravityX = 1f
rightGamePad.gravityY = 1f
}
override fun setVisible(isVisible: Boolean) {
controllerView?.apply {
this.isVisible = isVisible
if (isVisible) connect()
}
}
override fun connect() {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
}
private fun handleEvent(ev: Event) {
if (controllerId == -1)
controllerId = KenjinxNative.inputConnectGamepad(0)
controllerId.apply {
when (ev) {
is Event.Button -> {
if (ev.id == DUMMY_LEFT_STICK_PRESS_ID || ev.id == DUMMY_RIGHT_STICK_PRESS_ID) return
when (ev.action) {
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(ev.id, this)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(ev.id, this)
}
}
is Event.Direction -> {
when (ev.id) {
GamePadButtonInputId.DpadUp.ordinal -> {
if (ev.xAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadRight.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
} else if (ev.xAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadLeft.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadRight.ordinal, this)
}
if (ev.yAxis < 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadUp.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
} else if (ev.yAxis > 0) {
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
} else {
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadDown.ordinal, this)
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.DpadUp.ordinal, this)
}
}
GamePadButtonInputId.LeftStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(1, x, -y, this)
}
GamePadButtonInputId.RightStick.ordinal -> {
val setting = QuickSettings(activity)
val x = MathUtils.clamp(ev.xAxis * setting.controllerStickSensitivity, -1f, 1f)
val y = MathUtils.clamp(ev.yAxis * setting.controllerStickSensitivity, -1f, 1f)
KenjinxNative.inputSetStickAxis(2, x, -y, this)
}
}
}
}
}
}
}
private fun generateConfig6(isLeft: Boolean, useSwitchLayout: Boolean): GamePadConfig {
val distance = 0.3f
val buttonScale = 1f
if (isLeft) {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (Stick) */
// IMPORTANT: pressButtonId -> DUMMY_LEFT_STICK_PRESS_ID, so that double tap does not trigger L3
PrimaryDialConfig.Stick(
GamePadButtonInputId.LeftStick.ordinal,
DUMMY_LEFT_STICK_PRESS_ID,
setOf(),
"LeftStick",
null
),
listOf(
// D-Pad
SecondaryDialConfig.Cross(
/* sector */ 10,
/* size */ 3,
/* gap */ 2.5f,
distance,
CrossConfig(
GamePadButtonInputId.DpadUp.ordinal,
CrossConfig.Shape.STANDARD,
null,
setOf(),
CrossContentDescription(),
true,
null
),
SecondaryDialConfig.RotationProcessor()
),
// Minus
SecondaryDialConfig.SingleButton(
/* sector */ 1,
buttonScale,
2f,
ButtonConfig(
GamePadButtonInputId.Minus.ordinal,
"-",
true,
null,
"Minus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 2,
distance,
ButtonConfig(
GamePadButtonInputId.LeftShoulder.ordinal,
"L",
true,
null,
"LeftBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZL Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.LeftTrigger.ordinal,
"ZL",
true,
null,
"LeftTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
} else {
return GamePadConfig(
/* ringSegments = */ 12,
/* Primary (ABXY) */
PrimaryDialConfig.PrimaryButtons(
if (useSwitchLayout) {
// Switch/Nintendo: A=Right, X=Top, Y=Left, B=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null) // Bottom
)
} else {
// Xbox Style: B=Right, Y=Top, X=Left, A=Bottom
listOf(
ButtonConfig(GamePadButtonInputId.B.ordinal, "B", true, null, "B", setOf(), true, null), // Right
ButtonConfig(GamePadButtonInputId.Y.ordinal, "Y", true, null, "Y", setOf(), true, null), // Top
ButtonConfig(GamePadButtonInputId.X.ordinal, "X", true, null, "X", setOf(), true, null), // Left
ButtonConfig(GamePadButtonInputId.A.ordinal, "A", true, null, "A", setOf(), true, null) // Bottom
)
},
null,
0f,
true,
null
),
listOf(
// Right stick (unchanged)
// IMPORTANT: pressButtonId -> DUMMY_RIGHT_STICK_PRESS_ID, so that double tap does not trigger R3
SecondaryDialConfig.Stick(
/* sector */ 6,
/* size */ 3,
/* gap */ 2.5f,
0.7f,
GamePadButtonInputId.RightStick.ordinal,
DUMMY_RIGHT_STICK_PRESS_ID,
null,
setOf(),
"RightStick",
SecondaryDialConfig.RotationProcessor()
),
// Plus
SecondaryDialConfig.SingleButton(
/* sector */ 5,
buttonScale,
2f,
ButtonConfig(
GamePadButtonInputId.Plus.ordinal,
"+",
true,
null,
"Plus",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R Bumper
SecondaryDialConfig.DoubleButton(
/* sector */ 3,
distance,
ButtonConfig(
GamePadButtonInputId.RightShoulder.ordinal,
"R",
true,
null,
"RightBumper",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// ZR Trigger
SecondaryDialConfig.SingleButton(
/* sector */ 9,
buttonScale,
distance,
ButtonConfig(
GamePadButtonInputId.RightTrigger.ordinal,
"ZR",
true,
null,
"RightTrigger",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// R3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 5,
buttonScale,
0.0f,
ButtonConfig(
GamePadButtonInputId.RightStickButton.ordinal,
"R3",
true,
null,
"RightStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
// L3 as a separate button
SecondaryDialConfig.SingleButton(
/* sector */ 6,
buttonScale,
0.0f,
ButtonConfig(
GamePadButtonInputId.LeftStickButton.ordinal,
"L3",
true,
null,
"LeftStickButton",
setOf(),
true,
null
),
null,
SecondaryDialConfig.RotationProcessor()
),
)
)
}
}
@@ -1,510 +0,0 @@
package org.kenjinx.android
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.compose.runtime.MutableState
import org.kenjinx.android.service.EmulationService
import org.kenjinx.android.viewmodels.GameModel
import org.kenjinx.android.viewmodels.MainViewModel
import kotlin.concurrent.thread
@SuppressLint("ViewConstructor")
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context),
SurfaceHolder.Callback {
private var isProgressHidden: Boolean = false
private var progress: MutableState<String>? = null
private var progressValue: MutableState<Float>? = null
private var showLoading: MutableState<Boolean>? = null
private var game: GameModel? = null
private var _isClosed: Boolean = false
private var _renderingThreadWatcher: Thread? = null
private var _height: Int = 0
private var _width: Int = 0
private var _updateThread: Thread? = null
private var _guestThread: Thread? = null
private var _isInit: Boolean = false
private var _isStarted: Boolean = false
private val _nativeWindow: NativeWindow
private val mainHandler = Handler(Looper.getMainLooper())
// ---- Foreground service binding ----
private var emuBound = false
private var emuBinder: EmulationService.LocalBinder? = null
private var _startedViaService = false
private var _inputInitialized: Boolean = false
private val emuConn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
emuBinder = service as EmulationService.LocalBinder
emuBound = true
ghLog("EmulationService bound")
// If the start has already been prepared and no loop is currently running → start it in the service now
if (_isStarted && !_startedViaService && _guestThread == null) {
startRunLoopInService()
}
}
override fun onServiceDisconnected(name: ComponentName) {
ghLog("EmulationService unbound")
emuBound = false
emuBinder = null
_startedViaService = false
}
}
// Resize Stabilizer
private var stabilizerActive = false
// last known Android rotation (0,1,2,3)
private var lastRotation: Int? = null
// Debounce for resize kick
private var lastKickAt = 0L
var currentSurface: Long = -1
private set
val currentWindowHandle: Long
get() = _nativeWindow.nativePointer
init {
holder.addCallback(this)
_nativeWindow = NativeWindow(this)
mainViewModel.gameHost = this
}
private fun ghLog(msg: String) {
val enabled = BuildConfig.DEBUG && org.kenjinx.android.viewmodels.QuickSettings(mainViewModel.activity).enableDebugLogs
if (enabled) Log.d("GameHost", msg)
}
/**
* (Re)bind the current ANativeWindow to the renderer.
* Forces a fresh native pointer and passes it to C#.
*/
fun rebindNativeWindow(force: Boolean = false) {
if (_isClosed) return
try {
currentSurface = _nativeWindow.requeryWindowHandle()
_nativeWindow.swapInterval = 0
KenjinxNative.deviceSetWindowHandle(currentWindowHandle)
val w = if (holder.surfaceFrame.width() > 0) holder.surfaceFrame.width() else width
val h = if (holder.surfaceFrame.height() > 0) holder.surfaceFrame.height() else height
if (w > 0 && h > 0) {
if (MainActivity.mainViewModel?.rendererReady == true) {
try { KenjinxNative.graphicsRendererSetSize(w, h) } catch (_: Throwable) {}
}
if (_inputInitialized) {
try { KenjinxNative.inputSetClientSize(w, h) } catch (_: Throwable) {}
}
}
} catch (_: Throwable) { }
}
/**
* After a successful reattachment, safely "wake up" the swapchain/viewport:
* - Set rotation
* - Two consecutive resize kicks
*/
fun postReattachKicks(rotation: Int?) {
if (_isClosed) return
try {
KenjinxNative.setSurfaceRotationByAndroidRotation(rotation ?: 0)
val w = if (holder.surfaceFrame.width() > 0) holder.surfaceFrame.width() else width
val h = if (holder.surfaceFrame.height() > 0) holder.surfaceFrame.height() else height
if (w > 0 && h > 0 &&
MainActivity.mainViewModel?.rendererReady == true &&
_isStarted && _inputInitialized
) {
try { KenjinxNative.resizeRendererAndInput(w, h) } catch (_: Throwable) {}
mainHandler.postDelayed({
try { KenjinxNative.resizeRendererAndInput(w, h) } catch (_: Throwable) {}
}, 32)
}
} catch (_: Throwable) { }
}
// -------- Surface Lifecycle --------
override fun surfaceCreated(holder: SurfaceHolder) {
ghLog("surfaceCreated")
// Bind early so that the service is already in place before we start
ensureServiceStartedAndBound()
rebindNativeWindow(force = true)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
ghLog("surfaceChanged ${width}x$height")
if (_isClosed) return
// Always rebind – even if the size remains the same
rebindNativeWindow(force = true)
val sizeChanged = (_width != width || _height != height)
_width = width
_height = height
// Ensure service availability & start rendering
ensureServiceStartedAndBound()
start(holder)
// Stabilize resizing (adopts a plausible final size setting)
startStabilizedResize(expectedRotation = lastRotation)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
ghLog("surfaceDestroyed → shutdownBinding()")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
// Always bind and unbind (prevents leaks when swiping away tasks)
shutdownBinding()
// The actual emulator termination happens via close() / Exit Game
}
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(visibility)
if (visibility != VISIBLE) {
ghLog("window not visible → shutdownBinding()")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
shutdownBinding()
}
}
// -------- UI Progress --------
fun setProgress(info: String, progressVal: Float) {
showLoading?.apply {
progressValue?.apply { this.value = progressVal }
progress?.apply { this.value = info }
}
}
fun setProgressStates(
showLoading: MutableState<Boolean>?,
progressValue: MutableState<Float>?,
progress: MutableState<String>?
) {
this.showLoading = showLoading
this.progressValue = progressValue
this.progress = progress
showLoading?.apply { value = !isProgressHidden }
}
fun hideProgressIndicator() {
isProgressHidden = true
showLoading?.apply {
if (value == isProgressHidden) value = !isProgressHidden
}
}
// -------- Start/Stop Emulation --------
private fun start(surfaceHolder: SurfaceHolder) {
if (_isStarted) return
// Do NOT set _isStarted = true immediately → first prepare everything
rebindNativeWindow(force = true)
game = if (mainViewModel.isMiiEditorLaunched) null else mainViewModel.gameModel
// Input initialize
KenjinxNative.inputInitialize(width, height)
_inputInitialized = true
val id = mainViewModel.physicalControllerManager?.connect()
mainViewModel.motionSensorManager?.setControllerId(id ?: -1)
val currentRot = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
mainViewModel.activity.display?.rotation
} else null
lastRotation = currentRot
try {
KenjinxNative.setSurfaceRotationByAndroidRotation(currentRot ?: 0)
try { KenjinxNative.deviceSetWindowHandle(currentWindowHandle) } catch (_: Throwable) {}
// Gentle kick only when renderer is READY **and** input is initialized
if (width > 0 && height > 0 &&
MainActivity.mainViewModel?.rendererReady == true &&
_inputInitialized
) {
try { KenjinxNative.resizeRendererAndInput(width, height) } catch (_: Throwable) {}
}
} catch (_: Throwable) {}
val qs = org.kenjinx.android.viewmodels.QuickSettings(mainViewModel.activity)
try { KenjinxNative.graphicsSetFullscreenStretch(qs.stretchToFullscreen) } catch (_: Throwable) {}
// The host is now considered "started".
_isStarted = true
// Always try starting with the service first; if binding is not yet complete → wait briefly, then fall back
if (emuBound) {
startRunLoopInService()
} else {
ghLog("Service not yet bound → delayed runloop start")
mainHandler.postDelayed({
if (!_isStarted) return@postDelayed
if (emuBound) {
startRunLoopInService()
} else {
// Fallback: Local thread (this should rarely happen)
ghLog("Fallback: starting RunLoop in local thread")
_guestThread = thread(start = true, name = "KenjinxGuest") { runGame() }
}
}, 150)
}
_updateThread = thread(start = true, name = "KenjinxInput/Stats") {
var c = 0
while (_isStarted) {
KenjinxNative.inputUpdate()
Thread.sleep(1)
if (++c >= 1000) {
if (progressValue?.value == -1f) {
progress?.apply {
this.value = "Loading ${if (mainViewModel.isMiiEditorLaunched) "Mii Editor" else game?.titleName ?: ""}"
}
}
c = 0
mainViewModel.updateStats(
KenjinxNative.deviceGetGameFifo(),
KenjinxNative.deviceGetGameFrameRate(),
KenjinxNative.deviceGetGameFrameTime()
)
}
}
}
}
private fun runGame() {
mainViewModel.onEmulationStarted()
try {
KenjinxNative.graphicsRendererRunLoop()
} finally {
mainViewModel.onEmulationStopped()
}
game?.close()
}
fun close() {
ghLog("close()")
_isClosed = true
_isInit = false
_isStarted = false
_inputInitialized = false
KenjinxNative.uiHandlerSetResponse(false, "")
// Stop emulation in the service (if it was started there)
try {
if (emuBound && _startedViaService) {
emuBinder?.stopEmulation {
// Close is handled in the service hard-close path.
}
}
} catch (_: Throwable) { }
// Fallback: Terminate local thread
try { _updateThread?.join(200) } catch (_: Throwable) {}
try { _renderingThreadWatcher?.join(200) } catch (_: Throwable) {}
try { _guestThread?.join(1000) } catch (_: Throwable) {}
if (!(emuBound && _startedViaService)) {
try { KenjinxNative.deviceCloseEmulation() } catch (_: Throwable) {}
mainViewModel.onEmulationStopped()
}
// Release the bond
shutdownBinding()
// Explicitly terminate the service (if it is still running)
try {
mainViewModel.activity.stopService(Intent(mainViewModel.activity, EmulationService::class.java))
} catch (_: Throwable) { }
}
// -------- Orientation / Resize --------
/**
* Securely setting the renderer/input size
*/
@Synchronized
private fun safeSetSize(w: Int, h: Int) {
if (_isClosed || w <= 0 || h <= 0) return
try {
ghLog("safeSetSize: ${w}x$h (started=$_isStarted, inputInit=$_inputInitialized)")
KenjinxNative.graphicsRendererSetSize(w, h)
if (_isStarted && _inputInitialized) {
KenjinxNative.inputSetClientSize(w, h)
}
} catch (t: Throwable) {
Log.e("GameHost", "safeSetSize failed: ${t.message}", t)
}
}
/**
* Called by the activity when the rotation/layout changes.
* Detects 90°↔270° and forces (debounces) a requery/resize.
*/
fun onOrientationOrSizeChanged(rotation: Int? = null) {
if (_isClosed) return
val old = lastRotation
lastRotation = rotation
val isSideFlip = (old == 1 && rotation == 3) || (old == 3 && rotation == 1)
if (isSideFlip) {
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rotation) } catch (_: Throwable) {}
rebindNativeWindow(force = true)
val now = android.os.SystemClock.uptimeMillis()
if (now - lastKickAt >= 300L && _inputInitialized && MainActivity.mainViewModel?.rendererReady == true) {
lastKickAt = now
val w = if (holder.surfaceFrame.width() > 0) holder.surfaceFrame.width() else width
val h = if (holder.surfaceFrame.height() > 0) holder.surfaceFrame.height() else height
if (w > 0 && h > 0) {
try { KenjinxNative.resizeRendererAndInput(w, h) } catch (_: Throwable) {}
}
}
}
startStabilizedResize(rotation)
}
/**
* Wait a moment until the surface has its final dimensions after rotation,
* checks plausibility (portrait/landscape) and only then sets the size.
*/
private fun startStabilizedResize(expectedRotation: Int?) {
if (_isClosed) return
// Restart if already active
if (stabilizerActive) {
stabilizerActive = false
}
stabilizerActive = true
var attempts = 0
var stableCount = 0
var lastW = -1
var lastH = -1
val task = object : Runnable {
override fun run() {
if (!_isStarted || _isClosed) {
stabilizerActive = false
return
}
// Prefer real frame size
var w = holder.surfaceFrame.width()
var h = holder.surfaceFrame.height()
// Fallbacks
if (w <= 0 || h <= 0) {
w = width
h = height
}
// If rotation is known: Force plausibility (Landscape ↔ Portrait)
expectedRotation?.let { rot ->
val landscape = (rot == 1 || rot == 3) // ROTATION_90/270
if (landscape && h > w) {
val t = w; w = h; h = t
} else if (!landscape && w > h) {
val t = w; w = h; h = t
}
}
// Stability test
if (w == lastW && h == lastH && w > 0 && h > 0) {
stableCount++
} else {
stableCount = 0
lastW = w
lastH = h
}
attempts++
// 1 stable tick or a maximum of 12 attempts
if ((stableCount >= 1 || attempts >= 12) && w > 0 && h > 0) {
ghLog("resize stabilized after $attempts ticks → ${w}x$h")
safeSetSize(w, h)
stabilizerActive = false
return
}
if (stabilizerActive) {
mainHandler.postDelayed(this, 16)
}
}
}
mainHandler.post(task)
}
// ===== Service helpers =====
/** Callable from Activity/Surface lifecycle to ensure FGS is running */
fun ensureServiceStartedAndBound() {
val act = mainViewModel.activity
val intent = Intent(act, EmulationService::class.java)
try {
act.startForegroundService(intent)
} catch (_: Throwable) { }
try {
if (!emuBound) {
act.bindService(intent, emuConn, Context.BIND_AUTO_CREATE)
}
} catch (_: Throwable) { }
}
/** Call this from the Activity in onPause/onStop/onDestroy (and internally in surfaceDestroyed/onWindowVisibilityChanged) */
fun shutdownBinding() {
if (emuBound) {
try {
mainViewModel.activity.unbindService(emuConn)
} catch (_: Throwable) { }
emuBound = false
emuBinder = null
_startedViaService = false
ghLog("shutdownBinding() → unbound")
}
}
private fun startRunLoopInService() {
if (!emuBound) return
if (_startedViaService) return
_startedViaService = true
emuBinder?.startEmulation {
try {
mainViewModel.onEmulationStarted()
KenjinxNative.graphicsRendererRunLoop()
} catch (t: Throwable) {
Log.e("GameHost", "RunLoop crash in service", t)
} finally {
_startedViaService = false
mainViewModel.onEmulationStopped()
}
}
ghLog("RunLoop started in EmulationService")
}
}
@@ -1,27 +0,0 @@
package org.kenjinx.android
enum class GamePadButtonInputId {
None,
// Buttons
A,
B,
X,
Y,
LeftStickButton,
RightStickButton,
LeftShoulder,
RightShoulder,
LeftTrigger,
RightTrigger,
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
Minus,
Plus,
// Stick
LeftStick,
RightStick
}
@@ -1,7 +0,0 @@
package org.kenjinx.android
interface IGameController {
val isVisible: Boolean
fun setVisible(isVisible: Boolean)
fun connect()
}
File diff suppressed because it is too large Load Diff
@@ -1,20 +0,0 @@
package org.kenjinx.android
import android.app.Application
import android.content.Context
import java.io.File
class KenjinxApplication : Application() {
init {
instance = this
}
fun getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
companion object {
lateinit var instance: KenjinxApplication
private set
val context: Context get() = instance.applicationContext
}
}
@@ -1,371 +0,0 @@
package org.kenjinx.android
import android.os.Build
import com.sun.jna.JNIEnv
import com.sun.jna.Library
import com.sun.jna.Native
import org.kenjinx.android.viewmodels.GameInfo
import java.util.Collections
import android.view.Surface
import android.util.Log
import androidx.annotation.RequiresApi
interface KenjinxNativeJna : Library {
fun deviceInitialize(
memoryManagerMode: Int,
useNce: Boolean,
memoryConfiguration: Int,
systemLanguage: Int,
regionCode: Int,
vSyncMode: Int,
enableDockedMode: Boolean,
enablePptc: Boolean,
enableLowPowerPptc: Boolean,
enableJitCacheEviction: Boolean,
enableInternetAccess: Boolean,
enableFsIntegrityChecks: Boolean,
fsGlobalAccessLogMode: Int,
timeZone: String,
ignoreMissingServices: Boolean
): Boolean
fun graphicsInitialize(
rescale: Float = 1f,
maxAnisotropy: Float = 0f,
fastGpuTime: Boolean = true,
fast2DCopy: Boolean = true,
enableMacroJit: Boolean = false,
enableMacroHLE: Boolean = true,
enableShaderCache: Boolean = true,
enableTextureRecompression: Boolean = false,
backendThreading: Int = BackendThreading.Auto.ordinal
): Boolean
fun graphicsInitializeRenderer(
extensions: Array<String>,
extensionsLength: Int,
driver: Long
): Boolean
fun javaInitialize(appPath: String, env: JNIEnv): Boolean
fun deviceLaunchMiiEditor(): Boolean
fun deviceGetGameFrameRate(): Double
fun deviceGetGameFrameTime(): Double
fun deviceGetGameFifo(): Double
fun deviceLoadDescriptor(fileDescriptor: Int, gameType: Int, updateDescriptor: Int): Boolean
fun graphicsRendererSetSize(width: Int, height: Int)
fun graphicsRendererSetVsync(vSyncMode: Int)
fun graphicsRendererRunLoop()
fun graphicsSetFullscreenStretch(enable: Boolean)
fun deviceReloadFilesystem()
fun inputInitialize(width: Int, height: Int)
fun inputSetClientSize(width: Int, height: Int)
fun inputSetTouchPoint(x: Int, y: Int)
fun inputReleaseTouchPoint()
fun inputUpdate()
fun inputSetButtonPressed(button: Int, id: Int)
fun inputSetButtonReleased(button: Int, id: Int)
fun inputConnectGamepad(index: Int): Int
fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int)
fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int)
fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int)
fun deviceCloseEmulation()
fun deviceReinitEmulation()
fun deviceSignalEmulationClose()
fun detachWindow()
// >>> Rendering-related additions for the toggle:
fun deviceWaitForGpuDone(timeoutMs: Int)
fun deviceRecreateSwapchain()
fun graphicsSetBackendThreading(mode: Int)
fun graphicsSetPresentEnabled(enabled: Boolean)
// <<<
fun userGetOpenedUser(): String
fun userGetUserPicture(userId: String): String
fun userSetUserPicture(userId: String, picture: String)
fun userGetUserName(userId: String): String
fun userSetUserName(userId: String, userName: String)
fun userAddUser(username: String, picture: String)
fun userDeleteUser(userId: String)
fun userOpenUser(userId: String)
fun userCloseUser(userId: String)
fun loggingSetEnabled(logLevel: Int, enabled: Boolean)
fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): String
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
fun deviceGetInstalledFirmwareVersion(): String
fun uiHandlerSetup()
fun uiHandlerSetResponse(isOkPressed: Boolean, input: String)
fun deviceGetDlcTitleId(path: String, ncaPath: String): String
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
fun userGetAllUsers(): Array<String>
fun deviceGetDlcContentList(path: String, titleId: Long): Array<String>
fun loggingEnabledGraphicsLog(enabled: Boolean)
// Surface rotation (0/90/180/270 degrees)
fun deviceSetSurfaceRotation(degrees: Int)
// (optional alias): compact resize shortcut
fun deviceResize(width: Int, height: Int)
// Set window handle after each query
fun deviceSetWindowHandle(handle: Long)
// Amiibo
fun amiiboLoadBin(bytes: ByteArray, length: Int): Boolean
fun amiiboClear()
// AUDIO: Direct JNA bridge to C# exports
fun audioSetPaused(paused: Boolean)
fun audioSetMuted(muted: Boolean)
}
val jnaInstance: KenjinxNativeJna = Native.load(
"kenjinx",
KenjinxNativeJna::class.java,
Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true)
)
object KenjinxNative : KenjinxNativeJna by jnaInstance {
fun loggingSetEnabled(logLevel: LogLevel, enabled: Boolean) =
loggingSetEnabled(logLevel.ordinal, enabled)
// --- Rendering: Single-threaded option & secure wrappers --------------------
// 0 = Auto, 1 = SingleThread (Disable Threaded), 2 = Threaded
private const val THREADING_AUTO = 0
private const val THREADING_SINGLE = 1
private const val THREADING_THREADED = 2
override fun graphicsSetBackendThreading(mode: Int) {
try {
jnaInstance.graphicsSetBackendThreading(mode)
} catch (_: Throwable) {
Log.w("KenjinxNative", "graphicsSetBackendThreading not available")
}
}
override fun deviceRecreateSwapchain() {
try { jnaInstance.deviceRecreateSwapchain() } catch (_: Throwable) { /* ignore */ }
}
override fun deviceWaitForGpuDone(timeoutMs: Int) {
try { jnaInstance.deviceWaitForGpuDone(timeoutMs) } catch (_: Throwable) { /* ignore */ }
}
override fun graphicsSetPresentEnabled(enabled: Boolean) {
try { jnaInstance.graphicsSetPresentEnabled(enabled) } catch (_: Throwable) { /* ignore */ }
}
// Secure deviceResize implementation (pure rendering)
override fun deviceResize(width: Int, height: Int) {
try {
graphicsRendererSetSize(width, height)
inputSetClientSize(width, height)
} catch (_: Throwable) { /* ignore */ }
}
// Robust graphicsInitialize with QCOM heuristics + fallback → SingleThread
override fun graphicsInitialize(
rescale: Float,
maxAnisotropy: Float,
fastGpuTime: Boolean,
fast2DCopy: Boolean,
enableMacroJit: Boolean,
enableMacroHLE: Boolean,
enableShaderCache: Boolean,
enableTextureRecompression: Boolean,
backendThreading: Int
): Boolean {
val requested = backendThreading
val isQcom = "qcom".equals(Build.HARDWARE, true)
// Heuristic: On QCOM, when "Auto" is selected, first try single-threaded mode;
// explicitly set values remain unchanged.
val firstChoice =
if (isQcom && requested == THREADING_AUTO) THREADING_SINGLE else requested
Log.i(
"KenjinxNative",
"graphicsInitialize: request=$requested firstChoice=$firstChoice hw=${Build.HARDWARE}"
)
return try {
jnaInstance.graphicsInitialize(
rescale,
maxAnisotropy,
fastGpuTime,
fast2DCopy,
enableMacroJit,
enableMacroHLE,
enableShaderCache,
enableTextureRecompression,
firstChoice
)
} catch (t: Throwable) {
Log.e(
"KenjinxNative",
"graphicsInitialize failed (firstChoice=$firstChoice). Fallback → SingleThread",
t
)
try {
jnaInstance.graphicsInitialize(
rescale,
maxAnisotropy,
fastGpuTime,
fast2DCopy,
enableMacroJit,
enableMacroHLE,
enableShaderCache,
enableTextureRecompression,
THREADING_SINGLE
)
} catch (t2: Throwable) {
Log.e("KenjinxNative", "graphicsInitialize fallback failed", t2)
false
}
}
}
// --- Optional wrappers for audio (safer logging) ---
override fun audioSetPaused(paused: Boolean) {
try { jnaInstance.audioSetPaused(paused) }
catch (t: Throwable) { Log.w("KenjinxNative", "audioSetPaused unavailable", t) }
}
override fun audioSetMuted(muted: Boolean) {
try { jnaInstance.audioSetMuted(muted) }
catch (t: Throwable) { Log.w("KenjinxNative", "audioSetMuted unavailable", t) }
}
// ----------------------------------------------------
@RequiresApi(Build.VERSION_CODES.R)
@JvmStatic
fun frameEnded() = MainActivity.frameEnded()
@JvmStatic
fun test() { /* no-op */ }
@JvmStatic
fun getSurfacePtr(): Long = MainActivity.mainViewModel?.gameHost?.currentSurface ?: -1
@JvmStatic
fun getWindowHandle(): Long =
MainActivity.mainViewModel?.gameHost?.currentWindowHandle ?: -1
@JvmStatic
fun updateProgress(infoPtr: Long, progress: Float) {
// Get string from native pointer and push into progress overlay
val text = NativeHelpers.instance.getStringJava(infoPtr)
MainActivity.mainViewModel?.gameHost?.setProgress(text, progress)
}
@JvmStatic
fun onSurfaceSizeChanged(width: Int, height: Int) {
// No-Op: Placeholder – Hook if needed.
}
@JvmStatic
fun setSurfaceRotationByAndroidRotation(androidRotation: Int?) {
val degrees = when (androidRotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> 0
}
try { deviceSetSurfaceRotation(degrees) } catch (_: Throwable) {}
}
@JvmStatic
fun resizeRendererAndInput(width: Int, height: Int) {
try {
// Alternatively: deviceResize(width, height)
graphicsRendererSetSize(width, height)
inputSetClientSize(width, height)
} catch (_: Throwable) {}
}
override fun detachWindow() {
try { graphicsSetPresentEnabled(false) } catch (_: Throwable) {}
try { deviceWaitForGpuDone(100) } catch (_: Throwable) {}
try { jnaInstance.detachWindow() } catch (_: Throwable) {}
try { deviceSetWindowHandle(0) } catch (_: Throwable) {}
}
@JvmStatic
fun reattachWindowIfReady(): Boolean {
return try {
val handle = getWindowHandle()
if (handle <= 0) return false
deviceSetWindowHandle(handle)
deviceRecreateSwapchain()
true
} catch (_: Throwable) {
false
}
}
/**
* Variant A (Pointer → Strings via NativeHelpers).
* Used by older JNI/Interop paths.
*/
@JvmStatic
fun updateUiHandler(
newTitlePointer: Long,
newMessagePointer: Long,
newWatermarkPointer: Long,
newType: Int,
min: Int,
max: Int,
nMode: Int,
newSubtitlePointer: Long,
newInitialTextPointer: Long
) {
val title = NativeHelpers.instance.getStringJava(newTitlePointer)
val message = NativeHelpers.instance.getStringJava(newMessagePointer)
val watermark = NativeHelpers.instance.getStringJava(newWatermarkPointer)
val subtitle = NativeHelpers.instance.getStringJava(newSubtitlePointer)
val initialText = NativeHelpers.instance.getStringJava(newInitialTextPointer)
val mode = KeyboardMode.entries.getOrNull(nMode) ?: KeyboardMode.Default
MainActivity.mainViewModel?.activity?.uiHandler?.update(
newTitle = title,
newMessage = message,
newWatermark = watermark,
newType = newType,
min = min,
max = max,
newMode = mode,
newSubtitle = subtitle,
newInitialText = initialText
)
}
/**
* Variant B (strings directly). Used by newer JNI/interop paths.
* Signature exactly matches the C# call in AndroidUIHandler.cs / Interop.UpdateUiHandler(...).
*/
@JvmStatic
fun uiHandlerUpdate(
title: String,
message: String,
watermark: String,
type: Int,
min: Int,
max: Int,
nMode: Int,
subtitle: String,
initialText: String
) {
val mode = KeyboardMode.entries.getOrNull(nMode) ?: KeyboardMode.Default
MainActivity.mainViewModel?.activity?.uiHandler?.update(
newTitle = title,
newMessage = message,
newWatermark = watermark,
newType = type,
min = min,
max = max,
newMode = mode,
newSubtitle = subtitle,
newInitialText = initialText
)
}
}
@@ -1,63 +0,0 @@
package org.kenjinx.android
import android.content.Intent
import androidx.core.content.FileProvider
import net.lingala.zip4j.ZipFile
import org.kenjinx.android.viewmodels.MainViewModel
import java.io.File
import java.net.URLConnection
class Logging(private var viewModel: MainViewModel) {
val logPath = MainActivity.AppPath + "/Logs"
init {
File(logPath).mkdirs()
}
fun requestExport() {
val files = File(logPath).listFiles()
files?.apply {
val zipExportPath = MainActivity.AppPath + "/log.zip"
File(zipExportPath).delete()
var count = 0
if (files.isNotEmpty()) {
val zipFile = ZipFile(zipExportPath)
for (file in files) {
if (file.isFile) {
zipFile.addFile(file)
count++
}
}
zipFile.close()
}
if (count > 0) {
val zip = File(zipExportPath)
val uri = FileProvider.getUriForFile(
viewModel.activity,
viewModel.activity.packageName + ".fileprovider",
zip
)
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setDataAndType(uri, URLConnection.guessContentTypeFromName(zip.name))
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, "Share logs")
viewModel.activity.startActivity(chooser)
} else {
File(zipExportPath).delete()
}
}
}
fun clearLogs() {
if (File(logPath).exists()) {
File(logPath).deleteRecursively()
}
File(logPath).mkdirs()
}
}
enum class LogLevel {
Debug, Stub, Info, Warning, Error, Guest, AccessLog, Notice, Trace
}
@@ -1,734 +0,0 @@
package org.kenjinx.android
import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Environment
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.WindowManager
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.SimpleStorageHelper
import com.sun.jna.JNIEnv
import org.kenjinx.android.ui.theme.KenjinxAndroidTheme
import org.kenjinx.android.viewmodels.MainViewModel
import org.kenjinx.android.viewmodels.QuickSettings
import org.kenjinx.android.viewmodels.GameModel
import org.kenjinx.android.views.MainView
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.hardware.display.DisplayManager
import android.net.Uri
import android.view.Surface
import androidx.preference.PreferenceManager
import java.io.File
import androidx.activity.result.contract.ActivityResultContracts
import android.Manifest
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import android.content.BroadcastReceiver
import android.content.IntentFilter
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import org.kenjinx.android.service.EmulationService
import androidx.core.content.edit
class MainActivity : BaseActivity() {
private var physicalControllerManager: PhysicalControllerManager =
PhysicalControllerManager(this)
private lateinit var motionSensorManager: MotionSensorManager
private var _isInit: Boolean = false
private val handler = Handler(Looper.getMainLooper())
private val ENABLE_PRESENT_DELAY_MS = 400L
private val REATTACH_DELAY_MS = 300L
private var wantPresentEnabled = false
private val TAG_FG = "FgPresent"
private val delayedHandleIntent = object : Runnable { override fun run() { handleIntent() } }
var storedIntent: Intent = Intent()
var isGameRunning = false
var isActive = false
var storageHelper: SimpleStorageHelper? = null
lateinit var uiHandler: UiHandler
// Persistence for zombie detection
private val PREFS = "emu_core"
private val KEY_EMU_RUNNING = "emu_running"
// Display Rotation + Orientation Handling
private lateinit var displayManager: DisplayManager
private var lastKnownRotation: Int? = null
private var pulsingOrientation = false
private var lastPulseAt = 0L
private val TAG_ROT = "RotationDebug"
private fun rotLog(msg: String) {
val enabled = BuildConfig.DEBUG && QuickSettings(this).enableDebugLogs
if (enabled) Log.d(TAG_ROT, msg)
}
private val serviceStopReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == EmulationService.ACTION_STOPPED) {
handler.removeCallbacks(reattachWindowWhenReady)
handler.removeCallbacks(enablePresentWhenReady)
clearEmuRunningFlag()
hardColdReset("service stopped broadcast")
}
}
}
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
display?.displayId != displayId
} else {
@Suppress("DEPRECATION")
return
}
) return
val rot = display?.rotation
if (rot == lastKnownRotation) return
rotLog("onDisplayChanged: display.rotation=$rot → ${deg(rot)}°")
val pref = QuickSettings(this@MainActivity).orientationPreference
val old = lastKnownRotation
lastKnownRotation = rot
// 1) Inform Native/Renderer (applies to Sensor & SensorLandscape)
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {}
// 2) Initiate host resize (applies to Sensor & SensorLandscape)
if (isGameRunning) {
handler.post {
try { mainViewModel?.gameHost?.onOrientationOrSizeChanged(rot) } catch (_: Throwable) {}
}
}
// 3) Only with SENSOR_LANDSCAPE: gentle pulse with real 90↔270 flip
if (pref == QuickSettings.OrientationPreference.SensorLandscape && old != null && rot != null) {
val isSideFlip = (old == Surface.ROTATION_90 && rot == Surface.ROTATION_270) ||
(old == Surface.ROTATION_270 && rot == Surface.ROTATION_90)
if (isSideFlip) doOrientationPulse(rot)
}
}
}
private fun deg(r: Int?): Int = when (r) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> -1
}
private fun setPresentEnabled(enabled: Boolean, reason: String) {
wantPresentEnabled = enabled
try {
KenjinxNative.graphicsSetPresentEnabled(enabled)
Log.d(TAG_FG, "present=${if (enabled) "ENABLED" else "DISABLED"} ($reason)")
} catch (_: Throwable) {
Log.d(TAG_FG, "native toggle not available ($reason)")
}
}
private val enablePresentWhenReady = object : Runnable {
override fun run() {
val isReallyResumed = lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) && isActive
val hasFocusNow = hasWindowFocus()
val rendererReady = mainViewModel?.rendererReady == true
if (!isReallyResumed || !hasFocusNow || !rendererReady) {
handler.postDelayed(this, ENABLE_PRESENT_DELAY_MS)
return
}
setPresentEnabled(true, "focus regained + delay")
}
}
private val reattachWindowWhenReady = object : Runnable {
override fun run() {
val isReallyResumed = lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) && isActive
val hasFocusNow = hasWindowFocus()
if (!isReallyResumed || !hasFocusNow) {
handler.postDelayed(this, REATTACH_DELAY_MS)
return
}
try { mainViewModel?.gameHost?.rebindNativeWindow(force = true) } catch (_: Throwable) {}
if (!KenjinxNative.reattachWindowIfReady()) {
handler.postDelayed(this, REATTACH_DELAY_MS)
return
}
Log.d(TAG_FG, "window reattached")
}
}
private fun doOrientationPulse(currentRot: Int) {
val now = android.os.SystemClock.uptimeMillis()
if (pulsingOrientation || now - lastPulseAt < 350L) return
pulsingOrientation = true
lastPulseAt = now
// Short lock on the target page (prevents flickering)
val lock = if (currentRot == Surface.ROTATION_90)
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
else
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
try { requestedOrientation = lock } catch (_: Throwable) {}
handler.post {
if (isGameRunning) {
try { KenjinxNative.setSurfaceRotationByAndroidRotation(currentRot) } catch (_: Throwable) {}
try { mainViewModel?.gameHost?.onOrientationOrSizeChanged(currentRot) } catch (_: Throwable) {}
}
}
// After a short time back to SENSOR_LANDSCAPE
handler.postDelayed({
try { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE } catch (_: Throwable) {}
handler.post {
if (isGameRunning) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
KenjinxNative.setSurfaceRotationByAndroidRotation(display?.rotation)
}
} catch (_: Throwable) {}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
mainViewModel?.gameHost?.onOrientationOrSizeChanged(display?.rotation)
}
} catch (_: Throwable) {}
}
}
pulsingOrientation = false
}, 250)
}
companion object {
var mainViewModel: MainViewModel? = null
var AppPath: String = ""
var StorageHelper: SimpleStorageHelper? = null
const val EXTRA_BOOT_PATH = "bootPath"
const val EXTRA_FORCE_NCE_PPTC = "forceNceAndPptc"
const val EXTRA_TITLE_ID = "titleId"
const val EXTRA_TITLE_NAME = "titleName"
@RequiresApi(Build.VERSION_CODES.R)
@JvmStatic
fun frameEnded() {
mainViewModel?.activity?.apply {
if (isActive && QuickSettings(this).enablePerformanceMode) {
mainViewModel?.performanceManager?.setTurboMode(true)
}
}
mainViewModel?.gameHost?.hideProgressIndicator()
}
// <<< is called from the Native/Lib page to set the loading progress
@JvmStatic
fun updateProgress(info: String, percent: Float) {
// Route directly via the GameHost – it takes care of the progress states
mainViewModel?.gameHost?.setProgress(info, percent)
}
}
init {
storageHelper = SimpleStorageHelper(this)
StorageHelper = storageHelper
System.loadLibrary("kenjinxjni")
initVm()
}
private external fun initVm()
private fun initialize() {
if (_isInit) return
val appPath: String = AppPath
val quickSettings = QuickSettings(this)
KenjinxNative.loggingSetEnabled(LogLevel.Info, quickSettings.enableInfoLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Stub, quickSettings.enableStubLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Warning, quickSettings.enableWarningLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Error, quickSettings.enableErrorLogs)
KenjinxNative.loggingSetEnabled(LogLevel.AccessLog, quickSettings.enableFsAccessLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Guest, quickSettings.enableGuestLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Trace, quickSettings.enableTraceLogs)
KenjinxNative.loggingSetEnabled(LogLevel.Debug, quickSettings.enableDebugLogs)
KenjinxNative.loggingEnabledGraphicsLog(quickSettings.enableGraphicsLogs)
_isInit = KenjinxNative.javaInitialize(appPath, JNIEnv.CURRENT)
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ensureNotificationPermission()
motionSensorManager = MotionSensorManager(this)
Thread.setDefaultUncaughtExceptionHandler(crashHandler)
if (!Environment.isExternalStorageManager()
) {
storageHelper?.storage?.requestFullStorageAccess()
}
AppPath = this.getExternalFilesDir(null)!!.absolutePath
coldResetIfZombie("onCreate")
initialize()
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
WindowCompat.setDecorFitsSystemWindows(window, false)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
// Apply alignment
applyOrientationPreference()
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
uiHandler = UiHandler()
displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
mainViewModel = MainViewModel(this)
mainViewModel!!.physicalControllerManager = physicalControllerManager
mainViewModel!!.motionSensorManager = motionSensorManager
mainViewModel!!.refreshFirmwareVersion()
mainViewModel?.apply {
setContent {
KenjinxAndroidTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainView.Main(mainViewModel = this)
}
}
}
}
storedIntent = intent
rotLog("onCreate: initial display.rotation=${display?.rotation} → ${deg(display?.rotation)}°")
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
storedIntent = intent
}
override fun onSaveInstanceState(outState: Bundle) {
storageHelper?.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storageHelper?.onRestoreInstanceState(savedInstanceState)
}
@SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
event.apply { if (physicalControllerManager.onKeyEvent(this)) return true }
return super.dispatchKeyEvent(event)
}
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
ev?.apply { physicalControllerManager.onMotionEvent(this) }
return super.dispatchGenericMotionEvent(ev)
}
// --- Audio foreground/background gating ---
private fun setAudioForegroundState(inForeground: Boolean) {
// Preferred: pause instead of just muting
try { KenjinxNative.audioSetPaused(!inForeground) } catch (_: Throwable) {}
// Fallback: Master Mute
try { KenjinxNative.audioSetMuted(!inForeground) } catch (_: Throwable) {}
}
// --------- BACKGROUND STABILITY: Present gating ---------
override fun onStart() {
super.onStart()
coldResetIfZombie("onStart")
if (isGameRunning && mainViewModel?.rendererReady == true) {
try {
KenjinxNative.graphicsSetPresentEnabled(true)
Log.d(TAG_FG, "present=ENABLED (onStart)")
} catch (_: Throwable) {}
} else {
Log.d(TAG_FG, "skip enable present (onStart) — rendererReady=${mainViewModel?.rendererReady}")
setPresentEnabled(false, "cold reset: onStart (no game)")
}
}
override fun onStop() {
super.onStop()
if (isGameRunning) {
setAudioForegroundState(false)
handler.removeCallbacks(reattachWindowWhenReady)
handler.removeCallbacks(enablePresentWhenReady)
setPresentEnabled(false, "onStop")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
}
// IMPORTANT: Safely release the binding (prevents memory leak)
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level >= TRIM_MEMORY_UI_HIDDEN && isGameRunning) {
setAudioForegroundState(false)
if (mainViewModel?.rendererReady == true) {
try {
setPresentEnabled(false, "onTrimMemory:$level")
KenjinxNative.detachWindow()
} catch (_: Throwable) {}
} else {
Log.d(TAG_FG, "skip disable present (onTrimMemory) — rendererReady=${mainViewModel?.rendererReady}")
}
}
}
// --------------------------------------------------------
override fun onResume() {
super.onResume()
isActive = true
setAudioForegroundState(true)
coldResetIfZombie("onResume")
applyOrientationPreference()
// Enable display listener
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
lastKnownRotation = display?.rotation
rotLog("onResume: display.rotation=${display?.rotation} → ${deg(display?.rotation)}°")
}
try { displayManager.registerDisplayListener(displayListener, handler) } catch (_: Throwable) {}
try {
if (Build.VERSION.SDK_INT >= 33) {
registerReceiver(
serviceStopReceiver,
IntentFilter(EmulationService.ACTION_STOPPED),
RECEIVER_EXPORTED
)
} else {
@Suppress("DEPRECATION")
registerReceiver(serviceStopReceiver, IntentFilter(EmulationService.ACTION_STOPPED))
}
} catch (_: Throwable) {}
handler.removeCallbacks(reattachWindowWhenReady)
handler.removeCallbacks(enablePresentWhenReady)
try { mainViewModel?.gameHost?.rebindNativeWindow(force = true) } catch (_: Throwable) {}
handler.postDelayed(delayedHandleIntent, 10)
if (isGameRunning && QuickSettings(this).enableMotion) {
motionSensorManager.register()
}
if (isGameRunning) {
handler.postDelayed(reattachWindowWhenReady, REATTACH_DELAY_MS)
if (hasWindowFocus()) {
handler.postDelayed(enablePresentWhenReady, ENABLE_PRESENT_DELAY_MS)
}
} else {
setPresentEnabled(false, "cold reset: onResume (no game)")
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (!isGameRunning) return
handler.removeCallbacks(reattachWindowWhenReady)
handler.removeCallbacks(enablePresentWhenReady)
if (hasFocus && isActive) {
setAudioForegroundState(true)
// First ensure that the binding exists
try { mainViewModel?.gameHost?.ensureServiceStartedAndBound() } catch (_: Throwable) {}
setPresentEnabled(false, "focus gained → pre-rebind")
try { mainViewModel?.gameHost?.rebindNativeWindow(force = true) } catch (_: Throwable) {}
handler.postDelayed(reattachWindowWhenReady, 150L)
val rot = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display?.rotation else null
handler.postDelayed({ try { mainViewModel?.gameHost?.postReattachKicks(rot) } catch (_: Throwable) {} }, 200L)
handler.postDelayed(enablePresentWhenReady, 450L)
} else {
setAudioForegroundState(false)
setPresentEnabled(false, "focus lost")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
}
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onPause() {
super.onPause()
isActive = false
setAudioForegroundState(false)
handler.removeCallbacks(reattachWindowWhenReady)
handler.removeCallbacks(enablePresentWhenReady)
if (isGameRunning) {
setPresentEnabled(false, "onPause")
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
mainViewModel?.performanceManager?.setTurboMode(false)
motionSensorManager.unregister()
}
try { displayManager.unregisterDisplayListener(displayListener) } catch (_: Throwable) {}
try { unregisterReceiver(serviceStopReceiver) } catch (_: Throwable) {}
// Clean up binding (prevents memory leak when swiping away the task)
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
}
private fun handleIntent() {
when (storedIntent.action) {
Intent.ACTION_VIEW, "org.kenjinx.android.LAUNCH_GAME" -> {
val bootPathExtra = storedIntent.getStringExtra(EXTRA_BOOT_PATH)
val forceNceAndPptc = storedIntent.getBooleanExtra(EXTRA_FORCE_NCE_PPTC, false)
val titleId = storedIntent.getStringExtra(EXTRA_TITLE_ID) ?: ""
val titleName = storedIntent.getStringExtra(EXTRA_TITLE_NAME) ?: ""
val dataUri: Uri? = storedIntent.data
val chosenUri: Uri? = when {
!bootPathExtra.isNullOrEmpty() -> bootPathExtra.toUri()
dataUri != null -> dataUri
else -> null
}
if (chosenUri != null) {
val doc = when (chosenUri.scheme?.lowercase()) {
"content" -> DocumentFile.fromSingleUri(this, chosenUri)
"file" -> chosenUri.path?.let { File(it) }?.let { DocumentFile.fromFile(it) }
else -> {
chosenUri.path?.let { File(it) }?.takeIf { it.exists() }?.let { DocumentFile.fromFile(it) }
?: DocumentFile.fromSingleUri(this, chosenUri)
}
}
if (doc != null && doc.exists()) {
val gameModel = GameModel(doc, this)
gameModel.getGameInfo()
mainViewModel?.loadGameModel?.value = gameModel
mainViewModel?.bootPath?.value = "gameItem_${gameModel.titleName}"
mainViewModel?.forceNceAndPptc?.value = forceNceAndPptc
storedIntent = Intent()
return
} else {
Log.w("ShortcutDebug", "DocumentFile not found or not accessible: $chosenUri")
}
}
if (titleId.isNotEmpty() || titleName.isNotEmpty()) {
resolveGameByTitleIdOrName(titleId, titleName)?.let { doc ->
val gameModel = GameModel(doc, this)
gameModel.getGameInfo()
mainViewModel?.loadGameModel?.value = gameModel
mainViewModel?.bootPath?.value = "gameItem_${gameModel.titleName}"
mainViewModel?.forceNceAndPptc?.value = forceNceAndPptc
storedIntent = Intent()
return
}
}
}
}
}
private fun applyOrientationPreference() {
val pref = QuickSettings(this).orientationPreference
requestedOrientation = pref.value
val rot = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
this.display?.rotation
} else {
@Suppress("DEPRECATION")
null
}
rotLog("applyOrientationPreference: rot=$rot → ${deg(rot)}°, pref=${pref.name}")
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {}
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val rot = this.display?.rotation
val old = lastKnownRotation
lastKnownRotation = rot
rotLog("onConfigurationChanged: display.rotation=$rot → ${deg(rot)}°")
try { KenjinxNative.setSurfaceRotationByAndroidRotation(rot) } catch (_: Throwable) {}
val pref = QuickSettings(this).orientationPreference
val shouldPropagate =
pref == QuickSettings.OrientationPreference.Sensor ||
pref == QuickSettings.OrientationPreference.SensorLandscape
if (shouldPropagate && isGameRunning) {
handler.post { try { mainViewModel?.gameHost?.onOrientationOrSizeChanged(rot) } catch (_: Throwable) {} }
}
if (pref == QuickSettings.OrientationPreference.SensorLandscape && old != null && rot != null) {
val isSideFlip = (old == Surface.ROTATION_90 && rot == Surface.ROTATION_270) ||
(old == Surface.ROTATION_270 && rot == Surface.ROTATION_90)
if (isSideFlip) doOrientationPulse(rot)
}
}
private val requestNotifPerm = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { /* optional: Log/Toast */ }
private fun ensureNotificationPermission() {
if (Build.VERSION.SDK_INT >= 33) {
val granted = ContextCompat.checkSelfPermission(
this, Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
if (!granted) {
requestNotifPerm.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
private fun resolveGameByTitleIdOrName(titleIdHex: String?, displayName: String?): DocumentFile? {
val gamesRoot = getDefaultGamesTree() ?: return null
for (child in gamesRoot.listFiles()) {
if (!child.isFile) continue
if (!displayName.isNullOrBlank()) {
val n = child.name ?: ""
if (n.contains(displayName, ignoreCase = true)) return child
}
if (!titleIdHex.isNullOrBlank()) {
val tid = getTitleIdFast(child)
if (tid != null && tid.equals(titleIdHex, ignoreCase = true)) return child
}
}
if (!titleIdHex.isNullOrBlank()) {
for (child in gamesRoot.listFiles()) {
if (!child.isFile) continue
val tid = getTitleIdFast(child)
if (tid != null && tid.equals(titleIdHex, ignoreCase = true)) return child
}
}
return null
}
private fun getDefaultGamesTree(): DocumentFile? {
val vm = mainViewModel
if (vm?.defaultGameFolderUri != null) {
return DocumentFile.fromTreeUri(this, vm.defaultGameFolderUri!!)
}
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val legacyPath = prefs.getString("gameFolder", null)
if (!legacyPath.isNullOrEmpty()) {
// Without a SAF URI, tree listing is not possible
}
return null
}
private fun getTitleIdFast(file: DocumentFile): String? {
val name = file.name ?: return null
val dot = name.lastIndexOf('.')
if (dot <= 0 || dot >= name.length - 1) return null
val ext = name.substring(dot + 1).lowercase()
return try {
contentResolver.openFileDescriptor(file.uri, "r")?.use { pfd ->
val info = org.kenjinx.android.viewmodels.GameInfo()
KenjinxNative.deviceGetGameInfo(pfd.fd, ext, info)
info.TitleId?.lowercase()
}
} catch (_: Exception) { null }
}
@RequiresApi(Build.VERSION_CODES.R)
fun shutdownAndRestart() {
val packageManager = packageManager
val intent = packageManager.getLaunchIntentForPackage(packageName)
val componentName = intent?.component
val restartIntent = Intent.makeRestartActivityTask(componentName)
mainViewModel?.let { it.performanceManager?.setTurboMode(false) }
startActivity(restartIntent)
Runtime.getRuntime().exit(0)
}
override fun onDestroy() {
handler.removeCallbacks(enablePresentWhenReady)
handler.removeCallbacks(reattachWindowWhenReady)
// If the activity dies → the binding is guaranteed to be released
try { mainViewModel?.gameHost?.shutdownBinding() } catch (_: Throwable) {}
super.onDestroy()
}
// ---------- Helpers ----------
private fun setEmuRunningFlag(value: Boolean) {
try {
getSharedPreferences(PREFS, MODE_PRIVATE)
.edit {
putBoolean(KEY_EMU_RUNNING, value)
}
} catch (_: Throwable) { }
}
private fun clearEmuRunningFlag() = setEmuRunningFlag(false)
private fun hardColdReset(reason: String) {
Log.d(TAG_FG, "Cold graphics reset ($reason)")
isGameRunning = false
mainViewModel?.rendererReady = false
try { setPresentEnabled(false, "cold reset: $reason") } catch (_: Throwable) {}
try { KenjinxNative.detachWindow() } catch (_: Throwable) {}
try { stopService(Intent(this, EmulationService::class.java)) } catch (_: Throwable) {}
try { mainViewModel?.loadGameModel?.value = null } catch (_: Throwable) {}
try { mainViewModel?.bootPath?.value = "" } catch (_: Throwable) {}
try { mainViewModel?.forceNceAndPptc?.value = false } catch (_: Throwable) {}
storedIntent = Intent()
}
private fun coldResetIfZombie(phase: String) {
try {
val zombie = getSharedPreferences(PREFS, MODE_PRIVATE)
.getBoolean(KEY_EMU_RUNNING, false)
if (zombie) {
clearEmuRunningFlag()
setPresentEnabled(false, "kill stray: $phase")
hardColdReset("kill stray: $phase")
}
} catch (_: Throwable) { }
}
}
@@ -1,139 +0,0 @@
package org.kenjinx.android
import android.app.Activity
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener2
import android.hardware.SensorManager
import android.view.OrientationEventListener
class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 {
private var isRegistered: Boolean = false
private var gyro: Sensor?
private var accelerometer: Sensor?
private var sensorManager: SensorManager =
activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager
private var controllerId: Int = -1
private val motionGyroOrientation: FloatArray = FloatArray(3)
private val motionAcelOrientation: FloatArray = FloatArray(3)
init {
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
setOrientation90()
object : OrientationEventListener(activity) {
override fun onOrientationChanged(orientation: Int) {
when {
isWithinOrientationRange(orientation, 270) -> {
setOrientation270()
}
isWithinOrientationRange(orientation, 90) -> {
setOrientation90()
}
}
}
private fun isWithinOrientationRange(
currentOrientation: Int, targetOrientation: Int, epsilon: Int = 90
): Boolean {
return currentOrientation > targetOrientation - epsilon
&& currentOrientation < targetOrientation + epsilon
}
}
}
fun setOrientation270() {
motionGyroOrientation[0] = -1.0f
motionGyroOrientation[1] = 1.0f
motionGyroOrientation[2] = 1.0f
motionAcelOrientation[0] = 1.0f
motionAcelOrientation[1] = -1.0f
motionAcelOrientation[2] = -1.0f
}
fun setOrientation90() {
motionGyroOrientation[0] = 1.0f
motionGyroOrientation[1] = -1.0f
motionGyroOrientation[2] = 1.0f
motionAcelOrientation[0] = -1.0f
motionAcelOrientation[1] = 1.0f
motionAcelOrientation[2] = -1.0f
}
fun setControllerId(id: Int) {
controllerId = id
}
fun register() {
if (isRegistered)
return
gyro?.apply {
sensorManager.registerListener(
this@MotionSensorManager,
gyro,
SensorManager.SENSOR_DELAY_GAME
)
}
accelerometer?.apply {
sensorManager.registerListener(
this@MotionSensorManager,
accelerometer,
SensorManager.SENSOR_DELAY_GAME
)
}
isRegistered = true
}
fun unregister() {
sensorManager.unregisterListener(this)
isRegistered = false
if (controllerId != -1) {
KenjinxNative.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
KenjinxNative.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
}
}
override fun onSensorChanged(event: SensorEvent?) {
if (controllerId != -1)
if (isRegistered)
event?.apply {
when (sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
val x = motionAcelOrientation[0] * event.values[1]
val y = motionAcelOrientation[1] * event.values[0]
val z = motionAcelOrientation[2] * event.values[2]
KenjinxNative.inputSetAccelerometerData(
x,
y,
z,
controllerId
)
}
Sensor.TYPE_GYROSCOPE -> {
val x = motionGyroOrientation[0] * event.values[1]
val y = motionGyroOrientation[1] * event.values[0]
val z = motionGyroOrientation[2] * event.values[2]
KenjinxNative.inputSetGyroData(x, y, z, controllerId)
}
}
}
else {
KenjinxNative.inputSetAccelerometerData(0.0F, 0.0F, 0.0F, controllerId)
KenjinxNative.inputSetGyroData(0.0F, 0.0F, 0.0F, controllerId)
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// no-op
}
override fun onFlushCompleted(sensor: Sensor?) {
// no-op
}
}

Some files were not shown because too many files have changed in this diff Show More