Compare commits

..

23 Commits

Author SHA1 Message Date
xbzk 8a142b490f [core, video_core] Preserve GPU writes during NCE fault handling 2026-10-30 23:52:28 -03:00
PavelBARABANOV 943f1fc760 [gpu_thread] drop affinity pinning, fix BOTW perf regression (#4603)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4603
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-10-30 19:55:35 +01:00
crueter 072da77516 [android] Replace GitHub Icon with Forgejo icon in About page (#4535)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Authored-by: Nathantendo <204940629+Nathantendo@users.noreply.github.com>

Co-authored-by: Nathantendo <204940629+Nathantendo@users.noreply.github.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4535
Reviewed-by: lizzie <lizzie@eden-emu.dev>
2026-10-30 16:45:11 +01:00
PavelBARABANOV 035f4611cb [vk_rasterizer] Fix viewport Y flip for negative scale_y (#4558)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
credit: ReiKatari

Aggelos 1/2 viewport fix

https://github.com/ReiKatari/STORM_SWITCH/commit/ff269fbb49388c1b90cd97b944365629e0fcc164
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4558
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-30 05:05:24 +01:00
PavelBARABANOV ea92c081b7 [core] Speed up NCZBLOCK reads and skip double AES for pre-decrypted NCZ (#4582)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
- Use a 32-slot direct-mapped block cache and reuse one ZSTD_DCtx per
  BlockReader instead of re-creating it per block.
- Find the covering section with std::lower_bound instead of a linear scan
  (helps BKTR updates with thousands of sections).
- Mark BlockNczFile as HasDecryptedSections() and drop its CTR pass; NCZ
  body is stored decrypted, so NcaFileSystemDriver now skips its AES layers
  for these files (no double decryption).
- Recognize .xcz as XCZ in AppLoader_XCI::IdentifyType.

thanks Reikatari

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4582
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-10-30 04:40:15 +01:00
Soar Qin 69665063a9 [cheats] add runtime cheats toggles with master code support (#4334)
add per-cheat runtime toggles (thread-safe).
add master code handling (master code cannot be disabled, as noted in Atmosphere's implementation).
add configurable global / per-game initial states for cheat entries.
remove fixed opcode limits and do some fixes to text parsing / file loading.
do not run debug log while cheat logging is not enabled, to reduce performance impacts.
update the `Using Cheats` document to reflect the changes.

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4334
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: John <john@eden-emu.dev>
2026-10-30 03:47:38 +01:00
xbzk b4d5760014 [video_core] Revert 4490 and 4531 (#4584)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Reverting NCE Invalidation readbacks. Me and Maran had discussed the risk of this be costly without alternative before.
After 2 or 3 weeks, a new regression has shown up: Pavel's device gets slow as fuck in DKCR.

I've prolly tackled that one too, but i don't like the solution anymore.
I had to infiltrate so much stuff that the weight of be working on the symptom instead of the root cause has grown way too much.

I'm reverting so we can get a clean base, then i will research deeper so we can maybe find a more surgical yet wider fix.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4584
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Pavel Barabanov <pavelbarabanov94@gmail.com>
2026-10-30 03:26:33 +01:00
PavelBARABANOV 0731f40f4d [video_core/host1x] VP9 Video Playback Fixes (#4590)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
https://git.eden-emu.dev/eden-emu/eden/pulls/4542

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4590
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: John <john@eden-emu.dev>
2026-10-30 03:06:33 +01:00
PavelBARABANOV 3e6a5566cf [vk] handles unreachable unknown format 255 error (#4591)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
https://git.eden-emu.dev/eden-emu/eden/pulls/4529

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4591
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-10-30 02:47:10 +01:00
PavelBARABANOV d89e02d3ac [emit_spirv_image] remove the fix for LA/EOW (#4589)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
this is no longer needed; https://github.com/StevenMXZ/Adreno-Tools-Drivers/releases/tag/gamesir completely fixes these games

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4589
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-10-30 02:26:33 +01:00
crueter a2aa4d97e1 [docs] Remove AI restrictions, and adapt to new open registration (#4585)
Registration is now open. Enjoy

Signed-off-by: crueter <crueter@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4585
Reviewed-by: lizzie <lizzie@eden-emu.dev>
2026-10-29 17:10:37 +01:00
John 26a03d2473 Revert "[video_core] Improve asynchronous shader building (#2560)" (#4512)
Restores performance but need to check TOTK item icons and Jamboree graphic issues.

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4512
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:26:32 +01:00
xbzk b7c1ded159 [file_sys] added log for newer keys needed error (#4519)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
One log line which could have spared me from a lot of trouble.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4519
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:26:07 +01:00
John cbd4cf2d8c [hle][core] Hang Freeze Fix - Video Fixes - Revert Microsleep (#4316) (#4525)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Credit: amicuchu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

- Fixes Mario Strikers Intro Freezing from legacy.
- Microsleep no longer needed for Absolum Video freezing and other videos freezing.
- Revert `[service, nvhost] added machinery to allow microsleep between nvdec read requests to avoid guest panic in some games #4316`
- Tested with CPU/GPU decoding on Linux/Windows.
Special thanks to xbzk

<video src="/attachments/9f4a4494-78ee-4b65-ad26-628e83d5c83b" title="strikers" controls></video>

Co-authored-by: Your Name <John@John.localdomain>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4525
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:25:47 +01:00
PavelBARABANOV a3debcf731 [buffer_cashe] use old logic when Optimized Vertex Buffers is disabled (#4528)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Fixes vertex explosions on Turnip. Fixes Celeste on QCOM. Fixes new issues discovered after #3579

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4528
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:25:24 +01:00
xbzk 5239cbe2a7 [nce] handles duplicate NCE interrupts (#4530)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Another crash observed in Ender Magnolia: A deadlock caused between CPUCore_0/GPU/GPUFencingThread.

CPUCore_0 waits for GPU work inside the NCE fault handler
GPU waits for the syncpoint mutex
GPUFencingThread holds that mutex while waiting for CPUCore_0 NCE interrupt lock

The fix makes duplicate interrupts return before acquiring the lock, while the first request keeps its original behavior.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4530
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:24:45 +01:00
xbzk 0e11fe8b5e [video_core, settings] readback for nce invalidations (#4490) optimization and nuke toggles (#4502) (#4531)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Follow up for #4490: Originally i was racing locks between dirty and clean invalidations, but +94% were clean so it was causing useless locks. Solutioin was to share locks between both.

P.S.: THIS IS INTENDED TO FIX RANDOM CRASHES IN UE, OBJECTIVELY REPRODUCIBLE IN ENDER MAGNOLIA IN ANDROID.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4531
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:24:28 +01:00
xbzk 0689f73147 [ir] resolve conditional Phi inputs when tracking bindless texture handles (#4551)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
Aimed to fix Ender Magnolia missing shader textures. Main example is Yolvan character bullets.

By using windows version of the game, dxvk lib to make it vulkan, and renderdoc, it was possible to capture bullets, and by tracking backward composition and comparing with a similar scene from eden with missing bullets, it was possible to unveil the missing functionality: conditional Phi inputs resolve for bindless handles.
By ignoring the boolean predicate register in the shader, handles for these shaders were resolving to some old value instead of proper live conditional ones.

TESTS:
-Compare Yolvan bullets
-Usual roam on main titles for regression check.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4551
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:24:00 +01:00
wildcard a95efaf18f [vulkan] Fix TOTK dark squares (#4565)
Qualcomm and Turnip use different subpixel rounding for Vulkan texture
gather coordinates than the Switch's Maxwell GPU. This can select the
wrong texels near texture boundaries, producing large black squares in TOTK

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4565
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:23:45 +01:00
PavelBARABANOV 58968bd127 [core] Support block-compressed NSZ and XCZ (#4552)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
- Add NCZBLOCK (nsz -B / .xcz) reader: each read decodes only the covered blocks and re-encrypts CTR sections, no disk output.
- Allow installing solid .nsz to NAND via DecompressNCZForInstall (lazy, incremental decode with frontier, O(n) instead of O(n^2)).
- Accept BKTR updates with thousands of sections (MaxSectionCount = 0x10000).
- Wire .nsz/.xcz into loader, registered cache, scanner, and NAND install (content_manager.h), and reject solid NCZ from the game list.
- Bump the pinned zstd to 33618c8 in cpmfile.json, which includes the upstream AArch64 revert ([33618c89](https://github.com/facebook/zstd/commit/33618c89e511637f8a6ef955b3501766d71feb53)) for a ZSTD_decodeSequence branch optimization that could corrupt data on ARM64; without it, solid NCZ decoding on Android fails with "Data corruption detected".

NCZ format and CTR counter semantics follow the reference nsz tool and its
Rust port:
- https://github.com/nicoboss/nsz (Decompressor.py, BlockDecompressorReader.py)
- https://docs.rs/nsz-rs/0.3.0/src/nsz_rs/decompress.rs.html

The last-block edge case (decompressed size is an exact multiple of block
size) is handled as described in https://github.com/nicoboss/nsz/issues/210

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4552
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2026-10-26 17:23:18 +01:00
PavelBARABANOV 05f36d990c [chore] re-add out/ to .gitignore (#4578)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4578
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-10-26 16:23:23 +01:00
PavelBARABANOV eb88a40a8e [qt] fix graphics API toggle getting stuck on OpenGL_GLSL (#4577)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4577
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-10-25 16:06:46 +01:00
PavelBARABANOV 305f40100d Fix aarch and android builds (#4576)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
credit: John and Crueter

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4576
Reviewed-by: crueter <crueter@eden-emu.dev>
2026-10-25 10:03:16 +01:00
110 changed files with 9324 additions and 8203 deletions
-1
View File
@@ -1,5 +1,4 @@
- [ ] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [ ] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md).
- [ ] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.
-------------------
+1
View File
@@ -7,6 +7,7 @@
# Build directory
/[Bb]uild*/
doc-build/
out/
AppDir/
uruntime
+8 -31
View File
@@ -16,15 +16,14 @@ Eden is free, open-source, copyleft software, licensed under the terms of the [G
### Policies
- We are trialing a slightly relaxed AI policy to allow high-quality contributors to use AI *responsibly*. Please read our [detailed AI policy](docs/policies/AI.md) for more info.
- Usage of any form of profanity or otherwise unsavory language is generally discouraged.
- This is primarily because it rarely helps to actually understand what's going on.
- Remember that your comments should be focused and actually address what's happening. With very few exceptions, expletives are actively detrimental at best.
- New code must follow the same general style as the surrounding codebase. Exceptions may be granted in certain cases.
- AI usage is allowed, but we reserve the right to close and rework your PR if you don't fully understand the changes you're making.
- As a common courtesy, please disclose your AI usage under the PR description, as `Assisted-by: <AI model> (<how it helped>)`.
- Usage of any form of profanity or otherwise unsavory language is discouraged but not strictly disallowed.
- New code must follow the same style as the surrounding codebase. Exceptions may be granted in certain cases.
- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this.
- You should generally respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem.
- However, if you feel they are overstepping or are incorrect, don't be afraid to stand your ground! Maintainers are not always correct.
- While we do *not* adhere to the terms of a formal code of conduct, you will generally be expected to respect other developers, contributors, and community members.
- While we do *not* adhere to the terms of a formal code of conduct, you are expected to treat other developers, contributors, and community members respectfully.
- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity.
### Where do I start?
@@ -38,7 +37,8 @@ You may also search for open issues on [GitHub](https://github.com/eden-emulator
There are five primary ways to contribute.
- Submit pull requests directly to our source tree
- You must first request an account on our Forgejo. See [Signup](#how-do-i-sign-up)
- You must first [create an account](https://git.eden-emu.dev/user/sign_up).
- You may use an internal account, or you may use a GitHub/Codeberg account linked to our instance.
- Submit pull requests to our [GitHub mirror](https://github.com/eden-emulator/Issue-Reports)
- Note that this is subject to being removed by DMCA, so don't rely on this.
- Submit pull requests to our [Codeberg mirror](https://codeberg.org/eden-emu/eden)
@@ -50,31 +50,8 @@ There are five primary ways to contribute.
To test your changes, ensure to read the [build documentation](./docs/Build.md) for your specific platform(s) to ensure everything compiles and works properly. You should also make sure that your branch is up-to-date with the upstream `master` branch before opening a pull request.
### How do I sign up?
<details>
<summary>To sign up and begin contributing... (click to open)</summary>
Email [crueter@crueter.xyz](mailto:crueter@crueter.xyz) with the following format:
```txt
Subject: [Eden Git] Registration Request
Username: <Your Desired Username>
Email: <Your Desired Email>
I wish to sign up because... <your reason here>
```
Received mail that does not follow this format will be ignored.
Once your request is processed, you will receive a confirmation email with your temporary password and some information on Git access and policies. If you do not receive a response within 48 hours, feel free to send another email.
> [!WARNING]
> Some email providers may place the response email in your spam/junk folder; notable offenders include Gmail and Outlook. *Always* ensure to check your Spam or Junk folder.
</details>
## Non-code Contributions
Alongside the other contribution methods listed up top, you can also choose to contribute through documentation, organization, or community guides. These can be done either through the code contribution methods described above, or created externally and shared via our Discord community.
If you have an external tool/page that you believe would be handy to integrate/link into Eden, please additionally email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev). **Do not submit vibe-coded or AI generated tools or applications**.
If you have an external tool/page that you believe would be handy to integrate/link into Eden, please additionally email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev).
+2 -2
View File
@@ -351,13 +351,13 @@
},
"zstd": {
"find_args": "MODULE",
"hash": "cc5ad4b119a9c2ea57f0b71eeff01113bb506e0d17000159c5409cb8236d22e38c52d5e9e97e7947a4bf1b2dfc44b6c503ab2d9aedbd59458435c6a2849cb029",
"hash": "2b006c3c110c38bd2f5896b42d36a38931c8eae261f30552f9a9fd04458d218d8f1a1425f117fd12af545ed77042e4b2d1a1a9b6286c4d0b9d74ef79f20a8f08",
"min_version": "1.5",
"options": [
"ZSTD_BUILD_SHARED OFF"
],
"repo": "facebook/zstd",
"source_subdir": "build/cmake",
"version": "b8d6101fba"
"version": "33618c8"
}
}
+293 -283
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+340 -335
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
+288 -278
View File
File diff suppressed because it is too large Load Diff
-203
View File
@@ -1,203 +0,0 @@
# AI Policy
> [!WARNING]
> We are trialing a slightly relaxed AI policy to allow high-quality contributors to use AI *responsibly*.
> This policy is subject to change at any point in time, so check back frequently.
>
> This banner will be removed once we have finalized our revised policy.
- [AI Policy](#ai-policy)
- [TL;DR](#tldr)
- [Definitions](#definitions)
- [Scope](#scope)
- [Introduction](#introduction)
- [Requirements](#requirements)
- [Sign-off, Authorship, and Copyright](#sign-off-authorship-and-copyright)
- [Permitted Usage](#permitted-usage)
- [Recommended Prior Discussion](#recommended-prior-discussion)
- [Forbidden Usage](#forbidden-usage)
- [Human Effort Denial of Service](#human-effort-denial-of-service)
- [Disclosure](#disclosure)
- [Codebase Ingestion](#codebase-ingestion)
- [Enforcement](#enforcement)
- [Addendum: AI Commit Messages](#addendum-ai-commit-messages)
- [References](#references)
## TL;DR
AI-assisted contributions are welcome, provided you take responsibility for them.
- You **must** disclose any AI or LLM usage in your pull request description *and* commit message.
- You **must** be able to explain, defend, and revise every line of code you submit.
- Commit messages, PR descriptions, issues, and comments **must** be written by you. Verbatim AI output is not acceptable.
- Undisclosed AI usage will result in your contribution being rejected.
- Low-quality contributions, disclosed or not, will be reworked or rejected.
- Spamming automated PRs, comments, or issues is treated as a Denial of Service (DoS) attack on our reviewers; see [Human Effort Denial of Service](#human-effort-denial-of-service).
## Definitions
An *AI or LLM-based tool* is any piece of software that generates code, text, or other content through machine learning models. This includes, but is not limited to: chat assistants, agentic coding tools/harnesses, inline code completion suggestions you have accepted into the code, and AI-driven explanation/refactoring features.
For the purposes of this policy, basic deterministic/static development tools (such as clang-tidy, git, compilers, etc.) are not considered AI tools.
Terms such as "substantially", "large-scale", "majority", and "low-quality" are intentionally left vague, and will be interpreted at the maintainers' discretion. When in doubt, ask first.
## Scope
This policy applies to all code contributions, alongside issues and pull requests surrounding this codebase. This includes:
- Commit messages
- PR descriptions
- Code review comments
- Issue/PR comments
- Patches submitted via e-mail
Areas that are not directly related to code--such as our surrounding communities--are not covered by the terms of this policy. Additionally, spelling/grammar checking via AI or LLM-based tools is not covered under this document.
## Introduction
In the right hands, AI and LLM-based tools are incredibly useful for getting features and bugfixes out the door as fast as possible. At the same time, reviewer bandwidth is a scarce resource. We are therefore open to AI-assisted contributions, provided they are produced and disclosed properly.
The goal of this document is *not* to set arbitrary restrictions, but rather to keep our development healthy and stable, while still allowing contributors to use the tools they find helpful. A contribution must be worth more to the project than the time it takes to review it.
Throughout this document, **must** and **must not** are inflexible, binding requirements. Refusal to follow these is treated as a violation of the terms outlined in this document.
## Requirements
These apply to all contributions, whether AI-assisted or not. AI-assisted contributions also carry additional disclosure requirements as outlined in [Disclosure](#disclosure).
1. *Disclosure*. Any and all usage of AI or LLM-based tools within any contribution **must** be explicitly marked and disclosed as appropriate.
2. *Responsibility*. The human submitter of any contribution is held wholly responsible for the accuracy, completeness, and intent of what is submitted. You **must** be able to explain, defend, and revise your code during review.
3. *Review*. All code **must** be thoroughly reviewed by the author before opening a PR or marking it ready for review.
4. *Testing*. All code changes **must** be tested directly by the human author.
5. *Quality*. All submitted code is held to a strict standard of excellence. Low-quality AI-generated content does not uphold this standard.
6. *Licensing*. All code **must** be compatible with the GPLv3 or later. You **must** warrant that you have the right to submit it under those terms, and that it was not copied from work published under an incompatible license.
## Sign-off, Authorship, and Copyright
AI agents **must not** add `Signed-off-by:` tags. The human submitter **must** sign off on all changes.
Similarly, AI agents cannot hold authorship or copyright over code, and **must not** add `Co-authored-by:` or `Co-developed-by:` tags. The human submitter **must** author all changes.
## Permitted Usage
This is a non-exhaustive list of permitted uses of AI and LLM-based tools, provided they are disclosed properly.
- Drafting issues and pull requests, provided the final text and submitted code are substantially your own.
- Debugging issues, bugs, glitches, and crashes, whether pre-existing or introduced by your own changes.
- Reviewing your own changes before submitting a PR.
- Research, brainstorming, and codebase searching/understanding.
- Applying tedious but not particularly large changes, such as mass-migrating specific classes to polyfills or abstractions.
## Recommended Prior Discussion
Some usage cases are not necessarily forbidden nor permitted. These should be discussed with maintainers in an issue or e-mail *before* submission. Maintainers and reviewers may also comment on this during the review/submission phase.
- Generating boilerplate/scaffolding for new code.
- Writing unit tests.
- Large-scale refactors primarily driven by an AI agent.
- Any contribution where an AI agent or model was responsible for the majority of the implementation.
## Forbidden Usage
This is a non-exhaustive list of strictly forbidden uses of AI and LLM-based tools.
- Vibecoding, or changes entirely authored by AI agents without human intervention or understanding.
- If you don't choose to put in the necessary effort to understand every line of code you submitted, neither will we.
- Fully AI-generated commit messages, PR descriptions, issues, or comments. See [Addendum: AI Commit Messages](#addendum-ai-commit-messages).
- Submission of AI-assisted work that is not disclosed per the [Disclosure](#disclosure) requirements.
- Low-quality submissions that fail to satisfy our standard of code excellence.
- Submitting secrets, credentials, private user data, or unpublished security reports to third-party AI services.
- This includes console keys, encryption material, and private vulnerability disclosures.
- Verbatim AI code reviews (e.g. Codex/Copilot reviews).
## Human Effort Denial of Service
Using automated tools and AI to submit PRs, issues, and comments that we have to carefully review and handle is considered a [Denial-of-service attack](https://en.wikipedia.org/wiki/Denial-of-service_attack) on our human effort.
It takes very little effort on your side (an LLM prompt) to generate a large amount of effort on ours (careful and thorough code review). Please respect our time and don't do that.
## Disclosure
How you disclose AI usage is up to you. However, it **must** be explicit, easy to identify, and placed both within the commit message *and* the surrounding PR description. For patches submitted by e-mail, the cover letter counts as the PR description. We recommend an `Assisted-by:` footer:
```txt
Assisted-by: <model> (<what it was used for>)
```
For instance:
```txt
Assisted-by: Claude Opus 5.5 (initial draft and code review)
```
Contributions without a commit message (e.g. issues/comments) should instead use a plain sentence in the text body such as `I used <model> to help with <X>`. PRs and commit messages may opt to use this format as well.
Multi-commit series/squashes **must** maintain the trailer. Maintainers will handle this for merges to master, but you are responsible for ensuring the disclosure survives into the final commit/PR body of squashes within your branches.
Regardless, what matters is that reviewers can tell *at a glance* where and how AI was involved in your contribution.
## Codebase Ingestion
Ingestion of this codebase by LLMs for any purpose--such as training or prompt context--is permitted *for now*. This may change at any point as litigation and court rulings around AI and code copyright proceed.
AI models may have ingested AGPLv3 or otherwise copyleft-incompatible code and may regurgitate it. You remain responsible for ensuring that all code you submit is compatible with the GPLv3 or later.
## Enforcement
- If we suspect undisclosed AI usage in a contribution, we will ask for disclosure.
- Refusal to provide disclosure will result in rejection of the contribution, and your PR or issue will be closed. Maintainers may choose to rework it themselves if the change is worth having.
- Accounts that spam us with repeated automated PRs or comments will be blocked or permanently suspended from our Git instance.
- Maintainers reserve the right to close your issues and pull requests if you don't follow the guidelines and procedures outlined in this document, and refuse or fail to make changes as necessary to comply.
- Repeated failures to comply may result in loss of contribution privileges.
## Addendum: AI Commit Messages
The patchset for pull request [#3422](https://git.eden-emu.dev/eden-emu/eden/pulls/3422) was fed into a dedicated code AI model, and it produced the following:
```txt
profile_manager: Refactor user deletion to use index instead of UUID
This commit improves the user profile deletion flow in the following ways:
1. **UI layer (ConfigureProfileManager)**:
- Replaces UUID-based deletion with index-based deletion
- Passes the selected row index from the tree view to the confirmation dialog
- Emits deleteUser(int index) signal from ConfigureProfileManagerDeleteDialog
- Removes lambda capture and std::function callback in favor of direct signal/slot connection
2. **Delete dialog**:
- Changes SetInfo() signature to accept index instead of accept callback
- Stores index internally and emits it via deleteUser signal on acceptance
3. **Core ProfileManager**:
- Refactors RemoveUser(UUID) → RemoveProfileAtIndex(index)
- Removes duplicated stable_partition + save logic from RemoveUser()
- Centralizes profile removal and save-file writing in RemoveProfileAtIndex()
4. **Minor follow-up**:
- Adds profile_manager.ResetUserSaveFile() call in PopulateUserList() to ensure consistent state when refreshing the user list
These changes eliminate redundant code, reduce the risk of UUID → index mismatch bugs, and make the deletion path more straightforward and maintainable.
Fixes: potential inconsistency when user order changes between UI and backend
```
This:
- Is needlessly verbose (nobody cares about most of these details)
- Doesn't address the actual purpose of the PR (fixing a double-deletion bug in the profile manager)
- Has unicode arrows (this is bad for command-line editors)
- Uses corporate and word-salad language that pads out the message to make it look like a lot changed
A good commit message should be concise, descriptive, and convey the *intent* behind the change, such as the bug being fixed or the feature being added. It should *not* be a detailed changelog of every individual byte that's changed.
## References
This policy document has been adapted from the AI policies of numerous other free and open-source projects, whether restrictive or permissive.
- [Gitea](https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md#ai-contribution-policy)
- The [Linux kernel](https://kernel.org/doc/html/next/process/coding-assistants.html)
- [Gentoo Linux](https://wiki.gentoo.org/wiki/Project:Council/AI_policy)
- [cURL](https://curl.se/dev/contribute.html#on-ai-use-in-curl)
- [FastAPI](https://tiangolo.com/open-source/contributing/#automated-code-and-ai)
+13 -4
View File
@@ -45,12 +45,13 @@ Another option would be to launch the game in Eden and close it - then go into t
***INFO***: Make sure you select the correct directory or it will not work, the screen should show the **cheats** sub-directory.
</aside>
2. You should now see the cheat appear in the **Add-ons** screen.
3. Launch the game and confirm that the cheat is applied.
9. You should now see the cheat appear in the **Add-ons** screen.
10. Launch the game and confirm that the cheat is applied.
### Step 2: Multiple Cheats
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory.
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
@@ -122,7 +123,7 @@ Another option would be to launch the game in Eden and close it - then go into t
### Step 2: Multiple Cheats
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory. You **cannot** install multiple cheats with a single file.
In order to install multiple cheats, you must repeat the steps above with the new cheat, creating a new directory with the name of the cheat and cheats directory.
Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Windows or anything that can run Python) that can take a file with multiple cheats and create the files/structure for you with a provided Build ID. To download and run it, see the [GitHub Project](https://github.com/Ninjistix/nxCheat_Splitter) page.
@@ -154,3 +155,11 @@ Cheats are enabled by default, but can be disabled so they don't affect gameplay
3. *Select/Deselect* the name of the cheat you wish to enable/disable.
4. Click **OK** to close the window.
5. Launch the game to confirm the cheat is/is not active.
### Runtime Cheat Controls
* While a game is running, open **Cheats** from the menu bar to toggle individual entries from the loaded cheat files. The menu stays open while toggling entries and is hidden when the running game has no cheats. Runtime changes apply to the current game session only.
* Use **Configure > System > Core > Enable cheats by default** to choose whether regular cheats start enabled. The setting can also be overridden for an individual game from its **System** settings.
* Master Code entries (`{}` blocks) are always enabled and appear as disabled and checked items in the runtime menu.
+1 -1
View File
@@ -259,7 +259,7 @@ android {
externalNativeBuild {
cmake {
version = "3.31.6"
version = "3.31.5"
path = file("${edenDir}/CMakeLists.txt")
}
}
@@ -34,6 +34,13 @@ import org.yuzu.yuzu_emu.applets.web.WebBrowser
* with the native side of the Yuzu code.
*/
object NativeLibrary {
@Keep
class RuntimeCheat(
val id: Int,
val name: String,
val enabled: Boolean,
val isMaster: Boolean
)
@Keep
data class UpdateResult(
var tag: String = "",
@@ -197,6 +204,9 @@ object NativeLibrary {
*/
external fun isPaused(): Boolean
external fun getRuntimeCheats(): Array<RuntimeCheat>
external fun setRuntimeCheatEnabled(id: Int, enabled: Boolean): Boolean
/**
* Returns the performance stats for the current game
*/
@@ -13,10 +13,10 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
FASTMEM("cpuopt_fastmem"),
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"),
CORE_SYNC_CORE_SPEED("sync_core_speed"),
ENABLE_CHEATS_BY_DEFAULT("enable_cheats_by_default"),
RENDERER_USE_SPEED_LIMIT("use_speed_limit"),
USE_CUSTOM_CPU_TICKS("use_custom_cpu_ticks"),
SKIP_CPU_INNER_INVALIDATION("skip_cpu_inner_invalidation"),
NCE_INVALIDATION_GPU_READBACK("nce_invalidation_gpu_readback"),
FIX_BLOOM_EFFECTS("fix_bloom_effects"),
EMULATE_BGR565("emulate_bgr565"),
RESCALE_HACK("rescale_hack"),
@@ -316,6 +316,13 @@ abstract class SettingsItem(
descriptionId = R.string.use_sync_core_description
)
)
put(
SwitchSetting(
BooleanSetting.ENABLE_CHEATS_BY_DEFAULT,
titleId = R.string.enable_cheats_by_default,
descriptionId = R.string.enable_cheats_by_default_description
)
)
put(
SingleChoiceSetting(
IntSetting.REGION_INDEX,
@@ -859,13 +866,6 @@ abstract class SettingsItem(
descriptionId = R.string.skip_cpu_inner_invalidation_description
)
)
put(
SwitchSetting(
BooleanSetting.NCE_INVALIDATION_GPU_READBACK,
titleId = R.string.nce_invalidation_gpu_readback,
descriptionId = R.string.nce_invalidation_gpu_readback_description
)
)
put(
SwitchSetting(
BooleanSetting.FIX_BLOOM_EFFECTS,
@@ -510,6 +510,7 @@ class SettingsFragmentPresenter(
add(IntSetting.FAST_CPU_TIME.key)
add(IntSetting.FAST_GPU_TIME.key)
add(BooleanSetting.CORE_SYNC_CORE_SPEED.key)
add(BooleanSetting.ENABLE_CHEATS_BY_DEFAULT.key)
add(IntSetting.MEMORY_LAYOUT.key)
add(BooleanSetting.USE_CUSTOM_CPU_TICKS.key)
@@ -554,7 +555,6 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.hacks))
add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key)
add(BooleanSetting.NCE_INVALIDATION_GPU_READBACK.key)
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
@@ -734,6 +734,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
binding.pausedIcon.setOnClickListener {
if (this::emulationState.isInitialized && emulationState.isPaused) {
@@ -817,6 +818,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_load_amiibo -> handleLoadAmiiboSelection()
R.id.menu_cheats -> {
binding.drawerLayout.closeDrawer(binding.inGameMenu)
showRuntimeCheatDialog()
true
}
R.id.menu_controls -> {
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
null,
@@ -962,6 +969,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
emulationState.updateSurface()
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible =
NativeLibrary.getRuntimeCheats().isNotEmpty()
updateShowStatsOverlay()
updateSocOverlay()
@@ -987,6 +996,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
if (it) {
binding.inGameMenu.menu.findItem(R.id.menu_cheats)?.isVisible = false
binding.loadingText.setText(R.string.shutting_down)
ViewUtils.showView(binding.loadingIndicator)
ViewUtils.hideView(binding.inputContainer)
@@ -1119,6 +1129,45 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
NativePostProcessing.persist()
}
private fun showRuntimeCheatDialog() {
val cheats = NativeLibrary.getRuntimeCheats()
if (cheats.isEmpty()) return
val names = cheats.map { it.name.ifEmpty { getString(R.string.unnamed_cheat) } }.toTypedArray()
lateinit var dialog: androidx.appcompat.app.AlertDialog
dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.cheats)
.setMultiChoiceItems(names, cheats.map { it.enabled }.toBooleanArray()) { _, which, enabled ->
if (cheats[which].isMaster) {
dialog.listView.setItemChecked(which, true)
} else if (!NativeLibrary.setRuntimeCheatEnabled(cheats[which].id, enabled)) {
dialog.listView.setItemChecked(which, !enabled)
}
}
.setNegativeButton(R.string.disable_all_cheats) { _, _ ->
cheats.forEach {
if (!it.isMaster) NativeLibrary.setRuntimeCheatEnabled(it.id, false)
}
}
.setNeutralButton(R.string.enable_all_cheats) { _, _ ->
cheats.forEach {
if (!it.isMaster) NativeLibrary.setRuntimeCheatEnabled(it.id, true)
}
}
.setPositiveButton(R.string.done, null)
.create()
dialog.setOnShowListener {
val listView = dialog.listView
fun updateMasterVisuals() = repeat(listView.childCount) { index ->
listView.getChildAt(index).alpha =
if (cheats[listView.firstVisiblePosition + index].isMaster) 0.38f else 1f
}
listView.setOnScrollChangeListener { _, _, _, _, _ -> updateMasterVisuals() }
updateMasterVisuals()
}
dialog.show()
}
private fun addQuickSettings() {
binding.quickSettingsSheet.apply {
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
@@ -100,7 +100,7 @@ class Game(
companion object {
val extensions: Set<String> = HashSet(
listOf("xci", "nsp", "nca", "nro")
listOf("xci", "nsp", "nca", "nro", "nsz", "xcz")
)
}
}
@@ -109,7 +109,7 @@ object GameHelper {
// File extensions considered as external content, buuut should
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
private val externalContentExtensions = setOf("nsp", "xci", "nsz", "xcz")
private fun scanContentContainersRecursive(
files: Array<MinimalDocumentFile>,
@@ -76,7 +76,9 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
auto const file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error)
return false;
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && !Loader::IsBootableGameContainer(file, file_type))
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI ||
file_type == Loader::FileType::NSZ || file_type == Loader::FileType::XCZ) &&
!Loader::IsBootableGameContainer(file, file_type))
return false;
u64 program_id = 0;
return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success;
+26
View File
@@ -67,6 +67,7 @@ extern "C" {
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_real.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/frontend/applets/cabinet.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/error.h"
@@ -946,6 +947,31 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
}
jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getRuntimeCheats(JNIEnv* env, jclass clazz) {
const auto cheats = EmulationSession::GetInstance().System().GetRuntimeCheats();
const auto runtime_cheat_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary$RuntimeCheat");
const auto constructor = env->GetMethodID(runtime_cheat_class, "<init>", "(ILjava/lang/String;ZZ)V");
auto result = env->NewObjectArray(static_cast<jsize>(cheats.size()), runtime_cheat_class, nullptr);
for (jsize i = 0; i < static_cast<jsize>(cheats.size()); ++i) {
const auto& cheat = cheats[i];
const auto name = Common::Android::ToJString(env, cheat.name);
const auto item = env->NewObject(runtime_cheat_class, constructor, static_cast<jint>(cheat.id), name,
static_cast<jboolean>(cheat.enabled),
static_cast<jboolean>(cheat.is_master));
env->SetObjectArrayElement(result, i, item);
env->DeleteLocalRef(name);
env->DeleteLocalRef(item);
}
env->DeleteLocalRef(runtime_cheat_class);
return result;
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setRuntimeCheatEnabled(JNIEnv* env, jclass clazz,
jint id, jboolean enabled) {
return static_cast<jboolean>(EmulationSession::GetInstance().System().SetCheatEnabled(
static_cast<u32>(id), static_cast<bool>(enabled)));
}
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
using namespace VideoCore::Capture;
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="212"
android:viewportHeight="212"
android:width="212dp"
android:height="212dp">
<group
android:translateX="6"
android:translateY="6">
<path
android:pathData="M58 168v-98a50 50 0 0 1 50 -50h20"
android:strokeColor="#FFFFFF"
android:strokeWidth="25" />
<path
android:pathData="M58 168v-30a50 50 0 0 1 50 -50h20"
android:strokeColor="#FFFFFF"
android:strokeWidth="25" />
<path
android:pathData="M160 20A18 18 0 0 1 124 20A18 18 0 0 1 160 20Z"
android:strokeColor="#FFFFFF"
android:strokeWidth="15" />
<path
android:pathData="M160 88A18 18 0 0 1 124 88A18 18 0 0 1 160 88Z"
android:strokeColor="#FFFFFF"
android:strokeWidth="15" />
<path
android:pathData="M76 180A18 18 0 0 1 40 180A18 18 0 0 1 76 180Z"
android:strokeColor="#FFFFFF"
android:strokeWidth="15" />
</group>
</vector>
@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="98dp"
android:height="96dp"
android:viewportWidth="98"
android:viewportHeight="96">
<path
android:fillColor="?attr/colorControlNormal"
android:fillType="evenOdd"
android:pathData="M48.854,0C21.839,0 0,22 0,49.217c0,21.756 13.993,40.172 33.405,46.69 2.427,0.49 3.316,-1.059 3.316,-2.362 0,-1.141 -0.08,-5.052 -0.08,-9.127 -13.59,2.934 -16.42,-5.867 -16.42,-5.867 -2.184,-5.704 -5.42,-7.17 -5.42,-7.17 -4.448,-3.015 0.324,-3.015 0.324,-3.015 4.934,0.326 7.523,5.052 7.523,5.052 4.367,7.496 11.404,5.378 14.235,4.074 0.404,-3.178 1.699,-5.378 3.074,-6.6 -10.839,-1.141 -22.243,-5.378 -22.243,-24.283 0,-5.378 1.94,-9.778 5.014,-13.2 -0.485,-1.222 -2.184,-6.275 0.486,-13.038 0,0 4.125,-1.304 13.426,5.052a46.97,46.97 0,0 1,12.214 -1.63c4.125,0 8.33,0.571 12.213,1.63 9.302,-6.356 13.427,-5.052 13.427,-5.052 2.67,6.763 0.97,11.816 0.485,13.038 3.155,3.422 5.015,7.822 5.015,13.2 0,18.905 -11.404,23.06 -22.324,24.283 1.78,1.548 3.316,4.481 3.316,9.126 0,6.6 -0.08,11.897 -0.08,13.526 0,1.304 0.89,2.853 3.316,2.364 19.412,-6.52 33.405,-24.935 33.405,-46.691C97.707,22 75.788,0 48.854,0z" />
</vector>
@@ -250,7 +250,7 @@
style="@style/EdenButton.Secondary"
android:layout_width="56dp"
android:layout_height="56dp"
app:icon="@drawable/ic_github"
app:icon="@drawable/ic_forgejo"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconPadding="0dp" />
@@ -259,7 +259,7 @@
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
app:icon="@drawable/ic_github"
app:icon="@drawable/ic_forgejo"
app:iconSize="24dp"
app:iconGravity="textStart"
app:iconPadding="0dp"
@@ -43,6 +43,10 @@
android:icon="@drawable/ic_nfc"
android:title="@string/load_amiibo" />
<item
android:id="@+id/menu_cheats"
android:icon="@drawable/ic_code"
android:title="@string/cheats" />
<item
android:id="@+id/menu_overlay_controls"
@@ -587,8 +587,6 @@
<string name="fast_gpu_time_description">يجعل اللعبة تعتقد أن عمل وحدة معالجة الرسومات ينتهي بشكل أسرع مما هو عليه في الواقع، لذلك تتوقف عن خفض الدقة ومسافة العرض لتتناسب مع سرعات جهاز Switch.</string>
<string name="skip_cpu_inner_invalidation">تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية</string>
<string name="skip_cpu_inner_invalidation_description">يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب.</string>
<string name="nce_invalidation_gpu_readback">إعادة قراءة وحدة معالجة الرسومات قبل إبطال صلاحية NCE</string>
<string name="nce_invalidation_gpu_readback_description">إصلاح مؤقت. يعالج مشكلة تعطل لعبة Ender Magnolia (وغيرها من ألعاب UE). قد يؤدي إلى انخفاض في الأداء. يجري العمل على تحسينه.</string>
<string name="fix_bloom_effects">إصلاح تأثيرات التوهج</string>
<string name="fix_bloom_effects_description">يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى.</string>
<string name="emulate_bgr565">محاكاة BGR565</string>
@@ -696,7 +694,7 @@
<string name="control_stick">عصا التحكم</string>
<string name="right_stick">العصا اليمنى</string>
<string name="c_stick">عصا-C</string>
<string name="pressed">ضغط العصا</string>
<string name="pressed">مضغوط</string>
<string name="range">النطاق</string>
<string name="deadzone">المنطقة الميتة</string>
<string name="modifier">معدل</string>
@@ -36,7 +36,7 @@
<string name="input_overlay_options">虛擬按鍵設定</string>
<string name="input_overlay_options_description">調整虛擬按鍵的位置與顯示設定</string>
<string name="edit_overlay_layout">調整虛擬按鍵</string>
<string name="edit_overlay_layout">編輯虛擬按鍵</string>
<string name="edit_overlay_layout_description">調整虛擬按鍵的位置和大小</string>
@@ -253,8 +253,8 @@
<string name="install_firmware_warning">跳過安裝韌體?</string>
<string name="emulator_data">設定模擬器資料</string>
<string name="emulator_data_description">模擬器需要金鑰才能正常執行,同時建議安裝韌體以啟動 QLaunch 小程式</string>
<string name="permissions">授予通知權限</string>
<string name="permissions_description">授予 Eden 傳送通知的權限以使用模擬器的特定功能</string>
<string name="permissions">授予權限</string>
<string name="permissions_description">授予權限以使用模擬器的特定功能</string>
<string name="install_firmware_warning_description">許多遊戲需要韌體才能正常運作</string>
<string name="install_firmware_warning_help">https://yuzu-mirror.github.io/help/quickstart/#guide-introduction</string>
<string name="notifications">通知</string>
@@ -268,8 +268,8 @@
<string name="device_manufacturer">製造商</string>
<string name="device_model">型號</string>
<string name="product">型號代碼</string>
<string name="android_version">Android 版本</string>
<string name="android_security_patch">Android 安全性修補程式等級</string>
<string name="android_version">安卓版本</string>
<string name="android_security_patch">安卓安全性修補程式等級</string>
<string name="build_id">版本號碼</string>
<string name="general_information">基本資訊</string>
<string name="hardware">硬體</string>
@@ -287,7 +287,7 @@
<string name="warning_skip">跳過</string>
<string name="warning_cancel">取消</string>
<string name="install_amiibo_keys">安裝 Amiibo 金鑰</string>
<string name="install_amiibo_keys_description">安裝 Amiibo 以在日後遊玩時使用</string>
<string name="install_amiibo_keys_description">需要先安裝才能在遊戲中使用 Amiibo</string>
<string name="gpu_driver_fetcher">GPU 驅動程式下載器</string>
<string name="gpu_driver_manager">GPU 驅動程式管理員</string>
<string name="install_gpu_driver_description">安裝替代驅動程式以取得潛在的更佳效能或準確度</string>
@@ -417,17 +417,17 @@
<string name="custom_driver_not_supported">您的裝置不支援自訂驅動程式</string>
<string name="custom_driver_not_supported_description">此裝置不支援注入自訂驅動程式\n請以後再來查看是否已新增支援!</string>
<string name="manage_yuzu_data">管理 Eden 資料</string>
<string name="manage_yuzu_data_description">安裝韌體、金鑰,匯入/匯出使用者資料及安裝其它項目!</string>
<string name="manage_yuzu_data_description">安裝韌體、金鑰,導入/導出使用者資料及安裝其它項目!</string>
<string name="game_folders">遊戲資料夾</string>
<string name="deep_scan">深度掃描</string>
<string name="add_game_folder">新增遊戲資料夾</string>
<string name="folder_already_added">這個資料夾已經新增過了!</string>
<string name="game_folder_properties">遊戲資料夾設定</string>
<plurals name="saves_import_failed">
<item quantity="other">%d 個存檔匯入失敗</item>
<item quantity="other">%d 個存檔導入失敗</item>
</plurals>
<plurals name="saves_import_success">
<item quantity="other">成功匯入 %d 個存檔</item>
<item quantity="other">成功導入 %d 個存檔</item>
</plurals>
<string name="no_save_data_found">找不到儲存資料</string>
<string name="verify_installed_content">驗證已安裝內容的完整性</string>
@@ -577,10 +577,8 @@
<string name="fast_gpu_time_description">讓遊戲誤以為 GPU 工作能更快完成,避免遊戲為了配合 Switch 的時脈而降低解析度與渲染距離</string>
<string name="skip_cpu_inner_invalidation">跳過 CPU 內部失效處理</string>
<string name="skip_cpu_inner_invalidation_description">在記憶體更新期間跳過某些 CPU 端快取的失效處理,減少 CPU 使用率並提高其性能。可能會導致某些遊戲出現故障或當機</string>
<string name="nce_invalidation_gpu_readback">在 NCE 無效化前進行 GPU 回讀</string>
<string name="nce_invalidation_gpu_readback_description">(暫時性功能)修復《終結者瑪格諾莉亞:霧中綻放》及其它使用虛幻引擎之遊戲中的當機問題,可能會降低效能。此功能正在改善中</string>
<string name="fix_bloom_effects">修正光暈特效</string>
<string name="fix_bloom_effects_description">在《薩爾達傳說:織夢島》和《薩爾達傳說:智慧的再現》中減少光暈模糊的問題並移除《橫衝直撞:狂飆樂園重製版》中的光暈效果(適用於 Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
<string name="fix_bloom_effects_description">在 \"薩爾達傳說:織夢島\" 和 \"薩爾達傳說:智慧的再現\" 中減少光暈模糊的問題並移除 \"橫衝直撞:狂飆樂園重製版\" 中的光暈效果(適用於 Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常</string>
<string name="emulate_bgr565">模擬 BGR565</string>
<string name="emulate_bgr565_description">修正遊戲中出現的色彩反轉、圖形異常或陰影異常</string>
<string name="rescale_hack">啟用舊版重新縮放處理</string>
@@ -603,7 +601,7 @@
<string name="extensions">擴充功能</string>
<string name="dyna_state">擴展動態狀態</string>
<string name="dyna_state_description">控制 Vulkan 擴展動態狀態中可使用的功能數量,較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數</string>
<string name="dyna_state_description">控制擴展動態狀態中可使用的功能數量,較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數</string>
<string name="disabled">停用</string>
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
<string name="vertex_input_dynamic_state_description">啟用此功能可提供更彈性的頂點輸入處理方式,並可能減少頂點/緩衝區的管線編譯時間</string>
@@ -808,7 +806,7 @@
<string name="recommended_driver">推薦驅動程式:</string>
<string name="gpu_model">GPU 型號</string>
<string name="unsupported_gpu">不支援的 GPU</string>
<string name="unsupported_gpu_warning">您的 GPU 不支援驅動程式注入,不建議嘗試安裝自訂驅動程式</string>
<string name="unsupported_gpu_warning">您的 GPU 不支援驅動程式注入,不建議嘗試設定自訂驅動程式</string>
<!-- Preferences Screen -->
<string name="preferences_settings">設定</string>
@@ -892,7 +890,7 @@
<string name="select_content_type">內容類型</string>
<string name="updates_and_dlc">更新及 DLC</string>
<string name="mods_and_cheats">模組及金手指</string>
<string name="addon_notice">注意!</string>
<string name="addon_notice">重要的˙附加元件通知</string>
<!-- \"cheats/" "romfs/" and \"exefs/ should not be translated -->
<string name="addon_notice_description">若要安裝模組及金手指,您必須選擇一個包含 cheats/、romfs/ 或 exefs/ 的目錄。我們無法驗證這些內容是否與您的遊戲相容,所以請謹慎安裝!</string>
<string name="invalid_directory">無效的目錄</string>
@@ -981,7 +979,7 @@
<string name="emulation_pause">暫停模擬</string>
<string name="emulation_unpause">繼續模擬</string>
<string name="emulation_input_overlay">疊加層/虛擬按鍵設定</string>
<string name="load_amiibo">匯入 Amiibo</string>
<string name="load_amiibo">導入 Amiibo</string>
<string name="touchscreen">觸控螢幕</string>
<string name="lock_drawer">鎖定側邊選單</string>
<string name="unlock_drawer">解鎖側邊選單</string>
@@ -1128,16 +1126,6 @@
<string name="freedreno_clear_all">全部清除</string>
<string name="freedreno_saved">Freedreno 設定已儲存</string>
<string name="freedreno_cleared_all">已清除所有 Freedreno 變數</string>
<string name="freedreno_variable_added">變數 %1$s 已新增</string>
<string name="freedreno_preset_applied">設定檔 \"%1$s\" 已套用</string>
<string name="freedreno_error_empty_name">變數名稱不可空白</string>
<string name="freedreno_error_setting_variable">變數設定失敗</string>
<string name="freedreno_info_title">關於 Freedreno 設定</string>
<string name="freedreno_info_description">透過調整 Freedreno/Turnip GPU 驅動程式的設定來偵錯、分析和最佳化效能,會自動儲存變更。詳情請參閱 https://docs.mesa3d.org/drivers/freedreno.html</string>
<string name="freedreno_per_game_title">Freedreno 設定</string>
<string name="freedreno_per_game_description">調整此遊戲的 GPU 驅動程式設定</string>
<string name="freedreno_per_game_saved">Freedreno 設定已儲存</string>
<!-- Gamepad Buttons -->
<string name="gamepad_d_pad">方向鍵</string>
<string name="gamepad_left_stick">左搖桿</string>
@@ -1146,17 +1134,14 @@
<string name="gamepad_screenshot">螢幕截圖</string>
<!-- Disk shader cache -->
<string name="building_shaders">正在編譯著色器</string>
<string name="building_shaders">正在建置著色器</string>
<!-- App options -->
<string name="change_app_theme">變更應用程式主題</string>
<string name="theme_default">預設</string>
<string name="theme_material_you">Material You</string>
<string name="app_settings">Eden 介面設定</string>
<string name="theme_material_you">質感設計3</string>
<string name="app_settings">Eden介面設定</string>
<string name="theme_and_color">主題和顏色</string>
<string name="fullscreen_mode">全螢幕模式</string>
<string name="fullscreen_mode_description">在使用 Eden 時隱藏 Android 系統的導覽列,在直向時從螢幕底部往上滑以暫時顯示它們;橫向時則從螢幕側邊往內滑動</string>
<!-- Theme Modes -->
<string name="change_theme_mode">變更主題模式</string>
<string name="theme_mode_follow_system">跟隨系統</string>
@@ -1165,26 +1150,18 @@
<!-- Black backgrounds theme -->
<string name="use_black_backgrounds">黑色背景</string>
<string name="use_black_backgrounds_description">使用深色主題時套用黑色背景</string>
<!-- Buttons -->
<string name="enable_folder_button">遊戲資料夾</string>
<string name="enable_folder_button_description">在 Eden 首頁顯示新增遊戲資料夾的按鈕</string>
<string name="enable_qlaunch_button">QLaunch</string>
<string name="enable_qlaunch_button_description">在 Eden 首頁顯示 QLaunch 按鈕</string>
<string name="use_black_backgrounds_description">使用深色主題時,套用黑色背景。</string>
<!-- App Language -->
<string name="app_language">語言</string>
<string name="app_language_description">選擇 Eden 顯示的語言</string>
<string name="app_language_description">選擇Eden顯示的語言</string>
<string name="app_language_system">跟隨系統</string>
<!-- Static Themes -->
<string name="static_theme_color">主題色彩</string>
<string name="eden_theme">Eden</string>
<string name="violet">紫色</string>
<string name="blue">藍色</string>
<string name="cyan">青色</string>
<string name="red">紅色</string>
<string name="green">綠色</string>
<string name="yellow">黃色</string>
<string name="orange">橘色</string>
<string name="pink">粉紅色</string>
@@ -1206,28 +1183,14 @@
<string name="applets_menu_description">更改小程式前端和設定</string>
<string name="applet_hle">自訂前端</string>
<string name="applet_lle">Switch 小程式</string>
<string name="applet_lle">Switch小程式</string>
<string name="swkbd_applet">軟體鍵盤</string>
<string name="airplane_mode">飛航模式</string>
<string name="airplane_mode_description">將飛航模式傳遞給 Switch 作業系統</string>
<string name="airplane_mode_description">將飛航模式傳遞給Switch作業系統</string>
<string name="enable_overlay">啟用小程式虛擬按鍵</string>
<string name="enable_overlay_description">啟用 Horizon OS 內建的小程式虛擬按鍵,長按 Home 鍵 1 秒以顯示虛擬按鍵</string>
<!-- Profile Management -->
<string name="profile_manager">使用者管理</string>
<string name="profile_manager_description">更改使用者名稱、圖片及新增或刪除使用者</string>
<string name="profile_add_user">新增使用者</string>
<string name="profile_new_user">新增使用者</string>
<string name="profile_edit_user">編輯使用者資料</string>
<string name="profile_edit">編輯</string>
<string name="profile_delete">刪除</string>
<string name="profile_username">使用者名稱</string>
<string name="profile_uuid">使用者 ID (通用唯一辨識碼)</string>
<string name="profile_uuid_description">這是此使用者設定檔的唯一識別碼,建立後就無法進行更改</string>
<!-- Licenses screen strings -->
<string name="licenses">授權</string>
<string name="license_fidelityfx_fsr_description">來自 AMD 的升級圖像品質</string>
</resources>
</resources>
@@ -593,10 +593,8 @@
<string name="fast_gpu_time_description">Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks.</string>
<string name="skip_cpu_inner_invalidation">Skip CPU Inner Invalidation</string>
<string name="skip_cpu_inner_invalidation_description">Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games.</string>
<string name="nce_invalidation_gpu_readback">GPU Readback Before NCE Invalidation</string>
<string name="nce_invalidation_gpu_readback_description">Temporary. Fix Ender Magnolia (and other UE titles) crashes. May reduce performance. Improvement in progress.</string>
<string name="fix_bloom_effects">Fix Bloom Effects</string>
<string name="fix_bloom_effects_description">Reduces bloom blur in LA/EOW (Adreno A6XX - A7XX/ Turnip), removes bloom in Burnout. Warning: may cause graphical artifacts in other games.</string>
<string name="fix_bloom_effects_description">Removes bloom in Burnout.</string>
<string name="emulate_bgr565">Emulate BGR565</string>
<string name="emulate_bgr565_description">Fixes problems with inverted colors in games or strange artifacts or strange shadows.</string>
<string name="rescale_hack">Enable Legacy Rescale Pass</string>
@@ -842,6 +840,12 @@
<string name="preferences_graphics">Graphics</string>
<string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string>
<string name="quick_settings">Quick Settings</string>
<string name="cheats">Cheats</string>
<string name="enable_all_cheats">Enable All Cheats</string>
<string name="disable_all_cheats">Disable All Cheats</string>
<string name="unnamed_cheat">Unnamed Cheat</string>
<string name="enable_cheats_by_default">Enable cheats by default</string>
<string name="enable_cheats_by_default_description">Enable regular cheats when a game starts</string>
<string name="enable_quick_settings">Enable Quick Settings</string>
<string name="enable_quick_settings_description">Allow access to quick settings menu via swipe and menu button</string>
<string name="preferences_audio">Audio</string>
+4
View File
@@ -418,6 +418,10 @@ void SetGlobalFilter(const Filter& filter) {
logging_instance->filter = filter;
}
bool IsMessageEnabled(Class log_class, Level log_level) {
return logging_instance && logging_instance->filter.CheckMessage(log_class, log_level);
}
void SetColorConsoleBackendEnabled(bool enabled) {
if (logging_instance)
logging_instance->color_console_backend.enabled = enabled;
+1
View File
@@ -138,6 +138,7 @@ void Stop();
/// The global filter will prevent any messages from even being processed if they are filtered.
void SetGlobalFilter(const Filter& filter);
[[nodiscard]] bool IsMessageEnabled(Class log_class, Level log_level);
void SetColorConsoleBackendEnabled(bool enabled);
} // namespace Common::Log
+1 -2
View File
@@ -247,6 +247,7 @@ struct Values {
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core,
Specialization::Default};
SwitchableSetting<bool> enable_cheats_by_default{linkage, true, "enable_cheats_by_default", Category::Core};
// Cpu
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
@@ -639,8 +640,6 @@ struct Values {
Specialization::Default,
true,
true};
SwitchableSetting<bool> nce_invalidation_gpu_readback{
linkage, false, "nce_invalidation_gpu_readback", Category::RendererHacks};
SwitchableSetting<bool> async_presentation{linkage,
#ifdef __ANDROID__
true,
+9
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -46,4 +49,10 @@ std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed) {
return decompressed;
}
std::size_t DecompressDataZSTD(std::span<const u8> compressed, std::span<u8> output) {
const std::size_t result =
ZSTD_decompress(output.data(), output.size(), compressed.data(), compressed.size());
return ZSTD_isError(result) ? 0 : result;
}
} // namespace Common::Compression
+14
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -42,4 +45,15 @@ namespace Common::Compression {
*/
[[nodiscard]] std::vector<u8> DecompressDataZSTD(std::span<const u8> compressed);
/**
* Decompresses a single Zstandard frame into a caller-provided buffer of known size.
*
* @param compressed the compressed source memory region.
* @param output the destination buffer. Its size is the maximum number of bytes to write.
*
* @return the number of bytes written, or 0 if the frame is invalid or doesn't fit. An empty
* frame also returns 0.
*/
[[nodiscard]] std::size_t DecompressDataZSTD(std::span<const u8> compressed, std::span<u8> output);
} // namespace Common::Compression
+2
View File
@@ -33,6 +33,8 @@ add_library(core STATIC
crypto/encryption_layer.h
crypto/key_manager.cpp
crypto/key_manager.h
crypto/ncz_decompressor.cpp
crypto/ncz_decompressor.h
crypto/partition_data_manager.cpp
crypto/partition_data_manager.h
crypto/xts_encryption_layer.cpp
+7 -4
View File
@@ -165,10 +165,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
const Common::ProcessAddress addr =
(reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
if (memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) {
// We handled the access successfully and are returning to guest code.
const auto result = memory.InvalidateNCE(addr, Memory::YUZU_PAGESIZE);
if (result == Memory::NCEAccessFaultAction::Retry) {
return true;
}
if (result == Memory::NCEAccessFaultAction::Instruction) {
return HandleGuestAlignmentFault(guest_ctx, raw_info, raw_context);
}
// We couldn't handle the access.
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
@@ -369,8 +372,8 @@ void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) {
}
void ArmNce::SignalInterrupt(Kernel::KThread* thread) {
// Add break loop condition.
m_guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
constexpr u64 break_loop = static_cast<u64>(HaltReason::BreakLoop);
if ((m_guest_ctx.esr_el1.fetch_or(break_loop) & break_loop) != 0) return;
auto* params = &thread->GetNativeExecutionParameters();
LockThreadParameters(params);
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
/* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -5,9 +8,22 @@
#define __ASSEMBLY__
// Newer Linux UAPI headers check __ASSEMBLER__ instead of __ASSEMBLY__ to hide C declarations.
// Without it, asm-generic/signal.h redeclares sigset_t and clashes with glibc's. Only define it
// around these includes, since glibc headers also look at __ASSEMBLER__.
#ifndef __ASSEMBLER__
#define __ASSEMBLER__
#define EDEN_NCE_UNDEF_ASSEMBLER
#endif
#include <asm-generic/signal.h>
#include <asm-generic/unistd.h>
#ifdef EDEN_NCE_UNDEF_ASSEMBLER
#undef __ASSEMBLER__
#undef EDEN_NCE_UNDEF_ASSEMBLER
#endif
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
#define BreakFromRunCodeSignal SIGURG
#define GuestAccessFaultSignal SIGSEGV
+17 -34
View File
@@ -4,7 +4,6 @@
#include <array>
#include <atomic>
#include <memory>
#include <unordered_map>
#include <utility>
#include "game_settings.h"
@@ -249,30 +248,12 @@ struct System::Impl {
}
}
void NotifyNVDECChannelOpen(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
++nvdec_active_channels[process_id];
}
void NotifyNVDECChannelClose(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
const auto it = nvdec_active_channels.find(process_id);
if (it == nvdec_active_channels.end()) {
return;
}
if (--it->second == 0) {
nvdec_active_channels.erase(it);
}
void SetNVDECActive(bool is_nvdec_active) {
nvdec_active = is_nvdec_active;
}
bool GetNVDECActive() {
std::scoped_lock lock{nvdec_active_mutex};
return !nvdec_active_channels.empty();
}
bool IsNVDECActiveForProcess(u64 process_id) {
std::scoped_lock lock{nvdec_active_mutex};
return nvdec_active_channels.contains(process_id);
return nvdec_active;
}
void InitializeDebugger(System& system, u16 port) {
@@ -528,8 +509,6 @@ struct System::Impl {
mutable std::mutex suspend_guard;
std::mutex general_channel_mutex;
std::mutex nvdec_active_mutex;
std::unordered_map<u64, u32> nvdec_active_channels;
std::atomic_bool is_paused{};
std::atomic_bool is_shutting_down{};
std::atomic_bool is_powered_on{};
@@ -537,6 +516,7 @@ struct System::Impl {
bool extended_memory_layout : 1 = false;
bool exit_locked : 1 = false;
bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
void EnsureGeneralChannelInitialized(System& system) {
if (!general_channel_event) {
@@ -600,22 +580,14 @@ void System::UnstallApplication() {
impl->UnstallApplication();
}
void System::NotifyNVDECChannelOpen(u64 process_id) {
impl->NotifyNVDECChannelOpen(process_id);
}
void System::NotifyNVDECChannelClose(u64 process_id) {
impl->NotifyNVDECChannelClose(process_id);
void System::SetNVDECActive(bool is_nvdec_active) {
impl->SetNVDECActive(is_nvdec_active);
}
bool System::GetNVDECActive() {
return impl->GetNVDECActive();
}
bool System::IsNVDECActiveForProcess(u64 process_id) {
return impl->IsNVDECActiveForProcess(process_id);
}
void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
}
@@ -814,6 +786,17 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
}
std::vector<Memory::RuntimeCheatInfo> System::GetRuntimeCheats() const {
if (!impl->cheat_engine) {
return {};
}
return impl->cheat_engine->GetCheats();
}
bool System::SetCheatEnabled(u32 cheat_id, bool enabled) {
return impl->cheat_engine && impl->cheat_engine->SetCheatEnabled(cheat_id, enabled);
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
}
+4 -3
View File
@@ -46,6 +46,7 @@ enum class ResultStatus : u16;
namespace Core::Memory {
struct CheatEntry;
struct RuntimeCheatInfo;
class Memory;
} // namespace Core::Memory
@@ -191,10 +192,8 @@ public:
std::unique_lock<std::mutex> StallApplication();
void UnstallApplication();
void NotifyNVDECChannelOpen(u64 process_id);
void NotifyNVDECChannelClose(u64 process_id);
void SetNVDECActive(bool is_nvdec_active);
[[nodiscard]] bool GetNVDECActive();
[[nodiscard]] bool IsNVDECActiveForProcess(u64 process_id);
/**
* Initialize the debugger.
@@ -344,6 +343,8 @@ public:
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
u64 main_region_size);
[[nodiscard]] std::vector<Memory::RuntimeCheatInfo> GetRuntimeCheats() const;
bool SetCheatEnabled(u32 cheat_id, bool enabled);
void SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set);
+472
View File
@@ -0,0 +1,472 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include <zstd.h>
#include "common/common_types.h"
#include "common/logging.h"
#include "common/zstd_compression.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/ncz_decompressor.h"
#include "core/file_sys/vfs/vfs.h"
namespace Core::Crypto {
namespace {
constexpr u64 HeaderSize = 0x4000;
constexpr std::size_t SectionWire = 0x40;
constexpr std::size_t BlockFixed = 0x18;
constexpr std::size_t Chunk = 0x100000;
// Upper bound on NCZ section entries (64 bytes each, so the table is at most 4 MiB). A base NCA
// has a handful of sections, but nsz stores an update's patch (BKTR) data one entry per
// subsection, which is thousands of them.
constexpr u64 MaxSectionCount = 0x10000;
constexpr std::array<u8, 8> SectionsMagic{'N', 'C', 'Z', 'S', 'E', 'C', 'T', 'N'};
constexpr std::array<u8, 8> BlockMagic{'N', 'C', 'Z', 'B', 'L', 'O', 'C', 'K'};
struct Section {
s64 offset, size;
u64 crypto_type;
Key128 key, counter;
bool Ctr() const { return crypto_type == 3 || crypto_type == 4; }
};
struct BlockHeader {
u32 exponent;
u64 decompressed_size;
std::vector<u32> sizes;
};
u64 Le(const u8* p, std::size_t n) {
u64 v = 0;
for (std::size_t i = n; i-- > 0;) v = (v << 8) | p[i];
return v;
}
// Mirrors nsz AESCTR.seek(): upper 8 bytes = stored counter, lower 8 = absolute NCA offset / 16.
void SetIv(Key128& iv, const Key128& base, u64 off) {
iv = base;
u64 blk = off >> 4;
for (std::size_t i = 0; i < 8; ++i) { iv[15 - i] = static_cast<u8>(blk & 0xFF); blk >>= 8; }
}
// Parse header + section table + optional block header. Returns payload_start.
std::optional<std::pair<std::vector<Section>, std::optional<BlockHeader>>> ParseHeader(
const FileSys::VirtualFile& f, u64& payload_start) {
const u64 fsize = f->GetSize();
if (fsize < HeaderSize + 16) return std::nullopt;
const auto head = f->ReadBytes(16, HeaderSize);
if (head.size() != 16 || !std::equal(head.begin(), head.begin() + 8, SectionsMagic.begin()))
return std::nullopt;
const u64 count = Le(head.data() + 8, 8);
if (count == 0 || count > MaxSectionCount || count * SectionWire > fsize - HeaderSize - 16)
return std::nullopt;
const auto table = f->ReadBytes(count * SectionWire, HeaderSize + 16);
if (table.size() != count * SectionWire) return std::nullopt;
std::vector<Section> sections;
sections.reserve(count + 1);
for (u64 i = 0; i < count; ++i) {
const u8* e = table.data() + i * SectionWire;
Section s{};
s.offset = static_cast<s64>(Le(e, 8));
s.size = static_cast<s64>(Le(e + 8, 8));
s.crypto_type = Le(e + 16, 8);
std::memcpy(s.key.data(), e + 32, 16);
std::memcpy(s.counter.data(), e + 48, 16);
sections.push_back(s);
}
const u64 block_start = HeaderSize + 16 + count * SectionWire;
const auto fixed = f->ReadBytes(BlockFixed, block_start);
std::optional<BlockHeader> bh;
if (fixed.size() == BlockFixed &&
std::equal(fixed.begin(), fixed.begin() + 8, BlockMagic.begin())) {
BlockHeader b{};
b.exponent = fixed[11];
const s32 n = static_cast<s32>(Le(fixed.data() + 12, 4));
if (n <= 0) return std::nullopt;
b.decompressed_size = Le(fixed.data() + 16, 8);
const auto sizes = f->ReadBytes(static_cast<u64>(n) * 4, block_start + BlockFixed);
if (sizes.size() != static_cast<u64>(n) * 4) return std::nullopt;
b.sizes.resize(n);
for (s32 i = 0; i < n; ++i) b.sizes[i] = static_cast<u32>(Le(sizes.data() + i * 4, 4));
payload_start = block_start + BlockFixed + static_cast<u64>(n) * 4;
bh = std::move(b);
} else {
payload_start = block_start;
}
if (!sections.empty() && sections[0].offset > static_cast<s64>(HeaderSize)) {
Section fake{};
fake.offset = static_cast<s64>(HeaderSize);
fake.size = sections[0].offset - static_cast<s64>(HeaderSize);
sections.insert(sections.begin(), fake);
}
return std::make_pair(std::move(sections), std::move(bh));
}
// Sequential reader over independently-compressed blocks. Uses a direct-mapped cache (like
// NCZVirtualFile) instead of an LRU list: block_index % CacheSlots picks the slot, so a hit is
// O(1). Reuses one ZSTD_DCtx for the lifetime of the reader.
class BlockReader {
public:
static constexpr std::size_t CacheSlots = 32;
BlockReader(FileSys::VirtualFile f, const BlockHeader& bh, u64 payload_start)
: source{std::move(f)}, block_size{1ULL << bh.exponent}, sizes{bh.sizes},
decompressed_size{bh.decompressed_size} {
if (bh.exponent < 14 || bh.exponent > 32 || sizes.empty())
throw std::runtime_error("bad NCZBLOCK header");
u64 off = payload_start;
offsets.reserve(sizes.size());
for (u32 s : sizes) { offsets.push_back(off); off += s; }
dctx = ZSTD_createDCtx();
if (dctx == nullptr) throw std::runtime_error("ZSTD_createDCtx failed");
cache.resize(CacheSlots);
for (auto& slot : cache) slot.id = ~0ULL;
}
~BlockReader() {
if (dctx != nullptr) ZSTD_freeDCtx(dctx);
}
BlockReader(const BlockReader&) = delete;
BlockReader& operator=(const BlockReader&) = delete;
std::size_t ReadAt(u64 position, std::span<u8> out) {
std::size_t done = 0;
while (done < out.size() && position < decompressed_size) {
const u64 id = position / block_size;
if (id >= sizes.size()) break;
const std::vector<u8>* block = GetBlock(id);
if (block == nullptr) break;
const u64 in = position - id * block_size;
if (in >= block->size()) break;
const std::size_t n = static_cast<std::size_t>(
std::min<u64>(out.size() - done, block->size() - in));
std::memcpy(out.data() + done, block->data() + in, n);
done += n;
position += n;
}
return done;
}
private:
struct Slot {
u64 id = ~0ULL;
std::vector<u8> data;
};
const std::vector<u8>* GetBlock(u64 id) {
Slot& slot = cache[id % CacheSlots];
if (slot.id == id) {
return &slot.data; // O(1) hit
}
std::vector<u8> buffer;
buffer.swap(slot.data); // reuse existing allocation
if (!DecodeBlock(id, buffer)) {
return nullptr;
}
slot.data = std::move(buffer);
slot.id = id;
return &slot.data;
}
bool DecodeBlock(u64 id, std::vector<u8>& buffer) {
u64 want = block_size;
if (id + 1 == sizes.size()) {
const u64 rem = decompressed_size % block_size;
if (rem > 0) want = rem; // avoid the 0-size bug from the original nsz
}
try { buffer.resize(static_cast<std::size_t>(want)); }
catch (const std::bad_alloc&) { return false; }
const u32 csize = sizes[id];
if (csize < want) {
const auto comp = source->ReadBytes(csize, offsets[id]);
if (comp.size() != csize) return false;
const std::size_t res = ZSTD_decompressDCtx(
dctx, buffer.data(), buffer.size(), comp.data(), comp.size());
if (ZSTD_isError(res) || res != want) {
LOG_ERROR(Crypto, "Failed to decompress NCZBLOCK block {}: {}", id,
ZSTD_isError(res) ? ZSTD_getErrorName(res) : "size mismatch");
return false;
}
} else if (source->Read(buffer.data(), buffer.size(), offsets[id]) != buffer.size()) {
return false;
}
return true;
}
FileSys::VirtualFile source;
u64 block_size;
std::vector<u64> offsets;
std::vector<u32> sizes;
u64 decompressed_size;
ZSTD_DCtx* dctx = nullptr;
std::vector<Slot> cache; // direct-mapped, CacheSlots entries
};
class BlockNczFile final : public FileSys::VfsFile {
public:
BlockNczFile(std::string name_, std::vector<u8> hdr_, std::vector<Section> sections_,
std::unique_ptr<BlockReader> body_)
: name{std::move(name_)}, hdr{std::move(hdr_)}, sections{std::move(sections_)},
body{std::move(body_)} {
nca_size = HeaderSize;
for (const auto& s : sections)
nca_size = std::max<u64>(nca_size, static_cast<u64>(s.offset + s.size));
}
std::string GetName() const override { return name; }
std::size_t GetSize() const override { return static_cast<std::size_t>(nca_size); }
bool Resize(std::size_t) override { return false; }
FileSys::VirtualDir GetContainingDirectory() const override { return nullptr; }
bool IsWritable() const override { return false; }
bool IsReadable() const override { return true; }
// NCZ stores CTR sections decrypted; this file serves them as-is, and NcaFileSystemDriver
// skips its AES layers because of this flag.
bool HasDecryptedSections() const override { return true; }
std::size_t Read(u8* data, std::size_t len, std::size_t off) const override {
if (off >= nca_size) return 0;
len = static_cast<std::size_t>(std::min<u64>(len, nca_size - off));
const u64 end = off + len;
u64 pos = off;
if (pos < HeaderSize) {
const u64 n = std::min<u64>(end, HeaderSize) - pos;
std::memcpy(data, hdr.data() + pos, static_cast<std::size_t>(n));
pos += n;
}
std::scoped_lock lock{mutex};
// Sections are sorted by offset; find the first one whose stop > pos.
auto it = std::lower_bound(
sections.begin(), sections.end(), pos, [](const Section& s, u64 p) {
const u64 b = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
const u64 e = std::max<u64>(static_cast<u64>(s.offset) + static_cast<u64>(s.size), b);
return e <= p;
});
for (; it != sections.end() && pos < end; ++it) {
const auto& s = *it;
const u64 begin = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
const u64 stop = std::max<u64>(static_cast<u64>(s.offset + s.size), begin);
if (stop <= pos) continue;
if (pos < begin) {
const u64 g = std::min(end, begin);
std::memset(data + (pos - off), 0, static_cast<std::size_t>(g - pos));
pos = g;
if (pos >= end) break;
}
const u64 pe = std::min(end, stop);
const std::span<u8> piece(data + (pos - off), static_cast<std::size_t>(pe - pos));
if (body->ReadAt(pos - HeaderSize, piece) != piece.size())
return static_cast<std::size_t>(pos - off);
// No CTR here: NCZ body is already decrypted, and NcaFileSystemDriver skips AES.
pos = pe;
}
return len;
}
std::size_t Write(const u8*, std::size_t, std::size_t) override { return 0; }
bool Rename(std::string_view) override { return false; }
private:
const std::string name;
const std::vector<u8> hdr;
const std::vector<Section> sections;
const std::unique_ptr<BlockReader> body;
mutable std::mutex mutex;
u64 nca_size{};
};
// Solid NCZ: one zstd stream over the whole body. Decoded incrementally into memory, up to the
// requested offset, so sequential install reads are O(n) instead of O(n^2).
class SolidNczFile final : public FileSys::VfsFile {
public:
SolidNczFile(std::string name_, FileSys::VirtualFile source_,
std::vector<Section> sections_, u64 payload_start_)
: name{std::move(name_)}, source{std::move(source_)}, sections{std::move(sections_)},
payload_start{payload_start_} {
const auto raw_header = source->ReadBytes(HeaderSize, 0);
data.assign(raw_header.begin(), raw_header.end());
frontier = HeaderSize;
}
~SolidNczFile() override {
if (stream != nullptr) ZSTD_freeDStream(stream);
}
std::string GetName() const override { return name; }
std::size_t GetSize() const override {
u64 size = HeaderSize;
for (const auto& s : sections)
size = std::max<u64>(size, static_cast<u64>(s.offset + s.size));
return static_cast<std::size_t>(size);
}
bool Resize(std::size_t) override { return false; }
FileSys::VirtualDir GetContainingDirectory() const override { return nullptr; }
bool IsWritable() const override { return false; }
bool IsReadable() const override { return true; }
std::size_t Read(u8* out, std::size_t len, std::size_t off) const override {
std::scoped_lock lock{mutex};
const u64 nca_size = GetSize();
if (off >= nca_size) return 0;
len = static_cast<std::size_t>(std::min<u64>(len, nca_size - off));
const u64 needed = off + len;
if (needed > frontier && !DecodeTo(needed)) return 0;
std::memcpy(out, data.data() + off, len);
return len;
}
std::size_t Write(const u8*, std::size_t, std::size_t) override { return 0; }
bool Rename(std::string_view) override { return false; }
private:
bool DecodeTo(u64 needed) const {
if (stream == nullptr) {
stream = ZSTD_createDStream();
if (stream == nullptr) return false;
ZSTD_DCtx_setParameter(stream, ZSTD_d_windowLogMax, 31);
read_pos = payload_start;
}
const u64 file_size = source->GetSize();
std::vector<u8> out_buf(Chunk);
std::size_t index = 0;
for (; index < sections.size(); ++index) {
const auto& s = sections[index];
const u64 begin = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
const u64 end = std::max<u64>(static_cast<u64>(s.offset) + static_cast<u64>(s.size),
begin);
if (end > frontier) break;
}
for (; index < sections.size() && frontier < needed; ++index) {
const auto& s = sections[index];
const u64 begin = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
const u64 end = std::max<u64>(static_cast<u64>(s.offset) + static_cast<u64>(s.size),
begin);
if (end <= frontier) continue;
u64 pos = std::max<u64>(begin, frontier);
std::optional<AESCipher<Key128>> cipher;
if (s.Ctr()) cipher.emplace(s.key, Mode::CTR);
while (pos < end && frontier < needed) {
const u64 want = std::min<u64>(end - pos, out_buf.size());
std::size_t produced = 0;
while (produced < want) {
if (in.pos >= in.size) {
const u64 remaining = file_size - read_pos;
const std::size_t chunk = static_cast<std::size_t>(
std::min<u64>(Chunk, remaining));
if (chunk == 0) { LOG_ERROR(Crypto, "Solid NCZ: truncated"); return false; }
const auto bytes = source->ReadBytes(chunk, read_pos);
if (bytes.empty()) { LOG_ERROR(Crypto, "Solid NCZ: truncated"); return false; }
read_pos += bytes.size();
in_buf.assign(bytes.begin(), bytes.end());
in.src = in_buf.data();
in.size = in_buf.size();
in.pos = 0;
}
ZSTD_outBuffer outb{out_buf.data() + produced, want - produced, 0};
const std::size_t res = ZSTD_decompressStream(stream, &outb, &in);
if (ZSTD_isError(res)) {
LOG_ERROR(Crypto, "Solid NCZ: zstd error");
return false;
}
produced += outb.pos;
}
if (cipher) {
Key128 iv{};
SetIv(iv, s.counter, pos);
cipher->SetIV(iv);
cipher->Transcode(out_buf.data(), produced, out_buf.data(), Op::Encrypt);
}
data.insert(data.end(), out_buf.begin(), out_buf.begin() + produced);
pos += produced;
frontier = pos;
}
}
return frontier >= needed;
}
const std::string name;
const FileSys::VirtualFile source;
const std::vector<Section> sections;
const u64 payload_start;
mutable std::mutex mutex;
mutable std::vector<u8> data;
mutable ZSTD_DStream* stream = nullptr;
mutable u64 read_pos = 0;
mutable u64 frontier = 0;
mutable std::vector<u8> in_buf;
mutable ZSTD_inBuffer in{nullptr, 0, 0};
};
// Shared opener: block path always; solid only when allow_solid is true.
FileSys::VirtualFile OpenNcz(const FileSys::VirtualFile& f, std::string_view name,
bool allow_solid) {
if (!f) return nullptr;
u64 ps = 0;
auto parsed = ParseHeader(f, ps);
if (!parsed) return nullptr;
auto& [sections, block] = *parsed;
if (block) {
auto hdr = f->ReadBytes(HeaderSize, 0);
if (hdr.size() != HeaderSize) return nullptr;
std::unique_ptr<BlockReader> body;
try { body = std::make_unique<BlockReader>(f, *block, ps); }
catch (const std::exception& e) {
LOG_ERROR(Crypto, "Failed to open NCZBLOCK: {}", e.what());
return nullptr;
}
return std::make_shared<BlockNczFile>(std::string{name}, std::move(hdr),
std::move(sections), std::move(body));
}
if (allow_solid) {
return std::make_shared<SolidNczFile>(std::string{name}, f, std::move(sections), ps);
}
LOG_DEBUG(Crypto, "Solid NCZ {} skipped (only NCZBLOCK is supported for play)", name);
return nullptr;
}
} // Anonymous namespace
bool IsBlockCompressedNCZ(const FileSys::VirtualFile& f) {
if (!f) return false;
u64 ps = 0;
const auto p = ParseHeader(f, ps);
return p && p->second.has_value();
}
FileSys::VirtualFile DecompressNCZ(const FileSys::VirtualFile& f, std::string_view name) {
return OpenNcz(f, name, /*allow_solid=*/false);
}
FileSys::VirtualFile DecompressNCZForInstall(const FileSys::VirtualFile& f,
std::string_view name) {
return OpenNcz(f, name, /*allow_solid=*/true);
}
} // namespace Core::Crypto
+35
View File
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string_view>
#include "core/file_sys/vfs/vfs_types.h"
namespace Core::Crypto {
/**
* Opens an NCZBLOCK-compressed NCA (nsz -B / .xcz block format) as a VirtualFile backed by
* on-demand block decoding. Nothing is written to disk; each read decodes only the blocks it
* covers and re-encrypts CTR sections. Solid NCZ is not supported.
*
* @param ncz_file The raw NCZ payload (e.g. "<id>.ncz" entry from an NSZ/XCZ PFS0).
* @param output_name Name to give the returned VirtualFile (e.g. "<id>.nca").
*
* @return a VirtualFile for the NCA, or nullptr if the NCZ is malformed or not NCZBLOCK.
*/
[[nodiscard]] FileSys::VirtualFile DecompressNCZ(const FileSys::VirtualFile& ncz_file,
std::string_view output_name);
/**
* Returns true if the given file is an NCZ payload in block-compressed (NCZBLOCK) form,
* i.e. one that DecompressNCZ() can open. Solid NCZ returns false.
*/
[[nodiscard]] bool IsBlockCompressedNCZ(const FileSys::VirtualFile& ncz_file);
// Install to NAND: same as DecompressNCZ, but solid NCZ is decoded on demand too.
[[nodiscard]] FileSys::VirtualFile DecompressNCZForInstall(
const FileSys::VirtualFile& ncz_file, std::string_view output_name);
} // namespace Core::Crypto
+1
View File
@@ -45,6 +45,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base)
// Ensure we have the proper key area keys to continue.
const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration());
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) {
LOG_ERROR(Loader, "Cannot decrypt {}: keys for gen 0x{:02X}+ required.", file->GetName(), reader->GetKeyGeneration());
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return;
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -93,6 +93,11 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
// Initialize our header reader for the fs index.
R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
// NCZ files serve already-decrypted body data (CTR sections are stored decrypted).
// Skip the AES layers so we don't decrypt plaintext twice.
const bool is_pre_decrypted =
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
// Declare the storage we're opening.
VirtualFile storage;
@@ -158,7 +163,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
out_header_reader->GetPatchMetaDataHashDataInfo()));
}
if (patch_info.HasAesCtrExTable()) {
if (patch_info.HasAesCtrExTable() && !is_pre_decrypted) {
// Check the encryption type.
ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
@@ -196,32 +201,34 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
}
} else {
// Create the appropriate storage for the encryption type.
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
// Create the aes ctr storage.
VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
if (!is_pre_decrypted) {
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
// Create the aes ctr storage.
VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
}
// Potentially save storages to our context.
@@ -382,21 +389,25 @@ Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
}
// Create the appropriate storage for the encryption type.
switch (header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(
this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
const bool is_pre_decrypted =
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
if (!is_pre_decrypted) {
switch (header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
}
// Set output storage.
@@ -432,6 +443,11 @@ Result NcaFileSystemDriver::CreateAesCtrStorage(
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesCtrStorage::IvSize> iv{};
AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
@@ -469,6 +485,11 @@ Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile ba
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesXtsStorage::IvSize> iv{};
AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
@@ -809,6 +830,12 @@ Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
// Validate preconditions.
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
@@ -871,6 +898,11 @@ Result NcaFileSystemDriver::CreateAesCtrExStorage(
ASSERT(meta_storage != nullptr);
ASSERT(patch_info.HasAesCtrExTable());
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Read the bucket tree header.
BucketTree::Header header;
std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
@@ -104,6 +104,7 @@ public:
const NcaCompressionConfiguration& compression_cfg);
VirtualFile GetSharedBodyStorage();
VirtualFile GetStorage() const { return m_body_storage; }
u32 GetMagic() const;
NcaHeader::DistributionType GetDistributionType() const;
NcaHeader::ContentType GetContentType() const;
+4 -3
View File
@@ -503,10 +503,11 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
std::copy(res->begin(), res->end(), std::back_inserter(out));
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
} else if (auto const lower_res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
std::copy(lower_res->begin(), lower_res->end(), std::back_inserter(out));
}
}
}
}
+43 -3
View File
@@ -16,6 +16,7 @@
#include "common/random.h"
#include "common/string_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/ncz_decompressor.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
@@ -158,6 +159,25 @@ static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::
return nullptr;
}
// Checks that every .ncz entry in the container is NCZBLOCK. Solid NCZ has no random access,
// and the whole point of this build is to reject it at scan time so such files never appear
// in the game list. Containers without any .ncz (plain NSP/XCI) pass through.
static bool ContainerIsFullyBlockCompressed(const std::shared_ptr<NSP>& nsp) {
if (!nsp) {
return false;
}
for (const auto& entry : nsp->GetFiles()) {
const auto name = entry->GetName();
if (name.size() < 4 || name.substr(name.size() - 4) != ".ncz") {
continue;
}
if (!Core::Crypto::IsBlockCompressedNCZ(entry)) {
return false; // solid NCZ -> reject the whole container
}
}
return true; // no .ncz at all, or all block-compressed
}
template <typename Callback>
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
std::optional<u64> base_program_id, Callback&& on_entry) {
@@ -811,7 +831,14 @@ std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
}
static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) {
auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false)));
const auto id_string = Common::HexToString(id, false);
auto file = nsp.GetFile(fmt::format("{}.nca", id_string));
if (file == nullptr) {
if (auto ncz_file = nsp.GetFile(fmt::format("{}.ncz", id_string))) {
file = Core::Crypto::DecompressNCZForInstall(
ncz_file, fmt::format("{}.nca", id_string));
}
}
if (file == nullptr) {
return nullptr;
}
@@ -1273,6 +1300,12 @@ bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_
return false;
}
// Reject solid NCZ (.nsz without -B): only block-compressed NCZ is supported.
if (!ContainerIsFullyBlockCompressed(nsp)) {
LOG_DEBUG(Service_FS, "Skipping solid-compressed container: {}", file->GetName());
return false;
}
return ForEachContainerEntry(
nsp, only_content, base_program_id,
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
@@ -1415,8 +1448,9 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
}
const auto extension = Common::ToLower(filename.substr(dot_pos + 1));
if (extension == "nsp") {
// .xcz is an XCI container, but its updates only work through the NSP/PFS0 path
// (DecompressNCZ), not ProcessXCI. So route .xcz here on purpose.
if (extension == "nsp" || extension == "nsz" || extension == "xcz") {
ProcessNSP(file);
} else if (extension == "xci") {
ProcessXCI(file);
@@ -1434,6 +1468,12 @@ void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
return;
}
// Reject solid NCZ (.nsz without -B): Eden only supports block-compressed NCZ.
if (!ContainerIsFullyBlockCompressed(nsp)) {
LOG_DEBUG(Service_FS, "Skipping solid-compressed container: {}", file->GetName());
return;
}
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
}
+12
View File
@@ -12,6 +12,7 @@
#include "common/hex_util.h"
#include "common/logging.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/ncz_decompressor.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
@@ -246,6 +247,17 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
const auto id_string = Common::HexToString(rec.nca_id, false);
auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
auto ncz_file = pfs->GetFile(fmt::format("{}.ncz", id_string));
if (ncz_file != nullptr) {
next_file = Core::Crypto::DecompressNCZ(ncz_file, fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_DEBUG(Service_FS, "Skipping solid or unsupported NCZ content {}.ncz", id_string);
continue;
}
}
}
if (next_file == nullptr) {
if (rec.type != ContentRecordType::DeltaFragment) {
LOG_WARNING(Service_FS,
+3
View File
@@ -112,6 +112,9 @@ public:
// Returns whether or not the file can be read from.
virtual bool IsReadable() const = 0;
// Returns true if this file serves NCA body data that is already decrypted (e.g. NCZ).
virtual bool HasDecryptedSections() const { return false; }
// The primary method of reading from the file. Reads length bytes into data starting at offset
// into file. Returns number of bytes successfully read.
virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
+7
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -37,6 +40,10 @@ public:
std::size_t GetOffset() const;
bool HasDecryptedSections() const override {
return file && file->HasDecryptedSections();
}
private:
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
@@ -4,6 +4,10 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <chrono>
#include <thread>
#include "core/file_sys/errors.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/filesystem/fsp/fs_i_storage.h"
@@ -31,6 +35,14 @@ Result IStorage::Read(
R_UNLESS(length >= 0, FileSys::ResultInvalidSize);
R_UNLESS(offset >= 0, FileSys::ResultInvalidOffset);
const auto now = std::chrono::steady_clock::now();
if (now < next_read_tick) {
std::this_thread::sleep_for(next_read_tick - now);
}
next_read_tick = std::chrono::steady_clock::now() +
std::chrono::nanoseconds{std::min(length, MaxChargedReadSize) *
1'000'000'000 / ReadBytesPerSecond};
// Read the data from the Storage backend
backend->Read(out_bytes.data(), length, offset);
@@ -6,6 +6,8 @@
#pragma once
#include <chrono>
#include "core/file_sys/vfs/vfs.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -20,6 +22,10 @@ public:
private:
FileSys::VirtualFile backend;
static constexpr s64 ReadBytesPerSecond = 40'000'000;
static constexpr s64 MaxChargedReadSize = 64 * 1024;
std::chrono::steady_clock::time_point next_read_tick{};
Result Read(
OutBuffer<BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_bytes,
s64 offset, s64 length);
@@ -8,7 +8,6 @@
#include "common/assert.h"
#include "common/logging.h"
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
@@ -72,23 +71,17 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
system.SetNVDECActive(true);
sessions[fd] = session_id;
if (const auto* session = core.GetSession(session_id);
session != nullptr && session->process != nullptr) {
system.NotifyNVDECChannelOpen(session->process->GetId());
}
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
}
void nvhost_nvdec::OnClose(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
host1x.StopDevice(fd, Tegra::Host1x::ChannelType::NvDec);
system.SetNVDECActive(false);
auto it = sessions.find(fd);
if (it != sessions.end()) {
if (const auto* session = core.GetSession(it->second);
session != nullptr && session->process != nullptr) {
system.NotifyNVDECChannelClose(session->process->GetId());
}
sessions.erase(it);
}
}
+1 -15
View File
@@ -4,16 +4,12 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <fmt/ranges.h>
#include <string_view>
#include <thread>
#include "common/assert.h"
#include "common/logging.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/service.h"
@@ -37,7 +33,6 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
: SessionRequestHandler(system_.Kernel(), service_name_)
, system{system_}
, service_name{service_name_}
, is_i_storage{std::string_view{service_name_} == "IStorage"}
, handler_invoker{handler_invoker_}
, max_sessions{max_sessions_}
{}
@@ -82,22 +77,13 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,
}
void ServiceFrameworkBase::InvokeRequest(HLERequestContext& ctx) {
const auto command = ctx.GetCommand();
auto it = handlers.find(command);
const bool is_cmd_read = command == 0;
auto it = handlers.find(ctx.GetCommand());
FunctionInfoBase const* info = it == handlers.end() ? nullptr : &it->second;
if (info == nullptr || info->handler_callback == nullptr)
return ReportUnimplementedFunction(ctx, info);
LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
handler_invoker(this, info->handler_callback, ctx);
if (is_i_storage && is_cmd_read) {
const auto* const process = ctx.GetThread().GetOwnerProcess();
if (process != nullptr && system.IsNVDECActiveForProcess(process->GetId())) {
std::this_thread::sleep_for(std::chrono::microseconds{600});
}
}
}
void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) {
-2
View File
@@ -107,8 +107,6 @@ protected:
Core::System& system;
/// Identifier string used to connect to the service.
const char* service_name;
/// Whether this is the IStorage service.
const bool is_i_storage;
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker;
/// Maximum number of concurrent sessions that this service can handle.
+39 -9
View File
@@ -82,23 +82,27 @@ std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, File
return nullptr;
}
if (type == FileType::NSP) {
if (type == FileType::NSP || type == FileType::NSZ) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
if (type == FileType::XCI) {
if (type == FileType::XCI || type == FileType::XCZ) {
FileSys::XCI xci{file, program_id, program_index};
if (xci.GetStatus() != ResultStatus::Success) {
return nullptr;
if (xci.GetStatus() == ResultStatus::Success) {
auto secure_nsp = xci.GetSecurePartitionNSP();
if (secure_nsp != nullptr && secure_nsp->GetStatus() == ResultStatus::Success) {
return secure_nsp;
}
}
auto secure_nsp = xci.GetSecurePartitionNSP();
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
return nullptr;
// PFS0-shaped .xcz (NSZ-style container) does not parse as XCI; fall back to NSP.
if (type == FileType::XCZ) {
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
}
return secure_nsp;
return nullptr;
}
return nullptr;
@@ -144,7 +148,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
bool IsContainerType(FileType type) {
return type == FileType::NSP || type == FileType::XCI;
return type == FileType::NSP || type == FileType::NSZ || type == FileType::XCI ||
type == FileType::XCZ;
}
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
@@ -180,8 +185,12 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA;
else if (extension == "xci")
return FileType::XCI;
else if (extension == "xcz")
return FileType::XCZ;
else if (extension == "nsp")
return FileType::NSP;
else if (extension == "nsz")
return FileType::NSZ;
else if (extension == "kip")
return FileType::KIP;
return FileType::Unknown;
@@ -197,10 +206,14 @@ std::string GetFileTypeString(FileType type) {
return "NCA";
case FileType::XCI:
return "XCI";
case FileType::XCZ:
return "XCZ";
case FileType::NAX:
return "NAX";
case FileType::NSP:
return "NSP";
case FileType::NSZ:
return "NSZ";
case FileType::KIP:
return "KIP";
case FileType::DeconstructedRomDirectory:
@@ -326,12 +339,29 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
system.GetContentProvider(), program_id,
program_index);
// NCZ-compressed XCI container. XCI-shaped .xcz goes to the XCI loader, while
// PFS0-shaped (NSZ-style) .xcz containers fall back to the NSP loader.
case FileType::XCZ: {
FileSys::XCI probe{file, program_id, program_index};
if (probe.GetStatus() == ResultStatus::Success &&
probe.GetSecurePartitionNSP() != nullptr) {
return std::make_unique<AppLoader_XCI>(std::move(file),
system.GetFileSystemController(),
system.GetContentProvider(), program_id,
program_index);
}
return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
system.GetContentProvider(), program_id,
program_index);
}
// NX NAX (NintendoAesXts) file format.
case FileType::NAX:
return std::make_unique<AppLoader_NAX>(std::move(file));
// NX NSP (Nintendo Submission Package) file format
case FileType::NSP:
case FileType::NSZ:
return std::make_unique<AppLoader_NSP>(std::move(file), system.GetFileSystemController(),
system.GetContentProvider(), program_id,
program_index);
+2
View File
@@ -42,7 +42,9 @@ enum class FileType {
NRO,
NCA,
NSP,
NSZ,
XCI,
XCZ,
NAX,
KIP,
DeconstructedRomDirectory,
+11
View File
@@ -56,6 +56,17 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
// .nsz is NSP-compatible; return NSZ so the game list shows the right type.
if (nsp_file->GetName().ends_with(".nsz")) {
return FileType::NSZ;
}
// PFS0-shaped .xcz containers are handled by this loader too; report XCZ so the game
// list does not show them as NSP. XCI-shaped .xcz is identified by AppLoader_XCI.
if (nsp_file->GetName().ends_with(".xcz")) {
return FileType::XCZ;
}
const FileSys::NSP nsp(nsp_file);
if (nsp.GetStatus() != ResultStatus::Success) {
+2 -1
View File
@@ -54,7 +54,8 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
// Bootability is handled by AppLoader_XCI::Load().
if (xci.GetSecurePartitionNSP() != nullptr) {
return FileType::XCI;
return xci_file->GetName().ends_with(".xcz") ? FileType::XCZ
: FileType::XCI;
}
return FileType::Error;
+13 -8
View File
@@ -739,7 +739,7 @@ struct Memory::Impl {
PAddr last_address;
};
void InvalidateGPUMemory(u8* p, size_t size) {
bool TryInvalidateGPUMemory(const u8* p, size_t size) {
constexpr size_t sys_core = Core::Hardware::NUM_CPU_CORES - 1;
const size_t core = (std::min)(system.GetCurrentHostThreadID(),
sys_core); // any other calls threads go to syscore.
@@ -755,9 +755,13 @@ struct Memory::Impl {
sys_core_guard.unlock();
}
};
bool has_pending_writes = false;
auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer(
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size, true); });
p, scratch_buffers[core], [&](DAddr address) { has_pending_writes |= gpu.MustFlushRegion(address, size); });
if (has_pending_writes) return false;
gpu_device_memory->ApplyOpOnPointer(p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
return true;
}
Core::System& system;
@@ -961,7 +965,7 @@ void Memory::MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug)
impl->MarkRegionDebug(GetInteger(vaddr), size, debug);
}
bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
NCEAccessFaultAction Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
[[maybe_unused]] bool mapped = true;
[[maybe_unused]] bool rasterizer = false;
@@ -973,17 +977,18 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
mapped = false;
},
[&] { rasterizer = true; });
if (rasterizer) {
impl->InvalidateGPUMemory(ptr, size);
}
#ifdef __ANDROID__
if (!rasterizer && mapped) {
impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr));
}
#endif
return mapped && ptr != nullptr;
if (rasterizer) {
if (!impl->TryInvalidateGPUMemory(ptr, size)) {
return NCEAccessFaultAction::Instruction;
}
}
return mapped && ptr != nullptr ? NCEAccessFaultAction::Retry : NCEAccessFaultAction::Unmapped;
}
bool Memory::InvalidateSeparateHeap(void* fault_address) {
+8 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -46,6 +46,12 @@ constexpr std::size_t YUZU_PAGEBITS = 12;
constexpr u64 YUZU_PAGESIZE = 1ULL << YUZU_PAGEBITS;
constexpr u64 YUZU_PAGEMASK = YUZU_PAGESIZE - 1;
enum class NCEAccessFaultAction {
Unmapped,
Retry,
Instruction,
};
/// Virtual user-space memory regions
enum : u64 {
/// TLS (Thread-Local Storage) related.
@@ -493,7 +499,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
NCEAccessFaultAction InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
bool InvalidateSeparateHeap(void* fault_address);
+74 -16
View File
@@ -4,8 +4,11 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cctype>
#include <locale>
#include "common/hex_util.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/arm/debug.h"
#include "core/core.h"
@@ -28,11 +31,11 @@ constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
std::size_t start_index, char match) {
auto end_index = start_index;
while (data[end_index] != match) {
while (end_index < data.size() && data[end_index] != match) {
++end_index;
if (end_index > data.size()) {
return {};
}
}
if (end_index == data.size()) {
return {};
}
out_name_size = end_index - start_index;
@@ -130,6 +133,11 @@ void StandardVmCallbacks::CommandLog(std::string_view data) {
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
}
bool StandardVmCallbacks::IsCommandLogEnabled() const {
return Common::Log::IsMessageEnabled(Common::Log::Class::CheatEngine,
Common::Log::Level::Debug);
}
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
if ((in < metadata.main_nso_extents.base ||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
@@ -166,8 +174,9 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
if (data[i] == '{') {
current_entry = 0;
out[*current_entry].is_master = true;
if (out[*current_entry].definition.num_opcodes > 0) {
if (!out[*current_entry].definition.opcodes.empty()) {
return {};
}
@@ -203,32 +212,33 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
'\0';
i += name_size + 1;
} else if (::isxdigit(data[i])) {
if (!current_entry || out[*current_entry].definition.num_opcodes >=
out[*current_entry].definition.opcodes.size()) {
} else if (std::isxdigit(static_cast<unsigned char>(data[i]))) {
if (!current_entry || data.size() - i < 8) {
return {};
}
const auto hex = std::string(data.substr(i, 8));
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
if (!std::all_of(hex.begin(), hex.end(), [](char value) {
return std::isxdigit(static_cast<unsigned char>(value));
})) {
return {};
}
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
out[*current_entry].definition.opcodes.push_back(value);
i += 8;
i += 7;
} else {
return {};
}
}
out[0].enabled = out[0].definition.num_opcodes > 0;
out[0].enabled = out[0].is_master && !out[0].definition.opcodes.empty();
out[0].cheat_id = 0;
for (u32 i = 1; i < out.size(); ++i) {
out[i].enabled = out[i].definition.num_opcodes > 0;
out[i].enabled = Settings::values.enable_cheats_by_default.GetValue() &&
!out[i].definition.opcodes.empty();
out[i].cheat_id = i;
}
@@ -240,6 +250,10 @@ CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
metadata.main_nso_build_id = build_id_;
for (u32 i = 0; i < cheats.size(); ++i) {
cheats[i].cheat_id = i;
cheats[i].enabled = cheats[i].is_master || cheats[i].enabled;
}
}
CheatEngine::~CheatEngine() {
@@ -286,12 +300,56 @@ void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_regi
}
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
cheats = std::move(reload_cheats);
is_pending_reload.exchange(true);
{
std::scoped_lock lock{cheats_mutex};
cheats = std::move(reload_cheats);
for (u32 i = 0; i < cheats.size(); ++i) {
cheats[i].cheat_id = i;
cheats[i].enabled = cheats[i].is_master || cheats[i].enabled;
}
}
is_pending_reload.store(true);
}
std::vector<RuntimeCheatInfo> CheatEngine::GetCheats() const {
std::scoped_lock lock{cheats_mutex};
std::vector<RuntimeCheatInfo> result;
result.reserve(cheats.size());
for (const auto& cheat : cheats) {
if (cheat.definition.opcodes.empty()) {
continue;
}
result.push_back({
.id = cheat.cheat_id,
.name = cheat.definition.readable_name.data(),
.enabled = cheat.enabled || cheat.is_master,
.is_master = cheat.is_master,
});
}
return result;
}
bool CheatEngine::SetCheatEnabled(u32 cheat_id, bool enabled) {
std::scoped_lock lock{cheats_mutex};
const auto entry = std::find_if(cheats.begin(), cheats.end(), [cheat_id](const auto& cheat) {
return cheat.cheat_id == cheat_id && !cheat.definition.opcodes.empty();
});
if (entry == cheats.end()) {
return false;
}
if (entry->is_master) {
return enabled;
}
if (entry->enabled != enabled) {
entry->enabled = enabled;
is_pending_reload.store(true);
}
return true;
}
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
std::scoped_lock lock{cheats_mutex};
vm.LoadProgram(cheats);
}
+5
View File
@@ -9,6 +9,7 @@
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <vector>
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"
@@ -41,6 +42,7 @@ public:
void ResumeProcess() override;
void DebugLog(u8 id, u64 value) override;
void CommandLog(std::string_view data) override;
[[nodiscard]] bool IsCommandLogEnabled() const override;
private:
bool IsAddressInRange(VAddr address) const;
@@ -81,6 +83,8 @@ public:
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
void Reload(std::vector<CheatEntry> reload_cheats);
[[nodiscard]] std::vector<RuntimeCheatInfo> GetCheats() const;
bool SetCheatEnabled(u32 cheat_id, bool enabled);
private:
void FrameCallback(std::chrono::nanoseconds ns_late);
@@ -89,6 +93,7 @@ private:
CheatProcessMetadata metadata;
std::vector<CheatEntry> cheats;
mutable std::mutex cheats_mutex;
std::atomic_bool is_pending_reload{false};
std::shared_ptr<Core::Timing::EventType> event;
+15 -2
View File
@@ -1,8 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#include "common/common_types.h"
namespace Core::Memory {
@@ -24,14 +30,21 @@ struct CheatProcessMetadata {
struct CheatDefinition {
std::array<char, 0x40> readable_name{};
u32 num_opcodes{};
std::array<u32, 0x100> opcodes{};
std::vector<u32> opcodes;
};
struct CheatEntry {
bool enabled{};
bool is_master{};
u32 cheat_id{};
CheatDefinition definition{};
};
struct RuntimeCheatInfo {
u32 id{};
std::string name;
bool enabled{};
bool is_master{};
};
} // namespace Core::Memory
+36 -25
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -233,7 +236,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// Helper function for getting instruction dwords.
const auto GetNextDword = [&] {
if (instruction_ptr >= num_opcodes) {
if (instruction_ptr >= program.size()) {
valid = false;
return static_cast<u32>(0);
}
@@ -709,20 +712,20 @@ void DmntCheatVm::ResetState() {
}
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
// Reset opcode count.
num_opcodes = 0;
program.clear();
for (std::size_t i = 0; i < entries.size(); i++) {
if (entries[i].enabled) {
// Bounds check.
if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
num_opcodes = 0;
return false;
}
std::size_t program_size{};
for (const auto& entry : entries) {
if (entry.enabled || entry.is_master) {
program_size += entry.definition.opcodes.size();
}
}
program.reserve(program_size);
for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
program[num_opcodes++] = entries[i].definition.opcodes[n];
}
for (const auto& entry : entries) {
if (entry.enabled || entry.is_master) {
program.insert(program.end(), entry.definition.opcodes.begin(),
entry.definition.opcodes.end());
}
}
@@ -735,27 +738,35 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
// Get Keys down.
u64 kDown = callbacks->HidKeysDown();
callbacks->CommandLog("Started VM execution.");
callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
const bool command_log_enabled = callbacks->IsCommandLogEnabled();
if (command_log_enabled) {
callbacks->CommandLog("Started VM execution.");
callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(
fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
}
// Clear VM state.
ResetState();
// Loop until program finishes.
while (DecodeNextOpcode(cur_opcode)) {
callbacks->CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
if (command_log_enabled) {
callbacks->CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
}
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i,
registers[i]));
}
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i,
saved_values[i]));
}
LogOpcode(cur_opcode);
}
LogOpcode(cur_opcode);
// Increment conditional depth, if relevant.
if (cur_opcode.begin_conditional_block) {
+4 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -281,9 +281,9 @@ public:
virtual void DebugLog(u8 id, u64 value) = 0;
virtual void CommandLog(std::string_view data) = 0;
[[nodiscard]] virtual bool IsCommandLogEnabled() const = 0;
};
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
static constexpr std::size_t NumRegisters = 0x10;
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
@@ -294,7 +294,7 @@ public:
~DmntCheatVm();
std::size_t GetProgramSize() const {
return this->num_opcodes;
return program.size();
}
bool LoadProgram(const std::vector<CheatEntry>& cheats);
@@ -303,11 +303,10 @@ public:
private:
std::unique_ptr<Callbacks> callbacks;
std::size_t num_opcodes = 0;
std::size_t instruction_ptr = 0;
std::size_t condition_depth = 0;
bool decode_success = false;
std::array<u32, MaximumProgramOpcodeCount> program{};
std::vector<u32> program;
std::array<u64, NumRegisters> registers{};
std::array<u64, NumRegisters> saved_values{};
std::array<u64, NumStaticRegisters> static_registers{};
+4 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
@@ -163,7 +163,9 @@ inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vf
std::shared_ptr<FileSys::NSP> nsp;
FileSys::VirtualFile file = vfs.OpenFile(filename, FileSys::OpenMode::Read);
if (boost::to_lower_copy(file->GetName()).ends_with("nsp")) {
const auto lower_name = boost::to_lower_copy(file->GetName());
if (lower_name.ends_with("nsp") || lower_name.ends_with("nsz")) {
nsp = std::make_shared<FileSys::NSP>(file);
if (nsp->IsExtractedType()) {
return InstallResult::Failure;
+4 -4
View File
@@ -86,6 +86,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS "
"without affecting game speed (animations, physics, etc.).\n"
"Can help reduce stuttering at lower framerates."));
INSERT(Settings, enable_cheats_by_default, tr("Enable cheats by default"),
tr("Enables regular cheats when a game starts. Master Code entries are always enabled. "
"Individual cheats can still be toggled from Cheats in the menu bar while the game is "
"running."));
// Cpu
INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
@@ -194,10 +198,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"),
tr("Skips certain cache invalidations during memory updates, reducing CPU usage and "
"improving latency. This may cause soft-crashes."));
INSERT(Settings, nce_invalidation_gpu_readback,
tr("GPU Readback Before NCE Invalidation"),
tr("Flushes GPU buffer data back to guest memory before NCE invalidates the whole "
"guest page, preserving it while the CPU store proceeds."));
INSERT(Settings, vsync_mode, tr("VSync Mode:"),
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
+7 -3
View File
@@ -360,7 +360,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
if (target == ScanTarget::PopulateGameList &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::XCZ ||
file_type == Loader::FileType::NSP || file_type == Loader::FileType::NSZ) &&
!Loader::IsBootableGameContainer(file, file_type)) {
return true;
}
@@ -375,7 +376,9 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
program_id, file);
} else if (Settings::values.ext_content_from_game_dirs.GetValue() &&
(file_type == Loader::FileType::XCI ||
file_type == Loader::FileType::NSP)) {
file_type == Loader::FileType::XCZ ||
file_type == Loader::FileType::NSP ||
file_type == Loader::FileType::NSZ)) {
void(provider->AddEntriesFromContainer(file));
}
} else {
@@ -402,7 +405,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
};
if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::XCZ ||
file_type == Loader::FileType::NSP || file_type == Loader::FileType::NSZ)) {
for (const auto id : program_ids) {
// dravee suggested this, only viable way to
// not show sub-games in qlaunch for now.
+2 -1
View File
@@ -57,7 +57,8 @@ std::unique_ptr<EmuThread> emu_thread = nullptr;
const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"),
QStringLiteral("nca"), QStringLiteral("xci"),
QStringLiteral("nsp"), QStringLiteral("kip")};
QStringLiteral("xcz"), QStringLiteral("nsp"),
QStringLiteral("nsz"), QStringLiteral("kip")};
Core::Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
@@ -6,7 +6,6 @@
#include <boost/container/static_vector.hpp>
#include "common/settings.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
@@ -590,11 +589,6 @@ Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value&
Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
&EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type,
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
#ifdef __ANDROID__
if (!is_integer && Settings::values.fix_bloom_effects.GetValue()) {
result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f));
}
#endif
return is_integer ? ctx.OpBitcast(ctx.F32[4], result) : result;
}
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <functional>
#include <optional>
#include <type_traits>
#include <queue>
@@ -13,9 +17,10 @@
namespace Shader::IR {
template <typename Pred>
auto BreadthFirstSearch(const Value& value, Pred&& pred)
template <typename Pred, typename Resolve = std::identity>
auto BreadthFirstSearch(Value value, Pred&& pred, Resolve&& resolve = {})
-> std::invoke_result_t<Pred, const Inst*> {
value = resolve(value);
if (value.IsImmediate()) {
// Nothing to do with immediates
return std::nullopt;
@@ -36,7 +41,7 @@ auto BreadthFirstSearch(const Value& value, Pred&& pred)
}
// Visit the right most arguments first
for (size_t arg = inst->NumArgs(); arg--;) {
const Value arg_value{inst->Arg(arg)};
const Value arg_value{resolve(inst->Arg(arg))};
if (arg_value.IsImmediate()) {
continue;
}
+71 -16
View File
@@ -31,6 +31,60 @@ struct TextureInst {
using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
const IR::Block* ConditionalHeader(const IR::Block* body) {
const auto predecessors{body->ImmPredecessors()};
const auto successors{body->ImmSuccessors()};
if (predecessors.size() != 1 || successors.size() != 1) return nullptr;
const IR::Block* const header{predecessors[0]};
const auto targets{header->ImmSuccessors()};
if (targets.size() != 2 || targets[0] != body || targets[1] != successors[0] ||
targets[1]->ImmPredecessors().size() != 2 || header->empty() ||
header->back().GetOpcode() != IR::Opcode::ConditionRef) {
return nullptr;
}
return header;
}
std::pair<IR::Value, bool> Condition(IR::Value value) {
bool negated{};
while (const IR::Inst* inst = value.TryInstRecursive()) {
if (inst->GetOpcode() == IR::Opcode::LogicalNot) negated = !negated;
else if (inst->GetOpcode() != IR::Opcode::ConditionRef) break;
value = inst->Arg(0);
}
return {value.Resolve(), negated};
}
IR::Value ResolveConditionalPhi(IR::Value value, const IR::Block* block) {
if (!value.Resolve().IsPhi() || !(block = ConditionalHeader(block))) return value;
const auto [condition, negated]{Condition(block->back().Arg(0))};
boost::container::small_vector<const IR::Block*, 16> visited;
while (value.Resolve().IsPhi() && std::ranges::find(visited, block) == visited.end()) {
visited.push_back(block);
const IR::Inst* const phi{value.InstRecursive()};
const auto predecessors{block->ImmPredecessors()};
if (predecessors.size() == 1) {
block = predecessors[0];
continue;
}
if (predecessors.size() != 2 || phi->NumArgs() != 2) break;
const IR::Block* body{predecessors[0]};
const IR::Block* header{ConditionalHeader(body)};
if (header != predecessors[1]) {
body = predecessors[1];
header = ConditionalHeader(body);
if (header != predecessors[0]) break;
}
const auto [other_condition, other_negated]{Condition(header->back().Arg(0))};
if (condition == other_condition && (phi->PhiBlock(0) == header || phi->PhiBlock(0) == body)) {
const IR::Block* const incoming{negated == other_negated ? body : header};
value = phi->Arg(phi->PhiBlock(0) == incoming ? 0 : 1);
}
block = header;
}
return value;
}
constexpr u32 DESCRIPTOR_SIZE = 8;
constexpr u32 DESCRIPTOR_SIZE_SHIFT = u32(std::countr_zero(DESCRIPTOR_SIZE));
constexpr u32 DESCRIPTOR_MAX_COUNT = 1024;
@@ -299,23 +353,23 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
}
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info) {
if (const IR::Inst* key = v.InstRecursive()) {
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block);
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
if (const IR::Inst* key = v.TryInstRecursive()) {
if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second;
auto found = Track(v, env, host_info);
auto found = Track(v, env, host_info, block);
if (found) env.track_cache.emplace(key, *found);
return found;
}
return Track(v, env, host_info);
return Track(v, env, host_info, block);
}
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info);
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block);
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info) {
return IR::BreadthFirstSearch(value, [&env, &host_info](const IR::Inst* inst) {
return TryGetConstBuffer(inst, env, host_info);
});
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
return IR::BreadthFirstSearch(value, [&env, &host_info, block](const IR::Inst* inst) {
return TryGetConstBuffer(inst, env, host_info, block);
}, [&](IR::Value operand) { return ResolveConditionalPhi(operand, block); });
}
std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
@@ -339,13 +393,13 @@ std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
return ReadCbufCached(env, index_number, offset_number);
}
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info) {
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) {
switch (inst->GetOpcode()) {
default:
return std::nullopt;
case IR::Opcode::BitwiseOr32: {
std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
std::optional rhs{TrackCached(inst->Arg(1), env, host_info)};
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
std::optional rhs{TrackCached(inst->Arg(1), env, host_info, block)};
if (!lhs || !rhs) {
return std::nullopt;
}
@@ -375,7 +429,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
if (!shift.IsImmediate()) {
return std::nullopt;
}
std::optional lhs{TrackCached(inst->Arg(0), env, host_info)};
std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)};
if (lhs) {
lhs->shift_left = shift.U32();
}
@@ -403,7 +457,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
return std::nullopt;
} while (false);
}
std::optional lhs{TrackCached(op1, env, host_info)};
std::optional lhs{TrackCached(op1, env, host_info, block)};
if (lhs) {
lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
}
@@ -469,7 +523,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) {
ConstBufferAddr addr;
if (IsBindless(inst)) {
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info)};
const std::optional<ConstBufferAddr> track_addr{TrackCached(inst.Arg(0), env, host_info, block)};
if (!track_addr) {
throw NotImplementedException("Failed to track bindless texture constant buffer");
@@ -671,6 +725,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
env.track_cache.clear();
TextureInstVector to_replace;
for (IR::Block* const block : program.post_order_blocks) {
env.track_cache.clear();
for (IR::Inst& inst : block->Instructions()) {
if (!IsTextureInstruction(inst)) {
continue;
+68 -29
View File
@@ -125,7 +125,8 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
}
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
const DAddr page_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE);
memory_tracker.MarkRegionAsCpuModified(device_addr, size,IsRegionGpuModified(page_start, Common::AlignUp(device_addr + size, DEVICE_PAGESIZE) - page_start));
}
template <class P>
@@ -153,7 +154,7 @@ bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
if (!is_dirty) {
return false;
}
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
if (IsRegionGpuModified(device_addr, size)) {
return true;
}
WriteMemory(device_addr, size);
@@ -1738,36 +1739,74 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
template <class P>
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) {
upload_copies.clear();
u64 total_size_bytes = 0;
u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached;
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 range_size = end - start;
upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = start - buffer_start,
.size = range_size,
#ifdef __ANDROID__
const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue();
#else
constexpr bool use_optimized_vertex_buffers = true;
#endif
if (use_optimized_vertex_buffers) {
upload_copies.clear();
u64 total_size_bytes = 0;
u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached;
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 range_size = end - start;
upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = start - buffer_start,
.size = range_size,
});
total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, range_size);
};
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
DAddr upload_start = device_addr_out;
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
add_upload(upload_start, gpu_start);
upload_start = gpu_end;
});
add_upload(upload_start, device_addr_out + range_size);
});
total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, range_size);
};
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
DAddr upload_start = device_addr_out;
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
add_upload(upload_start, gpu_start);
upload_start = gpu_end;
if (total_size_bytes == 0) {
return true;
}
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
any_buffer_uploaded = true;
return false;
} else {
boost::container::small_vector<BufferCopy, 4> copies;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
DAddr buffer_start = buffer.CpuAddr();
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
const auto add_upload = [&](DAddr start, DAddr end) {
if (start == end) return;
const u64 upload_size = end - start;
copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = start - buffer_start,
.size = upload_size,
});
total_size_bytes += upload_size;
largest_copy = (std::max)(largest_copy, upload_size);
};
DAddr upload_start = device_addr_out;
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) {
add_upload(upload_start, gpu_start);
upload_start = gpu_end;
});
add_upload(upload_start, device_addr_out + range_size);
});
add_upload(upload_start, device_addr_out + range_size);
});
if (total_size_bytes == 0) {
return true;
if (total_size_bytes == 0) {
return true;
}
const std::span<BufferCopy> copies_span(copies.data(), copies.size());
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
return false;
}
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
any_buffer_uploaded = true;
return false;
}
template <class P>
@@ -71,9 +71,9 @@ public:
}
/// Mark region as CPU modified, notifying the device_tracker about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size, bool keep_tracking = false) {
IteratePages<true>(dirty_cpu_addr, query_size, [keep_tracking](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size, keep_tracking);
});
}
+5 -4
View File
@@ -115,19 +115,20 @@ struct WordManager {
/// @param enable If enabling or disabling
/// @param dirty_addr Base address to mark or unmark as modified
/// @param size Size in bytes to mark or unmark as modified
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
/// @param keep_tracking Keep CPU-modified pages protected for subsequent native writes.
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size, bool keep_tracking = false) noexcept {
std::span<u64> state_words = Span(type);
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
CollectChangedRanges(!enable || keep_tracking, index, untracked_words[index], mask, ranges);
}
if (enable) {
state_words[index] |= mask;
if (type == Type::CPU || type == Type::CachedCPU)
untracked_words[index] |= mask;
untracked_words[index] = (untracked_words[index] & ~mask) | (keep_tracking ? u64{0} : mask);
if (type == Type::CPU)
cached_words[index] &= ~mask;
} else {
@@ -139,7 +140,7 @@ struct WordManager {
}
});
if (!ranges.empty()) {
ApplyCollectedRanges(ranges, (!enable) ? 1 : -1);
ApplyCollectedRanges(ranges, (!enable || keep_tracking) ? 1 : -1);
}
}
+11 -15
View File
@@ -226,19 +226,7 @@ struct GPU::Impl {
}
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
if (Settings::values.nce_invalidation_gpu_readback.GetValue()) {
VideoCore::RasterizerInterface* rasterizer = renderer->ReadRasterizer();
if (preserve_gpu_writes && rasterizer->MustFlushRegion(addr, size, VideoCommon::CacheType::BufferCache)) {
const u64 fence = RequestSyncOperation([rasterizer, addr, size] {
rasterizer->FlushRegion(addr, size, VideoCommon::CacheType::BufferCache);
rasterizer->OnCacheInvalidation(addr, size);
});
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(fence);
return;
}
}
void InvalidateRegion(DAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
@@ -246,6 +234,10 @@ struct GPU::Impl {
return renderer->ReadRasterizer()->OnCPUWrite(addr, size);
}
bool MustFlushRegion(DAddr addr, u64 size) {
return renderer->ReadRasterizer()->MustFlushRegion(addr, size);
}
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size, is_async);
@@ -530,14 +522,18 @@ void GPU::FlushRegion(DAddr addr, u64 size) {
impl->FlushRegion(addr, size);
}
void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
impl->InvalidateRegion(addr, size, preserve_gpu_writes);
void GPU::InvalidateRegion(DAddr addr, u64 size) {
impl->InvalidateRegion(addr, size);
}
bool GPU::OnCPUWrite(DAddr addr, u64 size) {
return impl->OnCPUWrite(addr, size);
}
bool GPU::MustFlushRegion(DAddr addr, u64 size) {
return impl->MustFlushRegion(addr, size);
}
void GPU::FlushAndInvalidateRegion(DAddr addr, u64 size) {
impl->FlushAndInvalidateRegion(addr, size);
}
+4 -1
View File
@@ -248,12 +248,15 @@ public:
void FlushRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes = false);
void InvalidateRegion(DAddr addr, u64 size);
/// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
/// sensible, false otherwise, addr and size must be a valid combination
bool OnCPUWrite(DAddr addr, u64 size);
/// Check whether this range still has GPU writes that must be flushed to CPU memory.
bool MustFlushRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
void FlushAndInvalidateRegion(DAddr addr, u64 size);
-1
View File
@@ -30,7 +30,6 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
thread = std::jthread([&](std::stop_token stop_token) {
Common::SetCurrentThreadName("GPU");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadToPerformanceCores();
system.RegisterHostThread();
auto current_context = context.Acquire();

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