mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-28 15:31:12 +02:00
Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14cbcc2438 | |||
| 0a73fb5229 | |||
| 254250a83d | |||
| 83c727794e | |||
| a6c610cf26 | |||
| 0dafab4053 | |||
| 40b2fd0e6b | |||
| 610e9eb516 | |||
| d835cab32d | |||
| 46c2a81f4a | |||
| 1ebdef1a02 | |||
| bd80d603e2 | |||
| 76bb5fc18b | |||
| b1821cb60e | |||
| b1cc417c55 | |||
| 70620c245c | |||
| 8389a98648 | |||
| 7fe40a37ef | |||
| e05c74108f | |||
| cd7414814a | |||
| dfe1f39a4b | |||
| 3f1f20185c | |||
| 1845d5b222 | |||
| 1240268048 | |||
| a134ad3fbd | |||
| da30efbc55 | |||
| 2dbca791f6 | |||
| 74248bd35a | |||
| e715925d52 | |||
| 852b8e176f | |||
| b2b07abbc8 | |||
| ce2f2187bd | |||
| 8c077fc4cd | |||
| b88ca5b635 | |||
| 4755ec7a59 | |||
| e20875d8fc | |||
| 58bff28b04 | |||
| 570cd64c03 | |||
| b0a607539a | |||
| 64163f2d2a | |||
| 631ad933b2 | |||
| 6985aaf614 | |||
| d1466b0e9d | |||
| a02e8a5679 | |||
| e98280bee9 | |||
| 1d0a3e83fa | |||
| 4343874a69 | |||
| 11d5c0f96a | |||
| ca42578d4e | |||
| b7087a16ba | |||
| 1bdedc17ad | |||
| 32e63b5fa2 | |||
| 2e89bb890a | |||
| 842acf2c86 | |||
| 2d4c460011 | |||
| c42c67ab85 | |||
| 5bce1993e1 | |||
| 8c79a3a8de | |||
| ebb3cda782 | |||
| daf972b517 | |||
| cd25e61afa | |||
| 4a7aa1618a | |||
| 1aae75512b | |||
| 825b899bf8 | |||
| 73c16c3d45 | |||
| 124c97a88a | |||
| 02f9c5ffd2 | |||
| 042a10cf71 | |||
| d6a889828f | |||
| d06eb3f52f | |||
| 21c77d5dce | |||
| a80e0f10ba | |||
| ccb518dc05 | |||
| bb768ad570 | |||
| c028d925bc | |||
| 3557ff28b7 | |||
| 108bb3d28b | |||
| 5b12a7725d | |||
| 15d575aa31 | |||
| 47fe86be7b | |||
| 3db45f3c46 | |||
| 4481391474 | |||
| dee102cf92 | |||
| ad2f40b0e7 | |||
| d10080b757 | |||
| ce15cf7cd3 | |||
| 6b87b0052a | |||
| 9630da580d | |||
| 12fdd88a58 | |||
| 646aea7fbf | |||
| 186c0b0cc7 | |||
| ee1ffbaf2e | |||
| c931de0570 | |||
| 33f1fb1cf4 | |||
| aabc470314 | |||
| d82a6a273d | |||
| 4bb853d52a | |||
| 43ebdb1ffc | |||
| e310f0b151 | |||
| 9b915c8659 | |||
| 35ab33de6a | |||
| 5615ea9ced | |||
| d273fc4ad6 | |||
| e3e880e879 | |||
| 6b8115f27a | |||
| a24e7e8143 | |||
| 88b9393b44 | |||
| 2081d659d2 | |||
| 9eacaf2444 | |||
| c9eb764d2a | |||
| 72ced6b947 | |||
| ffae2350ca | |||
| 646542a397 | |||
| 1ae76d44c1 | |||
| 24d07ab28c | |||
| 651a999017 | |||
| 5c0e12fb0e | |||
| 712c505cd1 | |||
| 051522b54e | |||
| a1fdbef129 | |||
| ecd5c751f8 | |||
| 69678d02b6 | |||
| 8d031532d8 | |||
| c8e4818b01 | |||
| 2bac9cec32 | |||
| 6190fcaaef | |||
| 3272e1fcb5 | |||
| 8faeffdc7e | |||
| ddac8c8eb5 | |||
| c062931c9b | |||
| e4122dae1d | |||
| b75e81af5e | |||
| 2ed1328c93 | |||
| c70b857c4f | |||
| 23566a1f7d | |||
| 529b069499 | |||
| 9a07bd0570 | |||
| 05f6942bef | |||
| 70c1e9abed | |||
| 69e47bd2c0 | |||
| cdf9b556b2 | |||
| 7f5de6bcd6 | |||
| c682306788 | |||
| 9d2341eaea | |||
| d720a7b4b4 | |||
| 464212393e | |||
| ea209e6dab | |||
| b5f9f8b743 | |||
| a8fc994c0b | |||
| 2991bd18ef | |||
| 281d7a468e | |||
| 12f89745be | |||
| 7de5eb6884 | |||
| ac181b756f | |||
| 4a833e7206 | |||
| 06a08de68a | |||
| f8712e50e6 | |||
| fc63de39dd | |||
| bf25058dbd | |||
| 75104b6103 | |||
| 0d950195e9 | |||
| 04e88ab82c |
@@ -63,3 +63,4 @@ artifacts
|
||||
*.AppImage*
|
||||
/install*
|
||||
vulkansdk*.exe
|
||||
*.tar.zst
|
||||
|
||||
+7
-7
@@ -70,7 +70,7 @@ endif()
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
|
||||
# option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
|
||||
cmake_dependent_option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
|
||||
cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
|
||||
set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
|
||||
cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF)
|
||||
@@ -577,9 +577,9 @@ if (ENABLE_QT)
|
||||
|
||||
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
|
||||
|
||||
# if (YUZU_USE_QT_MULTIMEDIA)
|
||||
# find_package(Qt6 REQUIRED COMPONENTS Multimedia)
|
||||
# endif()
|
||||
if (YUZU_USE_QT_MULTIMEDIA)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
|
||||
endif()
|
||||
|
||||
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
|
||||
# yes Qt, we get it
|
||||
@@ -618,9 +618,9 @@ if (ENABLE_QT)
|
||||
if (PLATFORM_LINUX)
|
||||
list(APPEND YUZU_QT_COMPONENTS DBus)
|
||||
endif()
|
||||
# if (YUZU_USE_QT_MULTIMEDIA)
|
||||
# list(APPEND YUZU_QT_COMPONENTS Multimedia)
|
||||
# endif()
|
||||
if (YUZU_USE_QT_MULTIMEDIA)
|
||||
list(APPEND YUZU_QT_COMPONENTS Multimedia)
|
||||
endif()
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
|
||||
endif()
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# CMake Options
|
||||
|
||||
To change these options, add `-DOPTION_NAME=NEWVALUE` to the command line.
|
||||
|
||||
- On Qt Creator, go to Project -> Current Configuration
|
||||
|
||||
Notes:
|
||||
|
||||
- Defaults are marked per-platform.
|
||||
- "Non-UNIX" just means Windows/MSVC and Android (yes, macOS is UNIX
|
||||
- Android generally doesn't need to change anything; if you do, go to `src/android/app/build.gradle.kts`
|
||||
- To set a boolean variable to on, use `ON` for the value; to turn it off, use `OFF`
|
||||
- If a variable is mentioned as being e.g. "ON" for a specific platform(s), that means it is defaulted to OFF on others
|
||||
- TYPE is always boolean unless otherwise specified
|
||||
- Format:
|
||||
- `OPTION_NAME` (TYPE DEFAULT) DESCRIPTION
|
||||
|
||||
## Options
|
||||
|
||||
### Dependencies
|
||||
|
||||
These options control dependencies.
|
||||
|
||||
- `YUZU_USE_BUNDLED_FFMPEG` (ON for non-UNIX) Download a pre-built and configured FFmpeg
|
||||
- `YUZU_USE_EXTERNAL_FFMPEG` (ON for Solaris) Build FFmpeg from source
|
||||
- `YUZU_DOWNLOAD_ANDROID_VVL` (ON) Download validation layer binary for Android
|
||||
- `YUZU_DOWNLOAD_TIME_ZONE_DATA` (ON) Always download time zone binaries
|
||||
- Currently, build fails without this
|
||||
- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo)
|
||||
- `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib
|
||||
- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build
|
||||
- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source
|
||||
- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service
|
||||
- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental
|
||||
- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend
|
||||
- This option is subject for removal.
|
||||
- `YUZU_TESTS` (ON) Compile tests - requires Catch2
|
||||
- `ENABLE_LTO` (OFF) Enable link-time optimization
|
||||
- Not recommended on Windows
|
||||
- UNIX may be better off appending `-flto=thin` to compiler args
|
||||
- `USE_FASTER_LINKER` (OFF) Check if a faster linker is available
|
||||
- Not recommended outside of Linux
|
||||
|
||||
### Flavors
|
||||
|
||||
These options control executables and build flavors.
|
||||
|
||||
- `YUZU_LEGACY` (OFF): Apply patches to improve compatibility on some older GPUs at the cost of performance
|
||||
- `NIGHTLY_BUILD` (OFF): This is only used by CI. Do not use this unless you're making your own distribution and know what you're doing.
|
||||
- `YUZU_STATIC_BUILD` (OFF) Attempt to build using static libraries if possible
|
||||
- Not supported on Linux
|
||||
- Automatically set if `YUZU_USE_BUNDLED_QT` is on for non-Linux
|
||||
- `ENABLE_UPDATE_CHECKER` (OFF) Enable update checking functionality
|
||||
- `YUZU_DISABLE_LLVM` (OFF) Do not attempt to link to the LLVM demangler
|
||||
- Really only useful for CI or distribution builds
|
||||
|
||||
**Desktop only**:
|
||||
|
||||
- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli)
|
||||
- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable
|
||||
- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality
|
||||
- `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable
|
||||
- This is only usable on Alpine Linux.
|
||||
|
||||
### Desktop
|
||||
|
||||
The following options are desktop only.
|
||||
|
||||
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED)
|
||||
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend
|
||||
- Unavailable on Windows/ARM64
|
||||
- You probably shouldn't turn this off.
|
||||
|
||||
### Qt
|
||||
|
||||
Also desktop-only, but apply strictly to Qt
|
||||
|
||||
- `ENABLE_QT` (ON) Enable the Qt frontend (recommended)
|
||||
- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend
|
||||
- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries
|
||||
- Not recommended on Linux. For Windows and macOS, the provided build is statically linked.
|
||||
- `YUZU_QT_MIRROR` (string) What mirror to use for downloading the bundled Qt libraries
|
||||
- `YUZU_USE_QT_MULTIMEDIA` (OFF) Use QtMultimedia for camera support
|
||||
- `YUZU_USE_QT_WEB_ENGINE` (OFF) Use QtWebEngine for web applet implementation (requires the huge QtWebEngine dependency; not recommended)
|
||||
- `USE_DISCORD_PRESENCE` (OFF) Enables Discord Rich Presence (Qt frontend only)
|
||||
|
||||
### Retired Options
|
||||
|
||||
The following options were a part of Eden at one point, but have since been retired.
|
||||
|
||||
- `ENABLE_OPENSSL` - MbedTLS was fully replaced with OpenSSL in [#3606](https://git.eden-emu.dev/eden-emu/eden/pulls/3606), because OpenSSL straight-up performs better.
|
||||
- `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort.
|
||||
- `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now.
|
||||
- If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`.
|
||||
|
||||
See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed
|
||||
@@ -3,6 +3,7 @@
|
||||
This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more.
|
||||
|
||||
- **[General Build Instructions](Build.md)**
|
||||
- **[CMake Options](Options.md)**
|
||||
- **[Cross Compiling](CrossCompile.md)**
|
||||
- **[Development Guidelines](Development.md)**
|
||||
- **[Dependencies](Deps.md)**
|
||||
|
||||
+23
-4
@@ -18,7 +18,7 @@ First of all, you MUST have a valid reason to sign up for our Git. Valid reasons
|
||||
The following are not valid reasons to sign up:
|
||||
|
||||
- I want to contribute to Eden.
|
||||
* Be at least somewhat specific!
|
||||
* Be at least somewhat specific! We always welcome contributors and developers, but generic "I want to contribute" messages don't give us enough information.
|
||||
- I want to support Eden.
|
||||
* If you wish to support us through development, be more specific; otherwise, to support us, check out our [donations page](https://eden-emu.dev/donations).
|
||||
- I want to report issues.
|
||||
@@ -41,17 +41,36 @@ Email: <Your Desired Email>
|
||||
I wish to sign up because... <your reason here>
|
||||
```
|
||||
|
||||
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email.
|
||||
Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email. Do note that the email you sign up with is used to accredit commits on the web UI, and *must* match your configured GPG key.
|
||||
|
||||
## Patches
|
||||
|
||||
In general, PRs are the preferred method of tracking patches, as they allow us to go through our standard triage, CI, and testing process without having to deal with the minutiae of incremental patches. However, we also understand that many people prefer to use raw patches, and that's totally okay! While we currently don't have a mailing list, we do accept email patches. To do so:
|
||||
|
||||
1. Make your changes on a clean copy of the master branch
|
||||
2. Commit your changes with a descriptive, well-formed message (see the [commit message docs](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/Development.md#pull-requests)), and a proper description thoroughly explaining your changes.
|
||||
* Note that we don't need to know all the individual details about your code. A description explaining the motivation and general implementation of your changes is enough, alongside caveats and any potential blockers.
|
||||
3. Format your patch with `git format-patch -1 HEAD`.
|
||||
4. Email us with the subject `[Eden] [PATCH] <brief patch description...>`, with a brief description of your patch, and the previously-formatted patch file as an attachment.
|
||||
* If you don't include the first two bracketed parts, your email may be lost!
|
||||
|
||||
The following emails are currently set up to receive and process patches:
|
||||
|
||||
- [eden@eden-emu.dev](mailto:eden@eden-emu.dev]
|
||||
- [crueter@eden-emu.dev](mailto:eden@eden-emu.dev)
|
||||
|
||||
## Instructions
|
||||
|
||||
If you have read everything above and affirm that you will not abuse your access, click the summary below to get the email to send your request to.
|
||||
|
||||
<details>
|
||||
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam requests or the following email.</summary>
|
||||
<summary>I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam to the following email.</summary>
|
||||
|
||||
Email [crueter@crueter.xyz](mailto:crueter@crueter.xyz) with the format above.
|
||||
|
||||
Once your request is processed, you should receive a confirmation email from crueter with your password alongside a link to a repository containing instructions on SSH, etc. Note that you are required to change your password. If your request is rejected, you will receive a notice as such, asking for clarification if needed. If you do not receive a response in 48 hours, you may send another email.
|
||||
|
||||
</details>
|
||||
> [!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/Junk folder, until Google/Microsoft finally end their vendetta against the great evil of my `.xyz` domain.
|
||||
|
||||
</details>
|
||||
|
||||
+7
-2
@@ -17,7 +17,9 @@ AI is notorious for hallucinating facts out of thin air and sometimes outright l
|
||||
- Code that works, but is extraordinarily verbose or not nearly as efficient as it can be
|
||||
- Code that works well and is written well, but solves a different problem than was intended, or solves the same problem but in a completely incorrect way that will break other things horribly.
|
||||
|
||||
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone both familiar with the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar. Keep in mind: all code is held under a standard of excellence. If your code sucks, it will be rejected. AI-generated code just so happens to be a particularly sucky genre of code, and will thus be held to this same standard.
|
||||
Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone familiar with *both* the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar.
|
||||
|
||||
**All code is held under a STRICT STANDARD OF EXCELLENCE**. AI code is no different, and since it often produces subpar or outright terrible code, it will often fail to meet this excellence standard.
|
||||
|
||||
On a lesser-known note, LLM outputs often contain unicode symbols such as emojis or the arrow symbol. Please don't put Unicode symbols in your code. It messes with many an IDE, and the three people viewing your code on Lynx will be very unhappy.
|
||||
|
||||
@@ -25,7 +27,8 @@ On a lesser-known note, LLM outputs often contain unicode symbols such as emojis
|
||||
|
||||
## Acceptable Use
|
||||
|
||||
- As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
|
||||
As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**.
|
||||
|
||||
- Anything directly outside of the realm of the code written in your PR or patch is none of our business.
|
||||
- This primarily covers research.
|
||||
- However, we *still* strongly discourage this for the reasons mentioned above.
|
||||
@@ -104,4 +107,6 @@ This consolidates profile removal behavior, fixes potential race conditions in t
|
||||
|
||||
This has all of the same problems as the other one. Needlessly verbose, doesn't address *what* it actually fixes ("consolidates profile removal behavior"... okay, why? What does it fix?), etc. It even has the bonus of totally hallucinating the addition of a method!
|
||||
|
||||
On a more "philosophical" note, LLMs tend to be geared towards *corporate language*, as that's what they're trained on. This is why AI-generated commit messages feel like "word salad", and typically pad out the commit message to make it *look* like a lot of things were changed (trust me, it's like that in the corporate world). They typically also drift towards unneeded buzzwords and useless implementation details.
|
||||
|
||||
**Don't use AI for commit messages**.
|
||||
|
||||
@@ -9,7 +9,7 @@ pkgs.mkShellNoCC {
|
||||
# libraries
|
||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||
enet libopus vulkan-headers vulkan-utility-libraries
|
||||
spirv-tools spirv-headers vulkan-loader unzip mbedtls
|
||||
spirv-tools spirv-headers vulkan-loader unzip
|
||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||
libusb1 cubeb
|
||||
# eden
|
||||
|
||||
@@ -80,7 +80,6 @@ android {
|
||||
listOf(
|
||||
"-DENABLE_QT=0", // Don't use QT
|
||||
"-DENABLE_WEB_SERVICE=1", // Enable web service
|
||||
"-DENABLE_OPENSSL=ON",
|
||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||
"-DYUZU_USE_CPM=ON",
|
||||
"-DCPMUTIL_FORCE_BUNDLED=ON",
|
||||
|
||||
@@ -152,6 +152,10 @@ object NativeLibrary {
|
||||
|
||||
external fun surfaceDestroyed()
|
||||
|
||||
external fun getAppletCaptureBuffer(): ByteArray
|
||||
external fun getAppletCaptureWidth(): Int
|
||||
external fun getAppletCaptureHeight(): Int
|
||||
|
||||
/**
|
||||
* Unpauses emulation from a paused state.
|
||||
*/
|
||||
@@ -603,6 +607,12 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun addFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Adds a game-folder file to the manual filesystem provider, respecting the internal gate for
|
||||
* game-folder external-content mounting.
|
||||
*/
|
||||
external fun addGameFolderFileToFilesystemProvider(path: String)
|
||||
|
||||
/**
|
||||
* Clears all files added to the manual filesystem provider in our EmulationSession instance
|
||||
*/
|
||||
|
||||
@@ -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: 2023 yuzu Emulator Project
|
||||
@@ -204,9 +204,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
nfcReader.stopScanning()
|
||||
stopMotionSensorListener()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -339,6 +339,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (!NativeLibrary.isRunning() || NativeLibrary.isPaused()) {
|
||||
return
|
||||
}
|
||||
|
||||
val rotation = this.display?.rotation
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
flipMotionOrientation = true
|
||||
|
||||
+15
-2
@@ -1,10 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.fetcher
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
|
||||
@@ -15,8 +16,20 @@ class SpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecorat
|
||||
state: RecyclerView.State
|
||||
) {
|
||||
outRect.bottom = spacing
|
||||
if (parent.getChildAdapterPosition(view) == 0) {
|
||||
|
||||
val position = parent.getChildAdapterPosition(view)
|
||||
if (position == RecyclerView.NO_POSITION) return
|
||||
|
||||
if (position == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
|
||||
// If the item is in the first row, but NOT in first column add top spacing as well
|
||||
val layoutManager = parent.layoutManager
|
||||
if (layoutManager is GridLayoutManager && layoutManager.spanSizeLookup.getSpanGroupIndex(position, layoutManager.spanCount) == 0) {
|
||||
outRect.top = spacing
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -30,8 +30,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
BUFFER_REORDER_DISABLE("disable_buffer_reorder"),
|
||||
RENDERER_DEBUG("debug"),
|
||||
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
|
||||
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||
|
||||
@@ -34,6 +34,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
MAX_ANISOTROPY("max_anisotropy"),
|
||||
THEME("theme"),
|
||||
THEME_MODE("theme_mode"),
|
||||
STATIC_THEME_COLOR("static_theme_color"),
|
||||
APP_LANGUAGE("app_language"),
|
||||
OVERLAY_SCALE("control_scale"),
|
||||
OVERLAY_OPACITY("control_opacity"),
|
||||
|
||||
-32
@@ -141,20 +141,6 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.dynaStateValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_PROVOKING_VERTEX,
|
||||
titleId = R.string.provoking_vertex,
|
||||
descriptionId = R.string.provoking_vertex_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE,
|
||||
titleId = R.string.vertex_input_dynamic_state,
|
||||
descriptionId = R.string.vertex_input_dynamic_state_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_DESCRIPTOR_INDEXING,
|
||||
@@ -349,15 +335,6 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.astcDecodingMethodValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ASTC_RECOMPRESSION,
|
||||
titleId = R.string.astc_recompression,
|
||||
descriptionId = R.string.astc_recompression_description,
|
||||
choicesId = R.array.astcRecompressionMethodNames,
|
||||
valuesId = R.array.astcRecompressionMethodValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_VRAM_USAGE_MODE,
|
||||
@@ -670,15 +647,6 @@ abstract class SettingsItem(
|
||||
valuesId = R.array.dmaAccuracyValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.FRAME_PACING_MODE,
|
||||
titleId = R.string.frame_pacing_mode,
|
||||
descriptionId = R.string.frame_pacing_mode_description,
|
||||
choicesId = R.array.framePacingModeNames,
|
||||
valuesId = R.array.framePacingModeValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
|
||||
+16
-26
@@ -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: 2023 yuzu Emulator Project
|
||||
@@ -68,7 +68,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
when (val item = settingsViewModel.clickedItem) {
|
||||
val item = settingsViewModel.clickedItem ?: return@setPositiveButton
|
||||
clearDialogState()
|
||||
when (item) {
|
||||
is AnalogInputSetting -> {
|
||||
val stickParam = NativeInput.getStickParam(
|
||||
item.playerIndex,
|
||||
@@ -107,12 +109,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
}
|
||||
|
||||
else -> {
|
||||
settingsViewModel.clickedItem!!.setting.reset()
|
||||
item.setting.reset()
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface, _: Int ->
|
||||
clearDialogState()
|
||||
}
|
||||
.setOnCancelListener {
|
||||
clearDialogState()
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
@@ -186,27 +193,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
updateButtonState(isValid)
|
||||
}
|
||||
|
||||
/*
|
||||
* xbzk: these two events, along with attachRepeat feature,
|
||||
* were causing spinbox buttons to respond twice per press
|
||||
* cutting these out to retain accelerated press functionality
|
||||
* TODO: clean this out later if no issues arise
|
||||
*
|
||||
spinboxBinding.buttonDecrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current - 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
|
||||
spinboxBinding.buttonIncrement.setOnClickListener {
|
||||
val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
|
||||
val newValue = current + 1
|
||||
spinboxBinding.editValue.setText(newValue.toString())
|
||||
updateValidity(newValue)
|
||||
}
|
||||
*/
|
||||
|
||||
fun attachRepeat(button: View, delta: Int) {
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
var runnable: Runnable? = null
|
||||
@@ -439,9 +425,13 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
|
||||
private fun closeDialog() {
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
clearDialogState()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun clearDialogState() {
|
||||
settingsViewModel.clickedItem = null
|
||||
settingsViewModel.setSliderProgress(-1f)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
|
||||
|
||||
+14
-14
@@ -267,11 +267,9 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.DMA_ACCURACY.key)
|
||||
add(IntSetting.FRAME_PACING_MODE.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_VRAM_USAGE_MODE.key)
|
||||
add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key)
|
||||
add(IntSetting.RENDERER_ASTC_RECOMPRESSION.key)
|
||||
|
||||
add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key)
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
@@ -291,8 +289,6 @@ class SettingsFragmentPresenter(
|
||||
add(HeaderSetting(R.string.extensions))
|
||||
|
||||
add(IntSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
|
||||
@@ -1066,7 +1062,10 @@ class SettingsFragmentPresenter(
|
||||
IntSetting.THEME.getValueAsString()
|
||||
|
||||
override val defaultValue: Int = IntSetting.THEME.defaultValue
|
||||
override fun reset() = IntSetting.THEME.setInt(defaultValue)
|
||||
override fun reset() {
|
||||
IntSetting.THEME.setInt(defaultValue)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
}
|
||||
|
||||
add(HeaderSetting(R.string.app_settings))
|
||||
@@ -1124,23 +1123,24 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
val staticThemeColor: AbstractIntSetting = object : AbstractIntSetting {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(
|
||||
YuzuApplication.appContext
|
||||
)
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
IntSetting.STATIC_THEME_COLOR.getInt(needsGlobal)
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, value) }
|
||||
IntSetting.STATIC_THEME_COLOR.setInt(value)
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
|
||||
override val key: String = Settings.PREF_STATIC_THEME_COLOR
|
||||
override val key: String = IntSetting.STATIC_THEME_COLOR.key
|
||||
override val isRuntimeModifiable: Boolean = true
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0).toString()
|
||||
override val defaultValue: Any = 0
|
||||
IntSetting.STATIC_THEME_COLOR.getValueAsString(needsGlobal)
|
||||
|
||||
override val defaultValue: Any = IntSetting.STATIC_THEME_COLOR.defaultValue
|
||||
|
||||
override fun reset() {
|
||||
preferences.edit() { putInt(Settings.PREF_STATIC_THEME_COLOR, 0) }
|
||||
IntSetting.STATIC_THEME_COLOR.reset()
|
||||
settingsViewModel.setShouldRecreate(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.BatteryManager
|
||||
import android.os.BatteryManager.*
|
||||
@@ -97,6 +98,7 @@ import org.yuzu.yuzu_emu.utils.collect
|
||||
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.or
|
||||
@@ -141,6 +143,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
private var wasInputOverlayAutoHidden = false
|
||||
private var overlayTouchActive = false
|
||||
private var pausedFrameBitmap: Bitmap? = null
|
||||
|
||||
var shouldUseCustom = false
|
||||
private var isQuickSettingsMenuOpen = false
|
||||
@@ -703,6 +706,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_quick_settings)?.isVisible =
|
||||
BooleanSetting.ENABLE_QUICK_SETTINGS.getBoolean()
|
||||
|
||||
binding.pausedIcon.setOnClickListener {
|
||||
if (this::emulationState.isInitialized && emulationState.isPaused) {
|
||||
resumeEmulationFromUi()
|
||||
}
|
||||
}
|
||||
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
|
||||
val lockMode = IntSetting.LOCK_DRAWER.getInt()
|
||||
val titleId = if (lockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
|
||||
@@ -728,11 +737,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
when (it.itemId) {
|
||||
R.id.menu_pause_emulation -> {
|
||||
if (emulationState.isPaused) {
|
||||
emulationState.run(false)
|
||||
updatePauseMenuEntry(false)
|
||||
resumeEmulationFromUi()
|
||||
} else {
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
pauseEmulationAndCaptureFrame()
|
||||
}
|
||||
binding.inGameMenu.requestFocus()
|
||||
true
|
||||
@@ -826,6 +833,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
R.id.menu_exit -> {
|
||||
clearPausedFrame()
|
||||
emulationState.stop()
|
||||
NativeConfig.reloadGlobalConfig()
|
||||
emulationViewModel.setIsEmulationStopping(true)
|
||||
@@ -1197,6 +1205,71 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
private fun pauseEmulationAndCaptureFrame() {
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
capturePausedFrameFromCore()
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
private fun capturePausedFrameFromCore() {
|
||||
lifecycleScope.launch(Dispatchers.Default) {
|
||||
val frameData = NativeLibrary.getAppletCaptureBuffer()
|
||||
val width = NativeLibrary.getAppletCaptureWidth()
|
||||
val height = NativeLibrary.getAppletCaptureHeight()
|
||||
if (frameData.isEmpty() || width <= 0 || height <= 0) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Paused frame capture returned empty/invalid data. " +
|
||||
"size=${frameData.size}, width=$width, height=$height"
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val expectedSize = width * height * 4
|
||||
if (frameData.size < expectedSize) {
|
||||
Log.warning(
|
||||
"[EmulationFragment] Paused frame buffer smaller than expected. " +
|
||||
"size=${frameData.size}, expected=$expectedSize"
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(frameData, 0, expectedSize))
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
pausedFrameBitmap?.recycle()
|
||||
pausedFrameBitmap = bitmap
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePausedFrameVisibility() {
|
||||
val b = _binding ?: return
|
||||
val showPausedUi = this::emulationState.isInitialized && emulationState.isPaused
|
||||
b.pausedIcon.setVisible(showPausedUi)
|
||||
|
||||
val bitmap = if (showPausedUi) pausedFrameBitmap else null
|
||||
b.pausedFrameImage.setImageBitmap(bitmap)
|
||||
b.pausedFrameImage.setVisible(bitmap != null)
|
||||
}
|
||||
|
||||
private fun resumeEmulationFromUi() {
|
||||
clearPausedFrame()
|
||||
emulationState.resume()
|
||||
updatePauseMenuEntry(emulationState.isPaused)
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
private fun clearPausedFrame() {
|
||||
val b = _binding
|
||||
b?.pausedFrameImage?.setVisible(false)
|
||||
b?.pausedFrameImage?.setImageDrawable(null)
|
||||
pausedFrameBitmap?.recycle()
|
||||
pausedFrameBitmap = null
|
||||
}
|
||||
|
||||
private fun handleLoadAmiiboSelection(): Boolean {
|
||||
val binding = _binding ?: return true
|
||||
|
||||
@@ -1290,8 +1363,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
override fun onPause() {
|
||||
if (this::emulationState.isInitialized) {
|
||||
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
||||
emulationState.pause()
|
||||
updatePauseMenuEntry(true)
|
||||
pauseEmulationAndCaptureFrame()
|
||||
} else {
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
}
|
||||
super.onPause()
|
||||
@@ -1301,6 +1375,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
super.onDestroyView()
|
||||
amiiboLoadJob?.cancel()
|
||||
amiiboLoadJob = null
|
||||
clearPausedFrame()
|
||||
_binding?.surfaceInputOverlay?.touchEventListener = null
|
||||
_binding = null
|
||||
isAmiiboPickerOpen = false
|
||||
@@ -1321,6 +1396,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
b.inGameMenu.post {
|
||||
if (!this::emulationState.isInitialized || _binding == null) return@post
|
||||
updatePauseMenuEntry(emulationState.isPaused)
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1760,6 +1836,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
// Only update surface reference, don't trigger state changes
|
||||
emulationState.updateSurfaceReference(holder.surface)
|
||||
}
|
||||
updatePausedFrameVisibility()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
@@ -2090,6 +2167,29 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun resume() {
|
||||
if (state != State.PAUSED) {
|
||||
Log.warning("[EmulationFragment] Resume called while emulation is not paused.")
|
||||
return
|
||||
}
|
||||
if (!emulationCanStart.invoke()) {
|
||||
Log.warning("[EmulationFragment] Resume blocked by emulationCanStart check.")
|
||||
return
|
||||
}
|
||||
val currentSurface = surface
|
||||
if (currentSurface == null || !currentSurface.isValid) {
|
||||
Log.debug("[EmulationFragment] Resume requested with invalid surface.")
|
||||
return
|
||||
}
|
||||
|
||||
NativeLibrary.surfaceChanged(currentSurface)
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.unpauseEmulation()
|
||||
NativeLibrary.playTimeManagerStart()
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun changeProgram(programIndex: Int) {
|
||||
emulationThread.join()
|
||||
@@ -2111,7 +2211,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
||||
@Synchronized
|
||||
fun updateSurface() {
|
||||
if (surface != null) {
|
||||
if (surface != null && state == State.RUNNING) {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
}
|
||||
}
|
||||
@@ -2127,20 +2227,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
@Synchronized
|
||||
fun clearSurface() {
|
||||
if (surface == null) {
|
||||
Log.warning("[EmulationFragment] clearSurface called, but surface already null.")
|
||||
Log.debug("[EmulationFragment] clearSurface called, but surface already null.")
|
||||
} else {
|
||||
if (state == State.RUNNING) {
|
||||
pause()
|
||||
}
|
||||
NativeLibrary.surfaceDestroyed()
|
||||
surface = null
|
||||
Log.debug("[EmulationFragment] Surface destroyed.")
|
||||
when (state) {
|
||||
State.RUNNING -> {
|
||||
state = State.PAUSED
|
||||
}
|
||||
|
||||
State.PAUSED -> Log.warning(
|
||||
State.PAUSED -> Log.debug(
|
||||
"[EmulationFragment] Surface cleared while emulation paused."
|
||||
)
|
||||
|
||||
else -> Log.warning(
|
||||
else -> Log.debug(
|
||||
"[EmulationFragment] Surface cleared while emulation stopped."
|
||||
)
|
||||
}
|
||||
@@ -2148,29 +2248,35 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun runWithValidSurface(programIndex: Int = 0) {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
if (!emulationCanStart.invoke()) {
|
||||
return
|
||||
}
|
||||
val currentSurface = surface
|
||||
if (currentSurface == null || !currentSurface.isValid) {
|
||||
Log.debug("[EmulationFragment] runWithValidSurface called with invalid surface.")
|
||||
return
|
||||
}
|
||||
|
||||
when (state) {
|
||||
State.STOPPED -> {
|
||||
NativeLibrary.surfaceChanged(currentSurface)
|
||||
emulationThread = Thread({
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||
NativeLibrary.run(gamePath, programIndex, true)
|
||||
}, "NativeEmulation")
|
||||
emulationThread.start()
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
State.PAUSED -> {
|
||||
Log.debug("[EmulationFragment] Resuming emulation.")
|
||||
NativeLibrary.unpauseEmulation()
|
||||
NativeLibrary.playTimeManagerStart()
|
||||
Log.debug(
|
||||
"[EmulationFragment] Surface restored while emulation paused; " +
|
||||
"waiting for explicit resume."
|
||||
)
|
||||
}
|
||||
|
||||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.")
|
||||
}
|
||||
state = State.RUNNING
|
||||
}
|
||||
|
||||
private enum class State {
|
||||
|
||||
@@ -127,10 +127,6 @@ class AddonViewModel : ViewModel() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if there are multiple update versions
|
||||
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
|
||||
val hasMultipleUpdates = updates.size > 1
|
||||
|
||||
NativeConfig.setDisabledAddons(
|
||||
game!!.programId,
|
||||
_patchList.value.mapNotNull {
|
||||
@@ -140,7 +136,7 @@ class AddonViewModel : ViewModel() {
|
||||
if (PatchType.from(it.type) == PatchType.Update) {
|
||||
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
|
||||
it.name
|
||||
} else if (hasMultipleUpdates) {
|
||||
} else if (it.numericVersion != 0L) {
|
||||
"Update@${it.numericVersion}"
|
||||
} else {
|
||||
it.name
|
||||
|
||||
@@ -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
|
||||
|
||||
package org.yuzu.yuzu_emu.overlay
|
||||
@@ -20,7 +20,6 @@ import android.os.Looper
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.WindowInsets
|
||||
@@ -42,10 +41,10 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
/**
|
||||
* Draws the interactive input overlay on top of the
|
||||
* [SurfaceView] that is rendering emulation.
|
||||
* emulation rendering surface.
|
||||
*/
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
SurfaceView(context, attrs),
|
||||
View(context, attrs),
|
||||
OnTouchListener {
|
||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
|
||||
@@ -94,8 +94,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
||||
// Since Android 15, google automatically forces "games" to be 60 hrz
|
||||
// This ensures the display's max refresh rate is actually used
|
||||
display?.let {
|
||||
val supportedModes = it.supportedModes
|
||||
val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate }
|
||||
@@ -424,7 +422,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
)
|
||||
|
||||
val uriString = result.toString()
|
||||
val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
|
||||
val folder = gamesViewModel.folders.value.firstOrNull {
|
||||
it.uriString == uriString && it.type == org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT
|
||||
}
|
||||
if (folder != null) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
|
||||
@@ -61,6 +61,12 @@ object DirectoryInitialization {
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val staticThemeColor = preferences.migratePreference<Int>(Settings.PREF_STATIC_THEME_COLOR)
|
||||
if (staticThemeColor != null) {
|
||||
IntSetting.STATIC_THEME_COLOR.setInt(staticThemeColor)
|
||||
saveConfig = true
|
||||
}
|
||||
|
||||
val blackBackgrounds =
|
||||
preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
|
||||
if (blackBackgrounds != null) {
|
||||
|
||||
@@ -51,11 +51,24 @@ object GameHelper {
|
||||
|
||||
// Scan External Content directories and register all NSP/XCI files
|
||||
val externalContentDirs = NativeConfig.getExternalContentDirs()
|
||||
for (externalDir in externalContentDirs) {
|
||||
val uniqueExternalContentDirs = linkedSetOf<String>()
|
||||
externalContentDirs.forEach { externalDir ->
|
||||
if (externalDir.isNotEmpty()) {
|
||||
uniqueExternalContentDirs.add(externalDir)
|
||||
}
|
||||
}
|
||||
|
||||
val mountedContainerUris = mutableSetOf<String>()
|
||||
for (externalDir in uniqueExternalContentDirs) {
|
||||
if (externalDir.isNotEmpty()) {
|
||||
val externalDirUri = externalDir.toUri()
|
||||
if (FileUtil.isTreeUriValid(externalDirUri)) {
|
||||
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
|
||||
scanContentContainersRecursive(FileUtil.listFiles(externalDirUri), 3) {
|
||||
val containerUri = it.uri.toString()
|
||||
if (mountedContainerUris.add(containerUri)) {
|
||||
NativeLibrary.addFileToFilesystemProvider(containerUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,10 +78,13 @@ object GameHelper {
|
||||
val gameDirUri = gameDir.uriString.toUri()
|
||||
val isValid = FileUtil.isTreeUriValid(gameDirUri)
|
||||
if (isValid) {
|
||||
val scanDepth = if (gameDir.deepScan) 3 else 1
|
||||
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(gameDirUri),
|
||||
if (gameDir.deepScan) 3 else 1
|
||||
scanDepth,
|
||||
mountedContainerUris
|
||||
)
|
||||
} else {
|
||||
badDirs.add(index)
|
||||
@@ -103,9 +119,10 @@ object GameHelper {
|
||||
// be done better imo.
|
||||
private val externalContentExtensions = setOf("nsp", "xci")
|
||||
|
||||
private fun scanExternalContentRecursive(
|
||||
private fun scanContentContainersRecursive(
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int
|
||||
depth: Int,
|
||||
onContainerFound: (MinimalDocumentFile) -> Unit
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
@@ -113,14 +130,15 @@ object GameHelper {
|
||||
|
||||
files.forEach {
|
||||
if (it.isDirectory) {
|
||||
scanExternalContentRecursive(
|
||||
scanContentContainersRecursive(
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1
|
||||
depth - 1,
|
||||
onContainerFound
|
||||
)
|
||||
} else {
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
if (externalContentExtensions.contains(extension)) {
|
||||
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
|
||||
onContainerFound(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +147,8 @@ object GameHelper {
|
||||
private fun addGamesRecursive(
|
||||
games: MutableList<Game>,
|
||||
files: Array<MinimalDocumentFile>,
|
||||
depth: Int
|
||||
depth: Int,
|
||||
mountedContainerUris: MutableSet<String>
|
||||
) {
|
||||
if (depth <= 0) {
|
||||
return
|
||||
@@ -140,11 +159,20 @@ object GameHelper {
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(it.uri),
|
||||
depth - 1
|
||||
depth - 1,
|
||||
mountedContainerUris
|
||||
)
|
||||
} else {
|
||||
if (Game.extensions.contains(FileUtil.getExtension(it.uri))) {
|
||||
val game = getGame(it.uri, true)
|
||||
val extension = FileUtil.getExtension(it.uri).lowercase()
|
||||
val filePath = it.uri.toString()
|
||||
|
||||
if (externalContentExtensions.contains(extension) &&
|
||||
mountedContainerUris.add(filePath)) {
|
||||
NativeLibrary.addGameFolderFileToFilesystemProvider(filePath)
|
||||
}
|
||||
|
||||
if (Game.extensions.contains(extension)) {
|
||||
val game = getGame(it.uri, true, false)
|
||||
if (game != null) {
|
||||
games.add(game)
|
||||
}
|
||||
@@ -153,14 +181,20 @@ object GameHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun getGame(uri: Uri, addedToLibrary: Boolean): Game? {
|
||||
fun getGame(
|
||||
uri: Uri,
|
||||
addedToLibrary: Boolean,
|
||||
registerFilesystemProvider: Boolean = true
|
||||
): Game? {
|
||||
val filePath = uri.toString()
|
||||
if (!GameMetadata.getIsValid(filePath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
if (registerFilesystemProvider) {
|
||||
// Needed to update installed content information
|
||||
NativeLibrary.addFileToFilesystemProvider(filePath)
|
||||
}
|
||||
|
||||
var name = GameMetadata.getTitle(filePath)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
@@ -80,16 +80,14 @@ object PathUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// This really shouldn't be necessary, but the Android API seemingly
|
||||
// doesn't have a way of doing this?
|
||||
// Apparently, on certain devices the mount location can vary, so add
|
||||
// extra cases here if we discover any new ones.
|
||||
fun getRemovableStoragePath(idString: String): String? {
|
||||
var pathFile: File
|
||||
val possibleMountPaths = listOf("/mnt/media_rw/$idString", "/storage/$idString")
|
||||
|
||||
pathFile = File("/mnt/media_rw/$idString");
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
for (mountPath in possibleMountPaths) {
|
||||
val pathFile = File(mountPath);
|
||||
if (pathFile.exists()) {
|
||||
return pathFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
@@ -52,7 +52,7 @@ object ThemeHelper {
|
||||
}
|
||||
|
||||
private fun getSelectedStaticThemeColor(): Int {
|
||||
val themeIndex = preferences.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
|
||||
val themes = arrayOf(
|
||||
R.style.Theme_Eden_Main,
|
||||
R.style.Theme_Yuzu_Main_Violet,
|
||||
|
||||
@@ -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: 2023 yuzu Emulator Project
|
||||
@@ -11,8 +11,7 @@ import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
class GradientBorderCardView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@@ -44,12 +43,7 @@ class GradientBorderCardView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun updateThemeState() {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val themeIndex = try {
|
||||
prefs.getInt(Settings.PREF_STATIC_THEME_COLOR, 0)
|
||||
} catch (e: Exception) {
|
||||
0 // Default to Eden theme if error
|
||||
}
|
||||
val themeIndex = IntSetting.STATIC_THEME_COLOR.getInt(false)
|
||||
isEdenTheme = themeIndex == 0
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -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: 2023 yuzu Emulator Project
|
||||
@@ -27,10 +27,7 @@ if (ARCHITECTURE_arm64)
|
||||
target_link_libraries(yuzu-android PRIVATE adrenotools)
|
||||
endif()
|
||||
|
||||
if (ENABLE_OPENSSL OR ENABLE_WEB_SERVICE)
|
||||
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
|
||||
endif()
|
||||
|
||||
target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt)
|
||||
if (ENABLE_UPDATE_CHECKER)
|
||||
target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER)
|
||||
endif()
|
||||
|
||||
@@ -33,6 +33,12 @@ void AndroidConfig::ReadAndroidValues() {
|
||||
if (global) {
|
||||
ReadAndroidUIValues();
|
||||
ReadUIValues();
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
|
||||
Settings::values.ext_content_from_game_dirs = ReadBooleanSetting(
|
||||
std::string("ext_content_from_game_dirs"),
|
||||
std::make_optional(
|
||||
Settings::values.ext_content_from_game_dirs.GetDefault()));
|
||||
EndGroup();
|
||||
ReadOverlayValues();
|
||||
}
|
||||
ReadDriverValues();
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace AndroidSettings {
|
||||
|
||||
Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
|
||||
Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
|
||||
Settings::Setting<s32> static_theme_color{linkage, 5, "static_theme_color", Settings::Category::Android};
|
||||
Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
|
||||
Settings::Category::Android};
|
||||
Settings::Setting<s32> app_language{linkage, 0, "app_language", Settings::Category::Android};
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <android/native_window_jni.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "common/android/id_cache.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "input_common/drivers/android.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
@@ -14,6 +24,20 @@
|
||||
#include "jni/native.h"
|
||||
|
||||
void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
if (!surface) {
|
||||
LOG_INFO(Frontend, "EmuWindow_Android::OnSurfaceChanged received null surface");
|
||||
m_window_width = 0;
|
||||
m_window_height = 0;
|
||||
window_info.render_surface = nullptr;
|
||||
m_last_frame_rate_hint = -1.0f;
|
||||
m_pending_frame_rate_hint = -1.0f;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_smoothed_present_rate = 0.0f;
|
||||
m_last_frame_display_time = {};
|
||||
m_pending_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
m_window_width = ANativeWindow_getWidth(surface);
|
||||
m_window_height = ANativeWindow_getHeight(surface);
|
||||
|
||||
@@ -21,6 +45,7 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
|
||||
|
||||
window_info.render_surface = reinterpret_cast<void*>(surface);
|
||||
UpdateFrameRateHint();
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
|
||||
@@ -40,6 +65,9 @@ void EmuWindow_Android::OnTouchReleased(int id) {
|
||||
}
|
||||
|
||||
void EmuWindow_Android::OnFrameDisplayed() {
|
||||
UpdateObservedFrameRate();
|
||||
UpdateFrameRateHint();
|
||||
|
||||
if (!m_first_frame) {
|
||||
Common::Android::RunJNIOnFiber<void>(
|
||||
[&](JNIEnv* env) { EmulationSession::GetInstance().OnEmulationStarted(); });
|
||||
@@ -47,6 +75,175 @@ void EmuWindow_Android::OnFrameDisplayed() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_Android::UpdateObservedFrameRate() {
|
||||
const auto now = Clock::now();
|
||||
if (m_last_frame_display_time.time_since_epoch().count() != 0) {
|
||||
const auto frame_time = std::chrono::duration<float>(now - m_last_frame_display_time);
|
||||
const float seconds = frame_time.count();
|
||||
if (seconds > 0.0f) {
|
||||
const float instantaneous_rate = 1.0f / seconds;
|
||||
if (std::isfinite(instantaneous_rate) && instantaneous_rate >= 1.0f &&
|
||||
instantaneous_rate <= 240.0f) {
|
||||
constexpr float SmoothingFactor = 0.15f;
|
||||
if (m_smoothed_present_rate <= 0.0f) {
|
||||
m_smoothed_present_rate = instantaneous_rate;
|
||||
} else {
|
||||
m_smoothed_present_rate +=
|
||||
(instantaneous_rate - m_smoothed_present_rate) * SmoothingFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_last_frame_display_time = now;
|
||||
}
|
||||
|
||||
float EmuWindow_Android::QuantizeFrameRateHint(float frame_rate) {
|
||||
if (!std::isfinite(frame_rate) || frame_rate <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
frame_rate = std::clamp(frame_rate, 1.0f, 240.0f);
|
||||
|
||||
constexpr float Step = 0.5f;
|
||||
return std::round(frame_rate / Step) * Step;
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameTimeVerifiedHint() const {
|
||||
if (!EmulationSession::GetInstance().IsRunning()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const double frame_time_scale =
|
||||
EmulationSession::GetInstance().System().GetPerfStats().GetLastFrameTimeScale();
|
||||
if (!std::isfinite(frame_time_scale) || frame_time_scale <= 0.0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float verified_rate =
|
||||
std::clamp(60.0f / static_cast<float>(frame_time_scale), 0.0f, 240.0f);
|
||||
return QuantizeFrameRateHint(verified_rate);
|
||||
}
|
||||
|
||||
float EmuWindow_Android::GetFrameRateHint() const {
|
||||
const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f);
|
||||
const float frame_time_verified_hint = GetFrameTimeVerifiedHint();
|
||||
|
||||
if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) {
|
||||
const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f);
|
||||
if (std::fabs(observed_rate - m_last_frame_rate_hint) <= tolerance) {
|
||||
return m_last_frame_rate_hint;
|
||||
}
|
||||
}
|
||||
|
||||
const float observed_hint = QuantizeFrameRateHint(observed_rate);
|
||||
if (observed_hint > 0.0f) {
|
||||
if (frame_time_verified_hint > 0.0f) {
|
||||
const float tolerance = std::max(observed_hint * 0.20f, 3.0f);
|
||||
if (std::fabs(observed_hint - frame_time_verified_hint) <= tolerance) {
|
||||
return QuantizeFrameRateHint((observed_hint + frame_time_verified_hint) * 0.5f);
|
||||
}
|
||||
}
|
||||
return observed_hint;
|
||||
}
|
||||
|
||||
if (frame_time_verified_hint > 0.0f) {
|
||||
return frame_time_verified_hint;
|
||||
}
|
||||
|
||||
constexpr float NominalFrameRate = 60.0f;
|
||||
if (!Settings::values.use_speed_limit.GetValue()) {
|
||||
return NominalFrameRate;
|
||||
}
|
||||
|
||||
const u16 speed_limit = Settings::SpeedLimit();
|
||||
if (speed_limit == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const float speed_limited_rate =
|
||||
NominalFrameRate * (static_cast<float>(std::min<u16>(speed_limit, 100)) / 100.0f);
|
||||
return QuantizeFrameRateHint(speed_limited_rate);
|
||||
}
|
||||
|
||||
void EmuWindow_Android::UpdateFrameRateHint() {
|
||||
auto* const surface = reinterpret_cast<ANativeWindow*>(window_info.render_surface);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = Clock::now();
|
||||
const float frame_rate_hint = GetFrameRateHint();
|
||||
if (std::fabs(frame_rate_hint - m_last_frame_rate_hint) < 0.01f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = {};
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame_rate_hint == 0.0f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = now;
|
||||
} else if (m_last_frame_rate_hint >= 0.0f) {
|
||||
if (std::fabs(frame_rate_hint - m_pending_frame_rate_hint) >= 0.01f) {
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 1;
|
||||
m_pending_frame_rate_since = now;
|
||||
return;
|
||||
}
|
||||
|
||||
++m_pending_frame_rate_hint_votes;
|
||||
if (m_pending_frame_rate_since.time_since_epoch().count() == 0) {
|
||||
m_pending_frame_rate_since = now;
|
||||
}
|
||||
|
||||
const auto stable_for = now - m_pending_frame_rate_since;
|
||||
const float reference_rate = std::max(frame_rate_hint, 1.0f);
|
||||
const auto stable_duration = std::chrono::duration_cast<Clock::duration>(
|
||||
std::chrono::duration<float>(std::clamp(3.0f / reference_rate, 0.15f, 0.40f)));
|
||||
constexpr std::uint32_t MinStableVotes = 3;
|
||||
|
||||
if (m_pending_frame_rate_hint_votes < MinStableVotes || stable_for < stable_duration) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
m_pending_frame_rate_since = now;
|
||||
}
|
||||
|
||||
using SetFrameRateWithChangeStrategyFn =
|
||||
int32_t (*)(ANativeWindow*, float, int8_t, int8_t);
|
||||
using SetFrameRateFn = int32_t (*)(ANativeWindow*, float, int8_t);
|
||||
static const auto set_frame_rate_with_change_strategy =
|
||||
reinterpret_cast<SetFrameRateWithChangeStrategyFn>(
|
||||
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRateWithChangeStrategy"));
|
||||
static const auto set_frame_rate = reinterpret_cast<SetFrameRateFn>(
|
||||
dlsym(RTLD_DEFAULT, "ANativeWindow_setFrameRate"));
|
||||
|
||||
constexpr int8_t FrameRateCompatibilityDefault = 0;
|
||||
constexpr int8_t ChangeFrameRateOnlyIfSeamless = 0;
|
||||
|
||||
int32_t result = -1;
|
||||
if (set_frame_rate_with_change_strategy) {
|
||||
result = set_frame_rate_with_change_strategy(surface, frame_rate_hint,
|
||||
FrameRateCompatibilityDefault,
|
||||
ChangeFrameRateOnlyIfSeamless);
|
||||
} else if (set_frame_rate) {
|
||||
result = set_frame_rate(surface, frame_rate_hint, FrameRateCompatibilityDefault);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != 0) {
|
||||
LOG_DEBUG(Frontend, "Failed to update Android surface frame rate hint: {}", result);
|
||||
return;
|
||||
}
|
||||
|
||||
m_last_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint = frame_rate_hint;
|
||||
m_pending_frame_rate_hint_votes = 0;
|
||||
m_pending_frame_rate_since = {};
|
||||
}
|
||||
|
||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
|
||||
std::shared_ptr<Common::DynamicLibrary> driver_library)
|
||||
: m_driver_library{driver_library} {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
@@ -50,10 +55,24 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
void UpdateFrameRateHint();
|
||||
void UpdateObservedFrameRate();
|
||||
[[nodiscard]] float GetFrameRateHint() const;
|
||||
[[nodiscard]] float GetFrameTimeVerifiedHint() const;
|
||||
[[nodiscard]] static float QuantizeFrameRateHint(float frame_rate);
|
||||
|
||||
float m_window_width{};
|
||||
float m_window_height{};
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
|
||||
|
||||
bool m_first_frame = false;
|
||||
float m_last_frame_rate_hint = -1.0f;
|
||||
float m_pending_frame_rate_hint = -1.0f;
|
||||
float m_smoothed_present_rate = 0.0f;
|
||||
Clock::time_point m_last_frame_display_time{};
|
||||
Clock::time_point m_pending_frame_rate_since{};
|
||||
std::uint32_t m_pending_frame_rate_hint_votes = 0;
|
||||
};
|
||||
|
||||
@@ -96,6 +96,11 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobj
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) &&
|
||||
!Loader::IsBootableGameContainer(file, file_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
Loader::ResultStatus res = loader->ReadProgramId(program_id);
|
||||
if (res != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -89,6 +89,8 @@
|
||||
#include "jni/native.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/capture.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||
#include "video_core/shader_notify.h"
|
||||
@@ -215,107 +217,8 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
|
||||
|
||||
if (extension == "nsp") {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Meta) {
|
||||
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
|
||||
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto section0 = meta_nca->GetSubdirectories();
|
||||
if (!section0.empty()) {
|
||||
for (const auto& meta_file : section0[0]->GetFiles()) {
|
||||
if (meta_file->GetExtension() == "cnmt") {
|
||||
FileSys::CNMT cnmt(meta_file);
|
||||
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == FileSys::ContentRecordType::Control &&
|
||||
title_type == FileSys::TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = FileSys::ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
FileSys::NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type == FileSys::TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
std::string version_string;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
version_string = str_it->second;
|
||||
}
|
||||
|
||||
m_manual_provider->AddEntryWithVersion(
|
||||
title_type, content_type, title_id, version, version_string,
|
||||
nca->GetBaseFile());
|
||||
|
||||
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
|
||||
title_id, version, version_string);
|
||||
} else {
|
||||
// Use regular AddEntry for non-updates
|
||||
m_manual_provider->AddEntry(title_type, content_type, title_id,
|
||||
nca->GetBaseFile());
|
||||
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle XCI files
|
||||
if (extension == "xci") {
|
||||
FileSys::XCI xci{file};
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
const auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp) {
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_manual_provider->AddEntriesFromContainer(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
@@ -337,6 +240,13 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::ConfigureFilesystemProviderFromGameFolder(const std::string& filepath) {
|
||||
if (!Settings::values.ext_content_from_game_dirs.GetValue()) {
|
||||
return;
|
||||
}
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeSystem(bool reload) {
|
||||
if (!reload) {
|
||||
// Initialize logging system
|
||||
@@ -780,9 +690,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject i
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env, jobject instance) {
|
||||
ANativeWindow_release(EmulationSession::GetInstance().NativeWindow());
|
||||
if (auto* native_window = EmulationSession::GetInstance().NativeWindow(); native_window) {
|
||||
ANativeWindow_release(native_window);
|
||||
}
|
||||
EmulationSession::GetInstance().SetNativeWindow(nullptr);
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env, jobject instance,
|
||||
@@ -969,6 +880,40 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureBuffer(JNIEnv* env, jclass clazz) {
|
||||
using namespace VideoCore::Capture;
|
||||
|
||||
if (!EmulationSession::GetInstance().IsRunning()) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
const auto tiled = EmulationSession::GetInstance().System().GPU().GetAppletCaptureBuffer();
|
||||
if (tiled.size() < TiledSize) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
std::vector<u8> linear(LinearWidth * LinearHeight * BytesPerPixel);
|
||||
Tegra::Texture::UnswizzleTexture(linear, tiled, BytesPerPixel, LinearWidth, LinearHeight,
|
||||
LinearDepth, BlockHeight, BlockDepth);
|
||||
|
||||
auto buffer = env->NewByteArray(static_cast<jsize>(linear.size()));
|
||||
if (!buffer) {
|
||||
return env->NewByteArray(0);
|
||||
}
|
||||
|
||||
env->SetByteArrayRegion(buffer, 0, static_cast<jsize>(linear.size()),
|
||||
reinterpret_cast<const jbyte*>(linear.data()));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureWidth(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jint>(VideoCore::Capture::LinearWidth);
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_NativeLibrary_getAppletCaptureHeight(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jint>(VideoCore::Capture::LinearHeight);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
||||
jboolean reload) {
|
||||
// Initialize the emulated system.
|
||||
@@ -1572,6 +1517,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* e
|
||||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_addGameFolderFileToFilesystemProvider(
|
||||
JNIEnv* env, jobject jobj, jstring jpath) {
|
||||
EmulationSession::GetInstance().ConfigureFilesystemProviderFromGameFolder(
|
||||
Common::Android::GetJString(env, jpath));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) {
|
||||
EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,6 +49,7 @@ public:
|
||||
const Core::PerfStatsResults& PerfStats();
|
||||
int ShadersBuilding();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void ConfigureFilesystemProviderFromGameFolder(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
void SetAppletId(int applet_id);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#E6FFFFFF" />
|
||||
</shape>
|
||||
@@ -108,6 +108,22 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/paused_frame_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/paused_frame_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/input_container"
|
||||
android:layout_width="match_parent"
|
||||
@@ -142,6 +158,18 @@
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/paused_icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_white"
|
||||
android:contentDescription="@string/emulation_unpause"
|
||||
android:padding="14dp"
|
||||
android:src="@drawable/ic_play"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/black" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/show_stats_overlay_text"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
|
||||
@@ -506,8 +506,6 @@
|
||||
<string name="dyna_state">الحالة الديناميكية الموسعة</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>
|
||||
<string name="provoking_vertex">الرأس المثير</string>
|
||||
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">فهرسة الوصف</string>
|
||||
|
||||
@@ -488,8 +488,6 @@
|
||||
<string name="dyna_state">Úroveň EDS</string>
|
||||
<string name="dyna_state_description">Určuje počet funkcí využívaných v rámci rozšířeného dynamického stavu API Vulkan (Extended Dynamic State). Vyšší hodnoty umožňují využít více funkcí a mohou zvýšit výkon, ale u některých ovladačů a výrobců grafických karet mohou způsobovat problémy s kompatibilitou.</string>
|
||||
<string name="disabled">Vypnuto</string>
|
||||
<string name="vertex_input_dynamic_state">Dynamický stav vstupu vrcholů (Vertex Input)</string>
|
||||
<string name="vertex_input_dynamic_state_description">Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon.</string>
|
||||
<string name="provoking_vertex">Určující vrchol</string>
|
||||
<string name="provoking_vertex_description">Zlepšuje osvětlení a zpracování vrcholů v některých hrách. Podporováno pouze na GPU s API Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexování deskriptorů</string>
|
||||
|
||||
@@ -486,8 +486,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
|
||||
<string name="dyna_state">Erweiterter dynamischer Status</string>
|
||||
<string name="dyna_state_description">Steuert die Anzahl der Funktionen, die im \"Vertex Input Dynamic State\" werden können. Höhere Werte ermöglichen mehr Funktionen und können die Leistung steigern, können aber bei einigen Treibern und Anbietern zu Problemen führen.</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung.</string>
|
||||
<string name="provoking_vertex">Provokanter Vertex</string>
|
||||
<string name="provoking_vertex_description">Verbessert die Beleuchtung und die Vertex-Verarbeitung in einigen Spielen. Wird nur von GPUs mit Vulkan 1.0+ unterstützt.</string>
|
||||
<string name="descriptor_indexing">Deskriptor-Indizierung</string>
|
||||
|
||||
@@ -436,8 +436,6 @@
|
||||
<string name="renderer_asynchronous_shaders_description">Compile les shaders de manière asynchrone. Cela peut réduire les saccades mais peut aussi provoquer des problèmes graphiques.</string>
|
||||
<string name="dyna_state">État dynamique étendu</string>
|
||||
<string name="disabled">Désactivé</string>
|
||||
<string name="vertex_input_dynamic_state">État dynamique d\'entrée de sommet</string>
|
||||
<string name="vertex_input_dynamic_state_description">Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances.</string>
|
||||
<string name="provoking_vertex">Provoque des Vertex</string>
|
||||
<string name="provoking_vertex_description">Améliore l`éclairage et la gestion des vertex dans certains jeux. Pris en charge uniquement par les GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexation des descripteurs</string>
|
||||
|
||||
@@ -488,8 +488,6 @@
|
||||
<string name="dyna_state">Rozszerzony stan dynamiczny</string>
|
||||
<string name="dyna_state_description">Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe wartości pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować problemy z niektórymi sterownikami i u niektórych producentów.</string>
|
||||
<string name="disabled">Wyłączone</string>
|
||||
<string name="vertex_input_dynamic_state">Dynamiczny stan wejścia wierzchołków</string>
|
||||
<string name="vertex_input_dynamic_state_description">Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność.</string>
|
||||
<string name="provoking_vertex">Wierzchołek prowokujący</string>
|
||||
<string name="provoking_vertex_description">Poprawia oświetlenie i obsługę wierzchołków w niektórych grach. Obsługiwane tylko przez GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indeksowanie deskryptorów</string>
|
||||
|
||||
@@ -471,8 +471,6 @@
|
||||
<string name="renderer_asynchronous_shaders_description">Compila shaders de forma assíncrona. Isso pode reduzir engasgos, mas também pode introduzir falhas gráficas.</string>
|
||||
<string name="dyna_state">Extended Dynamic State</string>
|
||||
<string name="disabled">Desativado</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho.</string>
|
||||
<string name="provoking_vertex">Provoking Vertex</string>
|
||||
<string name="provoking_vertex_description">Vértice Provocante: Melhora a iluminação e o processamento de vértices em certos jogos. Suportado apenas em GPUs com Vulkan 1.0 ou superior.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
|
||||
@@ -498,8 +498,6 @@
|
||||
<string name="dyna_state">Расширенное динамическое состояние</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>
|
||||
<string name="provoking_vertex">Определяющая вершина</string>
|
||||
<string name="provoking_vertex_description">Улучшает освещение и обработку вершин в некоторых играх. Поддерживается только ГПУ с Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Индексирование дескрипторов</string>
|
||||
|
||||
@@ -502,8 +502,6 @@
|
||||
<string name="dyna_state">Розширений динамічний стан</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>
|
||||
<string name="provoking_vertex">Провокативна вершина</string>
|
||||
<string name="provoking_vertex_description">Покращує освітлення та взаємодію з вершинами у деяких іграх. Лише для ГП з підтримкою Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Індексація дескрипторів</string>
|
||||
|
||||
@@ -496,8 +496,6 @@
|
||||
<string name="dyna_state">扩展动态状态</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>
|
||||
<string name="provoking_vertex">引发顶点</string>
|
||||
<string name="provoking_vertex_description">改善某些游戏中的光照和顶点处理。仅支持Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
|
||||
@@ -467,8 +467,6 @@
|
||||
<string name="renderer_asynchronous_shaders_description">非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。</string>
|
||||
<string name="dyna_state">擴展動態狀態</string>
|
||||
<string name="disabled">已停用</string>
|
||||
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
|
||||
<string name="vertex_input_dynamic_state_description">啟用頂點輸入動態狀態以取得更佳的品質及性能</string>
|
||||
<string name="provoking_vertex">引發頂點</string>
|
||||
<string name="provoking_vertex_description">改善某些遊戲中的光照和頂點處理。僅支援Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
|
||||
@@ -632,14 +632,12 @@
|
||||
<item>@string/disabled</item>
|
||||
<item>ExtendedDynamicState 1</item>
|
||||
<item>ExtendedDynamicState 2</item>
|
||||
<item>ExtendedDynamicState 3</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="dynaStateValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="installKeysResults">
|
||||
|
||||
@@ -531,8 +531,6 @@
|
||||
<string name="dyna_state">Extended Dynamic State</string>
|
||||
<string name="dyna_state_description">Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
|
||||
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
|
||||
<string name="provoking_vertex">Provoking Vertex</string>
|
||||
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
@@ -1222,12 +1220,12 @@
|
||||
|
||||
<!-- Static Themes -->
|
||||
<string name="static_theme_color">Theme Color</string>
|
||||
<string name="eden_theme">Eden (Default)</string>
|
||||
<string name="eden_theme">Eden</string>
|
||||
<string name="violet">Violet</string>
|
||||
<string name="blue">Blue</string>
|
||||
<string name="cyan">Cyan</string>
|
||||
<string name="red">Red</string>
|
||||
<string name="green">Green</string>
|
||||
<string name="green">Green (Default)</string>
|
||||
<string name="yellow">Yellow</string>
|
||||
<string name="orange">Orange</string>
|
||||
<string name="pink">Pink</string>
|
||||
|
||||
+7
-10
@@ -453,7 +453,7 @@ struct Values {
|
||||
Category::RendererAdvanced};
|
||||
SwitchableSetting<AstcDecodeMode, true> accelerate_astc{linkage,
|
||||
#ifdef ANDROID
|
||||
AstcDecodeMode::Cpu,
|
||||
AstcDecodeMode::Gpu,
|
||||
#else
|
||||
AstcDecodeMode::Gpu,
|
||||
#endif
|
||||
@@ -557,6 +557,9 @@ struct Values {
|
||||
SwitchableSetting<bool> fix_bloom_effects{linkage, false, "fix_bloom_effects",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> rescale_hack{linkage, false, "rescale_hack",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
|
||||
Category::RendererHacks};
|
||||
|
||||
@@ -583,7 +586,7 @@ struct Values {
|
||||
|
||||
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
|
||||
#if defined (ANDROID) || defined (__APPLE__)
|
||||
ExtendedDynamicState::Disabled,
|
||||
ExtendedDynamicState::EDS1,
|
||||
#else
|
||||
ExtendedDynamicState::EDS2,
|
||||
#endif
|
||||
@@ -598,14 +601,6 @@ struct Values {
|
||||
Category::RendererExtensions,
|
||||
Specialization::Scalar};
|
||||
|
||||
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
|
||||
#if defined (ANDROID)
|
||||
false,
|
||||
#else
|
||||
true,
|
||||
#endif
|
||||
"vertex_input_dynamic_state", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
|
||||
|
||||
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
||||
@@ -753,6 +748,8 @@ struct Values {
|
||||
Category::DataStorage};
|
||||
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
||||
Category::DataStorage};
|
||||
Setting<bool> ext_content_from_game_dirs{linkage, true, "ext_content_from_game_dirs",
|
||||
Category::DataStorage};
|
||||
std::vector<std::string> external_content_dirs;
|
||||
|
||||
// Debugging
|
||||
|
||||
@@ -154,7 +154,7 @@ ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
|
||||
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
ENUM(GameListMode, TreeView, GridView);
|
||||
ENUM(SpeedMode, Standard, Turbo, Slow);
|
||||
|
||||
@@ -342,7 +342,6 @@ add_library(core STATIC
|
||||
hle/kernel/message_buffer.h
|
||||
hle/kernel/physical_core.cpp
|
||||
hle/kernel/physical_core.h
|
||||
hle/kernel/physical_memory.h
|
||||
hle/kernel/slab_helpers.h
|
||||
hle/kernel/svc.cpp
|
||||
hle/kernel/svc.h
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <numeric>
|
||||
#include <bit>
|
||||
#include "common/arm64/native_clock.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/literals.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/guest_context.h"
|
||||
@@ -46,8 +47,7 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
|
||||
|
||||
Patcher::~Patcher() = default;
|
||||
|
||||
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
const Kernel::CodeSet::Segment& code) {
|
||||
bool Patcher::PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code) {
|
||||
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
|
||||
const size_t image_size = program_image.size();
|
||||
|
||||
@@ -175,10 +175,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
const Kernel::CodeSet::Segment& code,
|
||||
Kernel::PhysicalMemory& program_image,
|
||||
EntryTrampolines* out_trampolines) {
|
||||
bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines) {
|
||||
const size_t patch_size = GetSectionSize();
|
||||
const size_t pre_patch_size = GetPreSectionSize();
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/k_typed_address.h"
|
||||
#include "core/hle/kernel/physical_memory.h"
|
||||
#include <utility>
|
||||
using ModuleID = std::array<u8, 32>; // NSO build ID
|
||||
struct PatchCacheKey {
|
||||
@@ -56,10 +55,8 @@ public:
|
||||
}
|
||||
explicit Patcher();
|
||||
~Patcher();
|
||||
bool PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
const Kernel::CodeSet::Segment& code);
|
||||
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
|
||||
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
|
||||
bool PatchText(std::span<const u8> program_image, const Kernel::CodeSet::Segment& code);
|
||||
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector<u8>& program_image, EntryTrampolines* out_trampolines);
|
||||
size_t GetSectionSize() const noexcept;
|
||||
size_t GetPreSectionSize() const noexcept;
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ struct CipherContext {
|
||||
static inline const std::string GetCipherName(Mode mode, u32 key_size) {
|
||||
std::string cipher;
|
||||
std::size_t effective_bits = key_size * 8;
|
||||
|
||||
switch (mode) {
|
||||
case Mode::CTR:
|
||||
cipher = "CTR";
|
||||
@@ -53,7 +52,6 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) {
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return fmt::format("AES-{}-{}", effective_bits, cipher);
|
||||
};
|
||||
|
||||
@@ -87,8 +85,7 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) {
|
||||
|
||||
// TODO: WHY TEMPLATE???????
|
||||
template <typename Key, std::size_t KeySize>
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
|
||||
: ctx(std::make_unique<CipherContext>()) {
|
||||
Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_unique<CipherContext>()) {
|
||||
|
||||
ctx->encryption_context = EVP_CIPHER_CTX_new();
|
||||
ctx->decryption_context = EVP_CIPHER_CTX_new();
|
||||
@@ -99,9 +96,7 @@ Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
ASSERT_MSG(ctx->encryption_context && ctx->decryption_context && ctx->cipher,
|
||||
"OpenSSL cipher context failed init!");
|
||||
|
||||
ASSERT(ctx->encryption_context && ctx->decryption_context && ctx->cipher && "OpenSSL cipher context failed init!");
|
||||
// now init ciphers
|
||||
ASSERT(EVP_CipherInit_ex2(ctx->encryption_context, ctx->cipher, key.data(), NULL, 1, NULL));
|
||||
ASSERT(EVP_CipherInit_ex2(ctx->decryption_context, ctx->cipher, key.data(), NULL, 0, NULL));
|
||||
@@ -165,8 +160,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* des
|
||||
template <typename Key, std::size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* dest,
|
||||
std::size_t sector_id, std::size_t sector_size, Op op) {
|
||||
ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
|
||||
|
||||
ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size.");
|
||||
for (std::size_t i = 0; i < size; i += sector_size) {
|
||||
SetIV(CalculateNintendoTweak(sector_id++));
|
||||
Transcode(src + i, sector_size, dest + i, op);
|
||||
@@ -177,8 +171,7 @@ template <typename Key, std::size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::SetIV(std::span<const u8> data) {
|
||||
const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1);
|
||||
const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1);
|
||||
|
||||
ASSERT_MSG(ret_enc == 1 && ret_dec == 1, "Failed to set IV on OpenSSL contexts");
|
||||
ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts");
|
||||
}
|
||||
|
||||
template class AESCipher<Key128>;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace Crypto {} // namespace Crypto
|
||||
@@ -1,19 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "key_manager.h"
|
||||
#include "mbedtls/cipher.h"
|
||||
|
||||
namespace Crypto {
|
||||
typedef std::array<u8, 0x20> SHA256Hash;
|
||||
|
||||
inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
|
||||
if (len != 0x40)
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Crypto
|
||||
@@ -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
|
||||
|
||||
@@ -160,15 +163,15 @@ u32 KIP::GetMainThreadCpuCore() const {
|
||||
return header.default_core;
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetTextSection() const {
|
||||
std::span<const u8> KIP::GetTextSection() const {
|
||||
return decompressed_sections[0];
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetRODataSection() const {
|
||||
std::span<const u8> KIP::GetRODataSection() const {
|
||||
return decompressed_sections[1];
|
||||
}
|
||||
|
||||
const std::vector<u8>& KIP::GetDataSection() const {
|
||||
std::span<const u8> KIP::GetDataSection() const {
|
||||
return decompressed_sections[2];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// 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 <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -70,9 +74,9 @@ public:
|
||||
u32 GetMainThreadStackSize() const;
|
||||
u32 GetMainThreadCpuCore() const;
|
||||
|
||||
const std::vector<u8>& GetTextSection() const;
|
||||
const std::vector<u8>& GetRODataSection() const;
|
||||
const std::vector<u8>& GetDataSection() const;
|
||||
std::span<const u8> GetTextSection() const;
|
||||
std::span<const u8> GetRODataSection() const;
|
||||
std::span<const u8> GetDataSection() const;
|
||||
|
||||
u32 GetTextOffset() const;
|
||||
u32 GetRODataOffset() const;
|
||||
|
||||
@@ -117,6 +117,12 @@ void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled, u32 version) {
|
||||
const std::string disabled_key = fmt::format("Update@{}", version);
|
||||
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
|
||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
@@ -155,8 +161,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
@@ -175,8 +180,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
break;
|
||||
@@ -580,8 +584,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
if (!update_versions.empty()) {
|
||||
checked_external = true;
|
||||
for (const auto& update_entry : update_versions) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
@@ -600,8 +603,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
if (!manual_update_versions.empty()) {
|
||||
checked_manual = true;
|
||||
for (const auto& update_entry : manual_update_versions) {
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
|
||||
if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) {
|
||||
update_disabled = false;
|
||||
enabled_version = update_entry.version;
|
||||
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
|
||||
@@ -704,9 +706,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
.name = "Update",
|
||||
@@ -732,9 +733,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
version_str = FormatTitleVersion(update_entry.version);
|
||||
}
|
||||
|
||||
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
|
||||
const auto update_disabled =
|
||||
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
|
||||
IsVersionedExternalUpdateDisabled(disabled, update_entry.version);
|
||||
|
||||
|
||||
Patch update_patch = {.enabled = !update_disabled,
|
||||
@@ -771,7 +771,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
|
||||
|
||||
for (const auto& [slot, entry] : all_updates) {
|
||||
if (slot == ContentProviderUnionSlot::External) {
|
||||
if (slot == ContentProviderUnionSlot::External ||
|
||||
slot == ContentProviderUnionSlot::FrontendManual) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,206 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static std::shared_ptr<NSP> OpenContainerAsNsp(const VirtualFile& file, Loader::FileType type) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::Unknown || type == Loader::FileType::Error) {
|
||||
type = Loader::IdentifyFile(file);
|
||||
if (type == Loader::FileType::Unknown) {
|
||||
type = Loader::GuessFromFilename(file->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::NSP) {
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
return nsp->GetStatus() == Loader::ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == Loader::FileType::XCI) {
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_partition;
|
||||
}
|
||||
|
||||
// SAF-backed files can occasionally fail type-guessing despite being valid NSP/XCI.
|
||||
// As a last resort, probe both container parsers directly.
|
||||
{
|
||||
auto nsp = std::make_shared<NSP>(file);
|
||||
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
|
||||
return nsp;
|
||||
}
|
||||
}
|
||||
{
|
||||
XCI xci(file);
|
||||
if (xci.GetStatus() == Loader::ResultStatus::Success) {
|
||||
auto secure_partition = xci.GetSecurePartitionNSP();
|
||||
if (secure_partition != nullptr) {
|
||||
return secure_partition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
bool ForEachContainerEntry(const std::shared_ptr<NSP>& nsp, bool only_content,
|
||||
std::optional<u64> base_program_id, Callback&& on_entry) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
if (ncas.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<u64, u32> versions;
|
||||
std::map<u64, std::string> version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
if (!nca) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
for (const auto& inner_file : subdirs[0]->GetFiles()) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (title_type == TitleType::Update && content_type == ContentRecordType::Control) {
|
||||
const auto romfs = nca->GetRomFS();
|
||||
if (!romfs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (!extracted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (!nacp_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const NACP nacp(nacp_file);
|
||||
auto version_string = nacp.GetVersionString();
|
||||
if (!version_string.empty()) {
|
||||
version_strings[title_id] = std::move(version_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool added_entries = false;
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
if (base_program_id.has_value() && GetBaseTitleID(title_id) != *base_program_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
if (only_content && title_type != TitleType::Update && title_type != TitleType::AOC) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry_file = nca ? nca->GetBaseFile() : nullptr;
|
||||
if (!entry_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 version = 0;
|
||||
std::string version_string;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
if (const auto version_it = versions.find(title_id); version_it != versions.end()) {
|
||||
version = version_it->second;
|
||||
}
|
||||
|
||||
if (const auto version_str_it = version_strings.find(title_id);
|
||||
version_str_it != version_strings.end()) {
|
||||
version_string = version_str_it->second;
|
||||
}
|
||||
}
|
||||
|
||||
on_entry(title_type, content_type, title_id, entry_file, version, version_string);
|
||||
added_entries = true;
|
||||
}
|
||||
}
|
||||
|
||||
return added_entries;
|
||||
}
|
||||
|
||||
static void UpsertExternalVersionEntry(std::vector<ExternalUpdateEntry>& multi_version_entries,
|
||||
u64 title_id, u32 version,
|
||||
const std::string& version_string,
|
||||
ContentRecordType content_type, const VirtualFile& file) {
|
||||
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
|
||||
[title_id, version](const ExternalUpdateEntry& entry) {
|
||||
return entry.title_id == title_id && entry.version == version;
|
||||
});
|
||||
|
||||
if (it == multi_version_entries.end()) {
|
||||
ExternalUpdateEntry update_entry;
|
||||
update_entry.title_id = title_id;
|
||||
update_entry.version = version;
|
||||
update_entry.version_string = version_string;
|
||||
update_entry.files[static_cast<std::size_t>(content_type)] = file;
|
||||
multi_version_entries.push_back(std::move(update_entry));
|
||||
return;
|
||||
}
|
||||
|
||||
it->files[static_cast<std::size_t>(content_type)] = file;
|
||||
if (it->version_string.empty() && !version_string.empty()) {
|
||||
it->version_string = version_string;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename EntryMap, typename VersionMap>
|
||||
static bool AddExternalEntriesFromContainer(const std::shared_ptr<NSP>& nsp, EntryMap& entries,
|
||||
VersionMap& versions,
|
||||
std::vector<ExternalUpdateEntry>& multi_version_entries) {
|
||||
return ForEachContainerEntry(
|
||||
nsp, true, std::nullopt,
|
||||
[&entries, &versions,
|
||||
&multi_version_entries](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& file, u32 version,
|
||||
const std::string& version_string) {
|
||||
entries[{title_id, content_type, title_type}] = file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
versions[title_id] = version;
|
||||
UpsertExternalVersionEntry(multi_version_entries, title_id, version, version_string,
|
||||
content_type, file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
@@ -1008,6 +1208,26 @@ void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRec
|
||||
}
|
||||
}
|
||||
|
||||
bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_content,
|
||||
std::optional<u64> base_program_id) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::Unknown);
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ForEachContainerEntry(
|
||||
nsp, only_content, base_program_id,
|
||||
[this](TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
const VirtualFile& entry_file, u32 version, const std::string& version_string) {
|
||||
if (title_type == TitleType::Update) {
|
||||
AddEntryWithVersion(title_type, content_type, title_id, version, version_string,
|
||||
entry_file);
|
||||
} else {
|
||||
AddEntry(title_type, content_type, title_id, entry_file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ManualContentProvider::ClearAllEntries() {
|
||||
entries.clear();
|
||||
multi_version_entries.clear();
|
||||
@@ -1091,14 +1311,6 @@ VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
|
||||
: load_dirs(std::move(load_directories)) {
|
||||
ExternalContentProvider::Refresh();
|
||||
@@ -1159,247 +1371,22 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
|
||||
}
|
||||
|
||||
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
|
||||
auto nsp = NSP(file);
|
||||
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::NSP);
|
||||
if (!nsp) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
|
||||
|
||||
const auto ncas = nsp.GetNCAs();
|
||||
|
||||
std::map<u64, u32> nsp_versions;
|
||||
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
nsp_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
nsp_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = nsp_versions.find(title_id);
|
||||
if (ver_it != nsp_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
|
||||
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = nsp_version_strings.find(title_id);
|
||||
if (str_it != nsp_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
}
|
||||
|
||||
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
|
||||
auto xci = XCI(file);
|
||||
if (xci.GetStatus() != Loader::ResultStatus::Success) {
|
||||
const auto nsp = OpenContainerAsNsp(file, Loader::FileType::XCI);
|
||||
if (!nsp) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto nsp = xci.GetSecurePartitionNSP();
|
||||
if (nsp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ncas = nsp->GetNCAs();
|
||||
|
||||
std::map<u64, u32> xci_versions;
|
||||
std::map<u64, std::string> xci_version_strings;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (content_type == ContentRecordType::Meta) {
|
||||
const auto subdirs = nca->GetSubdirectories();
|
||||
if (!subdirs.empty()) {
|
||||
const auto section0 = subdirs[0];
|
||||
const auto files = section0->GetFiles();
|
||||
for (const auto& inner_file : files) {
|
||||
if (inner_file->GetExtension() == "cnmt") {
|
||||
const CNMT cnmt(inner_file);
|
||||
const auto cnmt_title_id = cnmt.GetTitleID();
|
||||
const auto version = cnmt.GetTitleVersion();
|
||||
xci_versions[cnmt_title_id] = version;
|
||||
versions[cnmt_title_id] = version;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
|
||||
auto romfs = nca->GetRomFS();
|
||||
if (romfs) {
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted) {
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (!nacp_file) {
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
}
|
||||
if (nacp_file) {
|
||||
NACP nacp(nacp_file);
|
||||
auto ver_str = nacp.GetVersionString();
|
||||
if (!ver_str.empty()) {
|
||||
xci_version_strings[title_id] = ver_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::pair<u64, u32>, std::array<VirtualFile, size_t(ContentRecordType::Count)>> version_files;
|
||||
|
||||
for (const auto& [title_id, nca_map] : ncas) {
|
||||
for (const auto& [type_pair, nca] : nca_map) {
|
||||
const auto& [title_type, content_type] = type_pair;
|
||||
|
||||
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
|
||||
if (nca_file != nullptr) {
|
||||
entries[{title_id, content_type, title_type}] = nca_file;
|
||||
|
||||
if (title_type == TitleType::Update) {
|
||||
u32 version = 0;
|
||||
auto ver_it = xci_versions.find(title_id);
|
||||
if (ver_it != xci_versions.end()) {
|
||||
version = ver_it->second;
|
||||
}
|
||||
|
||||
version_files[{title_id, version}][size_t(content_type)] = nca_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [key, files_map] : version_files) {
|
||||
const auto& [title_id, version] = key;
|
||||
|
||||
std::string ver_str;
|
||||
auto str_it = xci_version_strings.find(title_id);
|
||||
if (str_it != xci_version_strings.end()) {
|
||||
ver_str = str_it->second;
|
||||
}
|
||||
|
||||
bool version_exists = false;
|
||||
for (auto& existing : multi_version_entries) {
|
||||
if (existing.title_id == title_id && existing.version == version) {
|
||||
existing.files = files_map;
|
||||
if (existing.version_string.empty() && !ver_str.empty()) {
|
||||
existing.version_string = ver_str;
|
||||
}
|
||||
version_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!version_exists && !files_map.empty()) {
|
||||
ExternalUpdateEntry update_entry{
|
||||
.title_id = title_id,
|
||||
.version = version,
|
||||
.version_string = ver_str,
|
||||
.files = files_map
|
||||
};
|
||||
multi_version_entries.push_back(update_entry);
|
||||
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
|
||||
title_id, version, ver_str, files_map.size());
|
||||
}
|
||||
}
|
||||
AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries);
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
@@ -1491,12 +1478,4 @@ VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRec
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
|
||||
size_t count = 0;
|
||||
for (const auto& entry : multi_version_entries)
|
||||
if (entry.title_id == title_id && entry.files[size_t(type)])
|
||||
++count;
|
||||
return count > 1;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -262,6 +263,8 @@ public:
|
||||
VirtualFile file);
|
||||
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
u32 version, const std::string& version_string, VirtualFile file);
|
||||
bool AddEntriesFromContainer(VirtualFile file, bool only_content = false,
|
||||
std::optional<u64> base_program_id = std::nullopt);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
@@ -276,7 +279,6 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
@@ -303,7 +305,6 @@ public:
|
||||
|
||||
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
|
||||
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
|
||||
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
|
||||
|
||||
private:
|
||||
void ScanDirectory(const VirtualDir& dir);
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/kernel/k_typed_address.h"
|
||||
#include "core/hle/kernel/physical_memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -97,8 +97,7 @@ struct CodeSet final {
|
||||
#endif
|
||||
|
||||
/// The overall data that backs this code set.
|
||||
Kernel::PhysicalMemory memory;
|
||||
|
||||
std::vector<u8> memory;
|
||||
/// The segments that comprise this code set.
|
||||
std::array<Segment, 3> segments;
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
// This encapsulation serves 2 purposes:
|
||||
// - First, to encapsulate host physical memory under a single type and set an
|
||||
// standard for managing it.
|
||||
// - Second to ensure all host backing memory used is aligned to 256 bytes due
|
||||
// to strict alignment restrictions on GPU memory.
|
||||
|
||||
using PhysicalMemoryVector = std::vector<u8, Common::AlignmentAllocator<u8, 256>>;
|
||||
class PhysicalMemory final : public PhysicalMemoryVector {
|
||||
using PhysicalMemoryVector::PhysicalMemoryVector;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -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
|
||||
|
||||
@@ -93,7 +96,7 @@ union Error::ErrorArguments {
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
void CopyArgumentData(const std::vector<u8>& data, T& variable) {
|
||||
void CopyArgumentData(std::span<const u8> data, T& variable) {
|
||||
ASSERT(data.size() >= sizeof(T));
|
||||
std::memcpy(&variable, data.data(), sizeof(T));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -775,7 +778,7 @@ Result SoftwareKeyboard::RequestExit() {
|
||||
|
||||
// Inline Software Keyboard Requests
|
||||
|
||||
void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestFinalize(std::span<const u8> request_data) {
|
||||
LOG_DEBUG(Service_AM, "Processing Request: Finalize");
|
||||
|
||||
ChangeState(SwkbdState::NotInitialized);
|
||||
@@ -783,17 +786,17 @@ void SoftwareKeyboard::RequestFinalize(const std::vector<u8>& request_data) {
|
||||
ExitKeyboard();
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestSetUserWordInfo(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestSetUserWordInfo(std::span<const u8> request_data) {
|
||||
LOG_WARNING(Service_AM, "SetUserWordInfo is not implemented.");
|
||||
|
||||
ReplyReleasedUserWordInfo();
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestSetCustomizeDic(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestSetCustomizeDic(std::span<const u8> request_data) {
|
||||
LOG_WARNING(Service_AM, "SetCustomizeDic is not implemented.");
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestCalc(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestCalc(std::span<const u8> request_data) {
|
||||
LOG_DEBUG(Service_AM, "Processing Request: Calc");
|
||||
|
||||
ASSERT(request_data.size() >= sizeof(SwkbdRequestCommand) + sizeof(SwkbdCalcArgCommon));
|
||||
@@ -930,17 +933,17 @@ void SoftwareKeyboard::RequestCalcNew() {
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestSetCustomizedDictionaries(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestSetCustomizedDictionaries(std::span<const u8> request_data) {
|
||||
LOG_WARNING(Service_AM, "SetCustomizedDictionaries is not implemented.");
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestUnsetCustomizedDictionaries(std::span<const u8> request_data) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) Processing Request: UnsetCustomizedDictionaries");
|
||||
|
||||
ReplyUnsetCustomizedDictionaries();
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestSetChangedStringV2Flag(std::span<const u8> request_data) {
|
||||
LOG_DEBUG(Service_AM, "Processing Request: SetChangedStringV2Flag");
|
||||
|
||||
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
|
||||
@@ -948,7 +951,7 @@ void SoftwareKeyboard::RequestSetChangedStringV2Flag(const std::vector<u8>& requ
|
||||
std::memcpy(&use_changed_string_v2, request_data.data() + sizeof(SwkbdRequestCommand), 1);
|
||||
}
|
||||
|
||||
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data) {
|
||||
void SoftwareKeyboard::RequestSetMovedCursorV2Flag(std::span<const u8> request_data) {
|
||||
LOG_DEBUG(Service_AM, "Processing Request: SetMovedCursorV2Flag");
|
||||
|
||||
ASSERT(request_data.size() == sizeof(SwkbdRequestCommand) + 1);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -124,16 +127,16 @@ private:
|
||||
|
||||
// Inline Software Keyboard Requests
|
||||
|
||||
void RequestFinalize(const std::vector<u8>& request_data);
|
||||
void RequestSetUserWordInfo(const std::vector<u8>& request_data);
|
||||
void RequestSetCustomizeDic(const std::vector<u8>& request_data);
|
||||
void RequestCalc(const std::vector<u8>& request_data);
|
||||
void RequestFinalize(std::span<const u8> request_data);
|
||||
void RequestSetUserWordInfo(std::span<const u8> request_data);
|
||||
void RequestSetCustomizeDic(std::span<const u8> request_data);
|
||||
void RequestCalc(std::span<const u8> request_data);
|
||||
void RequestCalcOld();
|
||||
void RequestCalcNew();
|
||||
void RequestSetCustomizedDictionaries(const std::vector<u8>& request_data);
|
||||
void RequestUnsetCustomizedDictionaries(const std::vector<u8>& request_data);
|
||||
void RequestSetChangedStringV2Flag(const std::vector<u8>& request_data);
|
||||
void RequestSetMovedCursorV2Flag(const std::vector<u8>& request_data);
|
||||
void RequestSetCustomizedDictionaries(std::span<const u8> request_data);
|
||||
void RequestUnsetCustomizedDictionaries(std::span<const u8> request_data);
|
||||
void RequestSetChangedStringV2Flag(std::span<const u8> request_data);
|
||||
void RequestSetMovedCursorV2Flag(std::span<const u8> request_data);
|
||||
|
||||
// Inline Software Keyboard Replies
|
||||
|
||||
|
||||
@@ -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 2020 yuzu Emulator Project
|
||||
@@ -34,22 +34,21 @@ namespace Service::AM::Frontend {
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void ParseRawValue(T& value, const std::vector<u8>& data) {
|
||||
void ParseRawValue(T& value, std::span<const u8> data) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"It's undefined behavior to use memcpy with non-trivially copyable objects");
|
||||
std::memcpy(&value, data.data(), data.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T ParseRawValue(const std::vector<u8>& data) {
|
||||
T ParseRawValue(std::span<const u8> data) {
|
||||
T value;
|
||||
ParseRawValue(value, data);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string ParseStringValue(const std::vector<u8>& data) {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
|
||||
data.size());
|
||||
std::string ParseStringValue(std::span<const u8> data) {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
std::string GetMainURL(const std::string& url) {
|
||||
@@ -72,7 +71,7 @@ std::string ResolveURL(const std::string& url) {
|
||||
return url.substr(0, index) + "lp1" + url.substr(index + 1);
|
||||
}
|
||||
|
||||
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
|
||||
WebArgInputTLVMap ReadWebArgs(std::span<const u8> web_arg, WebArgHeader& web_arg_header) {
|
||||
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
|
||||
|
||||
if (web_arg.size() == sizeof(WebArgHeader)) {
|
||||
|
||||
@@ -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 2024 yuzu Emulator Project
|
||||
@@ -36,8 +36,8 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u
|
||||
{16, nullptr, "ReleaseAudioInputDeviceNotification"}, //17.0.0+
|
||||
{17, nullptr, "AcquireAudioOutputDeviceNotification"}, //17.0.0+
|
||||
{18, nullptr, "ReleaseAudioOutputDeviceNotification"}, //17.0.0+
|
||||
{19, nullptr, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
|
||||
{20, nullptr, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
|
||||
{19, D<&IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled>, "SetAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
|
||||
{20, D<&IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled>, "IsAudioDeviceOutputVolumeAutoTuneEnabled"}, //18.0.0+
|
||||
{21, nullptr, "IsActiveOutputDeviceEstimatedLowLatency"} //21.0.0+
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -170,4 +170,15 @@ Result IAudioDevice::ListAudioOutputDeviceName(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioDevice::SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAudioDevice::IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
*out_enabled = false;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -49,6 +52,8 @@ private:
|
||||
Result ListAudioOutputDeviceName(
|
||||
OutArray<AudioDevice::AudioDeviceName, BufferAttr_HipcMapAlias> out_names,
|
||||
Out<s32> out_count);
|
||||
Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled);
|
||||
Result IsAudioDeviceOutputVolumeAutoTuneEnabled(Out<bool> out_enabled);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
std::unique_ptr<AudioCore::Renderer::AudioDevice> impl;
|
||||
|
||||
@@ -164,7 +164,7 @@ private:
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
u64 ReadLeb128(const std::vector<u8>& data, std::size_t& offset) {
|
||||
u64 ReadLeb128(std::span<const u8> data, std::size_t& offset) {
|
||||
u64 result{};
|
||||
u32 shift{};
|
||||
|
||||
@@ -180,7 +180,7 @@ private:
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> ReadString(const std::vector<u8>& data, std::size_t& offset,
|
||||
std::optional<std::string> ReadString(std::span<const u8> data, std::size_t& offset,
|
||||
std::size_t length) {
|
||||
if (length == 0) {
|
||||
return std::nullopt;
|
||||
@@ -193,7 +193,7 @@ private:
|
||||
return output;
|
||||
}
|
||||
|
||||
u32_le ReadAsU32(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
|
||||
u32_le ReadAsU32(std::span<const u8> data, std::size_t& offset, std::size_t length) {
|
||||
ASSERT(length == sizeof(u32));
|
||||
u32_le output{};
|
||||
std::memcpy(&output, data.data() + offset, sizeof(u32));
|
||||
@@ -201,7 +201,7 @@ private:
|
||||
return output;
|
||||
}
|
||||
|
||||
u64_le ReadAsU64(const std::vector<u8>& data, std::size_t& offset, std::size_t length) {
|
||||
u64_le ReadAsU64(std::span<const u8> data, std::size_t& offset, std::size_t length) {
|
||||
ASSERT(length == sizeof(u64));
|
||||
u64_le output{};
|
||||
std::memcpy(&output, data.data() + offset, sizeof(u64));
|
||||
@@ -209,7 +209,7 @@ private:
|
||||
return output;
|
||||
}
|
||||
|
||||
void ParseLog(const LogPacketHeaderEntry entry, const std::vector<u8>& log_data) {
|
||||
void ParseLog(const LogPacketHeaderEntry entry, std::span<const u8> log_data) {
|
||||
// Possible entries
|
||||
std::optional<std::string> text_log;
|
||||
std::optional<u32> line_number;
|
||||
|
||||
@@ -67,7 +67,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
||||
{44, D<&IApplicationManagerInterface::GetSdCardMountStatusChangedEvent>, "GetSdCardMountStatusChangedEvent"},
|
||||
{45, nullptr, "GetGameCardAttachmentEvent"},
|
||||
{46, nullptr, "GetGameCardAttachmentInfo"},
|
||||
{47, nullptr, "GetTotalSpaceSize"},
|
||||
{47, D<&IApplicationManagerInterface::GetTotalSpaceSize>, "GetTotalSpaceSize"},
|
||||
{48, D<&IApplicationManagerInterface::GetFreeSpaceSize>, "GetFreeSpaceSize"},
|
||||
{49, nullptr, "GetSdCardRemovedEvent"},
|
||||
{52, D<&IApplicationManagerInterface::GetGameCardUpdateDetectionEvent>, "GetGameCardUpdateDetectionEvent"},
|
||||
@@ -751,8 +751,12 @@ Result IApplicationManagerInterface::GetSdCardMountStatusChangedEvent(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size,
|
||||
FileSys::StorageId storage_id) {
|
||||
Result IApplicationManagerInterface::GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id) {
|
||||
LOG_DEBUG(Service_NS, "called");
|
||||
R_RETURN(IContentManagementInterface(system).GetTotalSpaceSize(out_total_space_size, storage_id));
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id) {
|
||||
LOG_DEBUG(Service_NS, "called");
|
||||
R_RETURN(IContentManagementInterface(system).GetFreeSpaceSize(out_free_space_size, storage_id));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
u32 flags, u64 application_id, Uid account_id);
|
||||
Result CheckSdCardMountStatus();
|
||||
Result GetSdCardMountStatusChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetTotalSpaceSize(Out<s64> out_total_space_size, FileSys::StorageId storage_id);
|
||||
Result GetFreeSpaceSize(Out<s64> out_free_space_size, FileSys::StorageId storage_id);
|
||||
Result GetGameCardUpdateDetectionEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result ResumeAll();
|
||||
|
||||
@@ -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 2018 yuzu Emulator Project
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/hle/kernel/k_shared_memory.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_memory.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/platform_service_manager.h"
|
||||
|
||||
@@ -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 2018 yuzu Emulator Project
|
||||
@@ -28,8 +28,10 @@ public:
|
||||
{10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"},
|
||||
{10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old2>, "SaveReportOld2"},
|
||||
{10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old2>, "SaveReportWithUserOld2"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10104, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old3>, "SaveReportOld3"},
|
||||
{10105, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old3>, "SaveReportWithUserOld3"},
|
||||
{10106, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"},
|
||||
{10107, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"},
|
||||
{10200, &PlayReport::RequestImmediateTransmission, "RequestImmediateTransmission"},
|
||||
{10300, &PlayReport::GetTransmissionStatus, "GetTransmissionStatus"},
|
||||
{10400, &PlayReport::GetSystemSessionId, "GetSystemSessionId"},
|
||||
|
||||
@@ -176,11 +176,9 @@ struct ProcessContext {
|
||||
// Calculate hash.
|
||||
Sha256Hash hash;
|
||||
{
|
||||
const u64 size = nro_header->GetSize();
|
||||
|
||||
const u64 size = nro_header->m_size;
|
||||
std::vector<u8> nro_data(size);
|
||||
m_process->GetMemory().ReadBlock(base_address, nro_data.data(), size);
|
||||
|
||||
u32 hash_len = 0;
|
||||
EVP_Digest(nro_data.data(), nro_data.size(), hash.data(), &hash_len, EVP_sha256(), nullptr);
|
||||
}
|
||||
@@ -204,9 +202,7 @@ struct ProcessContext {
|
||||
R_THROW(RO::ResultNotAuthorized);
|
||||
}
|
||||
|
||||
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size,
|
||||
u64* out_rw_size, u64 base_address, u64 expected_nro_size,
|
||||
u64 expected_bss_size) {
|
||||
Result ValidateNro(ModuleId* out_module_id, u64* out_rx_size, u64* out_ro_size, u64* out_rw_size, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) {
|
||||
// Ensure we have a process to work on.
|
||||
R_UNLESS(m_process != nullptr, RO::ResultInvalidProcess);
|
||||
|
||||
@@ -215,17 +211,17 @@ struct ProcessContext {
|
||||
m_process->GetMemory().ReadBlock(base_address, std::addressof(header), sizeof(header));
|
||||
|
||||
// Validate header.
|
||||
R_UNLESS(header.IsMagicValid(), RO::ResultInvalidNro);
|
||||
R_UNLESS(header.m_magic == NRO_HEADER_MAGIC, RO::ResultInvalidNro);
|
||||
|
||||
// Read sizes from header.
|
||||
const u64 nro_size = header.GetSize();
|
||||
const u64 text_ofs = header.GetTextOffset();
|
||||
const u64 text_size = header.GetTextSize();
|
||||
const u64 ro_ofs = header.GetRoOffset();
|
||||
const u64 ro_size = header.GetRoSize();
|
||||
const u64 rw_ofs = header.GetRwOffset();
|
||||
const u64 rw_size = header.GetRwSize();
|
||||
const u64 bss_size = header.GetBssSize();
|
||||
const u64 nro_size = header.m_size;
|
||||
const u64 text_ofs = header.m_text_offset;
|
||||
const u64 text_size = header.m_text_size;
|
||||
const u64 ro_ofs = header.m_ro_offset;
|
||||
const u64 ro_size = header.m_ro_size;
|
||||
const u64 rw_ofs = header.m_rw_offset;
|
||||
const u64 rw_size = header.m_rw_size;
|
||||
const u64 bss_size = header.m_bss_size;
|
||||
|
||||
// Validate sizes meet expected.
|
||||
R_UNLESS(nro_size == expected_nro_size, RO::ResultInvalidNro);
|
||||
@@ -251,7 +247,7 @@ struct ProcessContext {
|
||||
R_TRY(this->ValidateHasNroHash(base_address, std::addressof(header)));
|
||||
|
||||
// Check if NRO has already been loaded.
|
||||
const ModuleId* module_id = header.GetModuleId();
|
||||
const ModuleId* module_id = std::addressof(header.m_module_id);
|
||||
R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), RO::ResultAlreadyLoaded);
|
||||
|
||||
// Apply patches to NRO.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -112,52 +115,8 @@ private:
|
||||
};
|
||||
static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader has wrong size");
|
||||
|
||||
class NroHeader {
|
||||
public:
|
||||
static constexpr u32 Magic = Common::MakeMagic('N', 'R', 'O', '0');
|
||||
|
||||
public:
|
||||
bool IsMagicValid() const {
|
||||
return m_magic == Magic;
|
||||
}
|
||||
|
||||
u32 GetSize() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
u32 GetTextOffset() const {
|
||||
return m_text_offset;
|
||||
}
|
||||
|
||||
u32 GetTextSize() const {
|
||||
return m_text_size;
|
||||
}
|
||||
|
||||
u32 GetRoOffset() const {
|
||||
return m_ro_offset;
|
||||
}
|
||||
|
||||
u32 GetRoSize() const {
|
||||
return m_ro_size;
|
||||
}
|
||||
|
||||
u32 GetRwOffset() const {
|
||||
return m_rw_offset;
|
||||
}
|
||||
|
||||
u32 GetRwSize() const {
|
||||
return m_rw_size;
|
||||
}
|
||||
|
||||
u32 GetBssSize() const {
|
||||
return m_bss_size;
|
||||
}
|
||||
|
||||
const ModuleId* GetModuleId() const {
|
||||
return std::addressof(m_module_id);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr u32 NRO_HEADER_MAGIC = Common::MakeMagic('N', 'R', 'O', '0');
|
||||
struct NroHeader {
|
||||
u32 m_entrypoint_insn;
|
||||
u32 m_mod_offset;
|
||||
INSERT_PADDING_BYTES_NOINIT(0x8);
|
||||
|
||||
+5
-14
@@ -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
|
||||
@@ -75,21 +75,16 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
kip->GetKernelCapabilities());
|
||||
|
||||
Kernel::CodeSet codeset;
|
||||
Kernel::PhysicalMemory program_image;
|
||||
|
||||
const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, const std::vector<u8>& data, u32 offset) {
|
||||
codeset.memory.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
|
||||
const auto load_segment = [&codeset](Kernel::CodeSet::Segment& segment, std::span<const u8> data, u32 offset) {
|
||||
segment.addr = offset;
|
||||
segment.offset = offset;
|
||||
segment.size = PageAlignSize(u32(data.size()));
|
||||
program_image.resize(offset + data.size());
|
||||
std::memcpy(program_image.data() + offset, data.data(), data.size());
|
||||
std::memcpy(codeset.memory.data() + offset, data.data(), data.size());
|
||||
};
|
||||
|
||||
load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset());
|
||||
load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset());
|
||||
load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset());
|
||||
|
||||
program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize());
|
||||
codeset.DataSegment().size += kip->GetBSSSize();
|
||||
|
||||
// TODO: this is bad form of ASLR, it sucks
|
||||
@@ -98,13 +93,9 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
|
||||
: std::rand()) * 0x734287f27) & 0xfff000;
|
||||
|
||||
// Setup the process code layout
|
||||
if (process
|
||||
.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0, aslr_offset, false)
|
||||
.IsError()) {
|
||||
if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) {
|
||||
return {ResultStatus::ErrorNotInitialized, {}};
|
||||
}
|
||||
|
||||
codeset.memory = std::move(program_image);
|
||||
const VAddr base_address = GetInteger(process.GetEntryPoint());
|
||||
process.LoadModule(std::move(codeset), base_address);
|
||||
|
||||
|
||||
@@ -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 2018 yuzu Emulator Project
|
||||
@@ -9,11 +9,15 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <concepts>
|
||||
#include <algorithm>
|
||||
#include "common/concepts.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/kip.h"
|
||||
@@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
|
||||
u64 program_id = 0,
|
||||
std::size_t program_index = 0) {
|
||||
if (!file) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::NSP) {
|
||||
auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
|
||||
return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
|
||||
}
|
||||
|
||||
if (type == FileType::XCI) {
|
||||
FileSys::XCI xci{file, program_id, program_index};
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto secure_nsp = xci.GetSecurePartitionNSP();
|
||||
if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return secure_nsp;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
|
||||
if (!nsp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& ncas = nsp->GetNCAs();
|
||||
return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
|
||||
const auto& nca_map = title_entry.second;
|
||||
return nca_map.find(
|
||||
{FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
|
||||
nca_map.end();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
@@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsContainerType(FileType type) {
|
||||
return type == FileType::NSP || type == FileType::XCI;
|
||||
}
|
||||
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
|
||||
std::size_t program_index) {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == FileType::Unknown) {
|
||||
type = IdentifyFile(file);
|
||||
}
|
||||
|
||||
if (!IsContainerType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
|
||||
}
|
||||
|
||||
FileType GuessFromFilename(const std::string& name) {
|
||||
if (name == "main")
|
||||
return FileType::DeconstructedRomDirectory;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,12 +49,29 @@ enum class FileType {
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies the type of a bootable file based on the magic value in its header.
|
||||
* Identifies the type of a supported file/container based on its structure.
|
||||
* @param file open file
|
||||
* @return FileType of file
|
||||
*/
|
||||
FileType IdentifyFile(FileSys::VirtualFile file);
|
||||
|
||||
/**
|
||||
* Returns whether the file type represents a container format that can bundle multiple titles
|
||||
* (currently NSP/XCI).
|
||||
*/
|
||||
bool IsContainerType(FileType type);
|
||||
|
||||
/**
|
||||
* Returns whether a container file is bootable as a game (has Application/Program content).
|
||||
*
|
||||
* @param file open file
|
||||
* @param type optional file type; if Unknown it is auto-detected.
|
||||
* @param program_id optional program id hint for multi-program containers.
|
||||
* @param program_index optional program index hint for multi-program containers.
|
||||
*/
|
||||
bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
|
||||
u64 program_id = 0, std::size_t program_index = 0);
|
||||
|
||||
/**
|
||||
* Guess the type of a bootable file from its name
|
||||
* @param name String name of bootable 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 2018 yuzu Emulator Project
|
||||
@@ -160,7 +160,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
|
||||
}
|
||||
|
||||
// Build program image
|
||||
Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size));
|
||||
std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
|
||||
std::memcpy(program_image.data(), data.data(), program_image.size());
|
||||
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
|
||||
return {};
|
||||
|
||||
+40
-59
@@ -4,6 +4,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
@@ -40,17 +41,6 @@ struct MODHeader {
|
||||
};
|
||||
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
|
||||
|
||||
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
|
||||
const NSOSegmentHeader& header) {
|
||||
std::vector<u8> uncompressed_data =
|
||||
Common::Compression::DecompressDataLZ4(compressed_data, header.size);
|
||||
|
||||
ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size,
|
||||
uncompressed_data.size());
|
||||
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
constexpr u32 PageAlignSize(u32 size) {
|
||||
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
||||
}
|
||||
@@ -76,24 +66,16 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
|
||||
return FileType::NSO;
|
||||
}
|
||||
|
||||
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system,
|
||||
const FileSys::VfsFile& nso_file, VAddr load_base,
|
||||
bool should_pass_arguments, bool load_into_process,
|
||||
std::optional<FileSys::PatchManager> pm,
|
||||
std::vector<Core::NCE::Patcher>* patches,
|
||||
s32 patch_index) {
|
||||
if (nso_file.GetSize() < sizeof(NSOHeader)) {
|
||||
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system, const FileSys::VfsFile& nso_file, VAddr load_base, bool should_pass_arguments, bool load_into_process, std::optional<FileSys::PatchManager> pm, std::vector<Core::NCE::Patcher>* patches, s32 patch_index) {
|
||||
if (nso_file.GetSize() < sizeof(NSOHeader))
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
NSOHeader nso_header{};
|
||||
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header)) {
|
||||
if (sizeof(NSOHeader) != nso_file.ReadObject(&nso_header))
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
return std::nullopt;
|
||||
if (nso_header.segments.empty())
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Allocate some space at the beginning if we are patching in PreText mode.
|
||||
const size_t module_start = [&]() -> size_t {
|
||||
@@ -110,42 +92,44 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
return 0;
|
||||
}();
|
||||
|
||||
// Build program image
|
||||
auto const last_segment_it = &nso_header.segments[nso_header.segments.size() - 1];
|
||||
// Build program image directly in codeset memory :)
|
||||
Kernel::CodeSet codeset;
|
||||
Kernel::PhysicalMemory program_image;
|
||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||
std::vector<u8> data = nso_file.ReadBytes(nso_header.segments_compressed_size[i],
|
||||
nso_header.segments[i].offset);
|
||||
if (nso_header.IsSegmentCompressed(i)) {
|
||||
data = DecompressSegment(data, nso_header.segments[i]);
|
||||
codeset.memory.resize(module_start + last_segment_it->location + last_segment_it->size);
|
||||
{
|
||||
std::vector<u8> compressed_data(*std::ranges::max_element(nso_header.segments_compressed_size));
|
||||
std::vector<u8> decompressed_size(std::ranges::max_element(nso_header.segments, [](auto const& a, auto const& b) {
|
||||
return a.size < b.size;
|
||||
})->size);
|
||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||
if (nso_header.IsSegmentCompressed(i)) {
|
||||
int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
|
||||
ASSERT(r == int(nso_header.segments[i].size));
|
||||
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
|
||||
} else {
|
||||
std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
|
||||
}
|
||||
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
||||
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
||||
codeset.segments[i].size = nso_header.segments[i].size;
|
||||
}
|
||||
program_image.resize(module_start + nso_header.segments[i].location +
|
||||
static_cast<u32>(data.size()));
|
||||
std::memcpy(program_image.data() + module_start + nso_header.segments[i].location,
|
||||
data.data(), data.size());
|
||||
codeset.segments[i].addr = module_start + nso_header.segments[i].location;
|
||||
codeset.segments[i].offset = module_start + nso_header.segments[i].location;
|
||||
codeset.segments[i].size = nso_header.segments[i].size;
|
||||
}
|
||||
|
||||
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty()) {
|
||||
const auto arg_data{Settings::values.program_args.GetValue()};
|
||||
|
||||
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = program_image.size();
|
||||
program_image.resize(static_cast<u32>(program_image.size()) +
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
|
||||
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
|
||||
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
|
||||
arg_data.size());
|
||||
NSOArgumentHeader args_header{NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = codeset.memory.size();
|
||||
codeset.memory.resize(u32(codeset.memory.size()) + NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
|
||||
std::memcpy(codeset.memory.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
|
||||
std::memcpy(codeset.memory.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), arg_data.size());
|
||||
}
|
||||
|
||||
codeset.DataSegment().size += nso_header.segments[2].bss_size;
|
||||
u32 image_size{
|
||||
PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
|
||||
program_image.resize(image_size);
|
||||
u32 image_size = PageAlignSize(u32(codeset.memory.size()) + nso_header.segments[2].bss_size);
|
||||
codeset.memory.resize(image_size);
|
||||
|
||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||
codeset.segments[i].size = PageAlignSize(codeset.segments[i].size);
|
||||
@@ -154,8 +138,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
// Apply patches if necessary
|
||||
const auto name = nso_file.GetName();
|
||||
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
|
||||
std::span<u8> patchable_section(program_image.data() + module_start,
|
||||
program_image.size() - module_start);
|
||||
std::span<u8> patchable_section(codeset.memory.data() + module_start, codeset.memory.size() - module_start);
|
||||
std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
|
||||
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
|
||||
std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
|
||||
@@ -174,15 +157,15 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
//Set module ID using build_id from the NSO header
|
||||
patch->SetModuleID(nso_header.build_id);
|
||||
// Patch SVCs and MRS calls in the guest code
|
||||
while (!patch->PatchText(program_image, code)) {
|
||||
while (!patch->PatchText(codeset.memory, code)) {
|
||||
patch = &patches->emplace_back();
|
||||
patch->SetModuleID(nso_header.build_id); // In case the patcher is changed for big modules, the new patcher should also have the build_id
|
||||
}
|
||||
} else if (patch) {
|
||||
// Relocate code patch and copy to the program_image.
|
||||
// Relocate code patch and copy to the program image.
|
||||
// Save size before RelocateAndCopy (which may resize)
|
||||
const size_t size_before_relocate = program_image.size();
|
||||
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
|
||||
const size_t size_before_relocate = codeset.memory.size();
|
||||
if (patch->RelocateAndCopy(load_base, code, codeset.memory, &process.GetPostHandlers())) {
|
||||
// Update patch section.
|
||||
auto& patch_segment = codeset.PatchSegment();
|
||||
auto& post_patch_segment = codeset.PostPatchSegment();
|
||||
@@ -203,7 +186,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
}
|
||||
|
||||
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
|
||||
image_size = static_cast<u32>(program_image.size());
|
||||
image_size = static_cast<u32>(codeset.memory.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -234,9 +217,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
}
|
||||
|
||||
// Load codeset for current process
|
||||
codeset.memory = std::move(program_image);
|
||||
process.LoadModule(std::move(codeset), load_base);
|
||||
|
||||
return load_base + image_size;
|
||||
}
|
||||
|
||||
|
||||
+25
-11
@@ -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
|
||||
|
||||
@@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
|
||||
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
|
||||
const FileSys::NSP nsp(nsp_file);
|
||||
|
||||
if (nsp.GetStatus() == ResultStatus::Success) {
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
if (nsp.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// Extracted Type case
|
||||
if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
|
||||
FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Non-extracted NSPs can legitimately contain only update/DLC content.
|
||||
// Identify the container format itself; bootability is validated by Load().
|
||||
if (!nsp.GetNCAs().empty()) {
|
||||
return FileType::NSP;
|
||||
}
|
||||
|
||||
// Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
|
||||
for (const auto& entry : nsp.GetFiles()) {
|
||||
if (entry == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Non-Extracted Type case
|
||||
const auto program_id = nsp.GetProgramTitleID();
|
||||
if (!nsp.IsExtractedType() &&
|
||||
nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(
|
||||
nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
|
||||
const auto& name = entry->GetName();
|
||||
if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
|
||||
return FileType::NSP;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -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
|
||||
|
||||
@@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
|
||||
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
|
||||
const FileSys::XCI xci(xci_file);
|
||||
|
||||
if (xci.GetStatus() == ResultStatus::Success &&
|
||||
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
|
||||
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
|
||||
FileType::NCA) {
|
||||
if (xci.GetStatus() != ResultStatus::Success) {
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,6 +56,7 @@ public:
|
||||
enum class PlayReportType {
|
||||
Old,
|
||||
Old2,
|
||||
Old3,
|
||||
New,
|
||||
System,
|
||||
};
|
||||
|
||||
@@ -356,23 +356,18 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Fix bloom effects"),
|
||||
tr("Removes bloom in Burnout."));
|
||||
|
||||
INSERT(Settings,
|
||||
rescale_hack,
|
||||
tr("Enable Legacy Rescale Pass"),
|
||||
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
|
||||
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
|
||||
tr("Controls the number of features that can be used in Extended Dynamic State.\n"
|
||||
"Higher states allow for more features and can increase performance, but may cause "
|
||||
"additional graphical issues."));
|
||||
|
||||
INSERT(Settings,
|
||||
vertex_input_dynamic_state,
|
||||
tr("Vertex Input Dynamic State"),
|
||||
tr("Enables vertex input dynamic state feature for better quality and performance."));
|
||||
|
||||
INSERT(Settings,
|
||||
provoking_vertex,
|
||||
tr("Provoking Vertex"),
|
||||
tr("Improves lighting and vertex handling in some games.\n"
|
||||
"Only Vulkan 1.0+ devices support this extension."));
|
||||
|
||||
INSERT(Settings,
|
||||
descriptor_indexing,
|
||||
tr("Descriptor Indexing"),
|
||||
@@ -419,6 +414,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
"their resolution, details and supported controllers and depending on this setting.\n"
|
||||
"Setting to Handheld can help improve performance for low end systems."));
|
||||
INSERT(Settings, current_user, QString(), QString());
|
||||
INSERT(Settings, serial_unit, tr("Unit Serial"), QString());
|
||||
INSERT(Settings, serial_battery, tr("Battery Serial"), QString());
|
||||
INSERT(Settings, debug_knobs, tr("Debug knobs"), QString());
|
||||
|
||||
// Controls
|
||||
|
||||
@@ -790,7 +788,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
||||
PAIR(ExtendedDynamicState, Disabled, tr("Disabled")),
|
||||
PAIR(ExtendedDynamicState, EDS1, tr("ExtendedDynamicState 1")),
|
||||
PAIR(ExtendedDynamicState, EDS2, tr("ExtendedDynamicState 2")),
|
||||
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
|
||||
}});
|
||||
|
||||
translations->insert({Settings::EnumMetadata<Settings::GameListMode>::Index(),
|
||||
|
||||
@@ -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 2021 yuzu Emulator Project
|
||||
|
||||
@@ -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 2021 yuzu Emulator Project
|
||||
@@ -31,7 +31,7 @@ struct RenderAreaLayout {
|
||||
};
|
||||
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
|
||||
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
|
||||
constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
|
||||
constexpr u32 RENDERAREA_LAYOUT_OFFSET = sizeof(RescalingLayout);
|
||||
|
||||
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
|
||||
IR::Program& program, Bindings& bindings, bool optimize);
|
||||
|
||||
@@ -491,6 +491,9 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
|
||||
}
|
||||
|
||||
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
|
||||
if (!ctx.runtime_info.active_color_outputs[index]) {
|
||||
return;
|
||||
}
|
||||
const Id component_id{ctx.Const(component)};
|
||||
const AttributeType type{ctx.runtime_info.color_output_types[index]};
|
||||
if (type == AttributeType::Float) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 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 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -91,6 +94,17 @@ void AlphaTest(EmitContext& ctx) {
|
||||
} // Anonymous namespace
|
||||
|
||||
void EmitPrologue(EmitContext& ctx) {
|
||||
if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend) {
|
||||
// Initialize dual-source blending outputs - prevents MoltenVK crash.
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
const Id default_color{ctx.ConstantComposite(ctx.F32[4], zero, zero, zero, one)};
|
||||
for (u32 i = 0; i < 2; ++i) {
|
||||
if (Sirit::ValidId(ctx.frag_color[i])) {
|
||||
ctx.OpStore(ctx.frag_color[i], default_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.stage == Stage::VertexB) {
|
||||
const Id zero{ctx.Const(0.0f)};
|
||||
const Id one{ctx.Const(1.0f)};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "common/div_ceil.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv.h"
|
||||
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||
#include "shader_recompiler/frontend/ir/modifiers.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
namespace {
|
||||
@@ -1095,7 +1096,8 @@ void EmitContext::DefineRenderArea(const Info& info) {
|
||||
Decorate(push_constant_struct, spv::Decoration::Block);
|
||||
Name(push_constant_struct, "RenderAreaInfo");
|
||||
|
||||
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset, 0);
|
||||
MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset,
|
||||
RENDERAREA_LAYOUT_OFFSET);
|
||||
MemberName(push_constant_struct, render_are_member_index, "render_area");
|
||||
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
|
||||
@@ -1670,13 +1672,24 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
|
||||
break;
|
||||
case Stage::Fragment:
|
||||
for (u32 index = 0; index < 8; ++index) {
|
||||
if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors) {
|
||||
const bool need_dual_source = runtime_info.dual_source_blend && index <= 1;
|
||||
const bool should_declare = runtime_info.active_color_outputs[index] &&
|
||||
(info.stores_frag_color[index] ||
|
||||
profile.need_declared_frag_colors);
|
||||
if (!need_dual_source && !should_declare) {
|
||||
continue;
|
||||
}
|
||||
const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])};
|
||||
frag_color[index] = DefineOutput(*this, type, std::nullopt);
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
// Correct mapping for dual-source blending
|
||||
if (runtime_info.dual_source_blend && index <= 1) {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, 0u);
|
||||
Decorate(frag_color[index], spv::Decoration::Index, index);
|
||||
Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary");
|
||||
} else {
|
||||
Decorate(frag_color[index], spv::Decoration::Location, index);
|
||||
Name(frag_color[index], fmt::format("frag_color{}", index));
|
||||
}
|
||||
}
|
||||
if (info.stores_frag_depth) {
|
||||
frag_depth = DefineOutput(*this, F32[1], std::nullopt);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
|
||||
Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
|
||||
Optimization::TexturePass(env, program, host_info);
|
||||
|
||||
if (Settings::values.resolution_info.active) {
|
||||
if (Settings::values.resolution_info.active || Settings::values.rescale_hack.GetValue()) {
|
||||
Optimization::RescalingPass(program);
|
||||
}
|
||||
Optimization::DeadCodeEliminationPass(program);
|
||||
|
||||
@@ -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 2021 yuzu Emulator Project
|
||||
|
||||
@@ -67,11 +67,18 @@ void VisitMark(IR::Block& block, IR::Inst& inst) {
|
||||
if (must_patch_outside) {
|
||||
const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
IR::IREmitter ir{block, it};
|
||||
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
|
||||
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
|
||||
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
|
||||
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
|
||||
inst.ReplaceUsesWith(converted);
|
||||
if (Settings::values.rescale_hack.GetValue()) {
|
||||
const IR::F32 new_inst{&*block.PrependNewInst(it, inst)};
|
||||
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
|
||||
const IR::Value converted{ir.FPMul(new_inst, up_factor)};
|
||||
inst.ReplaceUsesWith(converted);
|
||||
} else {
|
||||
IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)};
|
||||
const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})};
|
||||
const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())};
|
||||
const IR::Value converted{ir.FPMul(new_bitcast, up_factor)};
|
||||
inst.ReplaceUsesWith(converted);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -200,11 +200,10 @@ static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env,
|
||||
return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf));
|
||||
}
|
||||
static inline bool IsTexturePixelFormatIntegerCached(Environment& env,
|
||||
const ConstBufferAddr& cbuf) {
|
||||
const ConstBufferAddr& cbuf) {
|
||||
return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf));
|
||||
}
|
||||
|
||||
|
||||
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env);
|
||||
static inline std::optional<ConstBufferAddr> TrackCached(const IR::Value& v, Environment& env) {
|
||||
if (const IR::Inst* key = v.InstRecursive()) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
|
||||
@@ -110,6 +110,12 @@ struct RuntimeInfo {
|
||||
|
||||
/// Output types for each color attachment
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
|
||||
/// Fragment color outputs that are active for the current pipeline.
|
||||
std::array<bool, 8> active_color_outputs{true, true, true, true, true, true, true, true};
|
||||
|
||||
/// Dual source blending
|
||||
bool dual_source_blend{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@@ -356,11 +356,22 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
|
||||
if (is_indexed) {
|
||||
BindHostIndexBuffer();
|
||||
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const auto& regs = maxwell3d->regs;
|
||||
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
|
||||
const bool allow_polygon_mode_emulation =
|
||||
regs.transform_feedback_enabled == 0 &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
|
||||
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
|
||||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
const auto polygon_mode = allow_polygon_mode_emulation
|
||||
? regs.polygon_mode_front
|
||||
: Maxwell::PolygonMode::Fill;
|
||||
if (polygon_mode != Maxwell::PolygonMode::Point) {
|
||||
runtime.BindQuadIndexBuffer(draw_state.topology, polygon_mode,
|
||||
draw_state.vertex_buffer.first,
|
||||
draw_state.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
BindHostVertexBuffers();
|
||||
@@ -760,8 +771,35 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes();
|
||||
runtime.BindIndexBuffer(buffer, new_offset, size);
|
||||
} else {
|
||||
const auto& regs = maxwell3d->regs;
|
||||
const bool allow_polygon_mode_emulation =
|
||||
regs.transform_feedback_enabled == 0 &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry) &&
|
||||
!regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation);
|
||||
auto effective_topology = draw_state.topology;
|
||||
auto polygon_mode = allow_polygon_mode_emulation ? regs.polygon_mode_front
|
||||
: Maxwell::PolygonMode::Fill;
|
||||
if (allow_polygon_mode_emulation) {
|
||||
switch (draw_state.topology) {
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
case Maxwell::PrimitiveTopology::QuadStrip:
|
||||
case Maxwell::PrimitiveTopology::Polygon:
|
||||
if (polygon_mode == Maxwell::PolygonMode::Point) {
|
||||
effective_topology = Maxwell::PrimitiveTopology::Points;
|
||||
} else if (draw_state.topology == Maxwell::PrimitiveTopology::Polygon &&
|
||||
polygon_mode == Maxwell::PolygonMode::Line) {
|
||||
effective_topology = Maxwell::PrimitiveTopology::LineStrip;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
polygon_mode = Maxwell::PolygonMode::Fill;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
polygon_mode = Maxwell::PolygonMode::Fill;
|
||||
}
|
||||
buffer.MarkUsage(offset, size);
|
||||
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
|
||||
runtime.BindIndexBuffer(effective_topology, polygon_mode, draw_state.index_buffer.format,
|
||||
draw_state.index_buffer.first, draw_state.index_buffer.count,
|
||||
buffer, offset, size);
|
||||
}
|
||||
@@ -1064,13 +1102,23 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
if (maxwell3d->regs.transform_feedback_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
HostBindings<typename P::Buffer> host_bindings;
|
||||
Buffer& null_buffer = slot_buffers[NULL_BUFFER_ID];
|
||||
bool reached_end = false;
|
||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||
const Binding& binding = channel_state->transform_feedback_buffers[index];
|
||||
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
|
||||
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
|
||||
break;
|
||||
const auto& control = maxwell3d->regs.transform_feedback.controls[index];
|
||||
if (reached_end ||
|
||||
(control.varying_count == 0 && control.stride == 0)) {
|
||||
reached_end = true;
|
||||
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Binding& binding = channel_state->transform_feedback_buffers[index];
|
||||
if (binding.size == 0 || binding.buffer_id == NULL_BUFFER_ID) {
|
||||
runtime.BindTransformFeedbackBuffer(index, null_buffer, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const u32 size = binding.size;
|
||||
@@ -1080,12 +1128,7 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
|
||||
const u32 offset = buffer.Offset(binding.device_addr);
|
||||
buffer.MarkUsage(offset, size);
|
||||
host_bindings.buffers.push_back(&buffer);
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(size);
|
||||
}
|
||||
if (host_bindings.buffers.size() > 0) {
|
||||
runtime.BindTransformFeedbackBuffers(host_bindings);
|
||||
runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <span>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <vector>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
#include "common/literals.h"
|
||||
@@ -94,10 +97,10 @@ static constexpr Binding NULL_BINDING{
|
||||
|
||||
template <typename Buffer>
|
||||
struct HostBindings {
|
||||
boost::container::small_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
boost::container::static_vector<Buffer*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::static_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
u32 min_index{NUM_VERTEX_BUFFERS};
|
||||
u32 max_index{0};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -19,12 +22,12 @@ ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
|
||||
void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) {
|
||||
ASSERT(memory_manager);
|
||||
program_id = program_id_;
|
||||
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
|
||||
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
|
||||
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
|
||||
dma_pusher.emplace(system, gpu, *memory_manager, *this);
|
||||
maxwell_3d.emplace(system, *memory_manager);
|
||||
fermi_2d.emplace(*memory_manager);
|
||||
kepler_compute.emplace(system, *memory_manager);
|
||||
maxwell_dma.emplace(system, *memory_manager);
|
||||
kepler_memory.emplace(system, *memory_manager);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user