mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-16 13:22:41 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c553826c2 | |||
| 0b4393e49e | |||
| 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
+1
-27
@@ -55,11 +55,6 @@ abstract class SettingsItem(
|
||||
|
||||
val isEditable: Boolean
|
||||
get() {
|
||||
// Can't change docked mode toggle when using handheld mode
|
||||
if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) {
|
||||
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
|
||||
}
|
||||
|
||||
// Can't edit enable_qlaunch_button if firmware is not available
|
||||
if (setting.key == BooleanSetting.ENABLE_QLAUNCH_BUTTON.key) {
|
||||
return NativeLibrary.isFirmwareAvailable()
|
||||
@@ -231,30 +226,9 @@ abstract class SettingsItem(
|
||||
)
|
||||
)
|
||||
|
||||
val dockedModeSetting = object : AbstractBooleanSetting {
|
||||
override val key = BooleanSetting.USE_DOCKED_MODE.key
|
||||
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
||||
if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) {
|
||||
return false
|
||||
}
|
||||
return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal)
|
||||
}
|
||||
|
||||
override fun setBoolean(value: Boolean) =
|
||||
BooleanSetting.USE_DOCKED_MODE.setBoolean(value)
|
||||
|
||||
override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||
BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal)
|
||||
|
||||
override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset()
|
||||
}
|
||||
|
||||
put(
|
||||
SwitchSetting(
|
||||
dockedModeSetting,
|
||||
BooleanSetting.USE_DOCKED_MODE,
|
||||
titleId = R.string.use_docked_mode,
|
||||
descriptionId = R.string.use_docked_mode_description
|
||||
)
|
||||
|
||||
+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
-10
@@ -1066,7 +1066,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 +1127,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()
|
||||
|
||||
@@ -424,7 +424,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,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -14,6 +17,14 @@
|
||||
#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;
|
||||
return;
|
||||
}
|
||||
|
||||
m_window_width = ANativeWindow_getWidth(surface);
|
||||
m_window_height = ANativeWindow_getHeight(surface);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1222,12 +1222,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>
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -753,6 +756,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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -356,6 +356,12 @@ 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"
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -1670,13 +1670,22 @@ 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;
|
||||
if (!need_dual_source && !info.stores_frag_color[index] &&
|
||||
!profile.need_declared_frag_colors) {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ struct RuntimeInfo {
|
||||
|
||||
/// Output types for each color attachment
|
||||
std::array<AttributeType, 8> color_output_types{};
|
||||
|
||||
/// Dual source blending
|
||||
bool dual_source_blend{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@@ -1032,7 +1032,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
|
||||
VkShaderModule frag_shader = *convert_float_to_depth_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1062,7 +1062,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
|
||||
VkShaderModule frag_shader = *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1093,7 +1093,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
|
||||
}
|
||||
const std::array stages = MakeStages(*full_screen_vert, *module);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -1135,7 +1135,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
||||
is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
|
||||
const std::array stages = MakeStages(*full_screen_vert, frag_shader);
|
||||
const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -61,7 +64,8 @@ public:
|
||||
.pDescriptorUpdateEntries = entries.data(),
|
||||
.templateType = type,
|
||||
.descriptorSetLayout = descriptor_set_layout,
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineBindPoint =
|
||||
is_compute ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.pipelineLayout = pipeline_layout,
|
||||
.set = 0,
|
||||
});
|
||||
@@ -122,7 +126,7 @@ private:
|
||||
});
|
||||
++binding;
|
||||
num_descriptors += descriptors[i].count;
|
||||
offset += sizeof(DescriptorUpdateEntry);
|
||||
offset += sizeof(DescriptorUpdateEntry) * descriptors[i].count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include <ranges>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include "video_core/renderer_vulkan/present/util.h"
|
||||
|
||||
namespace Vulkan {
|
||||
@@ -629,8 +630,8 @@ vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qc
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.magFilter = VK_FILTER_CUBIC_EXT,
|
||||
.minFilter = VK_FILTER_CUBIC_EXT,
|
||||
.magFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
|
||||
.minFilter = device.IsExtFilterCubicSupported() ? VK_FILTER_CUBIC_EXT : VK_FILTER_LINEAR,
|
||||
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
|
||||
@@ -137,14 +137,8 @@ try
|
||||
memory_allocator,
|
||||
scheduler,
|
||||
swapchain,
|
||||
#ifdef ANDROID
|
||||
surface)
|
||||
,
|
||||
#else
|
||||
*surface)
|
||||
,
|
||||
#endif
|
||||
blit_swapchain(device_memory,
|
||||
, blit_swapchain(device_memory,
|
||||
device,
|
||||
memory_allocator,
|
||||
present_manager,
|
||||
|
||||
@@ -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 Torzu Emulator Project
|
||||
@@ -22,7 +22,8 @@ BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const
|
||||
MemoryAllocator& memory_allocator_, PresentManager& present_manager_,
|
||||
Scheduler& scheduler_, const PresentFilters& filters_)
|
||||
: device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_},
|
||||
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, image_count{1},
|
||||
present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_},
|
||||
image_count{1}, image_index{0},
|
||||
swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {}
|
||||
|
||||
BlitScreen::~BlitScreen() = default;
|
||||
@@ -87,57 +88,49 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
|
||||
bool resource_update_required = false;
|
||||
bool presentation_recreate_required = false;
|
||||
|
||||
// Recreate dynamic resources if the adapting filter changed
|
||||
if (!window_adapt || scaling_filter != filters.get_scaling_filter()) {
|
||||
resource_update_required = true;
|
||||
}
|
||||
|
||||
// Recreate dynamic resources if the image count changed
|
||||
const size_t old_swapchain_image_count =
|
||||
std::exchange(image_count, current_swapchain_image_count);
|
||||
if (old_swapchain_image_count != current_swapchain_image_count) {
|
||||
if (image_count != current_swapchain_image_count) {
|
||||
resource_update_required = true;
|
||||
image_count = current_swapchain_image_count;
|
||||
}
|
||||
|
||||
// Recreate the presentation frame if the format or dimensions of the window changed
|
||||
const VkFormat old_swapchain_view_format =
|
||||
std::exchange(swapchain_view_format, current_swapchain_view_format);
|
||||
if (old_swapchain_view_format != current_swapchain_view_format ||
|
||||
if (swapchain_view_format != current_swapchain_view_format ||
|
||||
layout.width != frame->width || layout.height != frame->height) {
|
||||
resource_update_required = true;
|
||||
presentation_recreate_required = true;
|
||||
swapchain_view_format = current_swapchain_view_format;
|
||||
}
|
||||
|
||||
// If we have a pending resource update, perform it
|
||||
if (resource_update_required) {
|
||||
// Wait for idle to ensure no resources are in use
|
||||
WaitIdle();
|
||||
|
||||
// Update window adapt pass
|
||||
SetWindowAdaptPass();
|
||||
|
||||
// Update frame format if needed
|
||||
if (presentation_recreate_required) {
|
||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||
window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
image_index = 0;
|
||||
}
|
||||
|
||||
// Add additional layers if needed
|
||||
const VkExtent2D window_size{
|
||||
.width = layout.screen.GetWidth(),
|
||||
.height = layout.screen.GetHeight(),
|
||||
};
|
||||
|
||||
while (layers.size() < framebuffers.size()) {
|
||||
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
|
||||
window_size, window_adapt->GetDescriptorSetLayout(), filters);
|
||||
if (layers.size() != framebuffers.size()) {
|
||||
layers.clear();
|
||||
for (size_t i = 0; i < framebuffers.size(); ++i) {
|
||||
layers.emplace_back(device, memory_allocator, scheduler, device_memory, image_count,
|
||||
window_size, window_adapt->GetDescriptorSetLayout(), filters);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the draw
|
||||
window_adapt->Draw(rasterizer, scheduler, image_index, layers, framebuffers, layout, frame);
|
||||
|
||||
// Advance to next image
|
||||
if (++image_index >= image_count) {
|
||||
image_index = 0;
|
||||
}
|
||||
@@ -146,16 +139,20 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame,
|
||||
vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& layout,
|
||||
VkImageView image_view,
|
||||
VkFormat current_view_format) {
|
||||
const bool format_updated =
|
||||
std::exchange(swapchain_view_format, current_view_format) != current_view_format;
|
||||
bool format_updated = swapchain_view_format != current_view_format;
|
||||
swapchain_view_format = current_view_format;
|
||||
|
||||
if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) {
|
||||
WaitIdle();
|
||||
SetWindowAdaptPass();
|
||||
image_index = 0;
|
||||
}
|
||||
|
||||
const VkExtent2D extent{
|
||||
.width = layout.width,
|
||||
.height = layout.height,
|
||||
};
|
||||
|
||||
return CreateFramebuffer(image_view, extent, window_adapt->GetRenderPass());
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,14 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
|
||||
// Null buffer not supported, adjust offset and size
|
||||
offset = 0;
|
||||
size = 0;
|
||||
} else {
|
||||
// Align offset down to minTexelBufferOffsetAlignment
|
||||
const u32 alignment = static_cast<u32>(device->GetMinTexelBufferOffsetAlignment());
|
||||
if (alignment > 1) {
|
||||
const u32 aligned_offset = offset & ~(alignment - 1);
|
||||
size += offset - aligned_offset;
|
||||
offset = aligned_offset;
|
||||
}
|
||||
}
|
||||
const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
|
||||
return offset == view.offset && size == view.size && format == view.format;
|
||||
|
||||
@@ -285,7 +285,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
.requiredSubgroupSize = optional_subgroup_size ? *optional_subgroup_size : 32U,
|
||||
};
|
||||
bool use_setup_size = device.IsExtSubgroupSizeControlSupported() && optional_subgroup_size;
|
||||
pipeline = device.GetLogical().CreateComputePipeline({
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -299,7 +299,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool,
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}
|
||||
@@ -944,7 +944,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
||||
.codeSize = static_cast<u32>(code.size_bytes()),
|
||||
.pCode = code.data(),
|
||||
});
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline({
|
||||
pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
@@ -958,7 +958,7 @@ MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_,
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,11 +50,14 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
DescriptorLayoutBuilder builder{device};
|
||||
builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(false);
|
||||
uses_push_descriptor = builder.CanUsePushDescriptor();
|
||||
descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor);
|
||||
pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout);
|
||||
descriptor_update_template =
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false);
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor);
|
||||
if (!uses_push_descriptor) {
|
||||
descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info);
|
||||
}
|
||||
const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
@@ -64,26 +67,24 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
pipeline = device.GetLogical().CreateComputePipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stage{
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.pNext =
|
||||
device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr,
|
||||
.flags = 0,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.module = *spv_module,
|
||||
.pName = "main",
|
||||
.pSpecializationInfo = nullptr,
|
||||
},
|
||||
*pipeline_cache);
|
||||
.layout = *pipeline_layout,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
|
||||
// Log compute pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
@@ -241,11 +242,16 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data),
|
||||
rescaling_data.data());
|
||||
}
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
if (uses_push_descriptor) {
|
||||
cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout,
|
||||
0, descriptor_data);
|
||||
} else {
|
||||
const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()};
|
||||
const vk::Device& dev{device.GetLogical()};
|
||||
dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data);
|
||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0,
|
||||
descriptor_set, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,6 +58,7 @@ private:
|
||||
|
||||
vk::ShaderModule spv_module;
|
||||
vk::DescriptorSetLayout descriptor_set_layout;
|
||||
bool uses_push_descriptor{false};
|
||||
DescriptorAllocator descriptor_allocator;
|
||||
vk::PipelineLayout pipeline_layout;
|
||||
vk::DescriptorUpdateTemplate descriptor_update_template;
|
||||
|
||||
@@ -474,7 +474,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
|
||||
buffer_cache.BindHostStageBuffers(stage);
|
||||
PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
|
||||
samplers_it, views_it);
|
||||
const auto& info{stage_infos[0]};
|
||||
const auto& info{stage_infos[stage]};
|
||||
if (info.uses_render_area) {
|
||||
render_area.uses_render_area = true;
|
||||
render_area.words = {static_cast<float>(regs.surface_clip.width),
|
||||
@@ -946,29 +946,27 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR;
|
||||
}
|
||||
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline(
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
*pipeline_cache);
|
||||
pipeline = device.GetLogical().CreateGraphicsPipeline({
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = flags,
|
||||
.stageCount = static_cast<u32>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input_ci,
|
||||
.pInputAssemblyState = &input_assembly_ci,
|
||||
.pTessellationState = &tessellation_ci,
|
||||
.pViewportState = &viewport_ci,
|
||||
.pRasterizationState = &rasterization_ci,
|
||||
.pMultisampleState = &multisample_ci,
|
||||
.pDepthStencilState = &depth_stencil_ci,
|
||||
.pColorBlendState = &color_blend_ci,
|
||||
.pDynamicState = &dynamic_state_ci,
|
||||
.layout = *pipeline_layout,
|
||||
.renderPass = render_pass,
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = 0,
|
||||
}, *pipeline_cache);
|
||||
|
||||
// Log graphics pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
|
||||
@@ -121,10 +121,8 @@ VkResult MasterSemaphore::SubmitQueue(vk::CommandBuffer& cmdbuf, vk::CommandBuff
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
};
|
||||
static constexpr VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
|
||||
VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
|
||||
vk::CommandBuffer& upload_cmdbuf,
|
||||
@@ -143,7 +141,7 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf,
|
||||
const VkSemaphore* p_wait_sems =
|
||||
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
|
||||
const VkPipelineStageFlags* p_wait_masks =
|
||||
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
|
||||
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
|
||||
const VkSemaphore* p_signal_sems =
|
||||
(num_signal_semaphores > 0) ? signal_semaphores.data() : nullptr;
|
||||
const u64 wait_zero = 0; // dummy for binary wait
|
||||
@@ -180,7 +178,7 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf,
|
||||
const VkSemaphore* p_wait_sems =
|
||||
(num_wait_semaphores > 0) ? &wait_semaphore : nullptr;
|
||||
const VkPipelineStageFlags* p_wait_masks =
|
||||
(num_wait_semaphores > 0) ? wait_stage_masks.data() : nullptr;
|
||||
(num_wait_semaphores > 0) ? &wait_stage_mask : nullptr;
|
||||
const VkSemaphore* p_signal_sems =
|
||||
(num_signal_semaphores > 0) ? &signal_semaphore : nullptr;
|
||||
const std::array cmdbuffers{*upload_cmdbuf, *cmdbuf};
|
||||
|
||||
@@ -237,11 +237,38 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
info.convert_depth_mode = gl_ndc;
|
||||
break;
|
||||
case Shader::Stage::Fragment:
|
||||
case Shader::Stage::Fragment: {
|
||||
info.alpha_test_func = MaxwellToCompareFunction(
|
||||
key.state.UnpackComparisonOp(key.state.alpha_test_func.Value()));
|
||||
info.alpha_test_reference = std::bit_cast<float>(key.state.alpha_test_ref);
|
||||
|
||||
// Check for dual source blending
|
||||
const auto& blend0 = key.state.attachments[0];
|
||||
if (blend0.enable != 0) {
|
||||
using F = Maxwell::Blend::Factor;
|
||||
const auto src_rgb = blend0.SourceRGBFactor();
|
||||
const auto dst_rgb = blend0.DestRGBFactor();
|
||||
const auto src_a = blend0.SourceAlphaFactor();
|
||||
const auto dst_a = blend0.DestAlphaFactor();
|
||||
info.dual_source_blend =
|
||||
src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D ||
|
||||
src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL ||
|
||||
src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D ||
|
||||
dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL ||
|
||||
dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL ||
|
||||
src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D ||
|
||||
src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D ||
|
||||
src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL ||
|
||||
src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL ||
|
||||
dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D ||
|
||||
dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D ||
|
||||
dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL ||
|
||||
dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL;
|
||||
}
|
||||
|
||||
if (device.IsMoltenVK()) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]);
|
||||
@@ -258,6 +285,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -101,22 +101,14 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
||||
MemoryAllocator& memory_allocator_,
|
||||
Scheduler& scheduler_,
|
||||
Swapchain& swapchain_,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface_)
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_)
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface_)
|
||||
: instance{instance_}
|
||||
, render_window{render_window_}
|
||||
, device{device_}
|
||||
, memory_allocator{memory_allocator_}
|
||||
, scheduler{scheduler_}
|
||||
, swapchain{swapchain_}
|
||||
#ifdef ANDROID
|
||||
, surface{surface_}
|
||||
#else
|
||||
, surface_handle{surface_handle_}
|
||||
#endif
|
||||
, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}
|
||||
, use_present_thread{Settings::values.async_presentation.GetValue()}
|
||||
{
|
||||
@@ -299,11 +291,7 @@ void PresentManager::PresentThread(std::stop_token token) {
|
||||
}
|
||||
|
||||
void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||
#ifndef ANDROID
|
||||
swapchain.Create(surface_handle, frame->width, frame->height); // Pass raw pointer
|
||||
#else
|
||||
swapchain.Create(*surface, frame->width, frame->height); // Pass raw pointer
|
||||
#endif
|
||||
swapchain.Create(surface, frame->width, frame->height); // Pass raw pointer
|
||||
SetImageCount();
|
||||
}
|
||||
|
||||
@@ -322,7 +310,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
// Recreate surface and swapchain if needed.
|
||||
if (requires_recreation) {
|
||||
#ifdef ANDROID
|
||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||
surface = *CreateSurface(instance, render_window.GetWindowInfo()).address();
|
||||
#endif
|
||||
RecreateSwapchain(frame);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -15,8 +15,6 @@
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
} // namespace Core::Frontend
|
||||
@@ -46,11 +44,7 @@ public:
|
||||
MemoryAllocator& memory_allocator,
|
||||
Scheduler& scheduler,
|
||||
Swapchain& swapchain,
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface);
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle);
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface);
|
||||
~PresentManager();
|
||||
|
||||
/// Returns the last used presentation frame
|
||||
@@ -84,11 +78,7 @@ private:
|
||||
MemoryAllocator& memory_allocator;
|
||||
Scheduler& scheduler;
|
||||
Swapchain& swapchain;
|
||||
#ifdef ANDROID
|
||||
vk::SurfaceKHR& surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface;
|
||||
vk::CommandPool cmdpool;
|
||||
std::vector<Frame> frames;
|
||||
boost::container::deque<Frame*> present_queue;
|
||||
|
||||
@@ -1280,7 +1280,7 @@ void QueryCacheRuntime::EndHostConditionalRendering() {
|
||||
PauseHostConditionalRendering();
|
||||
impl->hcr_is_set = false;
|
||||
impl->is_hcr_running = false;
|
||||
impl->hcr_buffer = nullptr;
|
||||
impl->hcr_buffer = VkBuffer{};
|
||||
impl->hcr_offset = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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-3.0-or-later
|
||||
|
||||
@@ -35,7 +38,7 @@ public:
|
||||
~QueryCacheRuntime();
|
||||
|
||||
template <typename SyncValuesType>
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = nullptr);
|
||||
void SyncValues(std::span<SyncValuesType> values, VkBuffer base_src_buffer = VkBuffer{});
|
||||
|
||||
void Barriers(bool is_prebarrier);
|
||||
|
||||
|
||||
@@ -270,8 +270,8 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
};
|
||||
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
|
||||
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, WRITE_BARRIER);
|
||||
upload_cmdbuf.End();
|
||||
cmdbuf.End();
|
||||
|
||||
@@ -372,18 +372,12 @@ void Scheduler::EndRenderPass()
|
||||
};
|
||||
}
|
||||
cmdbuf.EndRenderPass();
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
vk::Span(barriers.data(), num_images) // Batched image barriers
|
||||
);
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
|
||||
});
|
||||
|
||||
state.renderpass = nullptr;
|
||||
state.renderpass = VkRenderPass{};
|
||||
num_renderpass_images = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
@@ -44,10 +44,10 @@ public:
|
||||
~Scheduler();
|
||||
|
||||
/// Sends the current execution context to the GPU.
|
||||
u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
u64 Flush(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
|
||||
/// Sends the current execution context to the GPU and waits for it to complete.
|
||||
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
void Finish(VkSemaphore signal_semaphore = {}, VkSemaphore wait_semaphore = {});
|
||||
|
||||
/// Waits for the worker thread to finish executing everything. After this function returns it's
|
||||
/// safe to touch worker resources.
|
||||
@@ -114,29 +114,36 @@ public:
|
||||
|
||||
/// Waits for the given GPU tick, optionally pacing frames.
|
||||
void Wait(u64 tick, double target_fps = 0.0) {
|
||||
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (start_time == std::chrono::steady_clock::time_point{} || current_target_fps != target_fps) {
|
||||
start_time = now;
|
||||
frame_counter = 0;
|
||||
current_target_fps = target_fps;
|
||||
}
|
||||
frame_counter++;
|
||||
std::chrono::duration<double> frame_interval(1.0 / current_target_fps);
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time > now) {
|
||||
std::this_thread::sleep_until(target_time);
|
||||
} else {
|
||||
start_time = now;
|
||||
frame_counter = 0;
|
||||
}
|
||||
}
|
||||
if (tick > 0) {
|
||||
if (tick >= master_semaphore->CurrentTick()) {
|
||||
Flush();
|
||||
}
|
||||
master_semaphore->Wait(tick);
|
||||
}
|
||||
if (Settings::values.use_speed_limit.GetValue() && target_fps > 0.0) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (last_target_fps != target_fps) {
|
||||
frame_interval = std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(1.0 / target_fps));
|
||||
max_frame_count = static_cast<int>(0.1 * target_fps);
|
||||
last_target_fps = target_fps;
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
frame_counter++;
|
||||
auto target_time = start_time + frame_interval * frame_counter;
|
||||
if (target_time >= now) {
|
||||
auto sleep_time = target_time - now;
|
||||
if (sleep_time > std::chrono::milliseconds(15)) {
|
||||
std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1));
|
||||
}
|
||||
while (std::chrono::steady_clock::now() < target_time) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
} else if (frame_counter > max_frame_count) {
|
||||
frame_counter = 0;
|
||||
start_time = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the master timeline semaphore.
|
||||
@@ -230,8 +237,8 @@ private:
|
||||
};
|
||||
|
||||
struct State {
|
||||
VkRenderPass renderpass = nullptr;
|
||||
VkFramebuffer framebuffer = nullptr;
|
||||
VkRenderPass renderpass{};
|
||||
VkFramebuffer framebuffer{};
|
||||
VkExtent2D render_area = {0, 0};
|
||||
GraphicsPipeline* graphics_pipeline = nullptr;
|
||||
bool is_rescaling = false;
|
||||
@@ -281,9 +288,11 @@ private:
|
||||
std::condition_variable_any event_cv;
|
||||
std::jthread worker_thread;
|
||||
|
||||
std::chrono::steady_clock::duration frame_interval{};
|
||||
std::chrono::steady_clock::time_point start_time{};
|
||||
double last_target_fps{};
|
||||
u64 max_frame_count{};
|
||||
u64 frame_counter{};
|
||||
double current_target_fps{};
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -109,38 +109,22 @@ VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& cap
|
||||
} // Anonymous namespace
|
||||
|
||||
Swapchain::Swapchain(
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface_,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_,
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface_,
|
||||
const Device& device_,
|
||||
Scheduler& scheduler_,
|
||||
u32 width_,
|
||||
u32 height_)
|
||||
#ifdef ANDROID
|
||||
: surface(surface_)
|
||||
#else
|
||||
: surface_handle{surface_handle_}
|
||||
#endif
|
||||
, device{device_}
|
||||
, scheduler{scheduler_}
|
||||
{
|
||||
#ifdef ANDROID
|
||||
Create(surface, width_, height_);
|
||||
#else
|
||||
Create(surface_handle, width_, height_);
|
||||
#endif
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain() = default;
|
||||
|
||||
void Swapchain::Create(
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface_,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle_,
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface_,
|
||||
u32 width_,
|
||||
u32 height_)
|
||||
{
|
||||
@@ -148,18 +132,10 @@ void Swapchain::Create(
|
||||
is_suboptimal = false;
|
||||
width = width_;
|
||||
height = height_;
|
||||
#ifdef ANDROID
|
||||
surface = surface_;
|
||||
#else
|
||||
surface_handle = surface_handle_;
|
||||
#endif
|
||||
|
||||
const auto physical_device = device.GetPhysical();
|
||||
#ifdef ANDROID
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)};
|
||||
#else
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface_handle)};
|
||||
#endif
|
||||
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface))};
|
||||
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -254,14 +230,8 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
|
||||
|
||||
void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
const auto physical_device{device.GetPhysical()};
|
||||
|
||||
#ifdef ANDROID
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface);
|
||||
#else
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(surface_handle)};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface_handle);
|
||||
#endif
|
||||
const auto formats{physical_device.GetSurfaceFormatsKHR(VkSurfaceKHR(surface))};
|
||||
const auto present_modes = physical_device.GetSurfacePresentModesKHR(VkSurfaceKHR(surface));
|
||||
|
||||
has_mailbox = std::find(present_modes.begin(), present_modes.end(), VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
!= present_modes.end();
|
||||
@@ -290,11 +260,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
#ifdef ANDROID
|
||||
.surface = surface,
|
||||
#else
|
||||
.surface = surface_handle,
|
||||
#endif
|
||||
.surface = VkSurfaceKHR(surface),
|
||||
.minImageCount = requested_image_count,
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
@@ -313,7 +279,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
.compositeAlpha = alpha_flags,
|
||||
.presentMode = present_mode,
|
||||
.clipped = VK_FALSE,
|
||||
.oldSwapchain = nullptr,
|
||||
.oldSwapchain = VkSwapchainKHR{},
|
||||
};
|
||||
const u32 graphics_family{device.GetGraphicsFamily()};
|
||||
const u32 present_family{device.GetPresentFamily()};
|
||||
@@ -345,11 +311,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
||||
swapchain_ci.flags |= VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR;
|
||||
}
|
||||
// Request the size again to reduce the possibility of a TOCTOU race condition.
|
||||
#ifdef ANDROID
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface);
|
||||
#else
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(surface_handle);
|
||||
#endif
|
||||
const auto updated_capabilities = physical_device.GetSurfaceCapabilitiesKHR(VkSurfaceKHR(surface));
|
||||
swapchain_ci.imageExtent = ChooseSwapExtent(updated_capabilities, width, height);
|
||||
// Don't add code within this and the swapchain creation.
|
||||
swapchain = device.GetLogical().CreateSwapchainKHR(swapchain_ci);
|
||||
|
||||
@@ -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
|
||||
@@ -11,8 +11,6 @@
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
@@ -25,11 +23,7 @@ class Scheduler;
|
||||
class Swapchain {
|
||||
public:
|
||||
explicit Swapchain(
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle,
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface,
|
||||
const Device& device,
|
||||
Scheduler& scheduler,
|
||||
u32 width,
|
||||
@@ -38,11 +32,7 @@ public:
|
||||
|
||||
/// Creates (or recreates) the swapchain with a given size.
|
||||
void Create(
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface,
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle,
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface,
|
||||
u32 width,
|
||||
u32 height);
|
||||
|
||||
@@ -128,11 +118,7 @@ private:
|
||||
|
||||
bool NeedsPresentModeUpdate() const;
|
||||
|
||||
#ifdef ANDROID
|
||||
VkSurfaceKHR surface;
|
||||
#else
|
||||
VkSurfaceKHR_T* surface_handle;
|
||||
#endif
|
||||
VkSurfaceKHR_T* surface;
|
||||
|
||||
const Device& device;
|
||||
Scheduler& scheduler;
|
||||
|
||||
@@ -2500,7 +2500,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -40,3 +40,6 @@
|
||||
#undef False
|
||||
#undef None
|
||||
#undef True
|
||||
|
||||
// "Catch-all" handle for both Android and.. the rest of platforms
|
||||
struct VkSurfaceKHR_T;
|
||||
|
||||
@@ -419,7 +419,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
: instance{instance_}, dld{dld_}, physical{physical_},
|
||||
format_properties(GetFormatProperties(physical)) {
|
||||
// Get suitability and device properties.
|
||||
const bool is_suitable = GetSuitability(surface != nullptr);
|
||||
const bool is_suitable = GetSuitability(surface != VkSurfaceKHR{});
|
||||
|
||||
const VkDriverId driver_id = properties.driver.driverID;
|
||||
|
||||
@@ -557,20 +557,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
}
|
||||
|
||||
if (is_nvidia) {
|
||||
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
||||
const auto arch = GetNvidiaArch();
|
||||
if (arch >= NvidiaArchitecture::Arch_AmpereOrNewer) {
|
||||
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
}
|
||||
|
||||
if (nv_major_version >= 510) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"NVIDIA Drivers >= 510 do not support MSAA->MSAA image blits. "
|
||||
"MSAA scaling will use 3D helpers. MSAA resolves work normally.");
|
||||
cant_blit_msaa = true;
|
||||
}
|
||||
|
||||
// Mali/ NVIDIA proprietary drivers: Shader stencil export not supported
|
||||
// Use hardware depth/stencil blits instead when available
|
||||
if (!extensions.shader_stencil_export) {
|
||||
@@ -594,12 +586,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
if (is_amd_driver) {
|
||||
// AMD drivers need a higher amount of Sets per Pool in certain circumstances like in XC2.
|
||||
sets_per_pool = 96;
|
||||
|
||||
// Disable VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT on AMD GCN4 and lower as it is broken.
|
||||
if (!features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD GCN4 and earlier have broken VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT");
|
||||
has_broken_cube_compatibility = true;
|
||||
}
|
||||
|
||||
// AMD drivers (2026+) have broken float16 math on DKCR
|
||||
if (features.shader_float16_int8.shaderFloat16) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"AMD drivers (2026+) have broken float16 math");
|
||||
features.shader_float16_int8.shaderFloat16 = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_qualcomm) {
|
||||
|
||||
@@ -107,6 +107,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
||||
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
|
||||
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
|
||||
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
|
||||
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
|
||||
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
|
||||
|
||||
// Define extensions which must be supported.
|
||||
@@ -317,6 +318,11 @@ public:
|
||||
return properties.properties.limits.minStorageBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns texel buffer offset alignment requirement.
|
||||
VkDeviceSize GetMinTexelBufferOffsetAlignment() const {
|
||||
return properties.properties.limits.minTexelBufferOffsetAlignment;
|
||||
}
|
||||
|
||||
/// Returns the maximum range for storage buffers.
|
||||
VkDeviceSize GetMaxStorageBufferRange() const {
|
||||
return properties.properties.limits.maxStorageBufferRange;
|
||||
|
||||
@@ -15,7 +15,7 @@ vk::SurfaceKHR CreateSurface(
|
||||
const vk::Instance& instance,
|
||||
[[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) {
|
||||
[[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
|
||||
VkSurfaceKHR unsafe_surface = nullptr;
|
||||
VkSurfaceKHR unsafe_surface = VkSurfaceKHR{};
|
||||
|
||||
#ifdef _WIN32
|
||||
if (window_info.type == Core::Frontend::WindowSystemType::Windows) {
|
||||
|
||||
@@ -404,13 +404,13 @@ public:
|
||||
|
||||
/// Construct a handle transferring the ownership from another handle.
|
||||
Handle(Handle&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, dld{rhs.dld} {}
|
||||
: handle{std::exchange(rhs.handle, Type{})}, owner{rhs.owner}, dld{rhs.dld} {}
|
||||
|
||||
/// Assign the current handle transferring the ownership from another handle.
|
||||
/// Destroys any previously held object.
|
||||
Handle& operator=(Handle&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
handle = std::exchange(rhs.handle, Type{});
|
||||
owner = rhs.owner;
|
||||
dld = rhs.dld;
|
||||
return *this;
|
||||
@@ -424,7 +424,7 @@ public:
|
||||
/// Destroys any held object.
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = nullptr;
|
||||
handle = Type{};
|
||||
}
|
||||
|
||||
/// Returns the address of the held object.
|
||||
@@ -440,7 +440,7 @@ public:
|
||||
|
||||
/// Returns true when there's a held object.
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != nullptr;
|
||||
return handle != Type{};
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
@@ -455,7 +455,7 @@ public:
|
||||
#endif
|
||||
|
||||
protected:
|
||||
Type handle = nullptr;
|
||||
Type handle{};
|
||||
OwnerType owner = nullptr;
|
||||
const Dispatch* dld = nullptr;
|
||||
|
||||
@@ -463,7 +463,7 @@ private:
|
||||
/// Destroys the held object if it exists.
|
||||
void Release() noexcept {
|
||||
if (handle) {
|
||||
Destroy(owner, handle, *dld);
|
||||
Destroy(OwnerType(owner), Type(handle), *dld);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -506,7 +506,7 @@ public:
|
||||
/// Destroys any held object.
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = nullptr;
|
||||
handle = {};
|
||||
}
|
||||
|
||||
/// Returns the address of the held object.
|
||||
@@ -522,7 +522,7 @@ public:
|
||||
|
||||
/// Returns true when there's a held object.
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != nullptr;
|
||||
return handle != Type{};
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
@@ -537,7 +537,7 @@ public:
|
||||
#endif
|
||||
|
||||
protected:
|
||||
Type handle = nullptr;
|
||||
Type handle{};
|
||||
const Dispatch* dld = nullptr;
|
||||
|
||||
private:
|
||||
@@ -607,7 +607,7 @@ private:
|
||||
std::unique_ptr<AllocationType[]> allocations;
|
||||
std::size_t num = 0;
|
||||
VkDevice device = nullptr;
|
||||
PoolType pool = nullptr;
|
||||
PoolType pool{};
|
||||
const DeviceDispatch* dld = nullptr;
|
||||
};
|
||||
|
||||
@@ -669,12 +669,12 @@ public:
|
||||
Image& operator=(const Image&) = delete;
|
||||
|
||||
Image(Image&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, usage{rhs.usage}, owner{rhs.owner},
|
||||
: handle{std::exchange(rhs.handle, VkImage{})}, usage{rhs.usage}, owner{rhs.owner},
|
||||
allocator{rhs.allocator}, allocation{rhs.allocation}, dld{rhs.dld} {}
|
||||
|
||||
Image& operator=(Image&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
handle = std::exchange(rhs.handle, VkImage{});
|
||||
usage = rhs.usage;
|
||||
owner = rhs.owner;
|
||||
allocator = rhs.allocator;
|
||||
@@ -693,11 +693,11 @@ public:
|
||||
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = nullptr;
|
||||
handle = VkImage{};
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != nullptr;
|
||||
return handle != VkImage{};
|
||||
}
|
||||
|
||||
void SetObjectNameEXT(const char* name) const;
|
||||
@@ -709,7 +709,7 @@ public:
|
||||
private:
|
||||
void Release() const noexcept;
|
||||
|
||||
VkImage handle = nullptr;
|
||||
VkImage handle{};
|
||||
VkImageUsageFlags usage{};
|
||||
VkDevice owner = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
@@ -730,13 +730,13 @@ public:
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
Buffer(Buffer&& rhs) noexcept
|
||||
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
|
||||
: handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
|
||||
allocation{rhs.allocation}, mapped{rhs.mapped},
|
||||
is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
|
||||
|
||||
Buffer& operator=(Buffer&& rhs) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(rhs.handle, nullptr);
|
||||
handle = std::exchange(rhs.handle, VkBuffer{});
|
||||
owner = rhs.owner;
|
||||
allocator = rhs.allocator;
|
||||
allocation = rhs.allocation;
|
||||
@@ -756,11 +756,11 @@ public:
|
||||
|
||||
void reset() noexcept {
|
||||
Release();
|
||||
handle = nullptr;
|
||||
handle = VkBuffer{};
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return handle != nullptr;
|
||||
return handle != VkBuffer{};
|
||||
}
|
||||
|
||||
/// Returns the host mapped memory, an empty span otherwise.
|
||||
@@ -786,7 +786,7 @@ public:
|
||||
private:
|
||||
void Release() const noexcept;
|
||||
|
||||
VkBuffer handle = nullptr;
|
||||
VkBuffer handle{};
|
||||
VkDevice owner = nullptr;
|
||||
VmaAllocator allocator = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
@@ -1020,10 +1020,10 @@ public:
|
||||
[[nodiscard]] PipelineLayout CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci) const;
|
||||
|
||||
[[nodiscard]] Pipeline CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci,
|
||||
VkPipelineCache cache = nullptr) const;
|
||||
VkPipelineCache cache = {}) const;
|
||||
|
||||
[[nodiscard]] Pipeline CreateComputePipeline(const VkComputePipelineCreateInfo& ci,
|
||||
VkPipelineCache cache = nullptr) const;
|
||||
VkPipelineCache cache = {}) const;
|
||||
|
||||
[[nodiscard]] Sampler CreateSampler(const VkSamplerCreateInfo& ci) const;
|
||||
|
||||
|
||||
@@ -412,10 +412,10 @@ if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(yuzu PRIVATE ENABLE_WEB_SERVICE)
|
||||
endif()
|
||||
|
||||
# if (YUZU_USE_QT_MULTIMEDIA)
|
||||
# target_link_libraries(yuzu PRIVATE Qt6::Multimedia)
|
||||
# target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA)
|
||||
# endif ()
|
||||
if (YUZU_USE_QT_MULTIMEDIA)
|
||||
target_link_libraries(yuzu PRIVATE Qt6::Multimedia)
|
||||
target_compile_definitions(yuzu PRIVATE YUZU_USE_QT_MULTIMEDIA)
|
||||
endif ()
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
target_link_libraries(yuzu PRIVATE Qt6::WebEngineCore Qt6::WebEngineWidgets)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user