Compare commits

...

10 Commits

Author SHA1 Message Date
xbzk cdb6228183 [core, *] support bundled application program IDs 2026-09-08 17:51:10 -03:00
lizzie e5b8b10188 [hle] Fix fsp-pr not being found (#4379)
Should be `fsp-pr`, not `fsp:pr`

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

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

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4379
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 21:17:19 +02:00
lizzie 8a10109e24 [vulkan] Masquerade VkInfo as PUBGMobile + UE (#4320)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

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

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

PR I was talking about with @CamilleLaVey , please tell me if this is a stupid idea OR has a chance to work
applicationname is a wild guess

Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4320
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 21:04:08 +02:00
CamilleLaVey a538cd9aff [shader_recompiler, vulkan] Virtual buffer pages with multi-range + sparse buffers binding (#4362)
Storage buffers can span GPU pages that aren't contiguous in host memory, but the buffer cache assumed one contiguous buffer per binding, so anything crossing a mapping boundary read the wrong bytes. Multi-range binding resolves the real segments and presents them to the shader as one buffer, aliasing their memory into a sparse VkBuffer or gathering them into a copy, all of this was mostly fixed by making a path to use sparse on buffers (the same way as texture cache has it) and retrieve properly the mapping ranges to the virtual pages, including the actual structure on the use of binding sparse.

Meanwhile this fixes Monster Hunter Sunbreak z-fighting, bad performance and mostly vertex explosions (coming from the contiguous buffer read from the shader's game) it's only the basis for a further investigation towards how are we gonna treat phi calculations, optimized them and mostly eradicate the currently excess of exposure on the textures where the lightning trace should not being reflected.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4362
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 20:42:20 +02:00
lizzie ce202292cf [gpu] Destroy GPU thread after all non-trivial members had been destroyed (#4368)
Should help fix any game hangs after closing the emulator, or sporadic hangs when restarting.

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

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

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

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4368
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 20:40:19 +02:00
lizzie 87e5d0b6e4 [hle] VirtualBoy classics fixes, QLaunch IPC fixes, implement extra ams SVCInfo (#4343)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

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

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

`OpenSystemApplicationProxy`
`OpenSystemApplicationProxy`
ALSO yes those cmds DO NOT take an attribute
Implemented SVC infos to match Atmosphere's values
Implement more stub I2C session cmds
stub CMD2103 for rhythm groove
better error handling on svc get info to match ams (see ams svc info)
Virtual Boy classic fix adept from https://git.ryujinx.app/projects/Kenji-NX/commit/2288084b531b185f5840a696b23265014f07e2f2 by @Mythrax
acc:u1 was missing the *Deprecated cmd

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4343
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 20:39:55 +02:00
lizzie fdd8d4252c [common] remove unused vector_math.h fluff (#4333)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

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

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

We don't use half of this file for anything -- it's dead code
also the NEON intrinsic replacements suck -- we are not beating GLM (plus we use registers for decomposition instead of using vec regs like everyone else does, so the "optimizations" dont matter)

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4333
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 20:39:39 +02:00
PavelBARABANOV 1036982d2f partial revert 54cd5fb8eb (#4355)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------
It fixes display issues when the overlay applet is enabled, thanks Bruno.

Fixes freezes when enabling real applet keyboard  in qlaunch.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4355
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
2026-09-08 20:39:08 +02:00
xbzk e27650cf42 [nce] avoid cyrilic utf8 strings being interpreted as exclusive store instructions (#4369)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

Problem: In cyrilic idioms, Absolum won't past beyond 1st area (intro miniboss).

From the user panic i`ve extracted these strings:

 > GlobalState.request_next()
 > save_state_to_saveGame(...)
 > platform.ProcessSave()
 > bin_serialize_to_file(...)
 > StandaloneTypeModel.Write(SaveGame, ...)
 > Write(SaveGame.AdventureSaveSlot, ...)
 > Write(PlayerState, ...)
 > ProtoBuf.ProtoWriter.WriteString(...) + 0xcc
 > ProtoBuf.Helpers.DebugAssert(...)
 > Debug.Fail(...)

By debugging ProtoBuf.ProtoWriter.WriteString(...) argument it was found that the string Отлично! (Great! expression for combos between 6 and 30 iirc) was reaching a size comparison of different values and causing an assert in guest side..

It seems the game tries to save highest combo level. I've managed to skip this bug by doing a perfect combo for entire level, so that the problematic word never was generated or attempted to be saved.

After further research, the issue chain was identified: Game maths literal string size via two ways and compare, and one of them was getting replaced by a wrong value due to Eden letting the literal string be interpreted as a real instruction.

 Logic added:
        const bool pair = decltype(l)::ExtractValue(raw) & 1;
        const bool fixed_rt2 = decltype(rt2)::ExtractValue(raw) == 0b11111;
        return pair || fixed_rt2;

   - pair checks bit 21 (L & 1): register-pair instructions actually use Rt2, so they remain accepted.
   - For single-register forms, require Rt2 == 31. The table word fails this check, so NCE leaves it intact.

Sources
https://www.scs.stanford.edu/~zyedidia/arm64/stxrb.html
https://www.scs.stanford.edu/~zyedidia/arm64/stxp.html
https://github.com/qemu/qemu/blob/master/target/arm/tcg/a64.decode#L344

Altough the solution is a case specific guard, it is data agnostic: This is a true fix to avoid a specific case in which a literal may be interpreted as a valid instruction, without interfering with other true instruction cases.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4369
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-08 20:10:41 +02:00
xbzk ecb2ae4076 [settings] move program_args and debug_knobs back to debugging category and make them pergameable (#4378)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

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

I have moved debug_knobs and program_args to System category, to make them pergameable without hassle.
But the hassle came: They were automatically readded to Qt's System tab, doubling them.
If it were to face hassle either way, the right thing is to move them back to Debugging category, then make them exceptionally pergameable.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4378
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-08 20:05:56 +02:00
84 changed files with 1827 additions and 1346 deletions
@@ -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-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -24,6 +27,6 @@ object SettingsFile {
fun loadCustomConfig(game: Game) { fun loadCustomConfig(game: Game) {
val fileName = FileUtil.getFilename(Uri.parse(game.path)) val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.programId, fileName) NativeConfig.initializePerGameConfig(game.applicationId, fileName)
} }
} }
@@ -189,7 +189,7 @@ class AddonsFragment : Fragment() {
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel, addonViewModel = addonViewModel,
documents = documents, documents = documents,
programId = args.game.programId programId = args.game.applicationId
) )
} }
@@ -347,7 +347,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
try { try {
if (GpuDriverHelper.isAdrenoGpu()) { if (GpuDriverHelper.isAdrenoGpu()) {
val programIdHex = game!!.programIdHex val programIdHex = game!!.applicationIdHex
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) { if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex") Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
} else { } else {
@@ -59,7 +59,7 @@ class FreedrenoSettingsFragment : Fragment() {
NativeFreedrenoConfig.initializeFreedrenoConfig() NativeFreedrenoConfig.initializeFreedrenoConfig()
if (isPerGameConfig) { if (isPerGameConfig) {
NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex) NativeFreedrenoConfig.loadPerGameConfig(game!!.applicationIdHex)
} else { } else {
NativeFreedrenoConfig.reloadFreedrenoConfig() NativeFreedrenoConfig.reloadFreedrenoConfig()
} }
@@ -157,7 +157,7 @@ class FreedrenoSettingsFragment : Fragment() {
binding.buttonSave.setOnClickListener { binding.buttonSave.setOnClickListener {
if (isPerGameConfig) { if (isPerGameConfig) {
NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex) NativeFreedrenoConfig.savePerGameConfig(game!!.applicationIdHex)
showSnackbar(getString(R.string.freedreno_per_game_saved)) showSnackbar(getString(R.string.freedreno_per_game_saved))
} else { } else {
NativeFreedrenoConfig.saveFreedrenoConfig() NativeFreedrenoConfig.saveFreedrenoConfig()
@@ -455,7 +455,7 @@ class GamePropertiesFragment : Fragment() {
val shaderCacheDir = File( val shaderCacheDir = File(
DirectoryInitialization.userDirectory + DirectoryInitialization.userDirectory +
"/cache/shader/" + args.game.settingsName.lowercase() "/cache/shader/" + args.game.shaderCacheName.lowercase()
) )
if (shaderCacheDir.exists()) { if (shaderCacheDir.exists()) {
add( add(
@@ -600,7 +600,7 @@ class GamePropertiesFragment : Fragment() {
val files = cacheSaveDir.listFiles() val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null var savesFolderFile: File? = null
if (files != null) { if (files != null) {
val savesFolderName = args.game.programIdHex val savesFolderName = args.game.applicationIdHex
for (file in files) { for (file in files) {
if (file.isDirectory && file.name == savesFolderName) { if (file.isDirectory && file.name == savesFolderName) {
savesFolderFile = file savesFolderFile = file
@@ -232,7 +232,7 @@ class InstallableFragment : Fragment() {
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
addonViewModel = addonViewModel, addonViewModel = addonViewModel,
documents = documents, documents = documents,
programId = addonViewModel.game?.programId programId = addonViewModel.game?.applicationId
) )
} }
@@ -142,10 +142,11 @@ class AddonViewModel : ViewModel() {
} }
fun onDeleteAddon(patch: Patch) { fun onDeleteAddon(patch: Patch) {
val currentGame = game ?: return
when (PatchType.from(patch.type)) { when (PatchType.from(patch.type)) {
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId) PatchType.Update -> NativeLibrary.removeUpdate(currentGame.programId)
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) PatchType.DLC -> NativeLibrary.removeDLC(currentGame.applicationId)
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) PatchType.Mod -> NativeLibrary.removeMod(currentGame.programId, patch.name)
} }
refreshAddons(force = true) refreshAddons(force = true)
} }
@@ -165,7 +166,7 @@ class AddonViewModel : ViewModel() {
} }
NativeConfig.setDisabledAddons( NativeConfig.setDisabledAddons(
currentGame.programId, currentGame.applicationId,
currentList.mapNotNull { currentList.mapNotNull {
if (it.enabled) { if (it.enabled) {
null null
@@ -199,6 +200,6 @@ class AddonViewModel : ViewModel() {
} }
private fun gameKey(game: Game): String { private fun gameKey(game: Game): String {
return "${game.programId}|${game.path}" return "${game.applicationId}|${game.path}"
} }
} }
@@ -150,7 +150,7 @@ class DriverViewModel : ViewModel() {
?: return@withContext ?: return@withContext
val shaderDir = File( val shaderDir = File(
externalFilesDir.absolutePath + externalFilesDir.absolutePath +
"/shader/" + game.settingsName.lowercase() "/shader/" + game.shaderCacheName.lowercase()
) )
if (shaderDir.exists()) { if (shaderDir.exists()) {
shaderDir.deleteRecursively() shaderDir.deleteRecursively()
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -35,19 +35,26 @@ class Game(
val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime" val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${path}_LastPlayed" val keyLastPlayedTime get() = "${path}_LastPlayed"
private val programIdLong: Long
get() = programId.toLongOrNull() ?: 0L
private val applicationIdLong: Long
get() = programIdLong and -8192L
val applicationId: String
get() = applicationIdLong.toString()
val settingsName: String val settingsName: String
get() { get() {
val programIdLong = programId.toLong() return if (applicationIdLong == 0L) {
return if (programIdLong == 0L) {
FileUtil.getFilename(Uri.parse(path)) FileUtil.getFilename(Uri.parse(path))
} else { } else {
"0" + programIdLong.toString(16).uppercase() "0" + applicationIdLong.toString(16).uppercase()
} }
} }
val programIdHex: String val programIdHex: String
get() { get() {
val programIdLong = programId.toLong()
return if (programIdLong == 0L) { return if (programIdLong == 0L) {
"0" "0"
} else { } else {
@@ -55,16 +62,32 @@ class Game(
} }
} }
val shaderCacheName: String
get() = if (programIdLong == 0L) {
FileUtil.getFilename(Uri.parse(path))
} else {
"0" + programIdLong.toString(16).uppercase()
}
val applicationIdHex: String
get() {
return if (applicationIdLong == 0L) {
"0"
} else {
"0" + applicationIdLong.toString(16).uppercase()
}
}
val saveZipName: String val saveZipName: String
get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${ get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
}.zip" }.zip"
val saveDir: String val saveDir: String
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId) get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(applicationId)
val addonDir: String val addonDir: String
get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/" get() = DirectoryInitialization.userDirectory + "/load/" + applicationIdHex + "/"
val launchIntent: Intent val launchIntent: Intent
get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -65,7 +65,7 @@ object CustomSettingsHandler {
// Initialize per-game config // Initialize per-game config
try { try {
val fileName = FileUtil.getFilename(Uri.parse(game.path)) val fileName = FileUtil.getFilename(Uri.parse(game.path))
NativeConfig.initializePerGameConfig(game.programId, fileName) NativeConfig.initializePerGameConfig(game.applicationId, fileName)
Log.info("[CustomSettingsHandler] Successfully applied custom settings") Log.info("[CustomSettingsHandler] Successfully applied custom settings")
return game return game
} catch (e: Exception) { } catch (e: Exception) {
@@ -333,20 +333,20 @@ object CustomSettingsHandler {
*/ */
fun findGameByTitleId(titleId: String, context: Context): Game? { fun findGameByTitleId(titleId: String, context: Context): Game? {
Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId") Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId")
// Convert hex title ID to decimal for comparison with programId // Convert the program ID to the application ID used by per-game settings.
val programIdDecimal = try { val applicationIdLong = try {
titleId.toLong(16).toString() titleId.toLong(16) and -8192L
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId") Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId")
return null return null
} }
val applicationIdDecimal = applicationIdLong.toString()
// Expected hex format with "0" prefix // Expected hex format with "0" prefix
val expectedHex = "0${titleId.uppercase()}" val expectedHex = "0${applicationIdLong.toString(16).uppercase()}"
// First check cached games for fast lookup // First check cached games for fast lookup
GameHelper.cachedGameList.find { game -> GameHelper.cachedGameList.find { game ->
game.programId == programIdDecimal || game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
game.programIdHex.equals(expectedHex, ignoreCase = true)
}?.let { foundGame -> }?.let { foundGame ->
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}") Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
return foundGame return foundGame
@@ -355,8 +355,7 @@ object CustomSettingsHandler {
Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...") Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...")
val allGames = GameHelper.getGames() val allGames = GameHelper.getGames()
val foundGame = allGames.find { game -> val foundGame = allGames.find { game ->
game.programId == programIdDecimal || game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
game.programIdHex.equals(expectedHex, ignoreCase = true)
} }
if (foundGame != null) { if (foundGame != null) {
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}") Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
@@ -170,12 +170,12 @@ object GameHelper {
val game = getGame(it.uri, true, false) val game = getGame(it.uri, true, false)
if (game != null) { if (game != null) {
games.add(game) games.add(game)
if (game.programId != "0") { if (game.applicationId != "0") {
gamesByProgramId[game.programId] = game gamesByProgramId[game.applicationId] = game
} }
} else if (mountedContainer) { } else if (mountedContainer) {
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId -> GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
gamesByProgramId[(programId and 0x800L.inv()).toString()] gamesByProgramId[(programId and -8192L).toString()]
}?.let { existingGame -> }?.let { existingGame ->
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId) NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
existingGame.version = GameMetadata.getVersion( existingGame.version = GameMetadata.getVersion(
+8 -7
View File
@@ -788,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj,
jstring jprogramId, jstring jprogramId,
jstring jupdatePath) { jstring jupdatePath) {
u64 program_id = EmulationSession::GetProgramId(env, jprogramId); const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
std::string updatePath = Common::Android::GetJString(env, jupdatePath); std::string updatePath = Common::Android::GetJString(env, jupdatePath);
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>( std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(
@@ -796,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en
for (const auto& item : nsp->GetNCAs()) { for (const auto& item : nsp->GetNCAs()) {
for (const auto& nca_details : item.second) { for (const auto& nca_details : item.second) {
if (nca_details.second->GetName().ends_with(".cnmt.nca")) { if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL; const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId());
if (update_id == program_id) { if (update_id == program_id) {
return true; return true;
} }
@@ -1491,7 +1491,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclas
} }
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) { jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
return FirmwareManager::GameRequiresFirmware(program_id); return FirmwareManager::GameRequiresFirmware(program_id);
} }
@@ -1575,20 +1575,21 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(), ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id); program_id);
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(
EmulationSession::GetProgramId(env, jprogramId));
ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id); ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
} }
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId, void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
jstring jname) { jstring jname) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(), ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
program_id, Common::Android::GetJString(env, jname)); program_id, Common::Android::GetJString(env, jname));
} }
@@ -1635,7 +1636,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj,
jstring jprogramId) { jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
if (program_id == 0) { if (program_id == 0) {
return Common::Android::ToJString(env, ""); return Common::Android::ToJString(env, "");
} }
@@ -12,6 +12,7 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/file_sys/common_funcs.h"
#include "frontend_common/config.h" #include "frontend_common/config.h"
#include "frontend_common/settings_generator.h" #include "frontend_common/settings_generator.h"
#include "native.h" #include "native.h"
@@ -56,7 +57,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj, void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
jstring jprogramId, jstring jprogramId,
jstring jfileName) { jstring jfileName) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
auto file_name = Common::Android::GetJString(env, jfileName); auto file_name = Common::Android::GetJString(env, jfileName);
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id); const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
per_game_config = per_game_config =
@@ -341,7 +342,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj, jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj,
jstring jprogramId) { jstring jprogramId) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
auto& disabledAddons = Settings::values.disabled_addons[program_id]; auto& disabledAddons = Settings::values.disabled_addons[program_id];
jobjectArray jdisabledAddonsArray = jobjectArray jdisabledAddonsArray =
env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(), env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(),
@@ -356,7 +357,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj, void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
jstring jprogramId, jstring jprogramId,
jobjectArray jdisabledAddons) { jobjectArray jdisabledAddons) {
auto program_id = EmulationSession::GetProgramId(env, jprogramId); const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
Settings::values.disabled_addons[program_id].clear(); Settings::values.disabled_addons[program_id].clear();
std::vector<std::string> disabled_addons; std::vector<std::string> disabled_addons;
const int size = env->GetArrayLength(jdisabledAddons); const int size = env->GetArrayLength(jdisabledAddons);
-1
View File
@@ -89,7 +89,6 @@ add_library(
param_package.h param_package.h
parent_of_member.h parent_of_member.h
point.h point.h
quaternion.h
range_map.h range_map.h
range_mutex.h range_mutex.h
range_sets.h range_sets.h
-79
View File
@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/vector_math.h"
namespace Common {
template <typename T>
class Quaternion {
public:
Vec3<T> xyz;
T w{};
[[nodiscard]] Quaternion<decltype(-T{})> Inverse() const {
return {-xyz, w};
}
[[nodiscard]] Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
return {xyz + other.xyz, w + other.w};
}
[[nodiscard]] Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
return {xyz - other.xyz, w - other.w};
}
[[nodiscard]] Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(
const Quaternion& other) const {
return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
w * other.w - Dot(xyz, other.xyz)};
}
[[nodiscard]] Quaternion<T> Normalized() const {
T length = std::sqrt(xyz.Length2() + w * w);
return {xyz / length, w / length};
}
[[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
const T x2 = xyz[0] * xyz[0];
const T y2 = xyz[1] * xyz[1];
const T z2 = xyz[2] * xyz[2];
const T xy = xyz[0] * xyz[1];
const T wz = w * xyz[2];
const T xz = xyz[0] * xyz[2];
const T wy = w * xyz[1];
const T yz = xyz[1] * xyz[2];
const T wx = w * xyz[0];
return {1.0f - 2.0f * (y2 + z2),
2.0f * (xy + wz),
2.0f * (xz - wy),
0.0f,
2.0f * (xy - wz),
1.0f - 2.0f * (x2 + z2),
2.0f * (yz + wx),
0.0f,
2.0f * (xz + wy),
2.0f * (yz - wx),
1.0f - 2.0f * (x2 + y2),
0.0f,
0.0f,
0.0f,
0.0f,
1.0f};
}
};
template <typename T>
[[nodiscard]] auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
}
[[nodiscard]] inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
}
} // namespace Common
+2 -2
View File
@@ -858,7 +858,7 @@ struct Values {
SwitchableSetting<std::string> program_args{linkage, SwitchableSetting<std::string> program_args{linkage,
std::string(), std::string(),
"program_args", "program_args",
Category::System, Category::Debugging,
Specialization::Default, Specialization::Default,
true, // save_ - persist in config file true, // save_ - persist in config file
false}; // runtime_modifiable_ - startup-only false}; // runtime_modifiable_ - startup-only
@@ -904,7 +904,7 @@ struct Values {
0, 0,
65535, 65535,
"debug_knobs", "debug_knobs",
Category::System, Category::Debugging,
Specialization::Countable, Specialization::Countable,
true, true,
true}; true};
+89 -713
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Tony Wasserka // SPDX-FileCopyrightText: 2014 Tony Wasserka
@@ -7,752 +7,128 @@
#pragma once #pragma once
#ifdef __ARM_NEON
#include <arm_neon.h>
#endif
#include <cmath> #include <cmath>
#include <type_traits> #include <type_traits>
namespace Common { namespace Common {
template <typename T> template <typename T, size_t N>
class Vec2; class Vec {
template <typename T>
class Vec3;
template <typename T>
class Vec4;
template <typename T>
class Vec2 {
public: public:
T x{}; std::array<T, N> elems{};
T y{};
constexpr Vec2() = default; constexpr Vec() = default;
constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {} constexpr Vec(T e0) noexcept : elems{e0} {}
constexpr Vec(T e0, T e1) noexcept : elems{e0, e1} {}
constexpr Vec(T e0, T e1, T e2) noexcept : elems{e0, e1, e2} {}
constexpr Vec(T e0, T e1, T e2, T e4) noexcept : elems{e0, e1, e2, e4} {}
//explicit constexpr Vec(const std::initializer_list<T> elems_) noexcept : elems{elems_} {}
template <typename T2> [[nodiscard]] constexpr Vec<decltype(T{} + T{}), N> operator+(const Vec o) const noexcept {
[[nodiscard]] constexpr Vec2<T2> Cast() const { Vec<decltype(T{} + T{}), N> r{};
return Vec2<T2>(static_cast<T2>(x), static_cast<T2>(y)); for (size_t i = 0; i < N; ++i)
r.elems[i] = elems[i] + o.elems[i];
return r;
} }
constexpr Vec<T, N> operator+=(const Vec<T, N> o) noexcept { return *this = *this + o; }
[[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) { [[nodiscard]] constexpr Vec<decltype(T{} - T{}), N> operator-(const Vec o) const noexcept {
return Vec2{f, f}; Vec<decltype(T{} - T{}), N> r{};
} for (size_t i = 0; i < N; ++i)
r.elems[i] = elems[i] - o.elems[i];
[[nodiscard]] constexpr Vec2<decltype(T{} + T{})> operator+(const Vec2& other) const { return r;
return {x + other.x, y + other.y};
}
constexpr Vec2& operator+=(const Vec2& other) {
x += other.x;
y += other.y;
return *this;
}
[[nodiscard]] constexpr Vec2<decltype(T{} - T{})> operator-(const Vec2& other) const {
return {x - other.x, y - other.y};
}
constexpr Vec2& operator-=(const Vec2& other) {
x -= other.x;
y -= other.y;
return *this;
} }
constexpr Vec<T, N> operator-=(const Vec<T, N> o) noexcept { return *this = *this - o; }
template <typename U = T> template <typename U = T>
[[nodiscard]] constexpr Vec2<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const { [[nodiscard]] constexpr Vec<std::enable_if_t<std::is_signed_v<U>, U>, N> operator-() const noexcept {
return {-x, -y}; Vec<U, N> r{};
} for (size_t i = 0; i < N; ++i)
[[nodiscard]] constexpr Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { r.elems[i] = -elems[i];
return {x * other.x, y * other.y}; return r;
} }
[[nodiscard]] constexpr Vec<decltype(T{} * T{}), N> operator*(const Vec o) const noexcept {
Vec<decltype(T{} * T{}), N> r{};
for (size_t i = 0; i < N; ++i)
r.elems[i] = elems[i] * o.elems[i];
return r;
}
template <typename V> template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} * V{})> operator*(const V& f) const { [[nodiscard]] constexpr Vec<decltype(T{} * V{}), N> operator*(const V f) const noexcept {
using TV = decltype(T{} * V{}); using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
Vec<TV, N> r{};
return { for (size_t i = 0; i < N; ++i)
static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)), r.elems[i] = TV(C(elems[i]) * C(f));
static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)), return r;
};
} }
template <typename V>
constexpr Vec<T, N> operator*=(const V f) noexcept { return *this = *this * f; }
template <typename V> template <typename V>
constexpr Vec2& operator*=(const V& f) { [[nodiscard]] constexpr Vec<decltype(T{} / V{}), N> operator/(const V f) const noexcept {
*this = *this * f;
return *this;
}
template <typename V>
[[nodiscard]] constexpr Vec2<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{}); using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
Vec<TV, N> r{};
return { for (size_t i = 0; i < N; ++i)
static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)), r.elems[i] = TV(C(elems[i]) / C(f));
static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)), return r;
};
} }
template <typename V> template <typename V>
constexpr Vec2& operator/=(const V& f) { constexpr Vec<T, N> operator/=(const V f) noexcept { return *this = *this / f; }
*this = *this / f;
return *this;
}
[[nodiscard]] constexpr T Length2() const { [[nodiscard]] constexpr T Length2() const noexcept {
return x * x + y * y; T r{};
for (size_t i = 0; i < N; ++i)
r += elems[i] * elems[i];
return r;
} }
// Only implemented for T=float // Only implemented for T=float
[[nodiscard]] float Length() const; [[nodiscard]] T Length() const { return T(std::sqrt(float(Length2()))); }
[[nodiscard]] float Normalize(); // returns the previous length, which is often useful [[nodiscard]] Vec<T, N> Normalized() const { return *this / Length(); }
[[nodiscard]] constexpr T& operator[](std::size_t i) noexcept { return elems[i]; }
[[nodiscard]] constexpr const T& operator[](std::size_t i) const noexcept { return elems[i]; }
[[nodiscard]] constexpr T& operator[](std::size_t i) { [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
return *((&x) + i); const T x2 = elems[0] * elems[0];
} const T y2 = elems[1] * elems[1];
[[nodiscard]] constexpr const T& operator[](std::size_t i) const { const T z2 = elems[2] * elems[2];
return *((&x) + i);
}
constexpr void SetZero() { const T xy = elems[0] * elems[1];
x = 0; const T wz = elems[3] * elems[2];
y = 0; const T xz = elems[0] * elems[2];
} const T wy = elems[3] * elems[1];
const T yz = elems[1] * elems[2];
// Common aliases: UV (texel coordinates), ST (texture coordinates) const T wx = elems[3] * elems[0];
[[nodiscard]] constexpr T& u() { return {
return x; 1.0f - 2.0f * (y2 + z2),
} 2.0f * (xy + wz),
[[nodiscard]] constexpr T& v() { 2.0f * (xz - wy),
return y; 0.0f,
} 2.0f * (xy - wz),
[[nodiscard]] constexpr T& s() { 1.0f - 2.0f * (x2 + z2),
return x; 2.0f * (yz + wx),
} 0.0f,
[[nodiscard]] constexpr T& t() { 2.0f * (xz + wy),
return y; 2.0f * (yz - wx),
} 1.0f - 2.0f * (x2 + y2),
0.0f,
[[nodiscard]] constexpr const T& u() const { 0.0f,
return x; 0.0f,
} 0.0f,
[[nodiscard]] constexpr const T& v() const { 1.0f
return y; };
}
[[nodiscard]] constexpr const T& s() const {
return x;
}
[[nodiscard]] constexpr const T& t() const {
return y;
}
// swizzlers - create a subvector of specific components
[[nodiscard]] constexpr Vec2 yx() const {
return Vec2(y, x);
}
[[nodiscard]] constexpr Vec2 vu() const {
return Vec2(y, x);
}
[[nodiscard]] constexpr Vec2 ts() const {
return Vec2(y, x);
} }
}; };
template <typename T, typename V> template <typename T, size_t N, typename V>
[[nodiscard]] constexpr Vec2<T> operator*(const V& f, const Vec2<T>& vec) { [[nodiscard]] constexpr Vec<T, N> operator*(const V f, const Vec<T, N> v) noexcept {
using C = std::common_type_t<T, V>; using C = std::common_type_t<T, V>;
Vec<T, N> r{};
return Vec2<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)), for (size_t i = 0; i < N; ++i)
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y))); r.elems[i] = T(C(f) * C(v.elems[i]));
} return r;
using Vec2f = Vec2<float>;
template <>
inline float Vec2<float>::Length() const {
return std::sqrt(x * x + y * y);
}
template <>
inline float Vec2<float>::Normalize() {
float length = Length();
*this /= length;
return length;
}
template <typename T>
class Vec3 {
public:
T x{};
T y{};
T z{};
constexpr Vec3() = default;
constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
template <typename T2>
[[nodiscard]] constexpr Vec3<T2> Cast() const {
return Vec3<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z));
}
[[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) {
return Vec3(f, f, f);
}
[[nodiscard]] constexpr Vec3<decltype(T{} + T{})> operator+(const Vec3& other) const {
return {x + other.x, y + other.y, z + other.z};
}
constexpr Vec3& operator+=(const Vec3& other) {
x += other.x;
y += other.y;
z += other.z;
return *this;
}
[[nodiscard]] constexpr Vec3<decltype(T{} - T{})> operator-(const Vec3& other) const {
return {x - other.x, y - other.y, z - other.z};
}
constexpr Vec3& operator-=(const Vec3& other) {
x -= other.x;
y -= other.y;
z -= other.z;
return *this;
}
template <typename U = T>
[[nodiscard]] constexpr Vec3<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z};
}
[[nodiscard]] constexpr Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
return {x * other.x, y * other.y, z * other.z};
}
template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} * V{})> operator*(const V& f) const {
using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
};
}
template <typename V>
constexpr Vec3& operator*=(const V& f) {
*this = *this * f;
return *this;
}
template <typename V>
[[nodiscard]] constexpr Vec3<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
};
}
template <typename V>
constexpr Vec3& operator/=(const V& f) {
*this = *this / f;
return *this;
}
void RotateFromOrigin(float roll, float pitch, float yaw) {
float temp = y;
y = std::cos(roll) * y - std::sin(roll) * z;
z = std::sin(roll) * temp + std::cos(roll) * z;
temp = x;
x = std::cos(pitch) * x + std::sin(pitch) * z;
z = -std::sin(pitch) * temp + std::cos(pitch) * z;
temp = x;
x = std::cos(yaw) * x - std::sin(yaw) * y;
y = std::sin(yaw) * temp + std::cos(yaw) * y;
}
[[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z;
}
// Only implemented for T=float
[[nodiscard]] float Length() const;
[[nodiscard]] Vec3 Normalized() const;
[[nodiscard]] float Normalize(); // returns the previous length, which is often useful
[[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
[[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
constexpr void SetZero() {
x = 0;
y = 0;
z = 0;
}
// Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates)
[[nodiscard]] constexpr T& u() {
return x;
}
[[nodiscard]] constexpr T& v() {
return y;
}
[[nodiscard]] constexpr T& w() {
return z;
}
[[nodiscard]] constexpr T& r() {
return x;
}
[[nodiscard]] constexpr T& g() {
return y;
}
[[nodiscard]] constexpr T& b() {
return z;
}
[[nodiscard]] constexpr T& s() {
return x;
}
[[nodiscard]] constexpr T& t() {
return y;
}
[[nodiscard]] constexpr T& q() {
return z;
}
[[nodiscard]] constexpr const T& u() const {
return x;
}
[[nodiscard]] constexpr const T& v() const {
return y;
}
[[nodiscard]] constexpr const T& w() const {
return z;
}
[[nodiscard]] constexpr const T& r() const {
return x;
}
[[nodiscard]] constexpr const T& g() const {
return y;
}
[[nodiscard]] constexpr const T& b() const {
return z;
}
[[nodiscard]] constexpr const T& s() const {
return x;
}
[[nodiscard]] constexpr const T& t() const {
return y;
}
[[nodiscard]] constexpr const T& q() const {
return z;
}
// swizzlers - create a subvector of specific components
// e.g. Vec2 uv() { return Vec2(x,y); }
// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all
// component names (x<->r) and permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
[[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); }
#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \
_DEFINE_SWIZZLER2(a, b, a##b); \
_DEFINE_SWIZZLER2(a, b, a2##b2); \
_DEFINE_SWIZZLER2(a, b, a3##b3); \
_DEFINE_SWIZZLER2(a, b, a4##b4); \
_DEFINE_SWIZZLER2(b, a, b##a); \
_DEFINE_SWIZZLER2(b, a, b2##a2); \
_DEFINE_SWIZZLER2(b, a, b3##a3); \
_DEFINE_SWIZZLER2(b, a, b4##a4)
DEFINE_SWIZZLER2(x, y, r, g, u, v, s, t);
DEFINE_SWIZZLER2(x, z, r, b, u, w, s, q);
DEFINE_SWIZZLER2(y, z, g, b, v, w, t, q);
#undef DEFINE_SWIZZLER2
#undef _DEFINE_SWIZZLER2
};
template <typename T, typename V>
[[nodiscard]] constexpr Vec3<T> operator*(const V& f, const Vec3<T>& vec) {
using C = std::common_type_t<T, V>;
return Vec3<T>(static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.x)),
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.y)),
static_cast<T>(static_cast<C>(f) * static_cast<C>(vec.z)));
}
template <>
inline float Vec3<float>::Length() const {
return std::sqrt(x * x + y * y + z * z);
}
template <>
inline Vec3<float> Vec3<float>::Normalized() const {
return *this / Length();
}
template <>
inline float Vec3<float>::Normalize() {
float length = Length();
*this /= length;
return length;
}
using Vec3f = Vec3<float>;
template <typename T>
class Vec4 {
public:
T x{};
T y{};
T z{};
T w{};
constexpr Vec4() = default;
constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_)
: x(x_), y(y_), z(z_), w(w_) {}
template <typename T2>
[[nodiscard]] constexpr Vec4<T2> Cast() const {
return Vec4<T2>(static_cast<T2>(x), static_cast<T2>(y), static_cast<T2>(z),
static_cast<T2>(w));
}
[[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) {
return Vec4(f, f, f, f);
}
[[nodiscard]] constexpr Vec4<decltype(T{} + T{})> operator+(const Vec4& other) const {
return {x + other.x, y + other.y, z + other.z, w + other.w};
}
constexpr Vec4& operator+=(const Vec4& other) {
x += other.x;
y += other.y;
z += other.z;
w += other.w;
return *this;
}
[[nodiscard]] constexpr Vec4<decltype(T{} - T{})> operator-(const Vec4& other) const {
return {x - other.x, y - other.y, z - other.z, w - other.w};
}
constexpr Vec4& operator-=(const Vec4& other) {
x -= other.x;
y -= other.y;
z -= other.z;
w -= other.w;
return *this;
}
template <typename U = T>
[[nodiscard]] constexpr Vec4<std::enable_if_t<std::is_signed_v<U>, U>> operator-() const {
return {-x, -y, -z, -w};
}
[[nodiscard]] constexpr Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
return {x * other.x, y * other.y, z * other.z, w * other.w};
}
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} * V{})> operator*(const V& f) const {
using TV = decltype(T{} * V{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(x) * static_cast<C>(f)),
static_cast<TV>(static_cast<C>(y) * static_cast<C>(f)),
static_cast<TV>(static_cast<C>(z) * static_cast<C>(f)),
static_cast<TV>(static_cast<C>(w) * static_cast<C>(f)),
};
}
template <typename V>
constexpr Vec4& operator*=(const V& f) {
*this = *this * f;
return *this;
}
template <typename V>
[[nodiscard]] constexpr Vec4<decltype(T{} / V{})> operator/(const V& f) const {
using TV = decltype(T{} / V{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(x) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(y) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(z) / static_cast<C>(f)),
static_cast<TV>(static_cast<C>(w) / static_cast<C>(f)),
};
}
template <typename V>
constexpr Vec4& operator/=(const V& f) {
*this = *this / f;
return *this;
}
[[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z + w * w;
}
[[nodiscard]] constexpr T& operator[](std::size_t i) {
return *((&x) + i);
}
[[nodiscard]] constexpr const T& operator[](std::size_t i) const {
return *((&x) + i);
}
constexpr void SetZero() {
x = 0;
y = 0;
z = 0;
w = 0;
}
// Common alias: RGBA (colors)
[[nodiscard]] constexpr T& r() {
return x;
}
[[nodiscard]] constexpr T& g() {
return y;
}
[[nodiscard]] constexpr T& b() {
return z;
}
[[nodiscard]] constexpr T& a() {
return w;
}
[[nodiscard]] constexpr const T& r() const {
return x;
}
[[nodiscard]] constexpr const T& g() const {
return y;
}
[[nodiscard]] constexpr const T& b() const {
return z;
}
[[nodiscard]] constexpr const T& a() const {
return w;
}
// Swizzlers - Create a subvector of specific components
// e.g. Vec2 uv() { return Vec2(x,y); }
// _DEFINE_SWIZZLER2 defines a single such function
// DEFINE_SWIZZLER2_COMP1 defines one-component functions for all component names (x<->r)
// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and
// permutations (xy<->yx)
#define _DEFINE_SWIZZLER2(a, b, name) \
[[nodiscard]] constexpr Vec2<T> name() const { return Vec2<T>(a, b); }
#define DEFINE_SWIZZLER2_COMP1(a, a2) \
_DEFINE_SWIZZLER2(a, a, a##a); \
_DEFINE_SWIZZLER2(a, a, a2##a2)
#define DEFINE_SWIZZLER2_COMP2(a, b, a2, b2) \
_DEFINE_SWIZZLER2(a, b, a##b); \
_DEFINE_SWIZZLER2(a, b, a2##b2); \
_DEFINE_SWIZZLER2(b, a, b##a); \
_DEFINE_SWIZZLER2(b, a, b2##a2)
DEFINE_SWIZZLER2_COMP2(x, y, r, g);
DEFINE_SWIZZLER2_COMP2(x, z, r, b);
DEFINE_SWIZZLER2_COMP2(x, w, r, a);
DEFINE_SWIZZLER2_COMP2(y, z, g, b);
DEFINE_SWIZZLER2_COMP2(y, w, g, a);
DEFINE_SWIZZLER2_COMP2(z, w, b, a);
DEFINE_SWIZZLER2_COMP1(x, r);
DEFINE_SWIZZLER2_COMP1(y, g);
DEFINE_SWIZZLER2_COMP1(z, b);
DEFINE_SWIZZLER2_COMP1(w, a);
#undef DEFINE_SWIZZLER2_COMP1
#undef DEFINE_SWIZZLER2_COMP2
#undef _DEFINE_SWIZZLER2
#define _DEFINE_SWIZZLER3(a, b, c, name) \
[[nodiscard]] constexpr Vec3<T> name() const { return Vec3<T>(a, b, c); }
#define DEFINE_SWIZZLER3_COMP1(a, a2) \
_DEFINE_SWIZZLER3(a, a, a, a##a##a); \
_DEFINE_SWIZZLER3(a, a, a, a2##a2##a2)
#define DEFINE_SWIZZLER3_COMP3(a, b, c, a2, b2, c2) \
_DEFINE_SWIZZLER3(a, b, c, a##b##c); \
_DEFINE_SWIZZLER3(a, c, b, a##c##b); \
_DEFINE_SWIZZLER3(b, a, c, b##a##c); \
_DEFINE_SWIZZLER3(b, c, a, b##c##a); \
_DEFINE_SWIZZLER3(c, a, b, c##a##b); \
_DEFINE_SWIZZLER3(c, b, a, c##b##a); \
_DEFINE_SWIZZLER3(a, b, c, a2##b2##c2); \
_DEFINE_SWIZZLER3(a, c, b, a2##c2##b2); \
_DEFINE_SWIZZLER3(b, a, c, b2##a2##c2); \
_DEFINE_SWIZZLER3(b, c, a, b2##c2##a2); \
_DEFINE_SWIZZLER3(c, a, b, c2##a2##b2); \
_DEFINE_SWIZZLER3(c, b, a, c2##b2##a2)
DEFINE_SWIZZLER3_COMP3(x, y, z, r, g, b);
DEFINE_SWIZZLER3_COMP3(x, y, w, r, g, a);
DEFINE_SWIZZLER3_COMP3(x, z, w, r, b, a);
DEFINE_SWIZZLER3_COMP3(y, z, w, g, b, a);
DEFINE_SWIZZLER3_COMP1(x, r);
DEFINE_SWIZZLER3_COMP1(y, g);
DEFINE_SWIZZLER3_COMP1(z, b);
DEFINE_SWIZZLER3_COMP1(w, a);
#undef DEFINE_SWIZZLER3_COMP1
#undef DEFINE_SWIZZLER3_COMP3
#undef _DEFINE_SWIZZLER3
};
template <typename T, typename V>
[[nodiscard]] constexpr Vec4<decltype(V{} * T{})> operator*(const V& f, const Vec4<T>& vec) {
using TV = decltype(V{} * T{});
using C = std::common_type_t<T, V>;
return {
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.x)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.y)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.z)),
static_cast<TV>(static_cast<C>(f) * static_cast<C>(vec.w)),
};
}
using Vec4f = Vec4<float>;
template <typename T>
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
return a.x * b.x + a.y * b.y;
}
template <typename T>
[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3<T>& a, const Vec3<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
template <typename T>
[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4<T>& a, const Vec4<T>& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
template <>
[[nodiscard]] inline float Dot(const Vec4<float>& a, const Vec4<float>& b) {
#ifdef __ARM_NEON
float32x4_t va = vld1q_f32(&a.x);
float32x4_t vb = vld1q_f32(&b.x);
float32x4_t result = vmulq_f32(va, vb);
#if defined(__aarch64__) // Use vaddvq_f32 in ARMv8 architectures
return vaddvq_f32(result);
#else // Use manual addition for older architectures
float32x2_t sum2 = vadd_f32(vget_high_f32(result), vget_low_f32(result));
return vget_lane_f32(vpadd_f32(sum2, sum2), 0);
#endif
#else
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
#endif
}
template <typename T>
[[nodiscard]] constexpr Vec3<decltype(T{} * T{} - T{} * T{})> Cross(const Vec3<T>& a,
const Vec3<T>& b) {
return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}
// linear interpolation via float: 0.0=begin, 1.0=end
template <typename X>
[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end,
const float t) {
return begin * (1.f - t) + end * t;
}
// linear interpolation via int: 0=begin, base=end
template <typename X, int base>
[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin,
const X& end,
const int t) {
return (begin * (base - t) + end * t) / base;
}
// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second
// interpolation.
template <typename X>
[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11,
const float s, const float t) {
auto y0 = Lerp(x00, x01, s);
auto y1 = Lerp(x10, x11, s);
return Lerp(y0, y1, t);
}
// Utility vector factories
template <typename T>
[[nodiscard]] constexpr Vec2<T> MakeVec(const T& x, const T& y) {
return Vec2<T>{x, y};
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const T& y, const T& z) {
return Vec3<T>{x, y, z};
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const Vec2<T>& zw) {
return MakeVec(x, y, zw[0], zw[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const Vec2<T>& xy, const T& z) {
return MakeVec(xy[0], xy[1], z);
}
template <typename T>
[[nodiscard]] constexpr Vec3<T> MakeVec(const T& x, const Vec2<T>& yz) {
return MakeVec(x, yz[0], yz[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const T& y, const T& z, const T& w) {
return Vec4<T>{x, y, z, w};
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const T& z, const T& w) {
return MakeVec(xy[0], xy[1], z, w);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec2<T>& yz, const T& w) {
return MakeVec(x, yz[0], yz[1], w);
}
// NOTE: This has priority over "Vec2<Vec2<T>> MakeVec(const Vec2<T>& x, const Vec2<T>& y)".
// Even if someone wanted to use an odd object like Vec2<Vec2<T>>, the compiler would error
// out soon enough due to misuse of the returned structure.
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec2<T>& xy, const Vec2<T>& zw) {
return MakeVec(xy[0], xy[1], zw[0], zw[1]);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const Vec3<T>& xyz, const T& w) {
return MakeVec(xyz[0], xyz[1], xyz[2], w);
}
template <typename T>
[[nodiscard]] constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
} }
} // namespace Common } // namespace Common
+4 -1
View File
@@ -121,7 +121,10 @@ union Exclusive {
constexpr explicit Exclusive(u32 raw_) : raw{raw_} {} constexpr explicit Exclusive(u32 raw_) : raw{raw_} {}
constexpr bool Verify() { constexpr bool Verify() {
return this->GetSig() == 0x10; if (this->GetSig() != 0x10) return false;
const bool pair = decltype(l)::ExtractValue(raw) & 1;
const bool fixed_rt2 = decltype(rt2)::ExtractValue(raw) == 0b11111;
return pair || fixed_rt2;
} }
constexpr u32 GetSig() { constexpr u32 GetSig() {
+2 -1
View File
@@ -17,6 +17,7 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "launch_timestamp_cache.h" #include "launch_timestamp_cache.h"
#include "core/core_timing.h" #include "core/core_timing.h"
@@ -396,7 +397,7 @@ struct System::Impl {
LOG_ERROR(Core, "Failed to find program id for ROM"); LOG_ERROR(Core, "Failed to find program id for ROM");
} }
GameSettings::LoadOverrides(program_id, gpu_core->Renderer()); GameSettings::LoadOverrides(FileSys::GetBaseTitleID(program_id), gpu_core->Renderer());
if (auto room_member = Network::GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info; Network::GameInfo game_info;
game_info.name = name; game_info.name = name;
+132 -62
View File
@@ -161,7 +161,7 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64
PatchManager::PatchManager(u64 title_id_, PatchManager::PatchManager(u64 title_id_,
const Service::FileSystem::FileSystemController& fs_controller_, const Service::FileSystem::FileSystemController& fs_controller_,
const ContentProvider& content_provider_) const ContentProvider& content_provider_)
: title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {} : title_id{title_id_}, application_id{GetBaseTitleID(title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
PatchManager::~PatchManager() = default; PatchManager::~PatchManager() = default;
@@ -169,13 +169,41 @@ u64 PatchManager::GetTitleID() const {
return title_id; return title_id;
} }
u64 PatchManager::GetUpdateTitleIDForContent() const {
const auto program_update_id = GetUpdateTitleID(title_id);
if (program_update_id == GetUpdateTitleID(application_id) || content_provider.HasEntry(program_update_id, ContentRecordType::Program)) {
return program_update_id;
}
return GetUpdateTitleID(application_id);
}
std::vector<VirtualDir> PatchManager::GetModificationLoadRoots() const {
std::vector<VirtualDir> roots;
roots.push_back(fs_controller.GetModificationLoadRoot(title_id));
if (application_id != title_id) {
roots.push_back(fs_controller.GetModificationLoadRoot(application_id));
}
std::erase(roots, nullptr);
return roots;
}
std::vector<VirtualDir> PatchManager::GetSDMCModificationLoadRoots() const {
std::vector<VirtualDir> roots;
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(title_id));
if (application_id != title_id) {
roots.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
}
std::erase(roots, nullptr);
return roots;
}
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
if (exefs == nullptr) if (exefs == nullptr)
return exefs; return exefs;
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
bool update_disabled = true; bool update_disabled = true;
std::optional<u32> enabled_version; std::optional<u32> enabled_version;
@@ -183,7 +211,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
bool checked_manual = false; bool checked_manual = false;
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider); const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
const auto update_tid = GetUpdateTitleID(title_id); const auto update_tid = GetUpdateTitleIDForContent();
if (content_union) { if (content_union) {
// First, check ExternalContentProvider // First, check ExternalContentProvider
@@ -303,17 +331,21 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
} }
// LayeredExeFS // LayeredExeFS
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto load_dirs = GetModificationLoadRoots();
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); const auto sdmc_load_dirs = GetSDMCModificationLoadRoots();
std::vector<VirtualDir> patch_dirs = {sdmc_load_dir}; std::vector<VirtualDir> patch_dirs;
if (load_dir != nullptr) { for (const auto& sdmc_load_dir : sdmc_load_dirs) {
patch_dirs.push_back(sdmc_load_dir);
}
for (const auto& load_dir : load_dirs) {
const auto load_patch_dirs = load_dir->GetSubdirectories(); const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
} }
std::sort(patch_dirs.begin(), patch_dirs.end(), std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); return l->GetName() < r->GetName();
});
std::vector<VirtualDir> layers; std::vector<VirtualDir> layers;
layers.reserve(patch_dirs.size() + 1); layers.reserve(patch_dirs.size() + 1);
@@ -346,7 +378,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
} }
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const { std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
const auto nso_build_id = fmt::format("{:0<64}", build_id); const auto nso_build_id = fmt::format("{:0<64}", build_id);
std::vector<VirtualFile> out; std::vector<VirtualFile> out;
@@ -405,15 +437,20 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto load_dirs = GetModificationLoadRoots();
if (load_dir == nullptr) { if (load_dirs.empty()) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return nso; return nso;
} }
auto patch_dirs = load_dir->GetSubdirectories(); std::vector<VirtualDir> patch_dirs;
std::sort(patch_dirs.begin(), patch_dirs.end(), for (const auto& load_dir : load_dirs) {
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
return l->GetName() < r->GetName();
});
const auto patches = CollectPatches(patch_dirs, build_id); const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso; auto out = nso;
@@ -448,29 +485,39 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto load_dirs = GetModificationLoadRoots();
if (load_dir == nullptr) { if (load_dirs.empty()) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return false; return false;
} }
auto patch_dirs = load_dir->GetSubdirectories(); std::vector<VirtualDir> patch_dirs;
std::sort(patch_dirs.begin(), patch_dirs.end(), for (const auto& load_dir : load_dirs) {
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
return l->GetName() < r->GetName();
});
return !CollectPatches(patch_dirs, build_id).empty(); return !CollectPatches(patch_dirs, build_id).empty();
} }
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const { std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto load_dirs = GetModificationLoadRoots();
if (load_dir == nullptr) { if (load_dirs.empty()) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return {}; return {};
} }
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
auto patch_dirs = load_dir->GetSubdirectories(); std::vector<VirtualDir> patch_dirs;
std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); for (const auto& load_dir : load_dirs) {
const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(),
[](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
// <mod dir> / <folder> / cheats / <build id>.txt // <mod dir> / <folder> / cheats / <build id>.txt
std::vector<Core::Memory::CheatEntry> out; std::vector<Core::Memory::CheatEntry> out;
@@ -486,39 +533,52 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
} }
// Uncareless user-friendly loading of patches (must start with 'cheat_') // Uncareless user-friendly loading of patches (must start with 'cheat_')
// <mod dir> / <cheat file>.txt // <mod dir> / <cheat file>.txt
for (auto const& f : load_dir->GetFiles()) { for (const auto& load_dir : load_dirs) {
auto const name = f->GetName(); for (auto const& f : load_dir->GetFiles()) {
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) { auto const name = f->GetName();
std::vector<u8> data(f->GetSize()); if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
if (f->Read(data.data(), data.size()) == data.size()) { std::vector<u8> data(f->GetSize());
const Core::Memory::TextCheatParser parser; if (f->Read(data.data(), data.size()) == data.size()) {
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size())); const Core::Memory::TextCheatParser parser;
std::copy(res.begin(), res.end(), std::back_inserter(out)); auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
} else { std::copy(res.begin(), res.end(), std::back_inserter(out));
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id); } else {
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
}
} }
} }
} }
return out; return out;
} }
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, u64 application_id, ContentRecordType type,
const Service::FileSystem::FileSystemController& fs_controller) { const Service::FileSystem::FileSystemController& fs_controller) {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); std::vector<VirtualDir> load_dirs{fs_controller.GetModificationLoadRoot(title_id)};
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); std::vector<VirtualDir> sdmc_load_dirs{fs_controller.GetSDMCModificationLoadRoot(title_id)};
if (application_id != title_id) {
load_dirs.push_back(fs_controller.GetModificationLoadRoot(application_id));
sdmc_load_dirs.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id));
}
std::erase(load_dirs, nullptr);
std::erase(sdmc_load_dirs, nullptr);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data && if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
type != ContentRecordType::HtmlDocument) || type != ContentRecordType::HtmlDocument) ||
(load_dir == nullptr && sdmc_load_dir == nullptr)) { (load_dirs.empty() && sdmc_load_dirs.empty())) {
return; return;
} }
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); std::vector<VirtualDir> patch_dirs;
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { for (const auto& load_dir : load_dirs) {
patch_dirs.push_back(sdmc_load_dir); const auto load_patch_dirs = load_dir->GetSubdirectories();
patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end());
} }
std::sort(patch_dirs.begin(), patch_dirs.end(), if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); patch_dirs.insert(patch_dirs.end(), sdmc_load_dirs.begin(), sdmc_load_dirs.end());
}
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
return l->GetName() < r->GetName();
});
std::vector<VirtualDir> layers; std::vector<VirtualDir> layers;
std::vector<VirtualDir> layers_ext; std::vector<VirtualDir> layers_ext;
@@ -590,8 +650,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
auto romfs = base_romfs; auto romfs = base_romfs;
// Game Updates // Game Updates
const auto update_tid = GetUpdateTitleID(title_id); const auto update_tid = GetUpdateTitleIDForContent();
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
bool update_disabled = true; bool update_disabled = true;
std::optional<u32> enabled_version; std::optional<u32> enabled_version;
@@ -698,7 +758,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
// LayeredFS // LayeredFS
if (apply_layeredfs) { if (apply_layeredfs) {
ApplyLayeredFS(romfs, title_id, type, fs_controller); ApplyLayeredFS(romfs, title_id, application_id, type, fs_controller);
} }
return romfs; return romfs;
@@ -710,10 +770,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
std::vector<Patch> out; std::vector<Patch> out;
const auto& disabled = Settings::values.disabled_addons[title_id]; const auto& disabled = Settings::values.disabled_addons[application_id];
// Game Updates // Game Updates
const auto update_tid = GetUpdateTitleID(title_id); const auto update_tid = GetUpdateTitleIDForContent();
std::vector<Patch> external_update_patches; std::vector<Patch> external_update_patches;
@@ -862,7 +922,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = "", .version = "",
.type = PatchType::Update, .type = PatchType::Update,
.program_id = title_id, .program_id = title_id,
.title_id = title_id, .title_id = update_tid,
.source = PatchSource::Unknown, .source = PatchSource::Unknown,
.numeric_version = 0}; .numeric_version = 0};
@@ -888,8 +948,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
// General Mods (LayeredFS and IPS) // General Mods (LayeredFS and IPS)
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); for (const auto& mod_dir : GetModificationLoadRoots()) {
if (mod_dir != nullptr) {
for (auto const& f : mod_dir->GetFiles()) for (auto const& f : mod_dir->GetFiles())
if (auto const name = f->GetName(); name.starts_with("cheat_")) { if (auto const name = f->GetName(); name.starts_with("cheat_")) {
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end(); auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
@@ -956,8 +1015,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
// SDMC mod directory (RomFS LayeredFS) // SDMC mod directory (RomFS LayeredFS)
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); for (const auto& sdmc_mod_dir : GetSDMCModificationLoadRoots()) {
if (sdmc_mod_dir != nullptr) {
std::string types; std::string types;
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs")))
AppendCommaIfNotEmpty(types, "LayeredExeFS"); AppendCommaIfNotEmpty(types, "LayeredExeFS");
@@ -992,10 +1050,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
dlc_match.reserve(dlc_entries_with_origin.size()); dlc_match.reserve(dlc_entries_with_origin.size());
for (const auto& [slot, entry] : dlc_entries_with_origin) { for (const auto& [slot, entry] : dlc_entries_with_origin) {
const auto base_tid = GetBaseTitleID(entry.title_id); const auto base_tid = GetBaseTitleID(entry.title_id);
const bool matches_base = base_tid == title_id; const bool matches_base = base_tid == application_id;
if (!matches_base) { if (!matches_base) {
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}", LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
entry.title_id, base_tid, title_id); entry.title_id, base_tid, application_id);
continue; continue;
} }
@@ -1070,16 +1128,22 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
std::optional<u32> PatchManager::GetGameVersion() const { std::optional<u32> PatchManager::GetGameVersion() const {
const auto update_tid = GetUpdateTitleID(title_id); const auto update_tid = GetUpdateTitleIDForContent();
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
return content_provider.GetEntryVersion(update_tid); return content_provider.GetEntryVersion(update_tid);
} }
return content_provider.GetEntryVersion(title_id); if (const auto version = content_provider.GetEntryVersion(title_id); version.has_value()) {
return version;
}
return content_provider.GetEntryVersion(application_id);
} }
PatchManager::Metadata PatchManager::GetControlMetadata() const { PatchManager::Metadata PatchManager::GetControlMetadata() const {
const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr && application_id != title_id) {
base_control_nca = content_provider.GetEntry(application_id, ContentRecordType::Control);
}
if (base_control_nca == nullptr) { if (base_control_nca == nullptr) {
return {}; return {};
} }
@@ -1155,8 +1219,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
auto metadata = pm.GetControlMetadata(); auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) if (metadata.first != nullptr)
return metadata; return metadata;
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()}; const auto update_id = FileSys::GetUpdateTitleID(application_id);
return pm_update.GetControlMetadata(); const auto application_update_id = FileSys::GetUpdateTitleID(GetBaseTitleID(application_id));
const FileSys::PatchManager pm_update{update_id, system.GetFileSystemController(), system.GetContentProvider()};
metadata = pm_update.GetControlMetadata();
if (metadata.first != nullptr || update_id == application_update_id)
return metadata;
const FileSys::PatchManager pm_application_update{application_update_id, system.GetFileSystemController(), system.GetContentProvider()};
return pm_application_update.GetControlMetadata();
} }
} // namespace FileSys } // namespace FileSys
+5
View File
@@ -10,6 +10,7 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs/vfs_types.h" #include "core/file_sys/vfs/vfs_types.h"
@@ -109,10 +110,14 @@ public:
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept; [[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
private: private:
[[nodiscard]] u64 GetUpdateTitleIDForContent() const;
[[nodiscard]] std::vector<VirtualDir> GetModificationLoadRoots() const;
[[nodiscard]] std::vector<VirtualDir> GetSDMCModificationLoadRoots() const;
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const; const std::string& build_id) const;
u64 title_id; u64 title_id;
u64 application_id;
const Service::FileSystem::FileSystemController& fs_controller; const Service::FileSystem::FileSystemController& fs_controller;
const ContentProvider& content_provider; const ContentProvider& content_provider;
}; };
+57 -71
View File
@@ -10,17 +10,15 @@
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/svc_version.h"
namespace Kernel::Svc { namespace Kernel::Svc {
/// Gets system/memory information for the current process /// Gets system/memory information for the current process
Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle, Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle, u64 info_sub_id) {
u64 info_sub_id) { LOG_TRACE(Kernel_SVC, "called info_id={:#x}, info_sub_id={:#x}, handle={:#08x}", info_id_type, info_sub_id, handle);
LOG_TRACE(Kernel_SVC, "called info_id={:#x}, info_sub_id={:#x}, handle={:#08x}", u32 info_id = u32(info_id_type);
info_id_type, info_sub_id, handle);
u32 info_id = static_cast<u32>(info_id_type);
switch (info_id_type) { switch (info_id_type) {
case InfoType::CoreMask: case InfoType::CoreMask:
case InfoType::PriorityMask: case InfoType::PriorityMask:
@@ -53,152 +51,123 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
case InfoType::CoreMask: case InfoType::CoreMask:
*result = process->GetCoreMask(); *result = process->GetCoreMask();
R_SUCCEED(); R_SUCCEED();
case InfoType::PriorityMask: case InfoType::PriorityMask:
*result = process->GetPriorityMask(); *result = process->GetPriorityMask();
R_SUCCEED(); R_SUCCEED();
case InfoType::AliasRegionAddress: case InfoType::AliasRegionAddress:
*result = GetInteger(process->GetPageTable().GetAliasRegionStart()); *result = GetInteger(process->GetPageTable().GetAliasRegionStart());
R_SUCCEED(); R_SUCCEED();
case InfoType::AliasRegionSize: case InfoType::AliasRegionSize:
*result = process->GetPageTable().GetAliasRegionSize(); *result = process->GetPageTable().GetAliasRegionSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::HeapRegionAddress: case InfoType::HeapRegionAddress:
*result = GetInteger(process->GetPageTable().GetHeapRegionStart()); *result = GetInteger(process->GetPageTable().GetHeapRegionStart());
R_SUCCEED(); R_SUCCEED();
case InfoType::HeapRegionSize: case InfoType::HeapRegionSize:
*result = process->GetPageTable().GetHeapRegionSize(); *result = process->GetPageTable().GetHeapRegionSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::AslrRegionAddress: case InfoType::AslrRegionAddress:
*result = GetInteger(process->GetPageTable().GetAliasCodeRegionStart()); *result = GetInteger(process->GetPageTable().GetAliasCodeRegionStart());
R_SUCCEED(); R_SUCCEED();
case InfoType::AslrRegionSize: case InfoType::AslrRegionSize:
*result = process->GetPageTable().GetAliasCodeRegionSize(); *result = process->GetPageTable().GetAliasCodeRegionSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::StackRegionAddress: case InfoType::StackRegionAddress:
*result = GetInteger(process->GetPageTable().GetStackRegionStart()); *result = GetInteger(process->GetPageTable().GetStackRegionStart());
R_SUCCEED(); R_SUCCEED();
case InfoType::StackRegionSize: case InfoType::StackRegionSize:
*result = process->GetPageTable().GetStackRegionSize(); *result = process->GetPageTable().GetStackRegionSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::TotalMemorySize: case InfoType::TotalMemorySize:
*result = process->GetTotalUserPhysicalMemorySize(system.Kernel()); *result = process->GetTotalUserPhysicalMemorySize(system.Kernel());
R_SUCCEED(); R_SUCCEED();
case InfoType::UsedMemorySize: case InfoType::UsedMemorySize:
*result = process->GetUsedUserPhysicalMemorySize(system.Kernel()); *result = process->GetUsedUserPhysicalMemorySize(system.Kernel());
R_SUCCEED(); R_SUCCEED();
case InfoType::SystemResourceSizeTotal: case InfoType::SystemResourceSizeTotal:
*result = process->GetTotalSystemResourceSize(); *result = process->GetTotalSystemResourceSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::SystemResourceSizeUsed: case InfoType::SystemResourceSizeUsed:
*result = process->GetUsedSystemResourceSize(); *result = process->GetUsedSystemResourceSize();
R_SUCCEED(); R_SUCCEED();
case InfoType::ProgramId: case InfoType::ProgramId:
*result = process->GetProgramId(); *result = process->GetProgramId();
R_SUCCEED(); R_SUCCEED();
case InfoType::UserExceptionContextAddress: case InfoType::UserExceptionContextAddress:
*result = GetInteger(process->GetProcessLocalRegionAddress()); *result = GetInteger(process->GetProcessLocalRegionAddress());
R_SUCCEED(); R_SUCCEED();
case InfoType::TotalNonSystemMemorySize: case InfoType::TotalNonSystemMemorySize:
*result = process->GetTotalNonSystemUserPhysicalMemorySize(system.Kernel()); *result = process->GetTotalNonSystemUserPhysicalMemorySize(system.Kernel());
R_SUCCEED(); R_SUCCEED();
case InfoType::UsedNonSystemMemorySize: case InfoType::UsedNonSystemMemorySize:
*result = process->GetUsedNonSystemUserPhysicalMemorySize(system.Kernel()); *result = process->GetUsedNonSystemUserPhysicalMemorySize(system.Kernel());
R_SUCCEED(); R_SUCCEED();
case InfoType::IsApplication: case InfoType::IsApplication:
*result = process->IsApplication(); *result = process->IsApplication();
R_SUCCEED(); R_SUCCEED();
case InfoType::FreeThreadCount: case InfoType::FreeThreadCount:
if (KResourceLimit* resource_limit = process->GetResourceLimit(); if (KResourceLimit* resource_limit = process->GetResourceLimit();
resource_limit != nullptr) { resource_limit != nullptr) {
const auto current_value = const auto current_value = resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax);
resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax); const auto limit_value = resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax);
const auto limit_value =
resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax);
*result = limit_value - current_value; *result = limit_value - current_value;
} else { } else {
*result = 0; *result = 0;
} }
R_SUCCEED(); R_SUCCEED();
case InfoType::AliasRegionExtraSize: { case InfoType::AliasRegionExtraSize: {
if (info_sub_id != 0) { R_UNLESS(info_sub_id == 0, ResultInvalidCombination);
return ResultInvalidCombination;
}
KProcess* current_process = GetCurrentProcessPointer(system.Kernel()); KProcess* current_process = GetCurrentProcessPointer(system.Kernel());
*result = current_process->GetPageTable().GetAliasRegionExtraSize(); *result = current_process->GetPageTable().GetAliasRegionExtraSize();
R_SUCCEED(); R_SUCCEED();
} }
default: default:
break; break;
} }
LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id); LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id);
R_THROW(ResultInvalidEnumValue); R_THROW(ResultInvalidEnumValue);
} }
case InfoType::DebuggerAttached: case InfoType::DebuggerAttached:
*result = 0; *result = 0;
R_SUCCEED(); R_SUCCEED();
case InfoType::ResourceLimit: { case InfoType::ResourceLimit: {
R_UNLESS(handle == 0, ResultInvalidHandle); R_UNLESS(handle == 0, ResultInvalidHandle);
R_UNLESS(info_sub_id == 0, ResultInvalidCombination); R_UNLESS(info_sub_id == 0, ResultInvalidCombination);
KProcess* const current_process = GetCurrentProcessPointer(system.Kernel()); KProcess* const current_process = GetCurrentProcessPointer(system.Kernel());
KHandleTable& handle_table = current_process->GetHandleTable(); KHandleTable& handle_table = current_process->GetHandleTable();
const auto resource_limit = current_process->GetResourceLimit(); if (auto const resource_limit = current_process->GetResourceLimit(); resource_limit) {
if (!resource_limit) { Handle resource_handle{};
R_TRY(handle_table.Add(system.Kernel(), std::addressof(resource_handle), resource_limit));
*result = resource_handle;
} else {
*result = Svc::InvalidHandle; *result = Svc::InvalidHandle;
// Yes, the kernel considers this a successful operation.
R_SUCCEED();
} }
// Yes, the kernel considers this a successful operation either way.
Handle resource_handle{};
R_TRY(handle_table.Add(system.Kernel(), std::addressof(resource_handle), resource_limit));
*result = resource_handle;
R_SUCCEED(); R_SUCCEED();
} }
case InfoType::RandomEntropy: case InfoType::RandomEntropy:
R_UNLESS(handle == 0, ResultInvalidHandle); R_UNLESS(handle == 0, ResultInvalidHandle);
R_UNLESS(info_sub_id < 4, ResultInvalidCombination); R_UNLESS(info_sub_id < 4, ResultInvalidCombination);
*result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id); *result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id);
R_SUCCEED(); R_SUCCEED();
case InfoType::InitialProcessIdRange: {
case InfoType::InitialProcessIdRange: LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query privileged process id bounds, returned 0/64");
LOG_WARNING(Kernel_SVC, R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
"(STUBBED) Attempted to query privileged process id bounds, returned 0"); switch (InitialProcessIdRangeInfo(info_sub_id)) {
*result = 0; case InitialProcessIdRangeInfo::Minimum:
R_SUCCEED(); *result = 0; //todo
R_SUCCEED();
case InitialProcessIdRangeInfo::Maximum:
*result = 64; //todo
R_SUCCEED();
default:
R_THROW(ResultInvalidCombination);
}
}
case InfoType::ThreadTickCount: { case InfoType::ThreadTickCount: {
constexpr u64 num_cpus = 4; constexpr u64 num_cpus = 4;
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, info_sub_id);
info_sub_id);
R_THROW(ResultInvalidCombination); R_THROW(ResultInvalidCombination);
} }
@@ -206,8 +175,7 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
.GetHandleTable() .GetHandleTable()
.GetObject<KThread>(system.Kernel(), Handle(handle)); .GetObject<KThread>(system.Kernel(), Handle(handle));
if (thread.IsNull()) { if (thread.IsNull()) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle={:#08x}", LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle={:#08x}", Handle(handle));
static_cast<Handle>(handle));
R_THROW(ResultInvalidHandle); R_THROW(ResultInvalidHandle);
} }
@@ -220,7 +188,6 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
u64 out_ticks = 0; u64 out_ticks = 0;
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
const u64 thread_ticks = current_thread->GetCpuTime(); const u64 thread_ticks = current_thread->GetCpuTime();
out_ticks = thread_ticks + (core_timing.GetClockTicks() - prev_ctx_ticks); out_ticks = thread_ticks + (core_timing.GetClockTicks() - prev_ctx_ticks);
} else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) {
out_ticks = core_timing.GetClockTicks() - prev_ctx_ticks; out_ticks = core_timing.GetClockTicks() - prev_ctx_ticks;
@@ -230,19 +197,40 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
R_SUCCEED(); R_SUCCEED();
} }
case InfoType::IdleTickCount: { case InfoType::IdleTickCount: {
// Verify the input handle is invalid.
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
// Verify the requested core is valid. // Verify the requested core is valid.
const bool core_valid = const bool core_valid =
(info_sub_id == 0xFFFFFFFFFFFFFFFF) || (info_sub_id == 0xFFFFFFFFFFFFFFFF)
(info_sub_id == static_cast<u64>(system.Kernel().CurrentPhysicalCoreIndex())); || (info_sub_id == u64(system.Kernel().CurrentPhysicalCoreIndex()));
R_UNLESS(core_valid, ResultInvalidCombination); R_UNLESS(core_valid, ResultInvalidCombination);
// Verify the input handle is invalid.
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
// Get the idle tick count. // Get the idle tick count.
*result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
R_SUCCEED(); R_SUCCEED();
} }
case InfoType::MesosphereMeta: {
enum MesosphereMetaInfo : u64 {
KernelVersion = 0,
IsKTraceEnabled = 1,
IsSingleStepEnabled = 2,
};
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
switch (MesosphereMetaInfo(info_sub_id)) {
case MesosphereMetaInfo::KernelVersion:
*result = Kernel::Svc::SupportedKernelVersion;
R_SUCCEED();
case MesosphereMetaInfo::IsKTraceEnabled:
*result = 0;
R_SUCCEED();
case MesosphereMetaInfo::IsSingleStepEnabled:
*result = 0;
R_SUCCEED();
default:
R_THROW(ResultInvalidCombination);
}
}
case InfoType::MesosphereCurrentProcess: { case InfoType::MesosphereCurrentProcess: {
// Verify the input handle is invalid. // Verify the input handle is invalid.
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
@@ -255,13 +243,11 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
KHandleTable& handle_table = current_process->GetHandleTable(); KHandleTable& handle_table = current_process->GetHandleTable();
// Get a new handle for the current process. // Get a new handle for the current process.
Handle tmp; Handle tmp{};
R_TRY(handle_table.Add(system.Kernel(), std::addressof(tmp), current_process)); R_TRY(handle_table.Add(system.Kernel(), std::addressof(tmp), current_process));
// Set the output. // Set the output.
*result = tmp; *result = tmp;
// We succeeded.
R_SUCCEED(); R_SUCCEED();
} }
default: default:
+2 -1
View File
@@ -1387,7 +1387,8 @@ public:
{5, &ACC_U1::GetProfile, "GetProfile"}, {5, &ACC_U1::GetProfile, "GetProfile"},
{6, nullptr, "GetProfileDigest"}, {6, nullptr, "GetProfileDigest"},
{50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"},
{51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, {51, &ACC_U1::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"},
{52, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
{60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"},
{99, nullptr, "DebugActivateOpenContextRetention"}, {99, nullptr, "DebugActivateOpenContextRetention"},
{100, nullptr, "GetUserRegistrationNotifier"}, {100, nullptr, "GetUserRegistrationNotifier"},
+1
View File
@@ -154,6 +154,7 @@ enum class AppletMessage : u32 {
DetectLongPressingCaptureButton = 91, DetectLongPressingCaptureButton = 91,
AlbumScreenShotTaken = 92, AlbumScreenShotTaken = 92,
AlbumRecordingSaved = 93, AlbumRecordingSaved = 93,
StartupLogoDisappeared = 95,
}; };
enum class LibraryAppletMode : u32 { enum class LibraryAppletMode : u32 {
+1
View File
@@ -91,6 +91,7 @@ struct Applet {
// Common state // Common state
bool sleep_lock_enabled{}; bool sleep_lock_enabled{};
bool vr_mode_enabled{}; bool vr_mode_enabled{};
bool vr_mode_enabled_3d{};
bool lcd_backlight_off_enabled{}; bool lcd_backlight_off_enabled{};
APM::CpuBoostMode boost_mode{}; APM::CpuBoostMode boost_mode{};
bool request_exit_to_library_applet_at_execute_next_program_enabled{}; bool request_exit_to_library_applet_at_execute_next_program_enabled{};
@@ -73,20 +73,16 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
R_TRY(m_manager_display_service->CreateManagedLayer( R_TRY(m_manager_display_service->CreateManagedLayer(
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()})); out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
this->GetLayerStackMask());
if (m_applet_id != AppletId::Application) { if (m_applet_id != AppletId::Application) {
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id); (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
(void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else { } else {
(void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id); (void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
} }
} }
(void)m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true);
m_managed_display_layers.emplace(*out_layer_id); m_managed_display_layers.emplace(*out_layer_id);
R_SUCCEED(); R_SUCCEED();
@@ -126,12 +122,11 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
// Ensure the overlay layer is visible // Ensure the overlay layer is visible
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id); m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
(void)m_display_service->GetContainer()->SetLayerStackMask(m_system_shared_layer_id,
this->GetLayerStackMask());
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
s32 initial_z = Foreground; s32 initial_z = 1;
(void)m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true);
if (m_applet_id == AppletId::OverlayDisplay) { if (m_applet_id == AppletId::OverlayDisplay) {
initial_z = Overlay; initial_z = -1;
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true); (void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
} }
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
+11 -2
View File
@@ -6,6 +6,7 @@
#include <optional> #include <optional>
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -104,8 +105,16 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
// TODO(DarkLordZach): When FSController/Game Card Support is added, if // TODO(DarkLordZach): When FSController/Game Card Support is added, if
// current_process_game_card use correct StorageId // current_process_game_card use correct StorageId
launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program)); auto base_slot = storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program);
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); if (!base_slot) {
base_slot = storage.GetSlotForEntry(FileSys::GetBaseTitleID(launch.title_id), FileSys::ContentRecordType::Program);
}
launch.base_game_storage_id = GetStorageIdForFrontendSlot(base_slot);
auto update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program);
if (!update_slot) {
update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(launch.title_id)), FileSys::ContentRecordType::Program);
}
launch.update_storage_id = GetStorageIdForFrontendSlot(update_slot);
system.GetARPManager().Register(launch.title_id, launch, out_control); system.GetARPManager().Register(launch.title_id, launch, out_control);
return process; return process;
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -73,8 +73,7 @@ Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy( Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy(
Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy, ClientProcessId pid, Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle, InCopyHandle<Kernel::KProcess> process_handle) {
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute) {
LOG_WARNING(Service_AM, "called"); LOG_WARNING(Service_AM, "called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) { if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
@@ -89,8 +88,7 @@ Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy(
Result IAllSystemAppletProxiesService::OpenSystemApplicationProxy( Result IAllSystemAppletProxiesService::OpenSystemApplicationProxy(
Out<SharedPointer<IApplicationProxy>> out_system_application_proxy, ClientProcessId pid, Out<SharedPointer<IApplicationProxy>> out_system_application_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle, InCopyHandle<Kernel::KProcess> process_handle) {
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute) {
LOG_DEBUG(Service_AM, "called"); LOG_DEBUG(Service_AM, "called");
if (const auto applet = this->GetAppletFromProcessId(pid); applet) { if (const auto applet = this->GetAppletFromProcessId(pid); applet) {
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -36,15 +36,13 @@ private:
InCopyHandle<Kernel::KProcess> process_handle, InCopyHandle<Kernel::KProcess> process_handle,
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute); InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute);
Result OpenOverlayAppletProxy(Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy, Result OpenOverlayAppletProxy(Out<SharedPointer<IOverlayAppletProxy>> out_overlay_applet_proxy,
ClientProcessId pid, InCopyHandle<Kernel::KProcess> process_handle, ClientProcessId pid, InCopyHandle<Kernel::KProcess> process_handle);
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute);
Result OpenLibraryAppletProxyOld( Result OpenLibraryAppletProxyOld(
Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid, Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle); InCopyHandle<Kernel::KProcess> process_handle);
Result OpenSystemApplicationProxy( Result OpenSystemApplicationProxy(
Out<SharedPointer<IApplicationProxy>> out_system_application_proxy, ClientProcessId pid, Out<SharedPointer<IApplicationProxy>> out_system_application_proxy, ClientProcessId pid,
InCopyHandle<Kernel::KProcess> process_handle, InCopyHandle<Kernel::KProcess> process_handle);
InLargeData<AppletAttribute, BufferAttr_HipcMapAlias> attribute);
Result GetSystemProcessCommonFunctions(); Result GetSystemProcessCommonFunctions();
Result GetAppletAlternativeFunctions(); Result GetAppletAlternativeFunctions();
@@ -158,7 +158,7 @@ Result IApplicationFunctions::EnsureSaveData(Out<u64> out_size, Common::UUID use
LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString()); LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString());
FileSys::SaveDataAttribute attribute{}; FileSys::SaveDataAttribute attribute{};
attribute.program_id = m_applet->program_id; attribute.program_id = FileSys::GetBaseTitleID(m_applet->program_id);
attribute.user_id = user_id.AsU128(); attribute.user_id = user_id.AsU128();
attribute.type = FileSys::SaveDataType::Account; attribute.type = FileSys::SaveDataType::Account;
@@ -238,7 +238,7 @@ Result IApplicationFunctions::ExtendSaveData(Out<u64> out_required_size, FileSys
static_cast<u8>(type), user_id.FormattedString(), normal_size, journal_size); static_cast<u8>(type), user_id.FormattedString(), normal_size, journal_size);
system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize( system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
type, m_applet->program_id, user_id.AsU128(), {normal_size, journal_size}); type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128(), {normal_size, journal_size});
// The following value is used to indicate the amount of space remaining on failure // The following value is used to indicate the amount of space remaining on failure
// due to running out of space. Since we always succeed, this should be 0. // due to running out of space. Since we always succeed, this should be 0.
@@ -252,7 +252,7 @@ Result IApplicationFunctions::GetSaveDataSize(Out<u64> out_normal_size, Out<u64>
LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString()); LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString());
const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize( const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
type, m_applet->program_id, user_id.AsU128()); type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128());
*out_normal_size = size.normal; *out_normal_size = size.normal;
*out_journal_size = size.journal; *out_journal_size = size.journal;
@@ -39,7 +39,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
{14, nullptr, "GetWakeupCount"}, //11.0.0+ {14, nullptr, "GetWakeupCount"}, //11.0.0+
{15, nullptr, "Unknown15"}, //19.0.0+ {15, nullptr, "Unknown15"}, //19.0.0+
{20, D<&ICommonStateGetter::PushToGeneralChannel>, "PushToGeneralChannel"}, {20, D<&ICommonStateGetter::PushToGeneralChannel>, "PushToGeneralChannel"},
{30, nullptr, "GetHomeButtonReaderLockAccessor"}, {30, D<&ICommonStateGetter::GetHomeButtonReaderLockAccessor>, "GetHomeButtonReaderLockAccessor"},
{31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"}, //2.0.0+ {31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"}, //2.0.0+
{32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"}, //7.0.0+ {32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"}, //7.0.0+
{40, nullptr, "GetCradleFwVersion"}, //2.0.0+ {40, nullptr, "GetCradleFwVersion"}, //2.0.0+
@@ -65,7 +65,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
{100, D<&ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"}, {100, D<&ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
{110, nullptr, "OpenMyGpuErrorHandler"}, {110, nullptr, "OpenMyGpuErrorHandler"},
{120, D<&ICommonStateGetter::GetAppletLaunchedHistory>, "GetAppletLaunchedHistory"}, //13.0.0+ {120, D<&ICommonStateGetter::GetAppletLaunchedHistory>, "GetAppletLaunchedHistory"}, //13.0.0+
{130, nullptr, "Unknown130"}, //21.0.0+ {130, D<&ICommonStateGetter::EnableStartupLogoDisappearedMessage>, "EnableStartupLogoDisappearedMessage"}, //21.0.0+
{200, D<&ICommonStateGetter::GetOperationModeSystemInfo>, "GetOperationModeSystemInfo"}, {200, D<&ICommonStateGetter::GetOperationModeSystemInfo>, "GetOperationModeSystemInfo"},
{300, D<&ICommonStateGetter::GetSettingsPlatformRegion>, "GetSettingsPlatformRegion"}, {300, D<&ICommonStateGetter::GetSettingsPlatformRegion>, "GetSettingsPlatformRegion"},
{400, nullptr, "ActivateMigrationService"}, {400, nullptr, "ActivateMigrationService"},
@@ -74,14 +74,17 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr<Ap
{501, nullptr, "SuppressDisablingSleepTemporarily"}, {501, nullptr, "SuppressDisablingSleepTemporarily"},
{502, nullptr, "IsSleepEnabled"}, {502, nullptr, "IsSleepEnabled"},
{503, nullptr, "IsDisablingSleepSuppressed"}, {503, nullptr, "IsDisablingSleepSuppressed"},
{600, nullptr, "Unknown600"}, //20.0.0+ {600, nullptr, "SetHidInputMagnificationForApplication"}, //20.0.0+
{610, D<&ICommonStateGetter::Unknown610>, "Unknown610"}, //21.0.0+ {610, D<&ICommonStateGetter::Unknown610>, "Unknown610"}, //21.0.0+
{611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+ {611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+
{900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+ {900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+
{910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+ {910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+
{1000, nullptr, "BeginVrMode3d"}, //19.0.0+ {1000, D<&ICommonStateGetter::BeginVrMode3d>, "BeginVrMode3d"}, //19.0.0+
{1001, nullptr, "EndVrMode3d"}, //19.0.0+ {1001, D<&ICommonStateGetter::EndVrMode3d>, "EndVrMode3d"}, //19.0.0+
{1002, nullptr, "IsVrModeEnabled3d"}, //19.0.0+ {1002, D<&ICommonStateGetter::IsVrModeEnabled3d>, "IsVrModeEnabled3d"}, //19.0.0+
{1003, D<&ICommonStateGetter::GetVrLaboGoggleViewport>, "GetVrLaboGoggleViewport"}, //21.0.0+
{1004, D<&ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle>, "GetPanelPhysicalSizeForSpecificTitle"}, //21.0.0+
{1005, D<&ICommonStateGetter::GetPanelResolutionForSpecificTitle>, "GetPanelResolutionForSpecificTitle"}, //21.0.0+
}; };
// clang-format on // clang-format on
@@ -311,6 +314,12 @@ Result ICommonStateGetter::PerformSystemButtonPressingIfInFocus(SystemButtonType
R_SUCCEED(); R_SUCCEED();
} }
Result ICommonStateGetter::EnableStartupLogoDisappearedMessage() {
LOG_WARNING(Service_AM, "(STUBBED) called");
m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::StartupLogoDisappeared);
R_SUCCEED();
}
Result ICommonStateGetter::GetOperationModeSystemInfo(Out<u32> out_operation_mode_system_info) { Result ICommonStateGetter::GetOperationModeSystemInfo(Out<u32> out_operation_mode_system_info) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");
*out_operation_mode_system_info = 0; *out_operation_mode_system_info = 0;
@@ -355,6 +364,12 @@ Result ICommonStateGetter::PushToGeneralChannel(SharedPointer<IStorage> storage)
R_SUCCEED(); R_SUCCEED();
} }
Result ICommonStateGetter::GetHomeButtonReaderLockAccessor(Out<SharedPointer<ILockAccessor>> out_lock_accessor) {
LOG_DEBUG(Service_AM, "called");
*out_lock_accessor = std::make_shared<ILockAccessor>(system);
R_SUCCEED();
}
Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled) { Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled) {
LOG_DEBUG(Service_AM, "called, enabled={} applet_id={}", enabled, m_applet->applet_id); LOG_DEBUG(Service_AM, "called, enabled={} applet_id={}", enabled, m_applet->applet_id);
@@ -363,14 +378,58 @@ Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled
R_SUCCEED(); R_SUCCEED();
} }
Result ICommonStateGetter::Unknown610() { Result ICommonStateGetter::Unknown610(u64 unk) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");
R_SUCCEED(); R_SUCCEED();
} }
Result ICommonStateGetter::Unknown611() { Result ICommonStateGetter::Unknown611(u8 unk) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");
R_SUCCEED(); R_SUCCEED();
} }
Result ICommonStateGetter::BeginVrMode3d() {
std::scoped_lock lk{m_applet->lock};
m_applet->vr_mode_enabled_3d = true;
LOG_WARNING(Service_AM, "VR Mode is {}", m_applet->vr_mode_enabled_3d ? "on" : "off");
R_SUCCEED();
}
Result ICommonStateGetter::EndVrMode3d() {
std::scoped_lock lk{m_applet->lock};
m_applet->vr_mode_enabled_3d = false;
LOG_WARNING(Service_AM, "VR Mode is {}", m_applet->vr_mode_enabled_3d ? "on" : "off");
R_SUCCEED();
}
Result ICommonStateGetter::IsVrModeEnabled3d(Out<bool> out_is_vr_mode_enabled_3d) {
LOG_WARNING(Service_AM, "(STUBBED) called");
std::scoped_lock lk{m_applet->lock};
*out_is_vr_mode_enabled_3d = m_applet->vr_mode_enabled_3d;
R_SUCCEED();
}
Result ICommonStateGetter::GetVrLaboGoggleViewport(Out<s32> out_x, Out<s32> out_y, Out<s32> out_width, Out<s32> out_height) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_x = 0;
*out_y = 0;
*out_width = 1280;
*out_height = 720;
R_SUCCEED();
}
Result ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle(Out<f32> out_width, Out<f32> out_height) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_width = 137250.0f / 1000.0f;
*out_height = 77200.0f / 1000.0f;
R_SUCCEED();
}
Result ICommonStateGetter::GetPanelResolutionForSpecificTitle(Out<s32> out_width, Out<s32> out_height) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_width = 1280;
*out_height = 720;
R_SUCCEED();
}
} // namespace Service::AM } // namespace Service::AM
@@ -56,15 +56,23 @@ private:
Result GetDefaultDisplayResolution(Out<s32> out_width, Out<s32> out_height); Result GetDefaultDisplayResolution(Out<s32> out_width, Out<s32> out_height);
Result GetBuiltInDisplayType(Out<s32> out_display_type); Result GetBuiltInDisplayType(Out<s32> out_display_type);
Result PerformSystemButtonPressingIfInFocus(SystemButtonType type); Result PerformSystemButtonPressingIfInFocus(SystemButtonType type);
Result EnableStartupLogoDisappearedMessage();
Result GetOperationModeSystemInfo(Out<u32> out_operation_mode_system_info); Result GetOperationModeSystemInfo(Out<u32> out_operation_mode_system_info);
Result GetAppletLaunchedHistory(Out<s32> out_count, Result GetAppletLaunchedHistory(Out<s32> out_count,
OutArray<AppletId, BufferAttr_HipcMapAlias> out_applet_ids); OutArray<AppletId, BufferAttr_HipcMapAlias> out_applet_ids);
Result GetSettingsPlatformRegion(Out<Set::PlatformRegion> out_settings_platform_region); Result GetSettingsPlatformRegion(Out<Set::PlatformRegion> out_settings_platform_region);
Result SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(); Result SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled();
Result PushToGeneralChannel(SharedPointer<IStorage> storage); // cmd 20 Result PushToGeneralChannel(SharedPointer<IStorage> storage); // cmd 20
Result GetHomeButtonReaderLockAccessor(Out<SharedPointer<ILockAccessor>> out_lock_accessor);
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled); Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
Result Unknown610(); Result Unknown610(u64 unk);
Result Unknown611(); Result Unknown611(u8 unk);
Result BeginVrMode3d();
Result EndVrMode3d();
Result IsVrModeEnabled3d(Out<bool> out_is_vr_mode_enabled_3d);
Result GetVrLaboGoggleViewport(Out<s32> out_x, Out<s32> out_y, Out<s32> out_width, Out<s32> out_height);
Result GetPanelPhysicalSizeForSpecificTitle(Out<f32> out_width, Out<f32> out_height);
Result GetPanelResolutionForSpecificTitle(Out<s32> out_width, Out<s32> out_height);
void SetCpuBoostMode(HLERequestContext& ctx); void SetCpuBoostMode(HLERequestContext& ctx);
@@ -14,6 +14,7 @@
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/bis_factory.h" #include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -340,7 +341,7 @@ Result FileSystemController::RegisterProcess(
registrations.emplace(process_id, Registration{ registrations.emplace(process_id, Registration{
.program_id = program_id, .program_id = program_id,
.romfs_factory = std::move(romfs_factory), .romfs_factory = std::move(romfs_factory),
.save_data_factory = CreateSaveDataFactory(program_id), .save_data_factory = CreateSaveDataFactory(FileSys::GetBaseTitleID(program_id)),
}); });
LOG_DEBUG(Service_FS, "Registered for process {}", process_id); LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
@@ -798,7 +799,7 @@ void LoopProcess(Core::System& system) {
const auto FileSystemProxyFactory = [&] { return std::make_shared<FSP_SRV>(system); }; const auto FileSystemProxyFactory = [&] { return std::make_shared<FSP_SRV>(system); };
server_manager->RegisterNamedService("fsp-ldr", std::make_shared<FSP_LDR>(system)); server_manager->RegisterNamedService("fsp-ldr", std::make_shared<FSP_LDR>(system));
server_manager->RegisterNamedService("fsp:pr", std::make_shared<FSP_PR>(system)); server_manager->RegisterNamedService("fsp-pr", std::make_shared<FSP_PR>(system));
server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory)); server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory));
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }
@@ -18,6 +18,7 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/errors.h" #include "core/file_sys/errors.h"
#include "core/file_sys/fs_directory.h" #include "core/file_sys/fs_directory.h"
@@ -313,7 +314,7 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface<IFileSyste
FileSys::ResultInvalidArgument); FileSys::ResultInvalidArgument);
if (attribute.program_id == 0) { if (attribute.program_id == 0) {
attribute.program_id = program_id; attribute.program_id = FileSys::GetBaseTitleID(program_id);
} }
FileSys::VirtualDir dir{}; FileSys::VirtualDir dir{};
+25 -7
View File
@@ -32,7 +32,6 @@ public:
RegisterHandlers(functions); RegisterHandlers(functions);
} }
~I2CSession() override = default; ~I2CSession() override = default;
Result Send(InBuffer<BufferAttr_HipcMapAlias> in_data, u32 transaction_option) { Result Send(InBuffer<BufferAttr_HipcMapAlias> in_data, u32 transaction_option) {
LOG_WARNING(Service, "(stubbed) topt={}", transaction_option); LOG_WARNING(Service, "(stubbed) topt={}", transaction_option);
R_THROW(ResultUnknown); R_THROW(ResultUnknown);
@@ -50,21 +49,40 @@ public:
: ServiceFramework{system_, "i2c"} : ServiceFramework{system_, "i2c"}
{ {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, nullptr, "OpenSessionForDev"}, {0, C<&I2C::OpenSessionForDev>, "OpenSessionForDev"},
{1, C<&I2C::OpenSession>, "OpenSession"}, {1, C<&I2C::OpenSession>, "OpenSession"},
{2, nullptr, "HasDevice"}, {2, C<&I2C::HasDevice>, "HasDevice"},
{3, nullptr, "HasDeviceForDev"}, {3, C<&I2C::HasDeviceForDev>, "HasDeviceForDev"},
{4, nullptr, "OpenSession2"}, {4, C<&I2C::OpenSession2>, "OpenSession2"},
}; };
RegisterHandlers(functions); RegisterHandlers(functions);
} }
~I2C() override = default; ~I2C() override = default;
Result OpenSessionForDev(OutInterface<I2CSession> out_session, s32 bus_idx, u32 slave_address, u32 addressing_mode, u32 speed_mode) {
Result OpenSession(I2CDevice device, OutInterface<I2CSession> out_session) {
LOG_DEBUG(Service, "(stubbed)"); LOG_DEBUG(Service, "(stubbed)");
*out_session = std::make_shared<I2CSession>(system); *out_session = std::make_shared<I2CSession>(system);
R_SUCCEED(); R_SUCCEED();
} }
Result OpenSession(OutInterface<I2CSession> out_session, I2CDevice device) {
LOG_DEBUG(Service, "(stubbed)");
*out_session = std::make_shared<I2CSession>(system);
R_SUCCEED();
}
Result HasDevice(Out<bool> out_has_device, I2CDevice device) {
LOG_DEBUG(Service, "(stubbed)");
*out_has_device = false;
R_SUCCEED();
}
Result HasDeviceForDev(Out<bool> out_has_device, I2CDevice device) {
LOG_DEBUG(Service, "(stubbed)");
*out_has_device = false;
R_SUCCEED();
}
Result OpenSession2(OutInterface<I2CSession> out_session, u32 device_code) {
LOG_DEBUG(Service, "(stubbed) device_code={}", device_code);
*out_session = std::make_shared<I2CSession>(system);
R_SUCCEED();
}
}; };
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {
@@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -30,15 +33,15 @@ struct DeviceSettings {
INSERT_PADDING_BYTES(0x20); // Reserved INSERT_PADDING_BYTES(0x20); // Reserved
// nn::settings::system::ConsoleSixAxisSensorAccelerationBias // nn::settings::system::ConsoleSixAxisSensorAccelerationBias
Common::Vec3<f32> console_six_axis_sensor_acceleration_bias; Common::Vec<f32, 3> console_six_axis_sensor_acceleration_bias;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias
Common::Vec3<f32> console_six_axis_sensor_angular_velocity_bias; Common::Vec<f32, 3> console_six_axis_sensor_angular_velocity_bias;
// nn::settings::system::ConsoleSixAxisSensorAccelerationGain // nn::settings::system::ConsoleSixAxisSensorAccelerationGain
std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain; std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain
std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain; std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias
Common::Vec3<f32> console_six_axis_sensor_angular_velocity_time_bias; Common::Vec<f32, 3> console_six_axis_sensor_angular_velocity_time_bias;
// nn::settings::system::ConsoleSixAxisSensorAngularAcceleration // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration
std::array<u8, 0x24> console_six_axis_sensor_angular_acceleration; std::array<u8, 0x24> console_six_axis_sensor_angular_acceleration;
}; };
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -153,15 +153,15 @@ struct SystemSettings {
INSERT_PADDING_BYTES(0x7FF8); // Reserved INSERT_PADDING_BYTES(0x7FF8); // Reserved
// nn::settings::system::ConsoleSixAxisSensorAccelerationBias // nn::settings::system::ConsoleSixAxisSensorAccelerationBias
Common::Vec3<f32> console_six_axis_sensor_acceleration_bias; Common::Vec<f32, 3> console_six_axis_sensor_acceleration_bias;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias
Common::Vec3<f32> console_six_axis_sensor_angular_velocity_bias; Common::Vec<f32, 3> console_six_axis_sensor_angular_velocity_bias;
// nn::settings::system::ConsoleSixAxisSensorAccelerationGain // nn::settings::system::ConsoleSixAxisSensorAccelerationGain
std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain; std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain
std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain; std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain;
// nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias
Common::Vec3<f32> console_six_axis_sensor_angular_velocity_time_bias; Common::Vec<f32, 3> console_six_axis_sensor_angular_velocity_time_bias;
// nn::settings::system::ConsoleSixAxisSensorAngularAcceleration // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration
std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_acceleration; std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_acceleration;
INSERT_PADDING_BYTES(0x70); // Reserved INSERT_PADDING_BYTES(0x70); // Reserved
@@ -13,6 +13,7 @@
#include "core/hle/service/vi/manager_display_service.h" #include "core/hle/service/vi/manager_display_service.h"
#include "core/hle/service/vi/system_display_service.h" #include "core/hle/service/vi/system_display_service.h"
#include "core/hle/service/vi/vi_results.h" #include "core/hle/service/vi/vi_results.h"
#include "service_creator.h"
namespace Service::VI { namespace Service::VI {
@@ -38,6 +39,7 @@ IApplicationDisplayService::IApplicationDisplayService(Core::System& system_,
{2031, C<&IApplicationDisplayService::DestroyStrayLayer>, "DestroyStrayLayer"}, {2031, C<&IApplicationDisplayService::DestroyStrayLayer>, "DestroyStrayLayer"},
{2101, C<&IApplicationDisplayService::SetLayerScalingMode>, "SetLayerScalingMode"}, {2101, C<&IApplicationDisplayService::SetLayerScalingMode>, "SetLayerScalingMode"},
{2102, C<&IApplicationDisplayService::ConvertScalingMode>, "ConvertScalingMode"}, {2102, C<&IApplicationDisplayService::ConvertScalingMode>, "ConvertScalingMode"},
{2103, C<&IApplicationDisplayService::Cmd2103>, "Cmd2103"},
{2450, C<&IApplicationDisplayService::GetIndirectLayerImageMap>, "GetIndirectLayerImageMap"}, {2450, C<&IApplicationDisplayService::GetIndirectLayerImageMap>, "GetIndirectLayerImageMap"},
{2451, nullptr, "GetIndirectLayerImageCropMap"}, {2451, nullptr, "GetIndirectLayerImageCropMap"},
{2460, C<&IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo>, "GetIndirectLayerImageRequiredMemoryInfo"}, {2460, C<&IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo>, "GetIndirectLayerImageRequiredMemoryInfo"},
@@ -289,6 +291,11 @@ Result IApplicationDisplayService::ConvertScalingMode(Out<ConvertedScaleMode> ou
} }
} }
Result IApplicationDisplayService::Cmd2103(Out<std::array<u8, 0x18>> out_unk18) {
LOG_WARNING(Service_VI, "(stubbed)");
R_SUCCEED();
}
Result IApplicationDisplayService::GetIndirectLayerImageMap( Result IApplicationDisplayService::GetIndirectLayerImageMap(
Out<u64> out_size, Out<u64> out_stride, Out<u64> out_size, Out<u64> out_stride,
OutBuffer<BufferAttr_HipcMapTransferAllowsNonSecure | BufferAttr_HipcMapAlias> out_buffer, OutBuffer<BufferAttr_HipcMapTransferAllowsNonSecure | BufferAttr_HipcMapAlias> out_buffer,
@@ -64,6 +64,7 @@ public:
Result GetDisplayVsyncEvent(OutCopyHandle<Kernel::KReadableEvent> out_vsync_event, Result GetDisplayVsyncEvent(OutCopyHandle<Kernel::KReadableEvent> out_vsync_event,
u64 display_id); u64 display_id);
Result ConvertScalingMode(Out<ConvertedScaleMode> out_scaling_mode, NintendoScaleMode mode); Result ConvertScalingMode(Out<ConvertedScaleMode> out_scaling_mode, NintendoScaleMode mode);
Result Cmd2103(Out<std::array<u8, 0x18>> out_unk18);
Result GetIndirectLayerImageMap( Result GetIndirectLayerImageMap(
Out<u64> out_size, Out<u64> out_stride, Out<u64> out_size, Out<u64> out_stride,
OutBuffer<BufferAttr_HipcMapTransferAllowsNonSecure | BufferAttr_HipcMapAlias> out_buffer, OutBuffer<BufferAttr_HipcMapTransferAllowsNonSecure | BufferAttr_HipcMapAlias> out_buffer,
@@ -300,28 +300,6 @@ Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64*
} }
} }
// Claim a presentation slot range.
u32 slot_base = 0;
std::array<bool, SharedBufferMaxSessions> in_use{};
for (const auto& [existing_aruid, existing] : m_sessions) {
const u32 index = existing.presentation_slot_base / SharedBufferSlotsPerSession;
if (index < in_use.size())
in_use[index] = true;
}
u32 index = 0;
while (index < in_use.size() && in_use[index])
index++;
if (index >= in_use.size()) {
LOG_ERROR(Service_VI, "Out of shared buffer presentation slots ({} sessions)", SharedBufferMaxSessions);
R_THROW(VI::ResultOperationFailed);
}
slot_base = index * SharedBufferSlotsPerSession;
// Map into process. // Map into process.
Common::ProcessAddress map_address{}; Common::ProcessAddress map_address{};
R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group, R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
@@ -330,7 +308,6 @@ Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64*
// Create new session. // Create new session.
auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{}); auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{});
auto& session = it->second; auto& session = it->second;
session.presentation_slot_base = slot_base;
auto& container = m_nvdrv->GetContainer(); auto& container = m_nvdrv->GetContainer();
session.session_id = container.OpenSession(owner_process); session.session_id = container.OpenSession(owner_process);
+9
View File
@@ -70,6 +70,15 @@ std::optional<IndexedProgram> ResolveIndexedProgram(Core::System& system, u64 pr
return IndexedProgram{std::move(update), target_id, true}; return IndexedProgram{std::move(update), target_id, true};
} }
const auto application_update_id =
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(target_id));
if (application_update_id != update_id) {
if (auto update = provider.GetEntryRaw(application_update_id, FileSys::ContentRecordType::Program)) {
LOG_INFO(Loader, "Program index {} has no base program, loading it from application update {:016X}", program_index, application_update_id);
return IndexedProgram{std::move(update), target_id, true};
}
}
LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container", LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container",
target_id, program_index); target_id, program_index);
return std::nullopt; return std::nullopt;
+8 -2
View File
@@ -11,6 +11,7 @@
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
@@ -76,8 +77,13 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
const auto& installed = system.GetContentProvider(); const auto& installed = system.GetContentProvider();
const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), const auto program_update_id = FileSys::GetUpdateTitleID(nca->GetTitleId());
FileSys::ContentRecordType::Program); auto update_nca = installed.GetEntry(program_update_id, FileSys::ContentRecordType::Program);
if (update_nca == nullptr) {
update_nca = installed.GetEntry(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nca->GetTitleId())),
FileSys::ContentRecordType::Program);
}
if (update_nca) { if (update_nca) {
exefs = update_nca->GetExeFS(); exefs = update_nca->GetExeFS();
+7 -2
View File
@@ -186,8 +186,13 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
} }
const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()), const auto program_update_id = FileSys::GetUpdateTitleID(nsp->GetProgramTitleID());
FileSys::ContentRecordType::Program); auto read = nsp->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program);
if (read == nullptr) {
read = nsp->GetNCAFile(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nsp->GetProgramTitleID())),
FileSys::ContentRecordType::Program);
}
if (read == nullptr) { if (read == nullptr) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
+8 -2
View File
@@ -9,6 +9,7 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
@@ -137,8 +138,13 @@ ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
return ResultStatus::ErrorXCIMissingProgramNCA; return ResultStatus::ErrorXCIMissingProgramNCA;
} }
const auto read = xci->GetSecurePartitionNSP()->GetNCAFile( const auto program_update_id = FileSys::GetUpdateTitleID(program_id);
FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program); auto read = xci->GetSecurePartitionNSP()->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program);
if (read == nullptr) {
read = xci->GetSecurePartitionNSP()->GetNCAFile(
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id)),
FileSys::ContentRecordType::Program);
}
if (read == nullptr) { if (read == nullptr) {
return ResultStatus::ErrorNoPackedUpdate; return ResultStatus::ErrorNoPackedUpdate;
} }
+21 -11
View File
@@ -299,12 +299,17 @@ void Config::ReadDataStorageValues() {
void Config::ReadDebuggingValues() { void Config::ReadDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
// Intentionally not using the QT default setting as this is intended to be changed in the ini if (global) {
Settings::values.record_frame_times = // Intentionally not using the QT default setting as this is intended to be changed in the ini
ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false)); Settings::values.record_frame_times =
ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false));
ReadCategory(Settings::Category::Debugging); ReadCategory(Settings::Category::Debugging);
ReadCategory(Settings::Category::DebuggingGraphics); ReadCategory(Settings::Category::DebuggingGraphics);
} else {
ReadSettingGeneric(&Settings::values.program_args);
ReadSettingGeneric(&Settings::values.debug_knobs);
}
EndGroup(); EndGroup();
} }
@@ -415,12 +420,12 @@ void Config::ReadLibraryAppletValues() {
void Config::ReadValues() { void Config::ReadValues() {
if (global) { if (global) {
ReadDataStorageValues(); ReadDataStorageValues();
ReadDebuggingValues();
ReadDisabledAddOnValues(); ReadDisabledAddOnValues();
ReadServiceValues(); ReadServiceValues();
ReadWebServiceValues(); ReadWebServiceValues();
ReadMiscellaneousValues(); ReadMiscellaneousValues();
} }
ReadDebuggingValues();
ReadLibraryAppletValues(); ReadLibraryAppletValues();
ReadNetworkValues(); ReadNetworkValues();
ReadControlValues(); ReadControlValues();
@@ -511,13 +516,13 @@ void Config::SaveValues() {
if (global) { if (global) {
LOG_DEBUG(Config, "Saving global generic configuration values"); LOG_DEBUG(Config, "Saving global generic configuration values");
SaveDataStorageValues(); SaveDataStorageValues();
SaveDebuggingValues();
SaveDisabledAddOnValues(); SaveDisabledAddOnValues();
SaveWebServiceValues(); SaveWebServiceValues();
SaveMiscellaneousValues(); SaveMiscellaneousValues();
} else { } else {
LOG_DEBUG(Config, "Saving only generic configuration values"); LOG_DEBUG(Config, "Saving only generic configuration values");
} }
SaveDebuggingValues();
SaveLibraryAppletValues(); SaveLibraryAppletValues();
SaveNetworkValues(); SaveNetworkValues();
SaveControlValues(); SaveControlValues();
@@ -600,11 +605,16 @@ void Config::SaveDataStorageValues() {
void Config::SaveDebuggingValues() { void Config::SaveDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
// Intentionally not using the QT default setting as this is intended to be changed in the ini if (global) {
WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times); // Intentionally not using the QT default setting as this is intended to be changed in the ini
WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
WriteCategory(Settings::Category::Debugging); WriteCategory(Settings::Category::Debugging);
WriteCategory(Settings::Category::DebuggingGraphics); WriteCategory(Settings::Category::DebuggingGraphics);
} else {
WriteSettingGeneric(&Settings::values.program_args);
WriteSettingGeneric(&Settings::values.debug_knobs);
}
EndGroup(); EndGroup();
} }
+31 -11
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-FileCopyrightText: 2024 yuzu Emulator Project
@@ -56,13 +56,14 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro
*/ */
inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) { inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
size_t count{}; size_t count{};
const auto application_id = FileSys::GetBaseTitleID(program_id);
const auto& fs_controller = system.GetFileSystemController(); const auto& fs_controller = system.GetFileSystemController();
const auto dlc_entries = system.GetContentProvider().ListEntriesFilter( const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
std::vector<u64> program_dlc_entries; std::vector<u64> program_dlc_entries;
for (const auto& entry : dlc_entries) { for (const auto& entry : dlc_entries) {
if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { if (FileSys::GetBaseTitleID(entry.title_id) == application_id) {
program_dlc_entries.push_back(entry.title_id); program_dlc_entries.push_back(entry.title_id);
} }
} }
@@ -83,9 +84,17 @@ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
*/ */
inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller, inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
const u64 program_id) { const u64 program_id) {
const auto update_id = program_id | 0x800; const auto remove_update = [&fs_controller](u64 update_id) {
return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
};
const auto update_id = FileSys::GetUpdateTitleID(program_id);
const auto application_update_id =
FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id));
if (update_id != application_update_id && remove_update(update_id)) {
return true;
}
return remove_update(application_update_id);
} }
/** /**
@@ -111,15 +120,26 @@ inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& f
inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller, inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
const u64 program_id, const std::string& mod_name) { const u64 program_id, const std::string& mod_name) {
// Check general Mods (LayeredFS and IPS) // Check general Mods (LayeredFS and IPS)
const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id); const auto remove_from_root = [&mod_name](const auto& root) {
if (mod_dir != nullptr) { return root != nullptr && root->DeleteSubdirectoryRecursive(mod_name);
return mod_dir->DeleteSubdirectoryRecursive(mod_name); };
if (remove_from_root(fs_controller.GetModificationLoadRoot(program_id))) {
return true;
}
if (FileSys::GetBaseTitleID(program_id) != program_id &&
remove_from_root(
fs_controller.GetModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
return true;
} }
// Check SDMC mod directory (RomFS LayeredFS) // Check SDMC mod directory (RomFS LayeredFS)
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id); if (remove_from_root(fs_controller.GetSDMCModificationLoadRoot(program_id))) {
if (sdmc_mod_dir != nullptr) { return true;
return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name); }
if (FileSys::GetBaseTitleID(program_id) != program_id &&
remove_from_root(
fs_controller.GetSDMCModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
return true;
} }
return false; return false;
+2 -1
View File
@@ -7,6 +7,7 @@
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/fs_types.h" #include "common/fs/fs_types.h"
#include "common/logging.h" #include "common/logging.h"
#include "core/file_sys/common_funcs.h"
#include "frontend_common/data_manager.h" #include "frontend_common/data_manager.h"
#include "mod_manager.h" #include "mod_manager.h"
@@ -40,7 +41,7 @@ std::vector<std::filesystem::path> GetModFolder(const std::string& root) {
} }
ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) { ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) {
const auto program_id_string = fmt::format("{:016X}", program_id); const auto program_id_string = fmt::format("{:016X}", FileSys::GetBaseTitleID(program_id));
const auto mod_name = path.filename(); const auto mod_name = path.filename();
const auto mod_dir = const auto mod_dir =
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name; DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
+2 -2
View File
@@ -170,12 +170,12 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
auto& emulated = console.motion_values.emulated; auto& emulated = console.motion_values.emulated;
raw_status = TransformToMotion(callback); raw_status = TransformToMotion(callback);
emulated.SetAcceleration(Common::Vec3f{ emulated.SetAcceleration(Common::Vec<f32, 3>{
raw_status.accel.x.value, raw_status.accel.x.value,
raw_status.accel.y.value, raw_status.accel.y.value,
raw_status.accel.z.value, raw_status.accel.z.value,
}); });
emulated.SetGyroscope(Common::Vec3f{ emulated.SetGyroscope(Common::Vec<f32, 3>{
raw_status.gyro.x.value, raw_status.gyro.x.value,
raw_status.gyro.y.value, raw_status.gyro.y.value,
raw_status.gyro.z.value, raw_status.gyro.z.value,
+6 -7
View File
@@ -18,7 +18,6 @@
#include "common/input.h" #include "common/input.h"
#include "common/param_package.h" #include "common/param_package.h"
#include "common/point.h" #include "common/point.h"
#include "common/quaternion.h"
#include "common/vector_math.h" #include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h" #include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_types.h" #include "hid_core/hid_types.h"
@@ -43,12 +42,12 @@ using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;
// Contains all motion related data that is used on the services // Contains all motion related data that is used on the services
struct ConsoleMotion { struct ConsoleMotion {
Common::Vec3f accel{}; Common::Vec<f32, 3> accel{};
Common::Vec3f gyro{}; Common::Vec<f32, 3> gyro{};
Common::Vec3f rotation{}; Common::Vec<f32, 3> rotation{};
std::array<Common::Vec3f, 3> orientation{}; std::array<Common::Vec<f32, 3>, 3> orientation{};
Common::Quaternion<f32> quaternion{}; Common::Vec<f32, 4> quaternion{};
Common::Vec3f gyro_bias{}; Common::Vec<f32, 3> gyro_bias{};
f32 verticalization_error{}; f32 verticalization_error{};
bool is_at_rest{}; bool is_at_rest{};
}; };
@@ -1051,12 +1051,12 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
auto& emulated = controller.motion_values[index].emulated; auto& emulated = controller.motion_values[index].emulated;
raw_status = TransformToMotion(callback); raw_status = TransformToMotion(callback);
emulated.SetAcceleration(Common::Vec3f{ emulated.SetAcceleration(Common::Vec<f32, 3>{
raw_status.accel.x.value, raw_status.accel.x.value,
raw_status.accel.y.value, raw_status.accel.y.value,
raw_status.accel.z.value, raw_status.accel.z.value,
}); });
emulated.SetGyroscope(Common::Vec3f{ emulated.SetGyroscope(Common::Vec<f32, 3>{
raw_status.gyro.x.value, raw_status.gyro.x.value,
raw_status.gyro.y.value, raw_status.gyro.y.value,
raw_status.gyro.z.value, raw_status.gyro.z.value,
+5 -5
View File
@@ -107,11 +107,11 @@ struct RingSensorForce {
using NfcState = Common::Input::NfcStatus; using NfcState = Common::Input::NfcStatus;
struct ControllerMotion { struct ControllerMotion {
Common::Vec3f accel{}; Common::Vec<f32, 3> accel{};
Common::Vec3f gyro{}; Common::Vec<f32, 3> gyro{};
Common::Vec3f rotation{}; Common::Vec<f32, 3> rotation{};
Common::Vec3f euler{}; Common::Vec<f32, 3> euler{};
std::array<Common::Vec3f, 3> orientation{}; std::array<Common::Vec<f32, 3>, 3> orientation{};
bool is_at_rest{}; bool is_at_rest{};
}; };
+90 -90
View File
@@ -26,20 +26,19 @@ void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
kd = new_kd; kd = new_kd;
} }
void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { void MotionInput::SetAcceleration(const Common::Vec<f32, 3>& acceleration) {
accel = acceleration; accel = acceleration;
accel[0] = std::clamp(accel[0], -AccelMaxValue, AccelMaxValue);
accel.x = std::clamp(accel.x, -AccelMaxValue, AccelMaxValue); accel[1] = std::clamp(accel[1], -AccelMaxValue, AccelMaxValue);
accel.y = std::clamp(accel.y, -AccelMaxValue, AccelMaxValue); accel[2] = std::clamp(accel[2], -AccelMaxValue, AccelMaxValue);
accel.z = std::clamp(accel.z, -AccelMaxValue, AccelMaxValue);
} }
void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { void MotionInput::SetGyroscope(const Common::Vec<f32, 3>& gyroscope) {
gyro = gyroscope - gyro_bias; gyro = gyroscope - gyro_bias;
gyro.x = std::clamp(gyro.x, -GyroMaxValue, GyroMaxValue); gyro[0] = std::clamp(gyro[0], -GyroMaxValue, GyroMaxValue);
gyro.y = std::clamp(gyro.y, -GyroMaxValue, GyroMaxValue); gyro[1] = std::clamp(gyro[1], -GyroMaxValue, GyroMaxValue);
gyro.z = std::clamp(gyro.z, -GyroMaxValue, GyroMaxValue); gyro[2] = std::clamp(gyro[2], -GyroMaxValue, GyroMaxValue);
// Auto adjust gyro_bias to minimize drift // Auto adjust gyro_bias to minimize drift
if (!IsMoving(IsAtRestRelaxed)) { if (!IsMoving(IsAtRestRelaxed)) {
@@ -59,25 +58,25 @@ void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
} }
} }
void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { void MotionInput::SetQuaternion(const Common::Vec<f32, 4>& quaternion) {
quat = quaternion; quat = quaternion;
} }
void MotionInput::SetEulerAngles(const Common::Vec3f& euler_angles) { void MotionInput::SetEulerAngles(const Common::Vec<f32, 3>& euler_angles) {
const float cr = std::cos(euler_angles.x * 0.5f); const float cr = std::cos(euler_angles[0] * 0.5f);
const float sr = std::sin(euler_angles.x * 0.5f); const float sr = std::sin(euler_angles[0] * 0.5f);
const float cp = std::cos(euler_angles.y * 0.5f); const float cp = std::cos(euler_angles[1] * 0.5f);
const float sp = std::sin(euler_angles.y * 0.5f); const float sp = std::sin(euler_angles[1] * 0.5f);
const float cy = std::cos(euler_angles.z * 0.5f); const float cy = std::cos(euler_angles[2] * 0.5f);
const float sy = std::sin(euler_angles.z * 0.5f); const float sy = std::sin(euler_angles[2] * 0.5f);
quat.w = cr * cp * cy + sr * sp * sy; quat[3] = cr * cp * cy + sr * sp * sy;
quat.xyz.x = sr * cp * cy - cr * sp * sy; quat[0] = sr * cp * cy - cr * sp * sy;
quat.xyz.y = cr * sp * cy + sr * cp * sy; quat[1] = cr * sp * cy + sr * cp * sy;
quat.xyz.z = cr * cp * sy - sr * sp * cy; quat[2] = cr * cp * sy - sr * sp * cy;
} }
void MotionInput::SetGyroBias(const Common::Vec3f& bias) { void MotionInput::SetGyroBias(const Common::Vec<f32, 3>& bias) {
gyro_bias = bias; gyro_bias = bias;
} }
@@ -98,7 +97,7 @@ void MotionInput::ResetRotations() {
} }
void MotionInput::ResetQuaternion() { void MotionInput::ResetQuaternion() {
quat = {{0.0f, 0.0f, -1.0f}, 0.0f}; quat = Common::Vec<f32, 4>{0.0f, 0.0f, -1.0f, 0.0f};
} }
bool MotionInput::IsMoving(f32 sensitivity) const { bool MotionInput::IsMoving(f32 sensitivity) const {
@@ -137,10 +136,10 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) {
ResetOrientation(); ResetOrientation();
} }
// Short name local variable for readability // Short name local variable for readability
f32 q1 = quat.w; f32 q1 = quat[3];
f32 q2 = quat.xyz[0]; f32 q2 = quat[0];
f32 q3 = quat.xyz[1]; f32 q3 = quat[1];
f32 q4 = quat.xyz[2]; f32 q4 = quat[2];
const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f; const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
// Ignore invalid elapsed time // Ignore invalid elapsed time
@@ -150,23 +149,23 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) {
const auto normal_accel = accel.Normalized(); const auto normal_accel = accel.Normalized();
auto rad_gyro = gyro * std::numbers::pi_v<float> * 2.f; auto rad_gyro = gyro * std::numbers::pi_v<float> * 2.f;
const f32 swap = rad_gyro.x; const f32 swap = rad_gyro[0];
rad_gyro.x = rad_gyro.y; rad_gyro[0] = rad_gyro[1];
rad_gyro.y = -swap; rad_gyro[1] = -swap;
rad_gyro.z = -rad_gyro.z; rad_gyro[2] = -rad_gyro[2];
// Clear gyro values if there is no gyro present // Clear gyro values if there is no gyro present
if (only_accelerometer) { if (only_accelerometer) {
rad_gyro.x = 0; rad_gyro[0] = 0;
rad_gyro.y = 0; rad_gyro[1] = 0;
rad_gyro.z = 0; rad_gyro[2] = 0;
} }
// Ignore drift correction if acceleration is not reliable // Ignore drift correction if acceleration is not reliable
if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) { if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
const f32 ax = -normal_accel.x; const f32 ax = -normal_accel[0];
const f32 ay = normal_accel.y; const f32 ay = normal_accel[1];
const f32 az = -normal_accel.z; const f32 az = -normal_accel[2];
// Estimated direction of gravity // Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3); const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
@@ -174,7 +173,7 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) {
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity // Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = { const Common::Vec<f32, 3> new_real_error{
az * vx - ax * vz, az * vx - ax * vz,
ay * vz - az * vy, ay * vz - az * vy,
ax * vy - ay * vx, ax * vy - ay * vx,
@@ -202,16 +201,16 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) {
rad_gyro += 10.0f * kd * derivative_error; rad_gyro += 10.0f * kd * derivative_error;
// Emulate gyro values for games that need them // Emulate gyro values for games that need them
gyro.x = -rad_gyro.y; gyro[0] = -rad_gyro[1];
gyro.y = rad_gyro.x; gyro[1] = rad_gyro[0];
gyro.z = -rad_gyro.z; gyro[2] = -rad_gyro[2];
UpdateRotation(elapsed_time); UpdateRotation(elapsed_time);
} }
} }
const f32 gx = rad_gyro.y; const f32 gx = rad_gyro[1];
const f32 gy = rad_gyro.x; const f32 gy = rad_gyro[0];
const f32 gz = rad_gyro.z; const f32 gz = rad_gyro[2];
// Integrate rate of change of quaternion // Integrate rate of change of quaternion
const f32 pa = q2; const f32 pa = q2;
@@ -222,57 +221,58 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) {
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1; quat[3] = q1;
quat.xyz[0] = q2; quat[0] = q2;
quat.xyz[1] = q3; quat[1] = q3;
quat.xyz[2] = q4; quat[2] = q4;
quat = quat.Normalized(); quat = quat.Normalized();
} }
std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const { std::array<Common::Vec<f32, 3>, 3> MotionInput::GetOrientation() const {
const Common::Quaternion<float> quad{ const Common::Vec<f32, 4> quad{
.xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w}, -quat[1],
.w = -quat.xyz[2], -quat[0],
-quat[3],
-quat[2],
}; };
const std::array<float, 16> matrix4x4 = quad.ToMatrix(); const std::array<f32, 16> matrix4x4 = quad.ToMatrix();
return {Common::Vec<f32, 3>(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), Common::Vec<f32, 3>(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), Common::Vec<f32, 3>(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
} }
Common::Vec3f MotionInput::GetAcceleration() const { Common::Vec<f32, 3> MotionInput::GetAcceleration() const {
return accel; return accel;
} }
Common::Vec3f MotionInput::GetGyroscope() const { Common::Vec<f32, 3> MotionInput::GetGyroscope() const {
return gyro; return gyro;
} }
Common::Vec3f MotionInput::GetGyroBias() const { Common::Vec<f32, 3> MotionInput::GetGyroBias() const {
return gyro_bias; return gyro_bias;
} }
Common::Quaternion<f32> MotionInput::GetQuaternion() const { Common::Vec<f32, 4> MotionInput::GetQuaternion() const {
return quat; return quat;
} }
Common::Vec3f MotionInput::GetRotations() const { Common::Vec<f32, 3> MotionInput::GetRotations() const {
return rotations; return rotations;
} }
Common::Vec3f MotionInput::GetEulerAngles() const { Common::Vec<f32, 3> MotionInput::GetEulerAngles() const {
// roll (x-axis rotation) // roll (x-axis rotation)
const float sinr_cosp = 2 * (quat.w * quat.xyz.x + quat.xyz.y * quat.xyz.z); const float sinr_cosp = 2 * (quat[3] * quat[0] + quat[1] * quat[2]);
const float cosr_cosp = 1 - 2 * (quat.xyz.x * quat.xyz.x + quat.xyz.y * quat.xyz.y); const float cosr_cosp = 1 - 2 * (quat[0] * quat[0] + quat[1] * quat[1]);
// pitch (y-axis rotation) // pitch (y-axis rotation)
const float sinp = std::sqrt(1 + 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); const float sinp = std::sqrt(1 + 2 * (quat[3] * quat[1] - quat[0] * quat[2]));
const float cosp = std::sqrt(1 - 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); const float cosp = std::sqrt(1 - 2 * (quat[3] * quat[1] - quat[0] * quat[2]));
// yaw (z-axis rotation) // yaw (z-axis rotation)
const float siny_cosp = 2 * (quat.w * quat.xyz.z + quat.xyz.x * quat.xyz.y); const float siny_cosp = 2 * (quat[3] * quat[2] + quat[0] * quat[1]);
const float cosy_cosp = 1 - 2 * (quat.xyz.y * quat.xyz.y + quat.xyz.z * quat.xyz.z); const float cosy_cosp = 1 - 2 * (quat[1] * quat[1] + quat[2] * quat[2]);
return { return {
std::atan2(sinr_cosp, cosr_cosp), std::atan2(sinr_cosp, cosr_cosp),
@@ -285,13 +285,13 @@ void MotionInput::ResetOrientation() {
if (!reset_enabled || only_accelerometer) { if (!reset_enabled || only_accelerometer) {
return; return;
} }
if (!IsMoving(IsAtRestRelaxed) && accel.z <= -0.9f) { if (!IsMoving(IsAtRestRelaxed) && accel[2] <= -0.9f) {
++reset_counter; ++reset_counter;
if (reset_counter > 900) { if (reset_counter > 900) {
quat.w = 0; quat[3] = 0;
quat.xyz[0] = 0; quat[0] = 0;
quat.xyz[1] = 0; quat[1] = 0;
quat.xyz[2] = -1; quat[2] = -1;
SetOrientationFromAccelerometer(); SetOrientationFromAccelerometer();
integral_error = {}; integral_error = {};
reset_counter = 0; reset_counter = 0;
@@ -309,15 +309,15 @@ void MotionInput::SetOrientationFromAccelerometer() {
while (!IsCalibrated(0.01f) && ++iterations < 100) { while (!IsCalibrated(0.01f) && ++iterations < 100) {
// Short name local variable for readability // Short name local variable for readability
f32 q1 = quat.w; f32 q1 = quat[3];
f32 q2 = quat.xyz[0]; f32 q2 = quat[0];
f32 q3 = quat.xyz[1]; f32 q3 = quat[1];
f32 q4 = quat.xyz[2]; f32 q4 = quat[2];
Common::Vec3f rad_gyro; Common::Vec<f32, 3> rad_gyro;
const f32 ax = -normal_accel.x; const f32 ax = -normal_accel[0];
const f32 ay = normal_accel.y; const f32 ay = normal_accel[1];
const f32 az = -normal_accel.z; const f32 az = -normal_accel[2];
// Estimated direction of gravity // Estimated direction of gravity
const f32 vx = 2.0f * (q2 * q4 - q1 * q3); const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
@@ -325,7 +325,7 @@ void MotionInput::SetOrientationFromAccelerometer() {
const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
// Error is cross product between estimated direction and measured direction of gravity // Error is cross product between estimated direction and measured direction of gravity
const Common::Vec3f new_real_error = { const Common::Vec<f32, 3> new_real_error = {
az * vx - ax * vz, az * vx - ax * vz,
ay * vz - az * vy, ay * vz - az * vy,
ax * vy - ay * vx, ax * vy - ay * vx,
@@ -338,9 +338,9 @@ void MotionInput::SetOrientationFromAccelerometer() {
rad_gyro += 5.0f * ki * integral_error; rad_gyro += 5.0f * ki * integral_error;
rad_gyro += 10.0f * kd * derivative_error; rad_gyro += 10.0f * kd * derivative_error;
const f32 gx = rad_gyro.y; const f32 gx = rad_gyro[1];
const f32 gy = rad_gyro.x; const f32 gy = rad_gyro[0];
const f32 gz = rad_gyro.z; const f32 gz = rad_gyro[2];
// Integrate rate of change of quaternion // Integrate rate of change of quaternion
const f32 pa = q2; const f32 pa = q2;
@@ -351,10 +351,10 @@ void MotionInput::SetOrientationFromAccelerometer() {
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
quat.w = q1; quat[3] = q1;
quat.xyz[0] = q2; quat[0] = q2;
quat.xyz[1] = q3; quat[1] = q3;
quat.xyz[2] = q4; quat[2] = q4;
quat = quat.Normalized(); quat = quat.Normalized();
} }
} }
+23 -21
View File
@@ -1,10 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#pragma once #pragma once
#include "common/common_types.h" #include "common/common_types.h"
#include "common/quaternion.h"
#include "common/vector_math.h" #include "common/vector_math.h"
namespace Core::HID { namespace Core::HID {
@@ -34,11 +36,11 @@ public:
MotionInput& operator=(MotionInput&&) = default; MotionInput& operator=(MotionInput&&) = default;
void SetPID(f32 new_kp, f32 new_ki, f32 new_kd); void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
void SetAcceleration(const Common::Vec3f& acceleration); void SetAcceleration(const Common::Vec<f32, 3>& acceleration);
void SetGyroscope(const Common::Vec3f& gyroscope); void SetGyroscope(const Common::Vec<f32, 3>& gyroscope);
void SetQuaternion(const Common::Quaternion<f32>& quaternion); void SetQuaternion(const Common::Vec<f32, 4>& quaternion);
void SetEulerAngles(const Common::Vec3f& euler_angles); void SetEulerAngles(const Common::Vec<f32, 3>& euler_angles);
void SetGyroBias(const Common::Vec3f& bias); void SetGyroBias(const Common::Vec<f32, 3>& bias);
void SetGyroThreshold(f32 threshold); void SetGyroThreshold(f32 threshold);
/// Applies a modifier on top of the normal gyro threshold /// Applies a modifier on top of the normal gyro threshold
@@ -53,13 +55,13 @@ public:
void Calibrate(); void Calibrate();
[[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const; [[nodiscard]] std::array<Common::Vec<f32, 3>, 3> GetOrientation() const;
[[nodiscard]] Common::Vec3f GetAcceleration() const; [[nodiscard]] Common::Vec<f32, 3> GetAcceleration() const;
[[nodiscard]] Common::Vec3f GetGyroscope() const; [[nodiscard]] Common::Vec<f32, 3> GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetGyroBias() const; [[nodiscard]] Common::Vec<f32, 3> GetGyroBias() const;
[[nodiscard]] Common::Vec3f GetRotations() const; [[nodiscard]] Common::Vec<f32, 3> GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; [[nodiscard]] Common::Vec<f32, 4> GetQuaternion() const;
[[nodiscard]] Common::Vec3f GetEulerAngles() const; [[nodiscard]] Common::Vec<f32, 3> GetEulerAngles() const;
[[nodiscard]] bool IsMoving(f32 sensitivity) const; [[nodiscard]] bool IsMoving(f32 sensitivity) const;
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const; [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
@@ -75,24 +77,24 @@ private:
f32 kd; f32 kd;
// PID errors // PID errors
Common::Vec3f real_error; Common::Vec<f32, 3> real_error;
Common::Vec3f integral_error; Common::Vec<f32, 3> integral_error;
Common::Vec3f derivative_error; Common::Vec<f32, 3> derivative_error;
// Quaternion containing the device orientation // Quaternion containing the device orientation
Common::Quaternion<f32> quat; Common::Vec<f32, 4> quat;
// Number of full rotations in each axis // Number of full rotations in each axis
Common::Vec3f rotations; Common::Vec<f32, 3> rotations;
// Acceleration vector measurement in G force // Acceleration vector measurement in G force
Common::Vec3f accel; Common::Vec<f32, 3> accel;
// Gyroscope vector measurement in radians/s. // Gyroscope vector measurement in radians/s.
Common::Vec3f gyro; Common::Vec<f32, 3> gyro;
// Vector to be subtracted from gyro measurements // Vector to be subtracted from gyro measurements
Common::Vec3f gyro_bias; Common::Vec<f32, 3> gyro_bias;
// Minimum gyro amplitude to detect if the device is moving // Minimum gyro amplitude to detect if the device is moving
f32 gyro_threshold = 0.0f; f32 gyro_threshold = 0.0f;
+4 -4
View File
@@ -609,10 +609,10 @@ static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an
struct SixAxisSensorState { struct SixAxisSensorState {
s64 delta_time{}; s64 delta_time{};
s64 sampling_number{}; s64 sampling_number{};
Common::Vec3f accel{}; Common::Vec<f32, 3> accel{};
Common::Vec3f gyro{}; Common::Vec<f32, 3> gyro{};
Common::Vec3f rotation{}; Common::Vec<f32, 3> rotation{};
std::array<Common::Vec3f, 3> orientation{}; std::array<Common::Vec<f32, 3>, 3> orientation{};
SixAxisSensorAttribute attribute{}; SixAxisSensorAttribute attribute{};
INSERT_PADDING_BYTES(4); // Reserved INSERT_PADDING_BYTES(4); // Reserved
}; };
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -196,7 +196,7 @@ struct ConsoleSixAxisSensorSharedMemoryFormat {
bool is_seven_six_axis_sensor_at_rest{}; bool is_seven_six_axis_sensor_at_rest{};
INSERT_PADDING_BYTES(3); // padding INSERT_PADDING_BYTES(3); // padding
f32 verticalization_error{}; f32 verticalization_error{};
Common::Vec3f gyro_bias{}; Common::Vec<f32, 3> gyro_bias{};
INSERT_PADDING_BYTES(4); // padding INSERT_PADDING_BYTES(4); // padding
}; };
static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20,
@@ -46,14 +46,11 @@ void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
next_seven_sixaxis_state.accel = motion_status.accel; next_seven_sixaxis_state.accel = motion_status.accel;
next_seven_sixaxis_state.gyro = motion_status.gyro; next_seven_sixaxis_state.gyro = motion_status.gyro;
next_seven_sixaxis_state.quaternion = { next_seven_sixaxis_state.quaternion = {
{ motion_status.quaternion[1],
motion_status.quaternion.xyz.y, motion_status.quaternion[0],
motion_status.quaternion.xyz.x, -motion_status.quaternion[3],
-motion_status.quaternion.w, -motion_status.quaternion[2],
},
-motion_status.quaternion.xyz.z,
}; };
seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state); seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state);
transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo, transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo,
sizeof(seven_sixaxis_lifo)); sizeof(seven_sixaxis_lifo));
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include "common/common_types.h" #include "common/common_types.h"
#include "common/quaternion.h" #include "common/vector_math.h"
#include "common/typed_address.h" #include "common/typed_address.h"
#include "hid_core/resources/controller_base.h" #include "hid_core/resources/controller_base.h"
#include "hid_core/resources/ring_lifo.h" #include "hid_core/resources/ring_lifo.h"
@@ -51,9 +51,9 @@ private:
u64 timestamp{}; u64 timestamp{};
u64 sampling_number{}; u64 sampling_number{};
u64 unknown{}; u64 unknown{};
Common::Vec3f accel{}; Common::Vec<f32, 3> accel{};
Common::Vec3f gyro{}; Common::Vec<f32, 3> gyro{};
Common::Quaternion<f32> quaternion{}; Common::Vec<f32, 4> quaternion{};
}; };
static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size"); static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size");
+6 -3
View File
@@ -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-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
@@ -93,9 +96,9 @@ void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
.accel = {0, 0, -1.0f}, .accel = {0, 0, -1.0f},
.orientation = .orientation =
{ {
Common::Vec3f{1.0f, 0, 0}, Common::Vec<f32, 3>{1.0f, 0, 0},
Common::Vec3f{0, 1.0f, 0}, Common::Vec<f32, 3>{0, 1.0f, 0},
Common::Vec3f{0, 0, 1.0f}, Common::Vec<f32, 3>{0, 0, 1.0f},
}, },
.attribute = {1}, .attribute = {1},
}; };
+36 -43
View File
@@ -88,8 +88,8 @@ void Mouse::UpdateStickInput() {
last_mouse_change *= maximum_stick_range; last_mouse_change *= maximum_stick_range;
} }
SetAxis(identifier, mouse_axis_x, last_mouse_change.x); SetAxis(identifier, mouse_axis_x, last_mouse_change[0]);
SetAxis(identifier, mouse_axis_y, -last_mouse_change.y); SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]);
// Decay input over time // Decay input over time
const float clamped_length = (std::min)(1.0f, length); const float clamped_length = (std::min)(1.0f, length);
@@ -104,20 +104,20 @@ void Mouse::UpdateMotionInput() {
const float sensitivity = const float sensitivity =
IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity; IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity;
const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x + const float rotation_velocity = std::sqrt(last_motion_change[0] * last_motion_change[0] +
last_motion_change.y * last_motion_change.y); last_motion_change[1] * last_motion_change[1]);
// Clamp rotation speed // Clamp rotation speed
if (rotation_velocity > maximum_rotation_speed / sensitivity) { if (rotation_velocity > maximum_rotation_speed / sensitivity) {
const float multiplier = maximum_rotation_speed / rotation_velocity / sensitivity; const float multiplier = maximum_rotation_speed / rotation_velocity / sensitivity;
last_motion_change.x = last_motion_change.x * multiplier; last_motion_change[0] = last_motion_change[0] * multiplier;
last_motion_change.y = last_motion_change.y * multiplier; last_motion_change[1] = last_motion_change[1] * multiplier;
} }
const BasicMotion motion_data{ const BasicMotion motion_data{
.gyro_x = last_motion_change.x * sensitivity, .gyro_x = last_motion_change[0] * sensitivity,
.gyro_y = last_motion_change.y * sensitivity, .gyro_y = last_motion_change[1] * sensitivity,
.gyro_z = last_motion_change.z * sensitivity, .gyro_z = last_motion_change[2] * sensitivity,
.accel_x = 0, .accel_x = 0,
.accel_y = 0, .accel_y = 0,
.accel_z = 0, .accel_z = 0,
@@ -125,53 +125,46 @@ void Mouse::UpdateMotionInput() {
}; };
if (IsMousePanningEnabled()) { if (IsMousePanningEnabled()) {
last_motion_change.x = 0; last_motion_change[0] = 0;
last_motion_change.y = 0; last_motion_change[1] = 0;
} }
last_motion_change.z = 0; last_motion_change[2] = 0;
SetMotion(motion_identifier, 0, motion_data); SetMotion(motion_identifier, 0, motion_data);
} }
void Mouse::Move(int x, int y, int center_x, int center_y) { void Mouse::Move(int x, int y, int center_x, int center_y) {
if (IsMousePanningEnabled()) { if (IsMousePanningEnabled()) {
const auto mouse_change = auto const mouse_change_int = Common::Vec<int, 2>(x, y) - Common::Vec<int, 2>(center_x, center_y);
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); auto const mouse_change = Common::Vec<float, 2>(float(mouse_change_int[0]), float(mouse_change_int[1]));
const float x_sensitivity = auto const x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue() * default_panning_sensitivity;
Settings::values.mouse_panning_x_sensitivity.GetValue() * default_panning_sensitivity; auto const y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue() * default_panning_sensitivity;
const float y_sensitivity = auto const deadzone_cw = Settings::values.mouse_panning_deadzone_counterweight.GetValue() * default_deadzone_counterweight;
Settings::values.mouse_panning_y_sensitivity.GetValue() * default_panning_sensitivity; last_motion_change += {-mouse_change[1] * x_sensitivity, -mouse_change[0] * y_sensitivity, 0};
const float deadzone_counterweight = last_mouse_change[0] += mouse_change[0] * x_sensitivity;
Settings::values.mouse_panning_deadzone_counterweight.GetValue() * last_mouse_change[1] += mouse_change[1] * y_sensitivity;
default_deadzone_counterweight; // Bind the mouse change to [0 <= deadzone_cw <= 1.0]
last_motion_change += {-mouse_change.y * x_sensitivity, -mouse_change.x * y_sensitivity, 0};
last_mouse_change.x += mouse_change.x * x_sensitivity;
last_mouse_change.y += mouse_change.y * y_sensitivity;
// Bind the mouse change to [0 <= deadzone_counterweight <= 1.0]
const float length = last_mouse_change.Length(); const float length = last_mouse_change.Length();
if (length < deadzone_counterweight && length != 0.0f) { if (length < deadzone_cw && length != 0.0f) {
last_mouse_change /= length; last_mouse_change /= length;
last_mouse_change *= deadzone_counterweight; last_mouse_change *= deadzone_cw;
} }
return; return;
} }
if (button_pressed) { if (button_pressed) {
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin; const auto mouse_move = Common::Vec<int, 2>(x, y) - mouse_origin;
const float x_sensitivity = const float x_sensitivity =
Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
const float y_sensitivity = const float y_sensitivity =
Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * x_sensitivity); SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity);
SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * y_sensitivity); SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity);
last_motion_change = { last_motion_change = {
static_cast<float>(-mouse_move.y) * x_sensitivity, float(-mouse_move[1]) * x_sensitivity,
static_cast<float>(-mouse_move.x) * y_sensitivity, float(-mouse_move[0]) * y_sensitivity,
last_motion_change.z, last_motion_change[2],
}; };
} }
} }
@@ -220,18 +213,18 @@ void Mouse::ReleaseButton(MouseButton button) {
SetAxis(identifier, mouse_axis_y, 0); SetAxis(identifier, mouse_axis_y, 0);
} }
last_motion_change.x = 0; last_motion_change[0] = 0;
last_motion_change.y = 0; last_motion_change[1] = 0;
button_pressed = false; button_pressed = false;
} }
void Mouse::MouseWheelChange(int x, int y) { void Mouse::MouseWheelChange(int x, int y) {
wheel_position.x += x; wheel_position[0] += x;
wheel_position.y += y; wheel_position[1] += y;
last_motion_change.z += static_cast<f32>(y); last_motion_change[2] += static_cast<f32>(y);
SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x)); SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position[0]));
SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y)); SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position[1]));
} }
void Mouse::ReleaseAllButtons() { void Mouse::ReleaseAllButtons() {
+5 -5
View File
@@ -107,11 +107,11 @@ private:
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
Common::Vec2<int> mouse_origin; Common::Vec<int, 2> mouse_origin;
Common::Vec2<int> last_mouse_position; Common::Vec<int, 2> last_mouse_position;
Common::Vec2<float> last_mouse_change; Common::Vec<float, 2> last_mouse_change;
Common::Vec3<float> last_motion_change; Common::Vec<float, 3> last_motion_change;
Common::Vec2<int> wheel_position; Common::Vec<int, 2> wheel_position;
bool button_pressed = false; bool button_pressed = false;
}; };
+2
View File
@@ -5,6 +5,7 @@
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/savedata_factory.h" #include "core/file_sys/savedata_factory.h"
#include "core/hle/service/am/am_types.h" #include "core/hle/service/am/am_types.h"
#include "frontend_common/content_manager.h" #include "frontend_common/content_manager.h"
@@ -305,6 +306,7 @@ void RemoveAllTransferableShaderCaches(u64 program_id) {
} }
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) { void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) {
program_id = FileSys::GetBaseTitleID(program_id);
const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
const auto config_file_name = const auto config_file_name =
program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
+3
View File
@@ -20,6 +20,7 @@ add_library(video_core STATIC
buffer_cache/buffer_cache.h buffer_cache/buffer_cache.h
buffer_cache/memory_tracker_base.h buffer_cache/memory_tracker_base.h
buffer_cache/usage_tracker.h buffer_cache/usage_tracker.h
buffer_cache/virtual_range_cache.h
buffer_cache/word_manager.h buffer_cache/word_manager.h
cache_types.h cache_types.h
capture.h capture.h
@@ -166,6 +167,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_fence_manager.h renderer_vulkan/vk_fence_manager.h
renderer_vulkan/vk_graphics_pipeline.cpp renderer_vulkan/vk_graphics_pipeline.cpp
renderer_vulkan/vk_graphics_pipeline.h renderer_vulkan/vk_graphics_pipeline.h
renderer_vulkan/vk_multi_range_buffer.cpp
renderer_vulkan/vk_multi_range_buffer.h
renderer_vulkan/vk_master_semaphore.cpp renderer_vulkan/vk_master_semaphore.cpp
renderer_vulkan/vk_master_semaphore.h renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_pipeline_cache.cpp renderer_vulkan/vk_pipeline_cache.cpp
+134 -26
View File
@@ -112,6 +112,13 @@ void BufferCache<P>::TickFrame() {
async_buffers_death_ring.clear(); async_buffers_death_ring.clear();
} }
template <class P>
void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
virtual_ranges.Unmap(as_id, gpu_addr, size);
}
}
template <class P> template <class P>
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) { void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) { if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
@@ -208,8 +215,8 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
BufferId buffer_b; BufferId buffer_b;
do { do {
channel_state->has_deleted_buffers = false; channel_state->has_deleted_buffers = false;
buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount)); buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount), false);
buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount)); buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount), false);
} while (channel_state->has_deleted_buffers); } while (channel_state->has_deleted_buffers);
auto& src_buffer = slot_buffers[buffer_a]; auto& src_buffer = slot_buffers[buffer_a];
auto& dest_buffer = slot_buffers[buffer_b]; auto& dest_buffer = slot_buffers[buffer_b];
@@ -265,7 +272,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
ClearDownload(*cpu_dst_address, size); ClearDownload(*cpu_dst_address, size);
gpu_modified_ranges.Subtract(*cpu_dst_address, size); gpu_modified_ranges.Subtract(*cpu_dst_address, size);
const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size)); const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size), false);
Buffer& dest_buffer = slot_buffers[buffer]; Buffer& dest_buffer = slot_buffers[buffer];
const u32 offset = dest_buffer.Offset(*cpu_dst_address); const u32 offset = dest_buffer.Offset(*cpu_dst_address);
runtime.ClearBuffer(dest_buffer, offset, size, value); runtime.ClearBuffer(dest_buffer, offset, size, value);
@@ -287,7 +294,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
template <class P> template <class P>
std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer( std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
DAddr device_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) { DAddr device_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
const BufferId buffer_id = FindBuffer(device_addr, size); const BufferId buffer_id = FindBuffer(device_addr, size, false);
Buffer& buffer = slot_buffers[buffer_id]; Buffer& buffer = slot_buffers[buffer_id];
// synchronize op // synchronize op
@@ -998,11 +1005,85 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index);
} }
template <class P>
void BufferCache<P>::ResolveMultiRangeStorage(Binding& binding, bool is_written,
std::vector<MultiRangeSegment>& pool) {
binding.segment_first = 0;
binding.segment_count = 0;
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
if (binding.gpu_addr == 0 || binding.size == 0) {
return;
}
if (is_written && !runtime.PrefersSparseSources()) {
return;
}
const VirtualSegments* found =
virtual_ranges.Query(*gpu_memory, binding.gpu_addr, binding.size);
if (!found || found->size() < 2) {
return;
}
const VirtualSegments segments = *found;
const u32 first = static_cast<u32>(pool.size());
const bool prefer_sparse = runtime.PrefersSparseSources();
for (const VirtualSegment& segment : segments) {
const BufferId buffer_id =
FindBuffer(segment.device_addr, segment.size, prefer_sparse);
if (!buffer_id) {
pool.resize(first);
return;
}
pool.push_back(MultiRangeSegment{
.buffer_id = buffer_id,
.device_addr = segment.device_addr,
.size = segment.size,
});
}
binding.segment_first = first;
binding.segment_count = static_cast<u32>(segments.size());
}
}
template <class P>
bool BufferCache<P>::BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
if (binding.segment_count < 2) {
return false;
}
if (binding.segment_first + binding.segment_count > pool.size()) {
return false;
}
const u64 key = (static_cast<u64>(gpu_memory->GetID()) << 48) ^ binding.gpu_addr;
runtime.ResetMultiRange();
for (u32 index = 0; index < binding.segment_count; ++index) {
const MultiRangeSegment& segment = pool[binding.segment_first + index];
Buffer& buffer = slot_buffers[segment.buffer_id];
TouchBuffer(buffer, segment.buffer_id);
if (SynchronizeBuffer(buffer, segment.device_addr, segment.size)) {
runtime.InvalidateMultiRange(key);
}
const u32 offset = buffer.Offset(segment.device_addr);
buffer.MarkUsage(offset, segment.size);
if (is_written) {
MarkWrittenBuffer(segment.buffer_id, segment.device_addr, segment.size);
}
runtime.PushMultiRangeSource(buffer, offset, segment.size);
}
return runtime.BindMultiRangeStorageBuffer(key, is_written);
} else {
return false;
}
}
template <class P> template <class P>
void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) { void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
u32 binding_index = 0; u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
const Binding& binding = channel_state->storage_buffers[stage][index]; const Binding& binding = channel_state->storage_buffers[stage][index];
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (BindMultiRangeStorage(binding, is_written, graphics_segments)) {
return;
}
Buffer& buffer = slot_buffers[binding.buffer_id]; Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id); TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size; const u32 size = binding.size;
@@ -1010,7 +1091,6 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.device_addr); const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size); buffer.MarkUsage(offset, size);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) { if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size); MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1139,6 +1219,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
u32 binding_index = 0; u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) { ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
const Binding& binding = channel_state->compute_storage_buffers[index]; const Binding& binding = channel_state->compute_storage_buffers[index];
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (BindMultiRangeStorage(binding, is_written, compute_segments)) {
return;
}
Buffer& buffer = slot_buffers[binding.buffer_id]; Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id); TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size; const u32 size = binding.size;
@@ -1146,8 +1231,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
const u32 offset = buffer.Offset(binding.device_addr); const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size); buffer.MarkUsage(offset, size);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (is_written) { if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size); MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1193,6 +1276,7 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P> template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) { void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
graphics_segments.clear();
BufferOperations([&]() { BufferOperations([&]() {
if (is_indexed) { if (is_indexed) {
UpdateIndexBuffer(); UpdateIndexBuffer();
@@ -1212,6 +1296,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
template <class P> template <class P>
void BufferCache<P>::DoUpdateComputeBuffers() { void BufferCache<P>::DoUpdateComputeBuffers() {
compute_segments.clear();
BufferOperations([&]() { BufferOperations([&]() {
UpdateComputeUniformBuffers(); UpdateComputeUniformBuffers();
UpdateComputeStorageBuffers(); UpdateComputeStorageBuffers();
@@ -1234,11 +1319,11 @@ void BufferCache<P>::UpdateIndexBuffer() {
auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size()); auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size());
u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE); u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE);
if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] { if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] {
inline_buffer_id = CreateBuffer(0, buffer_size); inline_buffer_id = CreateBuffer(0, buffer_size, false);
} }
if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] { if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] {
slot_buffers.erase(inline_buffer_id); slot_buffers.erase(inline_buffer_id);
inline_buffer_id = CreateBuffer(0, buffer_size); inline_buffer_id = CreateBuffer(0, buffer_size, false);
} }
channel_state->index_buffer = Binding{ channel_state->index_buffer = Binding{
.device_addr = 0, .device_addr = 0,
@@ -1261,7 +1346,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
channel_state->index_buffer = Binding{ channel_state->index_buffer = Binding{
.device_addr = *device_addr, .device_addr = *device_addr,
.size = size, .size = size,
.buffer_id = FindBuffer(*device_addr, size), .buffer_id = FindBuffer(*device_addr, size, false),
}; };
} }
@@ -1298,7 +1383,7 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) { if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
} }
const BufferId buffer_id = FindBuffer(*device_addr, size); const BufferId buffer_id = FindBuffer(*device_addr, size, false);
const Binding binding{ const Binding binding{
.device_addr = *device_addr, .device_addr = *device_addr,
.size = size, .size = size,
@@ -1319,7 +1404,7 @@ void BufferCache<P>::UpdateDrawIndirect() {
binding = Binding{ binding = Binding{
.device_addr = *device_addr, .device_addr = *device_addr,
.size = static_cast<u32>(size), .size = static_cast<u32>(size),
.buffer_id = FindBuffer(*device_addr, static_cast<u32>(size)), .buffer_id = FindBuffer(*device_addr, static_cast<u32>(size), false),
}; };
}; };
if (current_draw_indirect->include_count) { if (current_draw_indirect->include_count) {
@@ -1343,7 +1428,7 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
channel_state->dirty_uniform_buffers[stage] |= 1U << index; channel_state->dirty_uniform_buffers[stage] |= 1U << index;
} }
// Resolve buffer // Resolve buffer
binding.buffer_id = FindBuffer(binding.device_addr, binding.size); binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
}); });
} }
@@ -1352,8 +1437,10 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer // Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index]; Binding& binding = channel_state->storage_buffers[stage][index];
const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size); const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size, false);
binding.buffer_id = buffer_id; binding.buffer_id = buffer_id;
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
ResolveMultiRangeStorage(binding, is_written, graphics_segments);
}); });
} }
@@ -1361,7 +1448,7 @@ template <class P>
void BufferCache<P>::UpdateTextureBuffers(size_t stage) { void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index]; Binding& binding = channel_state->texture_buffers[stage][index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size); binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
}); });
} }
@@ -1385,7 +1472,7 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
channel_state->transform_feedback_buffers[index] = NULL_BINDING; channel_state->transform_feedback_buffers[index] = NULL_BINDING;
return; return;
} }
const BufferId buffer_id = FindBuffer(*device_addr, size); const BufferId buffer_id = FindBuffer(*device_addr, size, false);
channel_state->transform_feedback_buffers[index] = Binding{ channel_state->transform_feedback_buffers[index] = Binding{
.device_addr = *device_addr, .device_addr = *device_addr,
.size = size, .size = size,
@@ -1407,7 +1494,7 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
binding.size = cbuf.size; binding.size = cbuf.size;
} }
} }
binding.buffer_id = FindBuffer(binding.device_addr, binding.size); binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
}); });
} }
@@ -1416,7 +1503,10 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) { ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
// Resolve buffer // Resolve buffer
Binding& binding = channel_state->compute_storage_buffers[index]; Binding& binding = channel_state->compute_storage_buffers[index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size); binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
ResolveMultiRangeStorage(binding, is_written, compute_segments);
}); });
} }
@@ -1424,7 +1514,7 @@ template <class P>
void BufferCache<P>::UpdateComputeTextureBuffers() { void BufferCache<P>::UpdateComputeTextureBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
Binding& binding = channel_state->compute_texture_buffers[index]; Binding& binding = channel_state->compute_texture_buffers[index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size); binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
}); });
} }
@@ -1440,7 +1530,7 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
} }
template <class P> template <class P>
BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) { BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) {
if (device_addr == 0) { if (device_addr == 0) {
return NULL_BUFFER_ID; return NULL_BUFFER_ID;
} }
@@ -1450,10 +1540,18 @@ BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
Buffer& buffer = slot_buffers[buffer_id]; Buffer& buffer = slot_buffers[buffer_id];
WaitForGpuFenceIfNeeded(buffer); WaitForGpuFenceIfNeeded(buffer);
if (buffer.IsInBounds(device_addr, size)) { if (buffer.IsInBounds(device_addr, size)) {
return buffer_id; bool usable = true;
if constexpr (requires { buffer.IsSparseCompatible(); }) {
if (sparse_compatible && !buffer.IsSparseCompatible()) {
usable = false;
}
}
if (usable) {
return buffer_id;
}
} }
} }
return CreateBuffer(device_addr, size); return CreateBuffer(device_addr, size, sparse_compatible);
} }
template <class P> template <class P>
@@ -1575,13 +1673,15 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
} }
template <class P> template <class P>
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) { BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size,
bool sparse_compatible) {
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
wanted_size = static_cast<u32>(device_addr_end - device_addr); wanted_size = static_cast<u32>(device_addr_end - device_addr);
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin); const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size); const BufferId new_buffer_id =
slot_buffers.insert(runtime, overlap.begin, size, sparse_compatible);
auto& new_buffer = slot_buffers[new_buffer_id]; auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes(); const size_t size_bytes = new_buffer.SizeBytes();
runtime.ClearBuffer(new_buffer, 0, size_bytes, 0); runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
@@ -1745,7 +1845,7 @@ void BufferCache<P>::InlineMemoryImplementation(DAddr dest_address, size_t copy_
ClearDownload(dest_address, copy_size); ClearDownload(dest_address, copy_size);
gpu_modified_ranges.Subtract(dest_address, copy_size); gpu_modified_ranges.Subtract(dest_address, copy_size);
BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size)); BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size), false);
auto& buffer = slot_buffers[buffer_id]; auto& buffer = slot_buffers[buffer_id];
SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size)); SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size));
@@ -1831,6 +1931,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
template <class P> template <class P>
void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
if constexpr (requires { runtime.OnBufferDeleted(slot_buffers[buffer_id]); }) {
runtime.OnBufferDeleted(slot_buffers[buffer_id]);
}
bool dirty_index{false}; bool dirty_index{false};
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers; boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
const auto scalar_replace = [buffer_id](Binding& binding) { const auto scalar_replace = [buffer_id](Binding& binding) {
@@ -1934,9 +2037,14 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
// The end address used for size calculation does not need to be aligned // The end address used for size calculation does not need to be aligned
const DAddr cpu_end = Common::AlignUp(*device_addr + size, Core::DEVICE_PAGESIZE); const DAddr cpu_end = Common::AlignUp(*device_addr + size, Core::DEVICE_PAGESIZE);
u32 binding_size = static_cast<u32>(cpu_end - *aligned_device_addr);
if (is_written) {
binding_size = aligned_size;
}
const Binding binding{ const Binding binding{
.device_addr = *aligned_device_addr, .device_addr = *aligned_device_addr,
.size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_device_addr), .gpu_addr = aligned_gpu_addr,
.size = binding_size,
.buffer_id = BufferId{}, .buffer_id = BufferId{},
}; };
return binding; return binding;
@@ -29,6 +29,7 @@
#include "common/settings.h" #include "common/settings.h"
#include "common/slot_vector.h" #include "common/slot_vector.h"
#include "video_core/buffer_cache/buffer_base.h" #include "video_core/buffer_cache/buffer_base.h"
#include "video_core/buffer_cache/virtual_range_cache.h"
#include "video_core/control/channel_state_cache.h" #include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h" #include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h" #include "video_core/dirty_flags.h"
@@ -81,8 +82,17 @@ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
struct Binding { struct Binding {
DAddr device_addr{}; DAddr device_addr{};
GPUVAddr gpu_addr{};
u32 size{}; u32 size{};
BufferId buffer_id; BufferId buffer_id;
u32 segment_first{};
u32 segment_count{};
};
struct MultiRangeSegment {
BufferId buffer_id;
DAddr device_addr{};
u32 size{};
}; };
struct TextureBufferBinding : Binding { struct TextureBufferBinding : Binding {
@@ -215,6 +225,14 @@ public:
void TickFrame(); void TickFrame();
bool BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool);
void ResolveMultiRangeStorage(Binding& binding, bool is_written,
std::vector<MultiRangeSegment>& pool);
void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size);
void WriteMemory(DAddr device_addr, u64 size); void WriteMemory(DAddr device_addr, u64 size);
void CachedWriteMemory(DAddr device_addr, u64 size); void CachedWriteMemory(DAddr device_addr, u64 size);
@@ -414,7 +432,7 @@ private:
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size); void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size);
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size); [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
void WaitForGpuFenceIfNeeded(Buffer& buffer); void WaitForGpuFenceIfNeeded(Buffer& buffer);
@@ -422,7 +440,8 @@ private:
void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
[[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size); [[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size,
bool sparse_compatible);
void Register(BufferId buffer_id); void Register(BufferId buffer_id);
@@ -513,6 +532,9 @@ private:
using TickType = u64; using TickType = u64;
}; };
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache; Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
VirtualRangeCache virtual_ranges;
std::vector<MultiRangeSegment> graphics_segments;
std::vector<MultiRangeSegment> compute_segments;
u64 frame_tick = 0; u64 frame_tick = 0;
u64 total_used_memory = 0; u64 total_used_memory = 0;
u64 minimum_memory = 0; u64 minimum_memory = 0;
@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <atomic>
#include <limits>
#include <mutex>
#include <optional>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
#include "common/container/unordered_map.h"
#include "video_core/memory_manager.h"
namespace VideoCommon {
struct VirtualSegment {
GPUVAddr gpu_addr;
DAddr device_addr;
u32 size;
};
using VirtualSegments = boost::container::small_vector<VirtualSegment, 8>;
class VirtualRangeCache {
public:
static constexpr size_t MAX_ENTRIES = 8192;
static constexpr size_t MAX_DEFERRED = 4096;
const VirtualSegments* Query(Tegra::MemoryManager& memory, GPUVAddr gpu_addr, u32 size) {
if (has_deferred.load(std::memory_order_acquire)) {
ApplyDeferred();
}
if (entries.size() > MAX_ENTRIES) {
entries.clear();
}
const size_t as_id = memory.GetID();
const u64 key = MakeKey(as_id, gpu_addr);
const auto it = entries.find(key);
if (it != entries.end() && it->second.as_id == as_id &&
it->second.gpu_addr == gpu_addr && it->second.size == size) {
return &it->second.segments;
}
Entry entry{};
entry.as_id = as_id;
entry.gpu_addr = gpu_addr;
entry.size = size;
const auto ranges = memory.GetSubmappedRange(gpu_addr, size);
GPUVAddr expected = gpu_addr;
bool contiguous = true;
for (const auto& [range_addr, range_size] : ranges) {
if (range_addr != expected || range_size == 0) {
contiguous = false;
break;
}
const std::optional<DAddr> device_addr = memory.GpuToCpuAddress(range_addr);
if (!device_addr || *device_addr == 0) {
contiguous = false;
break;
}
if (range_size > static_cast<size_t>((std::numeric_limits<u32>::max)())) {
contiguous = false;
break;
}
entry.segments.push_back(VirtualSegment{
.gpu_addr = range_addr,
.device_addr = *device_addr,
.size = static_cast<u32>(range_size),
});
expected += range_size;
}
if (!contiguous || expected != gpu_addr + size) {
entry.segments.clear();
}
const auto result = entries.insert_or_assign(key, std::move(entry));
return &result.first->second.segments;
}
void Unmap(size_t as_id, GPUVAddr gpu_addr, u64 size) {
if (size == 0) {
return;
}
{
std::scoped_lock lock{deferred_mutex};
if (!deferred.empty()) {
DeferredUnmap& last = deferred.back();
if (last.as_id == as_id && last.gpu_addr + last.size == gpu_addr) {
last.size += size;
has_deferred.store(true, std::memory_order_release);
return;
}
}
if (deferred.size() >= MAX_DEFERRED) {
deferred.clear();
deferred_overflow = true;
} else {
deferred.push_back(DeferredUnmap{
.as_id = as_id,
.gpu_addr = gpu_addr,
.size = size,
});
}
}
has_deferred.store(true, std::memory_order_release);
}
private:
struct Entry {
VirtualSegments segments;
size_t as_id{};
GPUVAddr gpu_addr{};
u32 size{};
};
struct DeferredUnmap {
size_t as_id;
GPUVAddr gpu_addr;
u64 size;
};
static u64 MakeKey(size_t as_id, GPUVAddr gpu_addr) {
return (static_cast<u64>(as_id) << 48) ^ gpu_addr;
}
void ApplyDeferred() {
std::vector<DeferredUnmap> pending;
bool overflow = false;
{
std::scoped_lock lock{deferred_mutex};
has_deferred.store(false, std::memory_order_release);
pending.swap(deferred);
overflow = deferred_overflow;
deferred_overflow = false;
}
if (overflow) {
entries.clear();
return;
}
if (pending.empty() || entries.empty()) {
return;
}
for (auto it = entries.begin(); it != entries.end();) {
const Entry& entry = it->second;
const GPUVAddr entry_end = entry.gpu_addr + entry.size;
bool overlaps = false;
for (const DeferredUnmap& unmap : pending) {
if (unmap.as_id != entry.as_id) {
continue;
}
if (entry.gpu_addr < unmap.gpu_addr + unmap.size && unmap.gpu_addr < entry_end) {
overlaps = true;
break;
}
}
if (overlaps) {
it = entries.erase(it);
} else {
++it;
}
}
}
::Common::unordered_map<u64, Entry> entries;
std::vector<DeferredUnmap> deferred;
std::mutex deferred_mutex;
std::atomic<bool> has_deferred{false};
bool deferred_overflow{};
};
} // namespace VideoCommon
+6 -3
View File
@@ -55,11 +55,11 @@ constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) {
struct GPU::Impl { struct GPU::Impl {
explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_) explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_)
: system{system_} : gpu_thread{system_}
, system{system_}
, use_nvdec{use_nvdec_} , use_nvdec{use_nvdec_}
, shader_notify() , shader_notify()
, is_async{is_async_} , is_async{is_async_}
, gpu_thread{system_}
{} {}
~Impl() = default; ~Impl() = default;
@@ -301,6 +301,10 @@ struct GPU::Impl {
return out; return out;
} }
// Destruction of thread must be done before all (non trivial)
// previous members has been destroyed
VideoCommon::GPUThread::ThreadManager gpu_thread;
Core::System& system; Core::System& system;
std::unique_ptr<VideoCore::RendererBase> renderer; std::unique_ptr<VideoCore::RendererBase> renderer;
@@ -329,7 +333,6 @@ struct GPU::Impl {
const bool is_async; const bool is_async;
VideoCommon::GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context; std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
Tegra::Control::Scheduler scheduler; Tegra::Control::Scheduler scheduler;
@@ -52,7 +52,7 @@ constexpr std::array PROGRAM_LUT{
Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
: VideoCommon::BufferBase(null_params) {} : VideoCommon::BufferBase(null_params) {}
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, bool)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_) { : VideoCommon::BufferBase(cpu_addr_, size_bytes_) {
buffer.Create(); buffer.Create();
if (runtime.device.HasDebuggingToolAttached()) { if (runtime.device.HasDebuggingToolAttached()) {
@@ -23,7 +23,8 @@ class BufferCacheRuntime;
class Buffer : public VideoCommon::BufferBase { class Buffer : public VideoCommon::BufferBase {
public: public:
explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes); explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes,
bool sparse_compatible);
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams); explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams);
void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept; void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept;
@@ -56,7 +56,8 @@ size_t BytesPerIndex(VkIndexType index_type) {
} }
} }
vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size) { vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size,
VkDeviceSize sparse_alignment) {
VkBufferUsageFlags flags = VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
@@ -82,6 +83,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr, .pQueueFamilyIndices = nullptr,
}; };
if (sparse_alignment > 1) {
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal, sparse_alignment);
}
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
} }
} // Anonymous namespace } // Anonymous namespace
@@ -99,10 +103,14 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
} }
} }
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_,
bool sparse_compatible_)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
scheduler{&runtime.scheduler}, scheduler{&runtime.scheduler},
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} { buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes(),
runtime.SparseAlignmentFor(sparse_compatible_))},
tracker{SizeBytes()} {
sparse_compatible = sparse_compatible_;
if (runtime.device.HasDebuggingToolAttached()) { if (runtime.device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str()); buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str());
} }
@@ -348,7 +356,8 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_}, staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
quad_index_pass(device, scheduler, descriptor_pool, staging_pool, quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
compute_pass_descriptor_queue) { compute_pass_descriptor_queue),
multi_range_buffers(device_) {
const VkDriverIdKHR driver_id = device.GetDriverID(); const VkDriverIdKHR driver_id = device.GetDriverID();
limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
@@ -536,6 +545,37 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
}); });
} }
bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) {
if (multi_range_sources.empty() || multi_range_total == 0) {
return false;
}
const MultiRangeRef ref = multi_range_buffers.Get(device, scheduler, memory_allocator, key,
multi_range_sources, multi_range_total);
if (ref.handle == VK_NULL_HANDLE) {
return false;
}
if (is_written && !ref.sparse) {
return false;
}
if (ref.needs_gather) {
PreCopyBarrier();
VkDeviceSize dst_offset = 0;
for (const MultiRangeSource& source : multi_range_sources) {
const std::array<VideoCommon::BufferCopy, 1> copy{VideoCommon::BufferCopy{
.src_offset = u64(source.offset),
.dst_offset = u64(dst_offset),
.size = size_t(source.size),
}};
CopyBuffer(ref.handle, source.handle, copy, false);
dst_offset += source.size;
}
PostCopyBarrier();
multi_range_buffers.MarkGathered(key);
}
guest_descriptor_queue.AddBuffer(ref.handle, ref.address, 0, ref.size);
return true;
}
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, VkBuffer buffer, u32 base_vertex, u32 num_indices, VkBuffer buffer,
u32 offset, [[maybe_unused]] u32 size) { u32 offset, [[maybe_unused]] u32 size) {
@@ -8,11 +8,14 @@
#include <limits> #include <limits>
#include <boost/container/small_vector.hpp>
#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h" #include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/buffer_cache/usage_tracker.h" #include "video_core/buffer_cache/usage_tracker.h"
#include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h" #include "video_core/surface.h"
@@ -31,7 +34,8 @@ class BufferCacheRuntime;
class Buffer : public VideoCommon::BufferBase { class Buffer : public VideoCommon::BufferBase {
public: public:
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params); explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params);
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_); explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_,
bool sparse_compatible_);
[[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); [[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
@@ -43,6 +47,14 @@ public:
return device_address; return device_address;
} }
[[nodiscard]] bool IsSparseCompatible() const noexcept {
return sparse_compatible;
}
[[nodiscard]] vk::MemoryLocation Location() const noexcept {
return buffer.Location();
}
[[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept { [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
return tracker.IsUsed(offset, size); return tracker.IsUsed(offset, size);
} }
@@ -77,6 +89,7 @@ private:
VkDeviceAddress device_address{}; VkDeviceAddress device_address{};
u64 last_usage_tick{}; u64 last_usage_tick{};
bool is_null{}; bool is_null{};
bool sparse_compatible{};
}; };
class QuadArrayIndexBuffer; class QuadArrayIndexBuffer;
@@ -125,7 +138,7 @@ public:
void PreCopyBarrier(); void PreCopyBarrier();
void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, void CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies, bool barrier, std::span<const VideoCommon::BufferCopy> copies, bool barrier,
bool can_reorder_upload = false); bool can_reorder_upload = false);
@@ -155,6 +168,46 @@ public:
return ref.mapped_span; return ref.mapped_span;
} }
[[nodiscard]] VkDeviceSize SparseAlignmentFor(bool sparse_compatible) const noexcept {
if (!sparse_compatible || !multi_range_buffers.use_sparse) {
return 0;
}
return multi_range_buffers.block_size;
}
[[nodiscard]] bool PrefersSparseSources() const noexcept {
return multi_range_buffers.use_sparse;
}
void ResetMultiRange() noexcept {
multi_range_sources.clear();
multi_range_total = 0;
}
void PushMultiRangeSource(const Buffer& buffer, u32 offset, u32 size) {
const vk::MemoryLocation location = buffer.Location();
multi_range_sources.push_back(MultiRangeSource{
.handle = buffer.Handle(),
.memory = location.memory,
.memory_offset = location.offset,
.offset = offset,
.size = size,
.write_tick = buffer.getWriteTick(),
.memory_type = location.memory_type,
});
multi_range_total += size;
}
bool BindMultiRangeStorageBuffer(u64 key, bool is_written);
void InvalidateMultiRange(u64 key) {
multi_range_buffers.Invalidate(key);
}
void OnBufferDeleted(const Buffer& buffer) {
multi_range_buffers.DropOwner(scheduler, buffer.Handle());
}
void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) { void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) {
BindBuffer(buffer, offset, size); BindBuffer(buffer, offset, size);
} }
@@ -208,6 +261,10 @@ private:
std::unique_ptr<Uint8Pass> uint8_pass; std::unique_ptr<Uint8Pass> uint8_pass;
QuadIndexedPass quad_index_pass; QuadIndexedPass quad_index_pass;
MultiRangeBufferCache multi_range_buffers;
boost::container::small_vector<MultiRangeSource, 16> multi_range_sources;
VkDeviceSize multi_range_total{};
bool limit_dynamic_storage_buffers = false; bool limit_dynamic_storage_buffers = false;
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)(); u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
}; };
@@ -0,0 +1,338 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <mutex>
#include <utility>
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) {
sparse_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
sparse_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
if (!device.IsSparseBindingSupported()) {
return;
}
u32 memory_type_bits = 0;
const VkDeviceSize queried = QueryBlockSize(device, memory_type_bits);
if (queried == 0 || memory_type_bits == 0) {
return;
}
block_size = queried;
sparse_memory_type_bits = memory_type_bits;
use_sparse = true;
}
VkDeviceSize MultiRangeBufferCache::QueryBlockSize(const Device& device,
u32& memory_type_bits) const {
const VkDevice logical = *device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const VkBufferCreateInfo probe_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
.size = DEFAULT_BLOCK_SIZE,
.usage = sparse_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer probe{};
if (dld.vkCreateBuffer(logical, &probe_ci, nullptr, &probe) != VK_SUCCESS) {
return 0;
}
const SparseBuffer owned{probe, logical, dld};
const VkBufferMemoryRequirementsInfo2 reqs_info{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
.pNext = nullptr,
.buffer = probe,
};
VkMemoryRequirements2 reqs2{
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
.pNext = nullptr,
.memoryRequirements = {},
};
dld.vkGetBufferMemoryRequirements2(logical, &reqs_info, &reqs2);
memory_type_bits = reqs2.memoryRequirements.memoryTypeBits;
return reqs2.memoryRequirements.alignment;
}
u64 MultiRangeBufferCache::HashSources(std::span<const MultiRangeSource> sources) const {
u64 hash = 0xcbf29ce484222325ULL;
const auto mix = [&hash](u64 value) {
hash ^= value;
hash *= 0x100000001b3ULL;
};
for (const MultiRangeSource& source : sources) {
mix(u64(source.handle));
mix(u64(source.offset));
mix(u64(source.size));
}
return hash;
}
u64 MultiRangeBufferCache::HashContent(std::span<const MultiRangeSource> sources) const {
u64 hash = 0xcbf29ce484222325ULL;
for (const MultiRangeSource& source : sources) {
hash ^= source.write_tick;
hash *= 0x100000001b3ULL;
}
return hash;
}
bool MultiRangeBufferCache::CanBindSparse(std::span<const MultiRangeSource> sources) const {
return use_sparse &&
std::none_of(sources.begin(), sources.end(),
[block = block_size, bits = sparse_memory_type_bits](auto const& e) {
const VkDeviceSize memory_offset = e.memory_offset + e.offset;
return e.memory == VK_NULL_HANDLE || e.memory_type >= 32 ||
((bits >> e.memory_type) & 1) == 0 ||
(memory_offset % block) != 0 || (e.size % block) != 0;
});
}
SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total) {
const VkDevice logical = *device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
.size = total,
.usage = sparse_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer raw{};
if (dld.vkCreateBuffer(logical, &buffer_ci, nullptr, &raw) != VK_SUCCESS) {
return SparseBuffer{};
}
SparseBuffer handle{raw, logical, dld};
std::vector<VkSparseMemoryBind> binds;
binds.reserve(sources.size());
VkDeviceSize resource_offset = 0;
for (const MultiRangeSource& source : sources) {
binds.push_back(VkSparseMemoryBind{
.resourceOffset = resource_offset,
.size = source.size,
.memory = source.memory,
.memoryOffset = source.memory_offset + source.offset,
.flags = 0,
});
resource_offset += source.size;
}
const VkSparseBufferMemoryBindInfo buffer_bind{
.buffer = raw,
.bindCount = static_cast<u32>(binds.size()),
.pBinds = binds.data(),
};
const VkBindSparseInfo bind_info{
.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 0,
.pWaitSemaphores = nullptr,
.bufferBindCount = 1,
.pBufferBinds = &buffer_bind,
.imageOpaqueBindCount = 0,
.pImageOpaqueBinds = nullptr,
.imageBindCount = 0,
.pImageBinds = nullptr,
.signalSemaphoreCount = 0,
.pSignalSemaphores = nullptr,
};
const VkFenceCreateInfo fence_ci{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
};
vk::Fence fence = device.GetLogical().CreateFence(fence_ci);
VkResult bind_result = VK_ERROR_UNKNOWN;
{
std::scoped_lock lock{scheduler.submit_mutex};
bind_result = device.GetGraphicsQueue().BindSparse(bind_info, *fence);
}
if (bind_result != VK_SUCCESS) {
return SparseBuffer{};
}
fence.Wait();
return handle;
}
void MultiRangeBufferCache::RetireEntry(Scheduler& scheduler, Entry& entry) {
if (!entry.sparse_handle && !entry.gathered) {
return;
}
if (retired.size() == retired.capacity()) {
DrainRetired(scheduler);
}
if (retired.size() == retired.capacity()) {
u64 oldest = retired.front().tick;
for (const Retired& item : retired) {
if (item.tick < oldest) {
oldest = item.tick;
}
}
scheduler.Wait(oldest);
DrainRetired(scheduler);
}
retired.push_back(Retired{
.handle = std::move(entry.sparse_handle),
.gathered = std::move(entry.gathered),
.tick = scheduler.CurrentTick(),
});
}
void MultiRangeBufferCache::DrainRetired(Scheduler& scheduler) {
size_t index = 0;
while (index < retired.size()) {
if (scheduler.IsFree(retired[index].tick)) {
if (index + 1 != retired.size()) {
retired[index] = std::move(retired.back());
}
retired.pop_back();
} else {
++index;
}
}
}
MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& scheduler,
MemoryAllocator& memory_allocator, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total) {
if (sources.empty() || total == 0) {
return MultiRangeRef{};
}
if (!retired.empty()) {
DrainRetired(scheduler);
}
const u64 geometry = HashSources(sources);
const u64 content = HashContent(sources);
const auto it = entries.find(key);
if (it != entries.end() && it->second.geometry == geometry && it->second.size == total) {
Entry& entry = it->second;
if (entry.content != content) {
entry.content = content;
entry.dirty = true;
}
MultiRangeRef ref{
.handle = *entry.sparse_handle,
.address = entry.address,
.size = entry.size,
.sparse = true,
.needs_gather = false,
};
if (!entry.sparse_handle) {
ref.handle = *entry.gathered;
ref.sparse = false;
ref.needs_gather = entry.dirty;
}
return ref;
}
if (it != entries.end()) {
RetireEntry(scheduler, it->second);
entries.erase(it);
}
Entry entry{};
entry.geometry = geometry;
entry.content = content;
entry.size = total;
if (CanBindSparse(sources)) {
entry.sparse_handle = CreateSparse(device, scheduler, sources, total);
if (entry.sparse_handle) {
entry.owners.reserve(sources.size());
for (const MultiRangeSource& source : sources) {
entry.owners.push_back(source.handle);
}
}
}
if (!entry.sparse_handle) {
VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
const VkBufferCreateInfo gather_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = total,
.usage = flags,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
entry.gathered = memory_allocator.CreateBuffer(gather_ci, MemoryUsage::DeviceLocal);
entry.dirty = true;
}
if (device.IsBufferDeviceAddressSupported()) {
VkBuffer address_handle = *entry.sparse_handle;
if (!entry.sparse_handle) {
address_handle = *entry.gathered;
}
entry.address = device.GetLogical().GetBufferDeviceAddress(address_handle);
}
MultiRangeRef ref{
.handle = *entry.sparse_handle,
.address = entry.address,
.size = entry.size,
.sparse = true,
.needs_gather = false,
};
if (!entry.sparse_handle) {
ref.handle = *entry.gathered;
ref.sparse = false;
ref.needs_gather = true;
}
entries.emplace(key, std::move(entry));
return ref;
}
void MultiRangeBufferCache::MarkGathered(u64 key) {
if (auto const it = entries.find(key); it != entries.end()) {
it->second.dirty = false;
}
}
void MultiRangeBufferCache::DropOwner(Scheduler& scheduler, VkBuffer owner) {
if (owner == VK_NULL_HANDLE) {
return;
}
for (auto it = entries.begin(); it != entries.end();) {
Entry& entry = it->second;
bool owned = false;
for (const VkBuffer handle : entry.owners) {
if (handle == owner) {
owned = true;
break;
}
}
if (!owned) {
++it;
continue;
}
RetireEntry(scheduler, entry);
it = entries.erase(it);
}
}
void MultiRangeBufferCache::Invalidate(u64 key) {
if (auto const it = entries.find(key); it != entries.end()) {
it->second.dirty = true;
}
}
} // namespace Vulkan
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/container/unordered_map.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
using SparseBuffer = vk::Handle<VkBuffer, VkDevice, vk::DeviceDispatch>;
class Device;
class Scheduler;
struct MultiRangeSource {
VkBuffer handle{};
VkDeviceMemory memory{};
VkDeviceSize memory_offset{};
VkDeviceSize offset{};
VkDeviceSize size{};
u64 write_tick{};
u32 memory_type{};
};
struct MultiRangeRef {
VkBuffer handle{};
VkDeviceAddress address{};
VkDeviceSize size{};
bool sparse{};
bool needs_gather{};
};
class MultiRangeBufferCache final {
public:
static constexpr VkDeviceSize DEFAULT_BLOCK_SIZE = 64 * 1024;
static constexpr size_t MAX_RETIRED = 256;
explicit MultiRangeBufferCache(const Device& device);
YUZU_NON_COPYABLE(MultiRangeBufferCache);
[[nodiscard]] MultiRangeRef Get(const Device& device, Scheduler& scheduler,
MemoryAllocator& memory_allocator, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total);
void MarkGathered(u64 key);
void Invalidate(u64 key);
void DropOwner(Scheduler& scheduler, VkBuffer owner);
VkDeviceSize block_size{DEFAULT_BLOCK_SIZE};
bool use_sparse{};
private:
struct Retired {
SparseBuffer handle;
vk::Buffer gathered;
u64 tick{};
};
struct Entry {
vk::Buffer gathered;
SparseBuffer sparse_handle;
std::vector<VkBuffer> owners;
VkDeviceAddress address{};
VkDeviceSize size{};
u64 geometry{};
u64 content{};
bool dirty{true};
};
[[nodiscard]] u64 HashSources(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] u64 HashContent(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] bool CanBindSparse(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] SparseBuffer CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total);
[[nodiscard]] VkDeviceSize QueryBlockSize(const Device& device, u32& memory_type_bits) const;
void RetireEntry(Scheduler& scheduler, Entry& entry);
void DrainRetired(Scheduler& scheduler);
::Common::unordered_map<u64, Entry> entries;
boost::container::static_vector<Retired, MAX_RETIRED> retired;
u32 sparse_memory_type_bits{};
VkBufferUsageFlags sparse_usage{};
};
} // namespace Vulkan
@@ -819,6 +819,7 @@ void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
std::scoped_lock lock{texture_cache.mutex}; std::scoped_lock lock{texture_cache.mutex};
texture_cache.UnmapGPUMemory(as_id, addr, size); texture_cache.UnmapGPUMemory(as_id, addr, size);
} }
buffer_cache.UnmapGPUMemory(as_id, addr, size);
} }
void RasterizerVulkan::SignalFence(std::function<void()>&& func) { void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
@@ -1570,6 +1570,8 @@ void Device::SetupFamilies(VkSurfaceKHR surface) {
} }
if (graphics) { if (graphics) {
graphics_family = *graphics; graphics_family = *graphics;
graphics_family_sparse_binding =
(queue_family_properties[*graphics].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0;
} }
if (present) { if (present) {
present_family = *present; present_family = *present;
@@ -317,6 +317,10 @@ public:
return properties.driver.driverID; return properties.driver.driverID;
} }
bool IsSparseBindingSupported() const {
return features.features.sparseBinding && graphics_family_sparse_binding;
}
/// Returns true for tile-based deferred renderers. /// Returns true for tile-based deferred renderers.
bool IsTiler() const { bool IsTiler() const {
switch (GetDriverID()) { switch (GetDriverID()) {
@@ -1147,6 +1151,7 @@ private:
u32 instance_version{}; ///< Vulkan instance version. u32 instance_version{}; ///< Vulkan instance version.
u32 graphics_family{}; ///< Main graphics queue family index. u32 graphics_family{}; ///< Main graphics queue family index.
u32 present_family{}; ///< Main present queue family index. u32 present_family{}; ///< Main present queue family index.
bool graphics_family_sparse_binding{};
struct Extensions { struct Extensions {
#define EXTENSION(prefix, macro_name, var_name) bool var_name{}; #define EXTENSION(prefix, macro_name, var_name) bool var_name{};
@@ -275,9 +275,63 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsa
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{}; const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, const vk::MemoryLocation location{
is_coherent, .memory = alloc_info.deviceMemory,
device.GetDispatchLoader()); .offset = alloc_info.offset,
.memory_type = alloc_info.memoryType,
};
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
location, device.GetDispatchLoader());
}
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,
VkDeviceSize min_alignment) const {
if (min_alignment <= 1) {
return CreateBuffer(ci, usage);
}
VkMemoryPropertyFlags anv_flags = 0;
if (usage == MemoryUsage::Stream &&
device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) {
anv_flags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
}
u32 memory_type_bits = valid_memory_types;
if (usage == MemoryUsage::Stream) {
memory_type_bits = 0u;
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = memory_type_bits,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
vk::Check(vmaCreateBufferWithAlignment(allocator, &ci, &alloc_ci, min_alignment, &handle,
&allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
std::span<u8> mapped_data{};
if (data) {
mapped_data = std::span<u8>{data, ci.size};
}
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
const vk::MemoryLocation location{
.memory = alloc_info.deviceMemory,
.offset = alloc_info.offset,
.memory_type = alloc_info.memoryType,
};
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
location, device.GetDispatchLoader());
} }
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage) MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -107,6 +107,9 @@ namespace Vulkan {
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const; vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,
VkDeviceSize min_alignment) const;
/** /**
* Commits a memory with the specified requirements. * Commits a memory with the specified requirements.
* *
@@ -229,6 +229,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetPipelineExecutableStatisticsKHR); X(vkGetPipelineExecutableStatisticsKHR);
X(vkGetSemaphoreCounterValue); X(vkGetSemaphoreCounterValue);
X(vkMapMemory); X(vkMapMemory);
X(vkQueueBindSparse);
X(vkQueueSubmit); X(vkQueueSubmit);
X(vkQueueSubmit2); X(vkQueueSubmit2);
X(vkResetFences); X(vkResetFences);
@@ -459,8 +460,20 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
#else #else
constexpr VkFlags ci_flags{}; constexpr VkFlags ci_flags{};
#endif #endif
// DO NOT TOUCH, breaks RNDA3!! // DO NOT TOUCH OR CHANGE THE ENGINE NAME/APPLICATION NAME, breaks RNDA3!!
// Don't know why, but gloom + yellow line glitch appears // AMD drivers have fixes for Yuzu
// if remove => gloom + yellow line glitch appears
#ifdef __ANDROID__
const VkApplicationInfo application_info{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "PUBGMobile",
.applicationVersion = VK_MAKE_VERSION(1, 7, 0),
.pEngineName = "UnrealEngine",
.engineVersion = VK_MAKE_VERSION(4, 23, 0),
.apiVersion = VK_API_VERSION_1_3,
};
#else
const VkApplicationInfo application_info{ const VkApplicationInfo application_info{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr, .pNext = nullptr,
@@ -470,6 +483,7 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
.engineVersion = VK_MAKE_VERSION(1, 3, 0), .engineVersion = VK_MAKE_VERSION(1, 3, 0),
.apiVersion = VK_API_VERSION_1_3, .apiVersion = VK_API_VERSION_1_3,
}; };
#endif
const VkInstanceCreateInfo ci{ const VkInstanceCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
+22 -3
View File
@@ -345,6 +345,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetQueryPoolResults vkGetQueryPoolResults{}; PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{}; PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
PFN_vkMapMemory vkMapMemory{}; PFN_vkMapMemory vkMapMemory{};
PFN_vkQueueBindSparse vkQueueBindSparse{};
PFN_vkQueueSubmit vkQueueSubmit{}; PFN_vkQueueSubmit vkQueueSubmit{};
PFN_vkQueueSubmit2 vkQueueSubmit2{}; PFN_vkQueueSubmit2 vkQueueSubmit2{};
PFN_vkResetFences vkResetFences{}; PFN_vkResetFences vkResetFences{};
@@ -740,13 +741,20 @@ private:
const DeviceDispatch* dld = nullptr; const DeviceDispatch* dld = nullptr;
}; };
struct MemoryLocation {
VkDeviceMemory memory{};
VkDeviceSize offset{};
u32 memory_type{};
};
class Buffer { class Buffer {
public: public:
explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_, explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_,
VmaAllocation allocation_, std::span<u8> mapped_, bool is_coherent_, VmaAllocation allocation_, std::span<u8> mapped_, bool is_coherent_,
const DeviceDispatch& dld_) noexcept MemoryLocation location_, const DeviceDispatch& dld_) noexcept
: handle{handle_}, owner{owner_}, allocator{allocator_}, : handle{handle_}, owner{owner_}, allocator{allocator_},
allocation{allocation_}, mapped{mapped_}, is_coherent{is_coherent_}, dld{&dld_} {} allocation{allocation_}, mapped{mapped_}, location{location_},
is_coherent{is_coherent_}, dld{&dld_} {}
Buffer() = default; Buffer() = default;
Buffer(const Buffer&) = delete; Buffer(const Buffer&) = delete;
@@ -754,7 +762,7 @@ public:
Buffer(Buffer&& rhs) noexcept Buffer(Buffer&& rhs) noexcept
: handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator}, : handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
allocation{rhs.allocation}, mapped{rhs.mapped}, allocation{rhs.allocation}, mapped{rhs.mapped}, location{rhs.location},
is_coherent{rhs.is_coherent}, dld{rhs.dld} {} is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
Buffer& operator=(Buffer&& rhs) noexcept { Buffer& operator=(Buffer&& rhs) noexcept {
@@ -764,6 +772,7 @@ public:
allocator = rhs.allocator; allocator = rhs.allocator;
allocation = rhs.allocation; allocation = rhs.allocation;
mapped = rhs.mapped; mapped = rhs.mapped;
location = rhs.location;
is_coherent = rhs.is_coherent; is_coherent = rhs.is_coherent;
dld = rhs.dld; dld = rhs.dld;
return *this; return *this;
@@ -811,6 +820,10 @@ public:
void SetObjectNameEXT(const char* name) const; void SetObjectNameEXT(const char* name) const;
MemoryLocation Location() const noexcept {
return location;
}
private: private:
void Release() const noexcept; void Release() const noexcept;
@@ -819,6 +832,7 @@ private:
VmaAllocator allocator = nullptr; VmaAllocator allocator = nullptr;
VmaAllocation allocation = nullptr; VmaAllocation allocation = nullptr;
std::span<u8> mapped = {}; std::span<u8> mapped = {};
MemoryLocation location{};
bool is_coherent = false; bool is_coherent = false;
const DeviceDispatch* dld = nullptr; const DeviceDispatch* dld = nullptr;
}; };
@@ -843,6 +857,11 @@ public:
return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence); return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence);
} }
VkResult BindSparse(Span<VkBindSparseInfo> bind_infos,
VkFence fence = VK_NULL_HANDLE) const noexcept {
return dld->vkQueueBindSparse(queue, bind_infos.size(), bind_infos.data(), fence);
}
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
return dld->vkQueuePresentKHR(queue, &present_info); return dld->vkQueuePresentKHR(queue, &present_info);
} }
@@ -2936,10 +2936,10 @@ void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Di
} }
// Draw motion functions // Draw motion functions
void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec<f32, 3>& euler,
float size) { float size) {
std::array<Common::Vec3f, 8> cube{ std::array<Common::Vec<f32, 3>, 8> cube{
Common::Vec3f{-0.7f, -1, -0.5f}, Common::Vec<f32, 3>{-0.7f, -1, -0.5f},
{-0.7f, 1, -0.5f}, {-0.7f, 1, -0.5f},
{0.7f, 1, -0.5f}, {0.7f, 1, -0.5f},
{0.7f, -1, -0.5f}, {0.7f, -1, -0.5f},
@@ -2949,30 +2949,38 @@ void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common:
{0.7f, -1, 0.5f}, {0.7f, -1, 0.5f},
}; };
for (Common::Vec3f& point : cube) { for (Common::Vec<f32, 3>& point : cube) {
point.RotateFromOrigin(euler.x, euler.y, euler.z); float temp = point[1];
point[1] = std::cos(euler[0]) * point[1] - std::sin(euler[0]) * point[2];
point[2] = std::sin(euler[0]) * temp + std::cos(euler[0]) * point[2];
temp = point[0];
point[0] = std::cos(euler[1]) * point[0] + std::sin(euler[1]) * point[2];
point[2] = -std::sin(euler[1]) * temp + std::cos(euler[1]) * point[2];
temp = point[0];
point[0] = std::cos(euler[2]) * point[0] - std::sin(euler[2]) * point[1];
point[1] = std::sin(euler[2]) * temp + std::cos(euler[2]) * point[1];
point *= size; point *= size;
} }
const std::array<QPointF, 4> front_face{ const std::array<QPointF, 4> front_face{
center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[0][0], cube[0][1]},
center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[1][0], cube[1][1]},
center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[2][0], cube[2][1]},
center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[3][0], cube[3][1]},
}; };
const std::array<QPointF, 4> back_face{ const std::array<QPointF, 4> back_face{
center + QPointF{cube[4].x, cube[4].y}, center + QPointF{cube[4][0], cube[4][1]},
center + QPointF{cube[5].x, cube[5].y}, center + QPointF{cube[5][0], cube[5][1]},
center + QPointF{cube[6].x, cube[6].y}, center + QPointF{cube[6][0], cube[6][1]},
center + QPointF{cube[7].x, cube[7].y}, center + QPointF{cube[7][0], cube[7][1]},
}; };
DrawPolygon(p, front_face); DrawPolygon(p, front_face);
DrawPolygon(p, back_face); DrawPolygon(p, back_face);
p.drawLine(center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[4].x, cube[4].y}); p.drawLine(center + QPointF{cube[0][0], cube[0][1]}, center + QPointF{cube[4][0], cube[4][1]});
p.drawLine(center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[5].x, cube[5].y}); p.drawLine(center + QPointF{cube[1][0], cube[1][1]}, center + QPointF{cube[5][0], cube[5][1]});
p.drawLine(center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[6].x, cube[6].y}); p.drawLine(center + QPointF{cube[2][0], cube[2][1]}, center + QPointF{cube[6][0], cube[6][1]});
p.drawLine(center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[7].x, cube[7].y}); p.drawLine(center + QPointF{cube[3][0], cube[3][1]}, center + QPointF{cube[7][0], cube[7][1]});
} }
template <size_t N> template <size_t N>
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -198,7 +198,7 @@ private:
void DrawArrow(QPainter& p, QPointF center, Direction direction, float size); void DrawArrow(QPainter& p, QPointF center, Direction direction, float size);
// Draw motion functions // Draw motion functions
void Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, float size); void Draw3dCube(QPainter& p, QPointF center, const Common::Vec<f32, 3>& euler, float size);
// Draw primitive types // Draw primitive types
template <size_t N> template <size_t N>
@@ -24,6 +24,7 @@
#include "common/settings_input.h" #include "common/settings_input.h"
#include "configuration/shared_widget.h" #include "configuration/shared_widget.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/control_metadata.h" #include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h" #include "core/file_sys/xts_archive.h"
@@ -50,7 +51,7 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
std::vector<VkDeviceInfo::Record>& vk_device_records, std::vector<VkDeviceInfo::Record>& vk_device_records,
Core::System& system_) Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{FileSys::GetBaseTitleID(title_id_)},
system{system_}, system{system_},
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())}, builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} { tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
@@ -24,6 +24,7 @@
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "configuration/addon/mod_select_dialog.h" #include "configuration/addon/mod_select_dialog.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "frontend_common/mod_manager.h" #include "frontend_common/mod_manager.h"
@@ -137,7 +138,7 @@ void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) {
} }
void ConfigurePerGameAddons::SetTitleId(u64 id) { void ConfigurePerGameAddons::SetTitleId(u64 id) {
this->title_id = id; this->title_id = FileSys::GetBaseTitleID(id);
} }
void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { void ConfigurePerGameAddons::InstallMods(const QStringList& mods) {
+5 -2
View File
@@ -1926,7 +1926,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}; std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
const auto config_file_name = title_id == 0 const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename()) ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id); : fmt::format("{:016X}", FileSys::GetBaseTitleID(title_id));
QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig); QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
QtCommon::system->HIDCore().ReloadInputDevices(); QtCommon::system->HIDCore().ReloadInputDevices();
QtCommon::system->ApplySettings(); QtCommon::system->ApplySettings();
@@ -2548,9 +2548,11 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat
} }
const FileSys::NCA update_nca{packed_update_raw, nullptr}; const FileSys::NCA update_nca{packed_update_raw, nullptr};
const auto selected_update_id = FileSys::GetUpdateTitleID(title_id);
const auto application_update_id = FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(title_id));
if (type != FileSys::ContentRecordType::Program || if (type != FileSys::ContentRecordType::Program ||
update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) { (update_nca.GetTitleId() != selected_update_id && update_nca.GetTitleId() != application_update_id)) {
packed_update_raw = {}; packed_update_raw = {};
} }
@@ -4425,6 +4427,7 @@ void MainWindow::SetFPSSuffix() {
bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type) { u64* selected_title_id, u8* selected_content_record_type) {
program_id = FileSys::GetBaseTitleID(program_id);
using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>; using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
boost::container::flat_set<ContentInfo> available_title_ids; boost::container::flat_set<ContentInfo> available_title_ids;