mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-30 10:26:14 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94e1608719 | |||
| e5b656e372 | |||
| 297e797a32 | |||
| 48a95da874 | |||
| 2fd1dc0ff9 | |||
| 45b6ecff05 | |||
| 672bcbae01 | |||
| 28ab4a1a01 | |||
| ccf3de02cf | |||
| 1f03dee126 | |||
| 3df41c1e7a | |||
| f4a8f9421a |
+2
-2
@@ -340,10 +340,10 @@
|
||||
"version": "vulkan-sdk-%NUMERIC_VERSION%"
|
||||
},
|
||||
"xbyak": {
|
||||
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
|
||||
"hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36",
|
||||
"package": "xbyak",
|
||||
"repo": "herumi/xbyak",
|
||||
"version": "v7.35.2"
|
||||
"version": "v7.40.1"
|
||||
},
|
||||
"zlib": {
|
||||
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
||||
|
||||
Vendored
+2
@@ -509,6 +509,8 @@ QWidget#contentRichDialog QLabel#label_title_rich {
|
||||
}
|
||||
|
||||
QWidget#contentDialog QLabel#label_dialog {
|
||||
background: #2E2E2E;
|
||||
|
||||
padding: 20px 65px;
|
||||
}
|
||||
|
||||
|
||||
+27
-11
@@ -43,6 +43,7 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
|
||||
Firstly add your desired toggle:
|
||||
|
||||
Example: `src/common/setting.h`
|
||||
|
||||
```cpp
|
||||
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
|
||||
```
|
||||
@@ -67,6 +68,7 @@ Common Categories:
|
||||
Add the toggle to the Qt UI, where you wish for it to appear and place it there.
|
||||
|
||||
Example: `src/qt_common/config/shared_translation.cpp`
|
||||
|
||||
```cpp
|
||||
INSERT(Settings,
|
||||
your_setting_name,
|
||||
@@ -91,6 +93,7 @@ INSERT(Settings,
|
||||
Add where it should be in the settings.
|
||||
|
||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
|
||||
|
||||
```kts
|
||||
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
||||
```
|
||||
@@ -106,6 +109,7 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
||||
Add the toggle to the Kotlin (Android) UI
|
||||
|
||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
|
||||
|
||||
```kts
|
||||
put(
|
||||
SwitchSetting(
|
||||
@@ -123,6 +127,7 @@ put(
|
||||
Add your setting within the right category.
|
||||
|
||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
|
||||
|
||||
```kts
|
||||
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
||||
```
|
||||
@@ -137,6 +142,7 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
||||
Add your setting and description in the appropriate place.
|
||||
|
||||
Example: `src/android/app/src/main/res/values/strings.xml`
|
||||
|
||||
```xml
|
||||
<string name="your_setting_name">Your Setting Display Name</string>
|
||||
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
|
||||
@@ -150,6 +156,7 @@ Now the UI part is done find a place in the code for the toggle,
|
||||
And use it to your heart's desire!
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
const bool your_value = Settings::values.your_setting_name.GetValue();
|
||||
|
||||
@@ -196,25 +203,31 @@ Common advantages recap:
|
||||
|
||||
#### Accessing Debug Knobs (dev side)
|
||||
|
||||
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set:
|
||||
Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
|
||||
|
||||
```cpp
|
||||
//cpp side
|
||||
#include "common/settings.h"
|
||||
|
||||
//To use it as a general purpose uint var:
|
||||
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
|
||||
|
||||
// Check if bit 0 is set
|
||||
bool feature_enabled = Settings::getDebugKnobAt(0);
|
||||
bool feature_enabled = Settings::GetDebugKnobAt(0);
|
||||
|
||||
// Check if bit 15 is set
|
||||
bool another_feature = Settings::getDebugKnobAt(15);
|
||||
bool another_feature = Settings::GetDebugKnobAt(15);
|
||||
```
|
||||
|
||||
```kts
|
||||
//kotlin side
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
//To use it as a general purpose uint var
|
||||
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
|
||||
|
||||
// Check if bit x is set
|
||||
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15
|
||||
bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
|
||||
```
|
||||
|
||||
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
|
||||
@@ -247,6 +260,7 @@ There are two main confusions when talking about knobs:
|
||||
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
|
||||
|
||||
Debug knobs are **zero-based**, which means:
|
||||
|
||||
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
|
||||
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
|
||||
|
||||
@@ -259,6 +273,7 @@ Whenever you're instructing tests or reporting results, be precise about whether
|
||||
|
||||
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
|
||||
Examples:
|
||||
|
||||
- **knobs=0**: no knobs enabled
|
||||
- **knobs=1**: knob0 enabled, others disabled
|
||||
- **knobs=2**: knob1 enabled, others disabled
|
||||
@@ -270,6 +285,7 @@ Examples:
|
||||
|
||||
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
|
||||
Examples:
|
||||
|
||||
- **knob0**: knob 0 enabled, others disabled
|
||||
- **knob1**: knob 1 enabled, others disabled
|
||||
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
|
||||
@@ -282,12 +298,12 @@ Examples:
|
||||
|
||||
```cpp
|
||||
void SomeFunction() {
|
||||
if (Settings::getDebugKnobAt(0)) {
|
||||
if (Settings::GetDebugKnobAt(0)) {
|
||||
LOG_DEBUG(Common, "Debug feature 0 is enabled");
|
||||
// Additional debug code here
|
||||
}
|
||||
|
||||
if (Settings::getDebugKnobAt(1)) {
|
||||
|
||||
if (Settings::GetDebugKnobAt(1)) {
|
||||
LOG_DEBUG(Common, "Debug feature 1 is enabled");
|
||||
// Different debug behavior
|
||||
}
|
||||
@@ -299,7 +315,7 @@ void SomeFunction() {
|
||||
```cpp
|
||||
bool UseOptimizedPath() {
|
||||
// Skip optimization if debug bit 2 is set for testing
|
||||
return !Settings::getDebugKnobAt(2);
|
||||
return !Settings::GetDebugKnobAt(2);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -308,13 +324,13 @@ bool UseOptimizedPath() {
|
||||
```cpp
|
||||
void ExperimentalFeature() {
|
||||
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
|
||||
|
||||
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
||||
|
||||
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
||||
// Fallback to stable implementation
|
||||
StableImplementation();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Experimental implementation
|
||||
ExperimentalImplementation();
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ object NativeLibrary {
|
||||
|
||||
external fun refreshThreadPolicies()
|
||||
|
||||
external fun getDebugKnobAt(index: Int): Boolean
|
||||
external fun GetDebugKnobAt(index: Int): Boolean
|
||||
|
||||
/**
|
||||
* Set the current speed limit to the configured turbo speed.
|
||||
|
||||
@@ -35,8 +35,8 @@ object Settings {
|
||||
fun getPlayerString(player: Int): String =
|
||||
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
||||
|
||||
fun getDebugKnobAt(index: Int): Boolean {
|
||||
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
|
||||
fun GetDebugKnobAt(index: Int): Boolean {
|
||||
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
|
||||
}
|
||||
|
||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||
|
||||
+2
-3
@@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
||||
RENDERER_SPEED_LIMIT("speed_limit"),
|
||||
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
|
||||
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
|
||||
DEBUG_KNOBS("debug_knobs")
|
||||
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
|
||||
;
|
||||
|
||||
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
|
||||
@@ -29,4 +28,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
|
||||
|
||||
override fun reset() = NativeConfig.setShort(key, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class UShortSetting(override val key: String) : AbstractIntSetting {
|
||||
DEBUG_KNOBS("debug_knobs")
|
||||
;
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int =
|
||||
NativeConfig.getUnsignedShort(key, needsGlobal)
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
if (NativeConfig.isPerGameConfigLoaded()) {
|
||||
global = false
|
||||
}
|
||||
NativeConfig.setUnsignedShort(key, value)
|
||||
}
|
||||
|
||||
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
|
||||
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
|
||||
|
||||
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
|
||||
}
|
||||
+2
-1
@@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
||||
import org.yuzu.yuzu_emu.network.NetDataValidators
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
@@ -1034,7 +1035,7 @@ abstract class SettingsItem(
|
||||
)
|
||||
put(
|
||||
SpinBoxSetting(
|
||||
ShortSetting.DEBUG_KNOBS,
|
||||
UShortSetting.DEBUG_KNOBS,
|
||||
titleId = R.string.debug_knobs,
|
||||
descriptionId = R.string.debug_knobs_description,
|
||||
valueHint = R.string.debug_knobs_hint,
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||
@@ -1326,7 +1327,7 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(HeaderSetting(R.string.general))
|
||||
|
||||
add(ShortSetting.DEBUG_KNOBS.key)
|
||||
add(UShortSetting.DEBUG_KNOBS.key)
|
||||
add(StringSetting.PROGRAM_ARGS.key)
|
||||
|
||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||
|
||||
+1
-4
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -27,6 +24,6 @@ object SettingsFile {
|
||||
|
||||
fun loadCustomConfig(game: Game) {
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.applicationId, fileName)
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ class AddonsFragment : Fragment() {
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents,
|
||||
programId = args.game.applicationId
|
||||
programId = args.game.programId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
try {
|
||||
if (GpuDriverHelper.isAdrenoGpu()) {
|
||||
val programIdHex = game!!.applicationIdHex
|
||||
val programIdHex = game!!.programIdHex
|
||||
if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) {
|
||||
Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex")
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -59,7 +59,7 @@ class FreedrenoSettingsFragment : Fragment() {
|
||||
NativeFreedrenoConfig.initializeFreedrenoConfig()
|
||||
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.loadPerGameConfig(game!!.applicationIdHex)
|
||||
NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex)
|
||||
} else {
|
||||
NativeFreedrenoConfig.reloadFreedrenoConfig()
|
||||
}
|
||||
@@ -157,7 +157,7 @@ class FreedrenoSettingsFragment : Fragment() {
|
||||
|
||||
binding.buttonSave.setOnClickListener {
|
||||
if (isPerGameConfig) {
|
||||
NativeFreedrenoConfig.savePerGameConfig(game!!.applicationIdHex)
|
||||
NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex)
|
||||
showSnackbar(getString(R.string.freedreno_per_game_saved))
|
||||
} else {
|
||||
NativeFreedrenoConfig.saveFreedrenoConfig()
|
||||
|
||||
@@ -455,7 +455,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
|
||||
val shaderCacheDir = File(
|
||||
DirectoryInitialization.userDirectory +
|
||||
"/cache/shader/" + args.game.shaderCacheName.lowercase()
|
||||
"/cache/shader/" + args.game.settingsName.lowercase()
|
||||
)
|
||||
if (shaderCacheDir.exists()) {
|
||||
add(
|
||||
@@ -600,7 +600,7 @@ class GamePropertiesFragment : Fragment() {
|
||||
val files = cacheSaveDir.listFiles()
|
||||
var savesFolderFile: File? = null
|
||||
if (files != null) {
|
||||
val savesFolderName = args.game.applicationIdHex
|
||||
val savesFolderName = args.game.programIdHex
|
||||
for (file in files) {
|
||||
if (file.isDirectory && file.name == savesFolderName) {
|
||||
savesFolderFile = file
|
||||
|
||||
@@ -232,7 +232,7 @@ class InstallableFragment : Fragment() {
|
||||
fragmentManager = parentFragmentManager,
|
||||
addonViewModel = addonViewModel,
|
||||
documents = documents,
|
||||
programId = addonViewModel.game?.applicationId
|
||||
programId = addonViewModel.game?.programId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,11 +142,10 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun onDeleteAddon(patch: Patch) {
|
||||
val currentGame = game ?: return
|
||||
when (PatchType.from(patch.type)) {
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(currentGame.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(currentGame.applicationId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(currentGame.programId, patch.name)
|
||||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||
}
|
||||
refreshAddons(force = true)
|
||||
}
|
||||
@@ -166,7 +165,7 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
NativeConfig.setDisabledAddons(
|
||||
currentGame.applicationId,
|
||||
currentGame.programId,
|
||||
currentList.mapNotNull {
|
||||
if (it.enabled) {
|
||||
null
|
||||
@@ -200,6 +199,6 @@ class AddonViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun gameKey(game: Game): String {
|
||||
return "${game.applicationId}|${game.path}"
|
||||
return "${game.programId}|${game.path}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ class DriverViewModel : ViewModel() {
|
||||
?: return@withContext
|
||||
val shaderDir = File(
|
||||
externalFilesDir.absolutePath +
|
||||
"/shader/" + game.shaderCacheName.lowercase()
|
||||
"/shader/" + game.settingsName.lowercase()
|
||||
)
|
||||
if (shaderDir.exists()) {
|
||||
shaderDir.deleteRecursively()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -35,46 +35,23 @@ class Game(
|
||||
val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime"
|
||||
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
|
||||
get() {
|
||||
return if (applicationIdLong == 0L) {
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
} else {
|
||||
"0" + applicationIdLong.toString(16).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
val programIdHex: String
|
||||
get() {
|
||||
val programIdLong = programId.toLong()
|
||||
return if (programIdLong == 0L) {
|
||||
"0"
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
} else {
|
||||
"0" + programIdLong.toString(16).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
val shaderCacheName: String
|
||||
get() = if (programIdLong == 0L) {
|
||||
FileUtil.getFilename(Uri.parse(path))
|
||||
} else {
|
||||
"0" + programIdLong.toString(16).uppercase()
|
||||
}
|
||||
|
||||
val applicationIdHex: String
|
||||
val programIdHex: String
|
||||
get() {
|
||||
return if (applicationIdLong == 0L) {
|
||||
val programIdLong = programId.toLong()
|
||||
return if (programIdLong == 0L) {
|
||||
"0"
|
||||
} else {
|
||||
"0" + applicationIdLong.toString(16).uppercase()
|
||||
"0" + programIdLong.toString(16).uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +61,10 @@ class Game(
|
||||
}.zip"
|
||||
|
||||
val saveDir: String
|
||||
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(applicationId)
|
||||
get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId)
|
||||
|
||||
val addonDir: String
|
||||
get() = DirectoryInitialization.userDirectory + "/load/" + applicationIdHex + "/"
|
||||
get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/"
|
||||
|
||||
val launchIntent: Intent
|
||||
get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply {
|
||||
|
||||
@@ -47,7 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import java.util.Locale
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.doOnNextLayout
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
@@ -59,7 +58,6 @@ class GamesFragment : Fragment() {
|
||||
private var originalHeaderLeftMargin: Int? = null
|
||||
|
||||
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
||||
private var fallbackBottomInset: Int = 0
|
||||
private var pendingPostReloadListSettle = false
|
||||
private var pendingPostReloadListSettleGeneration = 0
|
||||
private var gameListSubmitGeneration = 0
|
||||
@@ -227,12 +225,7 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
|
||||
}
|
||||
if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) {
|
||||
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
|
||||
doOnNextLayout { //Carousel: important to avoid overlap issues
|
||||
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
|
||||
}
|
||||
} else {
|
||||
if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) {
|
||||
(this as? CarouselRecyclerView)?.setupCarousel(false)
|
||||
}
|
||||
adapter = gameAdapter
|
||||
@@ -590,11 +583,6 @@ class GamesFragment : Fragment() {
|
||||
qlaunchButton.layoutParams = mlpQLaunch
|
||||
}
|
||||
|
||||
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
||||
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
|
||||
fallbackBottomInset = bottomInset
|
||||
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -65,7 +65,7 @@ object CustomSettingsHandler {
|
||||
// Initialize per-game config
|
||||
try {
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.applicationId, fileName)
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
|
||||
return game
|
||||
} catch (e: Exception) {
|
||||
@@ -333,20 +333,20 @@ object CustomSettingsHandler {
|
||||
*/
|
||||
fun findGameByTitleId(titleId: String, context: Context): Game? {
|
||||
Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId")
|
||||
// Convert the program ID to the application ID used by per-game settings.
|
||||
val applicationIdLong = try {
|
||||
titleId.toLong(16) and -8192L
|
||||
// Convert hex title ID to decimal for comparison with programId
|
||||
val programIdDecimal = try {
|
||||
titleId.toLong(16).toString()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId")
|
||||
return null
|
||||
}
|
||||
val applicationIdDecimal = applicationIdLong.toString()
|
||||
|
||||
// Expected hex format with "0" prefix
|
||||
val expectedHex = "0${applicationIdLong.toString(16).uppercase()}"
|
||||
val expectedHex = "0${titleId.uppercase()}"
|
||||
// First check cached games for fast lookup
|
||||
GameHelper.cachedGameList.find { game ->
|
||||
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}?.let { foundGame ->
|
||||
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
|
||||
return foundGame
|
||||
@@ -355,7 +355,8 @@ object CustomSettingsHandler {
|
||||
Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...")
|
||||
val allGames = GameHelper.getGames()
|
||||
val foundGame = allGames.find { game ->
|
||||
game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}
|
||||
if (foundGame != null) {
|
||||
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
|
||||
|
||||
@@ -170,12 +170,12 @@ object GameHelper {
|
||||
val game = getGame(it.uri, true, false)
|
||||
if (game != null) {
|
||||
games.add(game)
|
||||
if (game.applicationId != "0") {
|
||||
gamesByProgramId[game.applicationId] = game
|
||||
if (game.programId != "0") {
|
||||
gamesByProgramId[game.programId] = game
|
||||
}
|
||||
} else if (mountedContainer) {
|
||||
GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId ->
|
||||
gamesByProgramId[(programId and -8192L).toString()]
|
||||
gamesByProgramId[(programId and 0x800L.inv()).toString()]
|
||||
}?.let { existingGame ->
|
||||
NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId)
|
||||
existingGame.version = GameMetadata.getVersion(
|
||||
|
||||
@@ -80,6 +80,12 @@ object NativeConfig {
|
||||
@Synchronized
|
||||
external fun setShort(key: String, value: Short)
|
||||
|
||||
@Synchronized
|
||||
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
|
||||
|
||||
@Synchronized
|
||||
external fun setUnsignedShort(key: String, value: Int)
|
||||
|
||||
@Synchronized
|
||||
external fun getInt(key: String, needsGlobal: Boolean): Int
|
||||
|
||||
|
||||
@@ -12,13 +12,16 @@ import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||
/**
|
||||
* CarouselRecyclerView encapsulates all carousel content for the games UI.
|
||||
* It manages overlapping cards, center snapping, custom drawing order,
|
||||
@@ -32,7 +35,9 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
private var overlapFactor: Float = 0f
|
||||
private var overlapPx: Int = 0
|
||||
private var bottomInset: Int = -1
|
||||
private var bottomInset: Int = 0
|
||||
private var latestWindowInsets: WindowInsetsCompat? = null
|
||||
private var cardGeometryInitialized: Boolean = false
|
||||
private var overlapDecoration: OverlappingDecoration? = null
|
||||
private var pagerSnapHelper: PagerSnapHelper? = null
|
||||
private var scalingScrollListener: OnScrollListener? = null
|
||||
@@ -91,6 +96,38 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
init {
|
||||
setChildrenDrawingOrderEnabled(true)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
|
||||
latestWindowInsets = insets
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ViewCompat.requestApplyInsets(this)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (w != oldw || h != oldh) {
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (isCarouselMode) updateChildScalesAndAlpha()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
ViewCompat.requestApplyInsets(this)
|
||||
post { updateCardGeometry() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAdapter(adapter: Adapter<*>?) {
|
||||
@@ -103,6 +140,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
super.setAdapter(adapter)
|
||||
|
||||
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
}
|
||||
|
||||
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
|
||||
@@ -253,40 +292,71 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyInsetsReady(newBottomInset: Int) {
|
||||
if (bottomInset != newBottomInset) {
|
||||
bottomInset = newBottomInset
|
||||
}
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int {
|
||||
val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) {
|
||||
0
|
||||
} else {
|
||||
setupCarousel(false)
|
||||
windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||
}
|
||||
val gestureInsets = windowInsets.getInsetsIgnoringVisibility(
|
||||
WindowInsetsCompat.Type.systemGestures()
|
||||
)
|
||||
val cutoutInsets = windowInsets.getInsetsIgnoringVisibility(
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom)
|
||||
}
|
||||
|
||||
fun notifyLaidOut(fallBackBottomInset: Int) {
|
||||
if (bottomInset < 0) bottomInset = fallBackBottomInset
|
||||
var gameAdapter = adapter as? GameAdapter ?: return
|
||||
var newCardSize = cardSize(bottomInset)
|
||||
if (gameAdapter.cardSize != newCardSize) {
|
||||
gameAdapter.setCardSize(newCardSize)
|
||||
}
|
||||
private fun updateCardGeometry() {
|
||||
if (!isCarouselMode || height <= 0) return
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
}
|
||||
}
|
||||
val gameAdapter = adapter as? GameAdapter ?: return
|
||||
val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return
|
||||
|
||||
fun cardSize(bottomInset: Int): Int {
|
||||
if (cardGeometryInitialized && !hasWindowFocus()) return
|
||||
|
||||
val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height)
|
||||
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
|
||||
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
|
||||
0f,
|
||||
1f
|
||||
)
|
||||
val scaledHeight = height * userFactor
|
||||
val availableHeight = height - bottomInset
|
||||
return minOf(scaledHeight.toInt(), availableHeight.toInt())
|
||||
val screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||
val screenHeight = resources.displayMetrics.heightPixels.toFloat()
|
||||
val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f))
|
||||
.pow(0.75f)
|
||||
.coerceIn(0.5f, 1f)
|
||||
val newCardSize = minOf(
|
||||
(height * userFactor).toInt(),
|
||||
height - newBottomInset,
|
||||
(height * aspectFactor).toInt()
|
||||
)
|
||||
if (newCardSize <= 0) return
|
||||
val insetChanged = bottomInset != newBottomInset
|
||||
val cardSizeChanged = gameAdapter.cardSize != newCardSize
|
||||
|
||||
bottomInset = newBottomInset
|
||||
cardGeometryInitialized = true
|
||||
|
||||
if (cardSizeChanged) gameAdapter.setCardSize(newCardSize)
|
||||
if (insetChanged || cardSizeChanged) setupCarousel(true)
|
||||
}
|
||||
|
||||
private fun applyCarouselPadding() {
|
||||
if (!isCarouselMode) return
|
||||
|
||||
val gameAdapter = adapter as? GameAdapter ?: return
|
||||
val cardSize = gameAdapter.cardSize
|
||||
if (cardSize <= 0 || bottomInset < 0) return
|
||||
|
||||
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0)
|
||||
val sidePadding = (width - cardSize) / 2
|
||||
if (paddingLeft != sidePadding || paddingTop != topPadding ||
|
||||
paddingRight != sidePadding || paddingBottom != 0
|
||||
) {
|
||||
setPadding(sidePadding, topPadding, sidePadding, 0)
|
||||
}
|
||||
clipToPadding = false
|
||||
}
|
||||
|
||||
fun setupCarousel(enabled: Boolean) {
|
||||
@@ -315,9 +385,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
internalFlingMultiplier
|
||||
).coerceIn(1f, 5f)
|
||||
|
||||
// Detach SnapHelper during setup
|
||||
pagerSnapHelper?.attachToRecyclerView(null)
|
||||
|
||||
// Add overlap decoration if not present
|
||||
if (overlapDecoration == null) {
|
||||
overlapDecoration = OverlappingDecoration(overlapPx)
|
||||
@@ -335,12 +402,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
addOnScrollListener(scalingScrollListener!!)
|
||||
}
|
||||
|
||||
if (cardSize > 0) {
|
||||
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically
|
||||
val sidePadding = (width - cardSize) / 2 // Center first/last card
|
||||
setPadding(sidePadding, topPadding, sidePadding, 0)
|
||||
clipToPadding = false
|
||||
}
|
||||
applyCarouselPadding()
|
||||
|
||||
if (pagerSnapHelper == null) {
|
||||
pagerSnapHelper = CenterPagerSnapHelper()
|
||||
@@ -362,6 +424,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
savedItemAnimator = null
|
||||
}
|
||||
cardGeometryInitialized = false
|
||||
useCustomDrawingOrder = false
|
||||
// Reset padding and fling
|
||||
setPadding(0, 0, 0, 0)
|
||||
|
||||
@@ -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,
|
||||
jstring jprogramId,
|
||||
jstring jupdatePath) {
|
||||
const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
u64 program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
std::string updatePath = Common::Android::GetJString(env, jupdatePath);
|
||||
std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>(
|
||||
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& nca_details : item.second) {
|
||||
if (nca_details.second->GetName().ends_with(".cnmt.nca")) {
|
||||
const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId());
|
||||
auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL;
|
||||
if (update_id == program_id) {
|
||||
return true;
|
||||
}
|
||||
@@ -1300,8 +1300,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
|
||||
Common::RefreshThreadPolicies();
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
|
||||
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
|
||||
@@ -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) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
|
||||
return FirmwareManager::GameRequiresFirmware(program_id);
|
||||
}
|
||||
@@ -1575,21 +1575,20 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
program_id);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj,
|
||||
jstring jprogramId) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(
|
||||
EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id);
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId,
|
||||
jstring jname) {
|
||||
const auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
program_id, Common::Android::GetJString(env, jname));
|
||||
}
|
||||
@@ -1636,7 +1635,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 jprogramId) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
if (program_id == 0) {
|
||||
return Common::Android::ToJString(env, "");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "native.h"
|
||||
@@ -57,7 +56,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,
|
||||
jstring jprogramId,
|
||||
jstring jfileName) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto file_name = Common::Android::GetJString(env, jfileName);
|
||||
const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
|
||||
per_game_config =
|
||||
@@ -131,6 +130,25 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
|
||||
setting->SetValue(value);
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
|
||||
jstring jkey,
|
||||
jboolean needGlobal) {
|
||||
auto setting = getSetting<u16>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
|
||||
jstring jkey, jint value) {
|
||||
auto setting = getSetting<u16>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
setting->SetValue(static_cast<u16>(value));
|
||||
}
|
||||
|
||||
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jboolean needGlobal) {
|
||||
auto setting = getSetting<int>(env, jkey);
|
||||
@@ -323,7 +341,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,
|
||||
jstring jprogramId) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
auto& disabledAddons = Settings::values.disabled_addons[program_id];
|
||||
jobjectArray jdisabledAddonsArray =
|
||||
env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(),
|
||||
@@ -338,7 +356,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv
|
||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj,
|
||||
jstring jprogramId,
|
||||
jobjectArray jdisabledAddons) {
|
||||
const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId));
|
||||
auto program_id = EmulationSession::GetProgramId(env, jprogramId);
|
||||
Settings::values.disabled_addons[program_id].clear();
|
||||
std::vector<std::string> disabled_addons;
|
||||
const int size = env->GetArrayLength(jdisabledAddons);
|
||||
|
||||
+27
-15
@@ -329,7 +329,7 @@ struct LogcatBackend : public Backend {
|
||||
}
|
||||
}();
|
||||
auto const df = GetDirectFormatArgs(entry);
|
||||
__android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
|
||||
__android_log_print(android_log_priority, "YuzuNative", "%s %s:%u:%s: %s", df.class_name, entry.filename, entry.line_num, entry.function, entry.message);
|
||||
}
|
||||
void Flush() noexcept override {}
|
||||
};
|
||||
@@ -428,21 +428,33 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, u
|
||||
auto const flush = ::Settings::values.log_flush_line.GetValue();
|
||||
char buffer[BUFSIZ];
|
||||
auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
|
||||
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
|
||||
logging_instance->ForEachBackend([=](Backend& backend) {
|
||||
backend.Write(Entry{
|
||||
.message = buffer,
|
||||
.message_len = (std::min)(result.size, sizeof(buffer) - 1),
|
||||
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
||||
.log_class = log_class,
|
||||
.log_level = log_level,
|
||||
.filename = TrimSourcePath(filename),
|
||||
.function = function,
|
||||
.line_num = line_num,
|
||||
Entry e{
|
||||
.message = nullptr,
|
||||
.message_len = 0,
|
||||
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
|
||||
.log_class = log_class,
|
||||
.log_level = log_level,
|
||||
.filename = TrimSourcePath(filename),
|
||||
.function = function,
|
||||
.line_num = line_num,
|
||||
};
|
||||
if (result.size <= sizeof(buffer) - 1) {
|
||||
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0';
|
||||
e.message = buffer;
|
||||
e.message_len = (std::min)(result.size, sizeof(buffer) - 1);
|
||||
logging_instance->ForEachBackend([=](Backend& backend) {
|
||||
backend.Write(e);
|
||||
if (flush) backend.Flush();
|
||||
});
|
||||
if (flush)
|
||||
backend.Flush();
|
||||
});
|
||||
} else {
|
||||
std::string s = fmt::vformat(format, args);
|
||||
e.message = s.c_str();
|
||||
e.message_len = s.size();
|
||||
logging_instance->ForEachBackend([=](Backend& backend) {
|
||||
backend.Write(e);
|
||||
if (flush) backend.Flush();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Common::Log
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
namespace Common::Net {
|
||||
|
||||
typedef struct {
|
||||
struct Asset {
|
||||
std::string name;
|
||||
std::string url;
|
||||
std::string path;
|
||||
std::string filename;
|
||||
} Asset;
|
||||
};
|
||||
|
||||
typedef struct Release {
|
||||
struct Release {
|
||||
std::string title;
|
||||
std::string body;
|
||||
std::string tag;
|
||||
@@ -39,7 +39,7 @@ typedef struct Release {
|
||||
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
|
||||
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
|
||||
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
|
||||
} Release;
|
||||
};
|
||||
|
||||
// Make a request via httplib, and return the response body if applicable.
|
||||
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
|
||||
|
||||
@@ -126,9 +126,9 @@ void LogSettings() {
|
||||
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
|
||||
setting->GetLabel());
|
||||
if (is_default)
|
||||
settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize()));
|
||||
settings_list.push_back(fmt::format("{}: {}", name, setting->Canonicalize()));
|
||||
else
|
||||
settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize()));
|
||||
settings_list.push_front(fmt::format("{}: {}", name, setting->Canonicalize()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ void LogSettings() {
|
||||
#undef LOG_PATH
|
||||
}
|
||||
|
||||
bool getDebugKnobAt(u8 i) {
|
||||
bool GetDebugKnobAt(u8 i) {
|
||||
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -904,7 +904,7 @@ struct Values {
|
||||
0,
|
||||
65535,
|
||||
"debug_knobs",
|
||||
Category::Debugging,
|
||||
Category::System,
|
||||
Specialization::Countable,
|
||||
true,
|
||||
true};
|
||||
@@ -947,7 +947,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
||||
|
||||
[[nodiscard]] size_t FrameGenMaxGenerations();
|
||||
|
||||
bool getDebugKnobAt(u8 i);
|
||||
bool GetDebugKnobAt(u8 i);
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
bool IsGPULevelHigh();
|
||||
|
||||
+1
-2
@@ -17,7 +17,6 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
|
||||
#include "launch_timestamp_cache.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -397,7 +396,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
GameSettings::LoadOverrides(FileSys::GetBaseTitleID(program_id), gpu_core->Renderer());
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
|
||||
+189
-236
@@ -3,10 +3,12 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <span>
|
||||
#include <cctype>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging.h"
|
||||
@@ -22,61 +24,30 @@ enum class IPSFileType {
|
||||
Error,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
|
||||
{"\\a", "\a"},
|
||||
{"\\b", "\b"},
|
||||
{"\\f", "\f"},
|
||||
{"\\n", "\n"},
|
||||
{"\\r", "\r"},
|
||||
{"\\t", "\t"},
|
||||
{"\\v", "\v"},
|
||||
{"\\\\", "\\"},
|
||||
{"\\\'", "\'"},
|
||||
{"\\\"", "\""},
|
||||
{"\\\?", "\?"},
|
||||
}};
|
||||
|
||||
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
|
||||
if (magic.size() != 5) {
|
||||
return IPSFileType::Error;
|
||||
static IPSFileType IdentifyMagic(std::span<const u8> magic) {
|
||||
if (magic.size() >= 5) {
|
||||
if (std::memcmp(magic.data(), "PATCH", 5) == 0)
|
||||
return IPSFileType::IPS;
|
||||
if (std::memcmp(magic.data(), "IPS32", 5) == 0)
|
||||
return IPSFileType::IPS32;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
|
||||
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
|
||||
return IPSFileType::IPS;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
|
||||
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
|
||||
return IPSFileType::IPS32;
|
||||
}
|
||||
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
|
||||
static constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
|
||||
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
|
||||
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
|
||||
static bool IsEOF(IPSFileType type, std::span<const u8> magic) {
|
||||
return (type == IPSFileType::IPS && magic.size() > 3 && std::memcmp(magic.data(), "EOF", 3) == 0)
|
||||
|| (type == IPSFileType::IPS32 && magic.size() > 4 && std::memcmp(magic.data(), "EEOF", 4) == 0);
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
|
||||
auto in_data = in->ReadAllBytes();
|
||||
auto const type = IdentifyMagic(in_data);
|
||||
if (type == IPSFileType::Error)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
if (in_data.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
||||
u64 offset = 5; // After header
|
||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||
@@ -85,12 +56,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
break;
|
||||
}
|
||||
|
||||
u32 real_offset{};
|
||||
if (type == IPSFileType::IPS32)
|
||||
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
|
||||
else
|
||||
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
|
||||
|
||||
u32 real_offset = (type == IPSFileType::IPS32)
|
||||
? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3])
|
||||
: ((temp[0] << 16) | (temp[1] << 8) | temp[2]);
|
||||
if (real_offset > in_data.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -113,34 +81,35 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = static_cast<u16>(in_data.size() - real_offset);
|
||||
rle_size = u16(in_data.size() - real_offset);
|
||||
std::memset(in_data.data() + real_offset, *data, rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
read = static_cast<u16>(in_data.size() - real_offset);
|
||||
read = u16(in_data.size() - real_offset);
|
||||
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
|
||||
return nullptr;
|
||||
offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsEOF(type, temp)) {
|
||||
return nullptr;
|
||||
if (IsEOF(type, temp)) {
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
struct IPSwitchRecord {
|
||||
std::array<uint8_t, 256 - sizeof(size_t)> data;
|
||||
size_t count;
|
||||
};
|
||||
struct IPSwitchCompiler::IPSwitchPatch {
|
||||
std::string name;
|
||||
ankerl::unordered_dense::map<u32, IPSwitchRecord> records;
|
||||
bool enabled;
|
||||
std::map<u32, std::vector<u8>> records;
|
||||
};
|
||||
|
||||
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
|
||||
Parse();
|
||||
Parse(patch_text->ReadAllBytes());
|
||||
}
|
||||
|
||||
IPSwitchCompiler::~IPSwitchCompiler() = default;
|
||||
@@ -149,201 +118,185 @@ std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
|
||||
return nso_build_id;
|
||||
}
|
||||
|
||||
bool IPSwitchCompiler::IsValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool StartsWith(std::string_view base, std::string_view check) {
|
||||
return base.size() >= check.size() && base.substr(0, check.size()) == check;
|
||||
}
|
||||
|
||||
static std::string EscapeStringSequences(std::string in) {
|
||||
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
|
||||
for (auto index = in.find(seq.first); index != std::string::npos;
|
||||
index = in.find(seq.first, index)) {
|
||||
in.replace(index, std::strlen(seq.first), seq.second);
|
||||
index += std::strlen(seq.second);
|
||||
static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
|
||||
IPSwitchRecord r{};
|
||||
for (auto it = sv.cbegin(); it != sv.cend(); ) {
|
||||
if (*it == '\\' && it + 1 < sv.cend()) {
|
||||
switch (it[1]) {
|
||||
case 'a': r.data[r.count] = '\a'; break;
|
||||
case 'b': r.data[r.count] = '\b'; break;
|
||||
case 'e': r.data[r.count] = '\e'; break;
|
||||
case 'f': r.data[r.count] = '\f'; break;
|
||||
case 'n': r.data[r.count] = '\n'; break;
|
||||
case 'r': r.data[r.count] = '\r'; break;
|
||||
case 't': r.data[r.count] = '\t'; break;
|
||||
case 'v': r.data[r.count] = '\v'; break;
|
||||
case '?': r.data[r.count] = '\?'; break;
|
||||
default: r.data[r.count] = it[1]; break;
|
||||
}
|
||||
++r.count;
|
||||
it += 2;
|
||||
} else {
|
||||
++r.count;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
return r;
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
} else if (StartsWith(line, "@big-endian")) {
|
||||
// Set values to read as big endian
|
||||
is_little_endian = false;
|
||||
} else if (StartsWith(line, "@flag print_values")) {
|
||||
// Force printing of applied values
|
||||
print_values = true;
|
||||
}
|
||||
}
|
||||
void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
|
||||
LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName());
|
||||
bool is_little_endian = true;
|
||||
s64 offset_shift = 0;
|
||||
//bool print_values = false;
|
||||
auto const parse_line = [&](std::string_view const line) {
|
||||
// Keep in mind lines have trimmed spaces (at the end & start)!
|
||||
LOG_INFO(Loader, "<{}>", line);
|
||||
// IPSwitch is case insensitive
|
||||
// Yes this is how the logic goes for the main reference parsers!
|
||||
if (line.size() > 2 && line[0] == '@') {
|
||||
switch (line[1]) {
|
||||
// yes, @nsobid too -- NSO Build ID Specifier
|
||||
case 'n':
|
||||
case 'N':
|
||||
nso_build_id = Common::HexStringToArray<0x20>(fmt::format("{:0<64}", line.substr(8)));
|
||||
break;
|
||||
// @stop
|
||||
case 's':
|
||||
case 'S':
|
||||
return false;
|
||||
// @enabled
|
||||
case 'e':
|
||||
case 'E':
|
||||
patches.push_back({{}, true});
|
||||
break;
|
||||
// @disabled
|
||||
case 'd':
|
||||
case 'D':
|
||||
patches.push_back({{}, false});
|
||||
break;
|
||||
// @flag
|
||||
case 'f':
|
||||
case 'F': {
|
||||
if (line.starts_with("@flag offset_shift")) {
|
||||
offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag
|
||||
} else if (line.starts_with("@flag print_values")) {
|
||||
//print_values = true; // Force printing of applied values
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'l':
|
||||
case 'L':
|
||||
is_little_endian = true;
|
||||
break;
|
||||
// IPS parsers dont support big endian no more, we do due to backcompat
|
||||
case 'b':
|
||||
case 'B':
|
||||
is_little_endian = false;
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Loader, "Unknown flag {}", line);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
size_t offset = size_t(std::strtoul(line.data(), nullptr, 16));
|
||||
offset += size_t(offset_shift);
|
||||
if (auto const first_quote = line.find_first_of("\"\'"); first_quote != std::string::npos) {
|
||||
// string replacement
|
||||
char quote = line[first_quote];
|
||||
auto const start = line.cbegin() + first_quote + 1;
|
||||
auto end = start;
|
||||
for (; end < line.cend() && *end != quote; )
|
||||
end += (*end == '\\') ? 2 : 1;
|
||||
if (start <= line.cend() && end <= line.cend()) {
|
||||
LOG_INFO(Loader, "[S] value @ {:#08X} ", offset);
|
||||
patches.back().records.insert_or_assign(u32(offset), EscapeStringSequences({start, end}));
|
||||
} else {
|
||||
LOG_WARNING(Loader, "invalid string");
|
||||
}
|
||||
} else if (auto const first_space = line.find_last_of(" /\t\r\n"); first_space != std::string::npos) {
|
||||
IPSwitchRecord r{}; // hex replacement
|
||||
auto const start = line.cbegin() + first_space + 1;
|
||||
auto const end = line.cend();
|
||||
if (start <= line.cend() && end <= line.cend()) {
|
||||
// Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun!
|
||||
auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
|
||||
std::memcpy(r.data.data(), hs.data(), hs.size());
|
||||
r.count = hs.size();
|
||||
LOG_INFO(Loader, "[H] value @ {:#08X}", offset);
|
||||
patches.back().records.insert_or_assign(u32(offset), std::move(r));
|
||||
} else {
|
||||
LOG_WARNING(Loader, "invalid line");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Loader, "unhandled line!");
|
||||
}
|
||||
}
|
||||
return true; //continue
|
||||
};
|
||||
|
||||
void IPSwitchCompiler::Parse() {
|
||||
const auto bytes = patch_text->ReadAllBytes();
|
||||
std::stringstream s;
|
||||
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(s, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
// Remove midline comments
|
||||
std::size_t comment_index = std::string::npos;
|
||||
bool within_string = false;
|
||||
for (std::size_t k = 0; k < line.size(); ++k) {
|
||||
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
|
||||
within_string = !within_string;
|
||||
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
|
||||
comment_index = k;
|
||||
for (auto it = bytes.begin(); it < bytes.end(); ) {
|
||||
auto const start = it;
|
||||
auto end = start;
|
||||
for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end)
|
||||
;
|
||||
it = end + 1; //prepare for next line
|
||||
std::string_view const sline{
|
||||
reinterpret_cast<const char*>(bytes.data() + std::distance(bytes.begin(), start)),
|
||||
size_t(std::distance(start, end))
|
||||
};
|
||||
if (sline.size() > 0) {
|
||||
auto p = sline.cbegin();
|
||||
// skip space off line
|
||||
for (; p < sline.cend() && std::isspace(*p); ++p)
|
||||
;
|
||||
// now make a nominal preprocessed line: remove comments
|
||||
char quote = '\0';
|
||||
auto const sline_start = p;
|
||||
for (; p < sline.cend(); ) {
|
||||
// we dont check for "//", IPS checks for '/' only...
|
||||
if ((!quote && p[0] == '/')
|
||||
|| (!quote && p[0] == '#')) {
|
||||
break;
|
||||
} else if (p[0] == '\"' || p[0] == '\'') {
|
||||
quote = (p[0] == quote) ? '\0' : p[0];
|
||||
++p;
|
||||
} else if (p + 1 < sline.cend() && p[0] == '\\') {
|
||||
p += 2;
|
||||
} else {
|
||||
++p;
|
||||
}
|
||||
}
|
||||
// now we have the preprocessed string ;)
|
||||
std::string_view pp_str(sline_start, p);
|
||||
if (pp_str.size() > 0 && !parse_line(pp_str)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
|
||||
last_comment = line.substr(comment_index + 2);
|
||||
line = line.substr(0, comment_index);
|
||||
}
|
||||
|
||||
if (StartsWith(line, "@stop")) {
|
||||
// Force stop
|
||||
break;
|
||||
} else if (StartsWith(line, "@nsobid-")) {
|
||||
// NSO Build ID Specifier
|
||||
const auto raw_build_id = fmt::format("{:0<64}", line.substr(8));
|
||||
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
|
||||
} else if (StartsWith(line, "#")) {
|
||||
// Mandatory Comment
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
|
||||
patch_text->GetName(), line.substr(1));
|
||||
} else if (StartsWith(line, "//")) {
|
||||
// Normal Comment
|
||||
last_comment = line.substr(2);
|
||||
if (last_comment.find_first_not_of(' ') == std::string::npos)
|
||||
continue;
|
||||
if (last_comment.find_first_not_of(' ') != 0)
|
||||
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
|
||||
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
|
||||
// Start of patch
|
||||
const auto enabled = StartsWith(line, "@enabled");
|
||||
if (i == 0)
|
||||
return;
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
|
||||
patch_text->GetName(), last_comment, line.substr(1));
|
||||
|
||||
IPSwitchPatch patch{last_comment, enabled, {}};
|
||||
|
||||
// Read rest of patch
|
||||
while (true) {
|
||||
if (i + 1 >= lines.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& patch_line = lines[++i];
|
||||
|
||||
// Patch line may contain comments
|
||||
if (StartsWith(patch_line, "//") || StartsWith(patch_line, "#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start of new patch
|
||||
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for a flag
|
||||
if (StartsWith(patch_line, "@")) {
|
||||
ParseFlag(patch_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
// 9 - first char of replacement val
|
||||
if (patch_line[9] == '\"') {
|
||||
// string replacement
|
||||
auto end_index = patch_line.find('\"', 10);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
while (patch_line[end_index - 1] == '\\') {
|
||||
end_index = patch_line.find('\"', end_index + 1);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
}
|
||||
|
||||
auto value = patch_line.substr(10, end_index - 10);
|
||||
value = EscapeStringSequences(value);
|
||||
replace.reserve(value.size());
|
||||
std::copy(value.begin(), value.end(), std::back_inserter(replace));
|
||||
} else {
|
||||
// hex replacement
|
||||
const auto value =
|
||||
patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9);
|
||||
replace = Common::HexStringToVector(value, is_little_endian);
|
||||
}
|
||||
|
||||
if (print_values) {
|
||||
LOG_INFO(Loader,
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset {:#08x} "
|
||||
"with byte string '{}'",
|
||||
patch_text->GetName(), offset, Common::HexToString(replace));
|
||||
}
|
||||
|
||||
patch.records.insert_or_assign(static_cast<u32>(offset), std::move(replace));
|
||||
}
|
||||
|
||||
patches.push_back(std::move(patch));
|
||||
} else if (StartsWith(line, "@")) {
|
||||
ParseFlag(line);
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
|
||||
if (in == nullptr || !valid)
|
||||
if (in == nullptr)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
if (!patch.enabled)
|
||||
continue;
|
||||
|
||||
for (const auto& record : patch.records) {
|
||||
if (record.first >= in_data.size())
|
||||
continue;
|
||||
auto replace_size = record.second.size();
|
||||
if (record.first + replace_size > in_data.size())
|
||||
replace_size = in_data.size() - record.first;
|
||||
for (std::size_t i = 0; i < replace_size; ++i)
|
||||
in_data[i + record.first] = record.second[i];
|
||||
if (patch.enabled) {
|
||||
for (const auto& record : patch.records) {
|
||||
if (record.first < in_data.size()) {
|
||||
auto replace_size = record.second.count;
|
||||
if (record.first + replace_size > in_data.size())
|
||||
replace_size = in_data.size() - record.first;
|
||||
std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
|
||||
} else {
|
||||
LOG_WARNING(Loader, "record offs={:x},size={:x}", record.first, record.second.data.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
@@ -20,24 +23,17 @@ public:
|
||||
~IPSwitchCompiler();
|
||||
|
||||
std::array<u8, 0x20> GetBuildID() const;
|
||||
bool IsValid() const;
|
||||
VirtualFile Apply(const VirtualFile& in) const;
|
||||
|
||||
private:
|
||||
struct IPSwitchPatch;
|
||||
|
||||
void ParseFlag(const std::string& flag);
|
||||
void Parse();
|
||||
|
||||
bool valid = false;
|
||||
void Parse(std::span<u8 const> bytes);
|
||||
|
||||
VirtualFile patch_text;
|
||||
std::vector<IPSwitchPatch> patches;
|
||||
std::array<u8, 0x20> nso_build_id{};
|
||||
bool is_little_endian = false;
|
||||
s64 offset_shift = 0;
|
||||
bool print_values = false;
|
||||
std::string last_comment = "";
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -161,7 +161,7 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64
|
||||
PatchManager::PatchManager(u64 title_id_,
|
||||
const Service::FileSystem::FileSystemController& fs_controller_,
|
||||
const ContentProvider& content_provider_)
|
||||
: title_id{title_id_}, application_id{GetBaseTitleID(title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
|
||||
: title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {}
|
||||
|
||||
PatchManager::~PatchManager() = default;
|
||||
|
||||
@@ -169,41 +169,13 @@ u64 PatchManager::GetTitleID() const {
|
||||
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 {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
if (exefs == nullptr)
|
||||
return exefs;
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
bool update_disabled = true;
|
||||
std::optional<u32> enabled_version;
|
||||
@@ -211,7 +183,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
bool checked_manual = false;
|
||||
|
||||
const auto* content_union = static_cast<const ContentProviderUnion*>(&content_provider);
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
|
||||
if (content_union) {
|
||||
// First, check ExternalContentProvider
|
||||
@@ -331,21 +303,17 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
|
||||
// LayeredExeFS
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
const auto sdmc_load_dirs = GetSDMCModificationLoadRoots();
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
for (const auto& sdmc_load_dir : sdmc_load_dirs) {
|
||||
patch_dirs.push_back(sdmc_load_dir);
|
||||
}
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
std::vector<VirtualDir> patch_dirs = {sdmc_load_dir};
|
||||
if (load_dir != nullptr) {
|
||||
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();
|
||||
});
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
@@ -377,9 +345,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const {
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
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 nso_build_id = fmt::format("{:0<64}", build_id);
|
||||
|
||||
std::vector<VirtualFile> out;
|
||||
@@ -393,16 +360,11 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() == "ips") {
|
||||
auto name = file->GetName();
|
||||
|
||||
const auto this_build_id =
|
||||
fmt::format("{:0<64}", name.substr(0, name.find('.')));
|
||||
const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.')));
|
||||
if (nso_build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
} else if (file->GetExtension() == "pchtxt") {
|
||||
IPSwitchCompiler compiler{file};
|
||||
if (!compiler.IsValid())
|
||||
continue;
|
||||
|
||||
const auto this_build_id = Common::HexToString(compiler.GetBuildID());
|
||||
if (nso_build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
@@ -410,7 +372,6 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -444,20 +405,15 @@ 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);
|
||||
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return nso;
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
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(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::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);
|
||||
|
||||
auto out = nso;
|
||||
@@ -492,39 +448,29 @@ 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);
|
||||
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
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(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::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();
|
||||
}
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
const auto load_dirs = GetModificationLoadRoots();
|
||||
if (load_dirs.empty()) {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
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(); });
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::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
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
@@ -540,52 +486,39 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
|
||||
}
|
||||
// Uncareless user-friendly loading of patches (must start with 'cheat_')
|
||||
// <mod dir> / <cheat file>.txt
|
||||
for (const auto& load_dir : load_dirs) {
|
||||
for (auto const& f : load_dir->GetFiles()) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
for (auto const& f : load_dir->GetFiles()) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, u64 application_id, ContentRecordType type,
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type,
|
||||
const Service::FileSystem::FileSystemController& fs_controller) {
|
||||
std::vector<VirtualDir> load_dirs{fs_controller.GetModificationLoadRoot(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);
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data &&
|
||||
type != ContentRecordType::HtmlDocument) ||
|
||||
(load_dirs.empty() && sdmc_load_dirs.empty())) {
|
||||
(load_dir == nullptr && sdmc_load_dir == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
std::vector<VirtualDir> patch_dirs;
|
||||
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());
|
||||
}
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories();
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) {
|
||||
patch_dirs.insert(patch_dirs.end(), sdmc_load_dirs.begin(), sdmc_load_dirs.end());
|
||||
patch_dirs.push_back(sdmc_load_dir);
|
||||
}
|
||||
std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) {
|
||||
return l->GetName() < r->GetName();
|
||||
});
|
||||
std::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_ext;
|
||||
@@ -657,8 +590,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
auto romfs = base_romfs;
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
bool update_disabled = true;
|
||||
std::optional<u32> enabled_version;
|
||||
@@ -765,7 +698,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
||||
|
||||
// LayeredFS
|
||||
if (apply_layeredfs) {
|
||||
ApplyLayeredFS(romfs, title_id, application_id, type, fs_controller);
|
||||
ApplyLayeredFS(romfs, title_id, type, fs_controller);
|
||||
}
|
||||
|
||||
return romfs;
|
||||
@@ -777,10 +710,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
std::vector<Patch> out;
|
||||
const auto& disabled = Settings::values.disabled_addons[application_id];
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
|
||||
std::vector<Patch> external_update_patches;
|
||||
|
||||
@@ -929,7 +862,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
.version = "",
|
||||
.type = PatchType::Update,
|
||||
.program_id = title_id,
|
||||
.title_id = update_tid,
|
||||
.title_id = title_id,
|
||||
.source = PatchSource::Unknown,
|
||||
.numeric_version = 0};
|
||||
|
||||
@@ -955,7 +888,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
for (const auto& mod_dir : GetModificationLoadRoots()) {
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (auto const& f : mod_dir->GetFiles())
|
||||
if (auto const name = f->GetName(); name.starts_with("cheat_")) {
|
||||
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
|
||||
@@ -1022,7 +956,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
// SDMC mod directory (RomFS LayeredFS)
|
||||
for (const auto& sdmc_mod_dir : GetSDMCModificationLoadRoots()) {
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredExeFS");
|
||||
@@ -1057,10 +992,10 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
dlc_match.reserve(dlc_entries_with_origin.size());
|
||||
for (const auto& [slot, entry] : dlc_entries_with_origin) {
|
||||
const auto base_tid = GetBaseTitleID(entry.title_id);
|
||||
const bool matches_base = base_tid == application_id;
|
||||
const bool matches_base = base_tid == title_id;
|
||||
if (!matches_base) {
|
||||
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
|
||||
entry.title_id, base_tid, application_id);
|
||||
entry.title_id, base_tid, title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1135,22 +1070,16 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
}
|
||||
|
||||
std::optional<u32> PatchManager::GetGameVersion() const {
|
||||
const auto update_tid = GetUpdateTitleIDForContent();
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
return content_provider.GetEntryVersion(update_tid);
|
||||
}
|
||||
|
||||
if (const auto version = content_provider.GetEntryVersion(title_id); version.has_value()) {
|
||||
return version;
|
||||
}
|
||||
return content_provider.GetEntryVersion(application_id);
|
||||
return content_provider.GetEntryVersion(title_id);
|
||||
}
|
||||
|
||||
PatchManager::Metadata PatchManager::GetControlMetadata() const {
|
||||
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);
|
||||
}
|
||||
const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr) {
|
||||
return {};
|
||||
}
|
||||
@@ -1226,14 +1155,8 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
auto metadata = pm.GetControlMetadata();
|
||||
if (metadata.first != nullptr)
|
||||
return metadata;
|
||||
const auto update_id = FileSys::GetUpdateTitleID(application_id);
|
||||
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();
|
||||
const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()};
|
||||
return pm_update.GetControlMetadata();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
@@ -110,14 +109,10 @@ public:
|
||||
[[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept;
|
||||
|
||||
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,
|
||||
const std::string& build_id) const;
|
||||
|
||||
u64 title_id;
|
||||
u64 application_id;
|
||||
const Service::FileSystem::FileSystemController& fs_controller;
|
||||
const ContentProvider& content_provider;
|
||||
};
|
||||
|
||||
@@ -1216,7 +1216,7 @@ Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_messa
|
||||
}
|
||||
|
||||
Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr, bool is_hle) {
|
||||
KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) {
|
||||
// Lock the session.
|
||||
KScopedLightLock lk{m_lock};
|
||||
|
||||
@@ -1248,7 +1248,7 @@ Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, u
|
||||
KEvent* event = request->GetEvent();
|
||||
|
||||
// Check whether we're closed.
|
||||
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed());
|
||||
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed);
|
||||
|
||||
Result result = ResultSuccess;
|
||||
if (!closed) {
|
||||
|
||||
@@ -54,14 +54,14 @@ public:
|
||||
|
||||
Result OnRequest(KernelCore& kernel, KSessionRequest* request);
|
||||
Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr, bool is_hle = false);
|
||||
KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false);
|
||||
Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr,
|
||||
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
|
||||
std::weak_ptr<Service::SessionRequestManager> manager = {});
|
||||
|
||||
Result SendReplyHLE(KernelCore& kernel) {
|
||||
R_RETURN(this->SendReply(kernel, 0, 0, 0, true));
|
||||
Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) {
|
||||
R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed));
|
||||
}
|
||||
|
||||
Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context,
|
||||
|
||||
@@ -17,56 +17,12 @@
|
||||
|
||||
namespace Kernel::Svc {
|
||||
|
||||
constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250);
|
||||
const auto MAX_MSG_SIZE = 0x1000;
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
Result OutputDebugString(Core::System& system, u64 address, u64 len) {
|
||||
static struct DebugFlusher {
|
||||
std::string msg_buffer;
|
||||
std::mutex msg_mutex;
|
||||
std::condition_variable msg_cv;
|
||||
std::chrono::steady_clock::time_point last_msg_time;
|
||||
std::optional<std::jthread> thread;
|
||||
} flusher_data;
|
||||
R_SUCCEED_IF(len == 0);
|
||||
// Only start the thread the very first time this function is called
|
||||
if (!flusher_data.thread) {
|
||||
flusher_data.thread.emplace([](std::stop_token stop_token) {
|
||||
while (!stop_token.stop_requested()) {
|
||||
std::unique_lock lock(flusher_data.msg_mutex);
|
||||
flusher_data.msg_cv.wait(lock, [&stop_token] {
|
||||
return !flusher_data.msg_buffer.empty() || stop_token.stop_requested();
|
||||
});
|
||||
if (stop_token.stop_requested() && flusher_data.msg_buffer.empty())
|
||||
break;
|
||||
auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME;
|
||||
bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] {
|
||||
return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested();
|
||||
});
|
||||
if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) {
|
||||
if (!flusher_data.msg_buffer.empty()) {
|
||||
// Remove trailing newline as LOG_INFO adds that anyways
|
||||
if (flusher_data.msg_buffer.back() == '\n')
|
||||
flusher_data.msg_buffer.pop_back();
|
||||
|
||||
LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer);
|
||||
flusher_data.msg_buffer.clear();
|
||||
}
|
||||
if (stop_token.stop_requested()) break;
|
||||
}
|
||||
}
|
||||
flusher_data.msg_cv.notify_all();
|
||||
});
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(flusher_data.msg_mutex);
|
||||
const auto old_size = flusher_data.msg_buffer.size();
|
||||
flusher_data.msg_buffer.resize(old_size + len);
|
||||
GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len);
|
||||
flusher_data.last_msg_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
flusher_data.msg_cv.notify_one();
|
||||
std::string msg_buffer(len, 0);
|
||||
GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data(), len);
|
||||
LOG_INFO(Debug_Emulated, "{}", msg_buffer);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <optional>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -105,16 +104,8 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
|
||||
|
||||
// TODO(DarkLordZach): When FSController/Game Card Support is added, if
|
||||
// current_process_game_card use correct StorageId
|
||||
auto base_slot = storage.GetSlotForEntry(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);
|
||||
launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program));
|
||||
launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
|
||||
|
||||
system.GetARPManager().Register(launch.title_id, launch, out_control);
|
||||
return process;
|
||||
|
||||
@@ -158,7 +158,7 @@ Result IApplicationFunctions::EnsureSaveData(Out<u64> out_size, Common::UUID use
|
||||
LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString());
|
||||
|
||||
FileSys::SaveDataAttribute attribute{};
|
||||
attribute.program_id = FileSys::GetBaseTitleID(m_applet->program_id);
|
||||
attribute.program_id = m_applet->program_id;
|
||||
attribute.user_id = user_id.AsU128();
|
||||
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);
|
||||
|
||||
system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize(
|
||||
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128(), {normal_size, journal_size});
|
||||
type, 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
|
||||
// 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());
|
||||
|
||||
const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize(
|
||||
type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128());
|
||||
type, m_applet->program_id, user_id.AsU128());
|
||||
|
||||
*out_normal_size = size.normal;
|
||||
*out_journal_size = size.journal;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/bis_factory.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/errors.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -228,12 +227,13 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
|
||||
std::string src_path(Common::FS::SanitizePath(src_path_));
|
||||
std::string dest_path(Common::FS::SanitizePath(dest_path_));
|
||||
auto src = GetDirectoryRelativeWrapped(backing, src_path);
|
||||
if (src == nullptr)
|
||||
return FileSys::ResultPathNotFound;
|
||||
|
||||
if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
|
||||
// Use more-optimized vfs implementation rename.
|
||||
if (src == nullptr)
|
||||
return FileSys::ResultPathNotFound;
|
||||
if (!src->Rename(Common::FS::GetFilename(dest_path))) {
|
||||
// TODO(DarkLordZach): Find a better error code for this
|
||||
std::string full_src_path = backing->GetFullPath() + "/" + src_path;
|
||||
std::string full_dest_path = backing->GetFullPath() + "/" + dest_path;
|
||||
if (!Common::FS::RenameDir(full_src_path, full_dest_path)) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
return ResultSuccess;
|
||||
@@ -340,7 +340,7 @@ Result FileSystemController::RegisterProcess(
|
||||
registrations.emplace(process_id, Registration{
|
||||
.program_id = program_id,
|
||||
.romfs_factory = std::move(romfs_factory),
|
||||
.save_data_factory = CreateSaveDataFactory(FileSys::GetBaseTitleID(program_id)),
|
||||
.save_data_factory = CreateSaveDataFactory(program_id),
|
||||
});
|
||||
|
||||
LOG_DEBUG(Service_FS, "Registered for process {}", process_id);
|
||||
|
||||
@@ -24,7 +24,7 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe
|
||||
{3, D<&IFileSystem::DeleteDirectory>, "DeleteDirectory"},
|
||||
{4, D<&IFileSystem::DeleteDirectoryRecursively>, "DeleteDirectoryRecursively"},
|
||||
{5, D<&IFileSystem::RenameFile>, "RenameFile"},
|
||||
{6, nullptr, "RenameDirectory"},
|
||||
{6, D<&IFileSystem::RenameDirectory>, "RenameDirectory"},
|
||||
{7, D<&IFileSystem::GetEntryType>, "GetEntryType"},
|
||||
{8, D<&IFileSystem::OpenFile>, "OpenFile"},
|
||||
{9, D<&IFileSystem::OpenDirectory>, "OpenDirectory"},
|
||||
@@ -88,6 +88,14 @@ Result IFileSystem::RenameFile(
|
||||
R_RETURN(backend->RenameFile(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
|
||||
}
|
||||
|
||||
Result IFileSystem::RenameDirectory(
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path) {
|
||||
LOG_DEBUG(Service_FS, "called. directory '{}' to directory '{}'", old_path->str, new_path->str);
|
||||
|
||||
R_RETURN(backend->RenameDirectory(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
|
||||
}
|
||||
|
||||
Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path,
|
||||
u32 mode) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -36,6 +39,8 @@ public:
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path);
|
||||
Result RenameFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
|
||||
Result RenameDirectory(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
|
||||
Result OpenFile(OutInterface<IFile> out_interface,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, u32 mode);
|
||||
Result OpenDirectory(OutInterface<IDirectory> out_interface,
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/fs_directory.h"
|
||||
@@ -314,7 +313,7 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface<IFileSyste
|
||||
FileSys::ResultInvalidArgument);
|
||||
|
||||
if (attribute.program_id == 0) {
|
||||
attribute.program_id = FileSys::GetBaseTitleID(program_id);
|
||||
attribute.program_id = program_id;
|
||||
}
|
||||
|
||||
FileSys::VirtualDir dir{};
|
||||
|
||||
@@ -393,7 +393,7 @@ Result ServerManager::CompleteSyncRequest(Session* session) {
|
||||
}
|
||||
|
||||
// Send the reply.
|
||||
res = server_session->SendReplyHLE(m_system.Kernel());
|
||||
res = server_session->SendReplyHLE(m_system.Kernel(), service_res == IPC::ResultSessionClosed);
|
||||
|
||||
// If the session has been closed, we're done.
|
||||
if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
|
||||
|
||||
@@ -70,15 +70,6 @@ std::optional<IndexedProgram> ResolveIndexedProgram(Core::System& system, u64 pr
|
||||
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",
|
||||
target_id, program_index);
|
||||
return std::nullopt;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "common/hex_util.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
@@ -77,13 +76,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
|
||||
LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update");
|
||||
|
||||
const auto& installed = system.GetContentProvider();
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(nca->GetTitleId());
|
||||
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);
|
||||
}
|
||||
const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()),
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
if (update_nca) {
|
||||
exefs = update_nca->GetExeFS();
|
||||
|
||||
@@ -186,13 +186,8 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
}
|
||||
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(nsp->GetProgramTitleID());
|
||||
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);
|
||||
}
|
||||
const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()),
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
if (read == nullptr) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.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/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -138,13 +137,8 @@ ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) {
|
||||
return ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
}
|
||||
|
||||
const auto program_update_id = FileSys::GetUpdateTitleID(program_id);
|
||||
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);
|
||||
}
|
||||
const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
|
||||
FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
|
||||
if (read == nullptr) {
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
calculate_location_descriptor();
|
||||
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)]);
|
||||
code.sub(eax, 1);
|
||||
code.and_(eax, u32(A32JitState::RSBPtrMask));
|
||||
code.and_(eax, u32(A32JitState::RSB_PTR_MASK));
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
|
||||
@@ -37,9 +37,9 @@ using namespace Backend::X64;
|
||||
|
||||
static RunCodeCallbacks GenRunCodeCallbacks(A32::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A32::UserConfig& conf) {
|
||||
return RunCodeCallbacks{
|
||||
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
ArgCallback(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)),
|
||||
ArgCallback(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
conf.enable_cycle_counting,
|
||||
};
|
||||
}
|
||||
@@ -79,7 +79,7 @@ struct Jit::Impl {
|
||||
jit_interface->is_executing = true;
|
||||
const CodePtr current_codeptr = [this] {
|
||||
// RSB optimization
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSBPtrMask;
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSB_PTR_MASK;
|
||||
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
|
||||
jit_state.rsb_ptr = new_rsb_ptr;
|
||||
return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]);
|
||||
|
||||
@@ -27,6 +27,9 @@ struct A32JitState {
|
||||
|
||||
A32JitState() { ResetRSB(); }
|
||||
|
||||
static constexpr std::size_t RSB_SIZE = 8; // MUST be a power of 2.
|
||||
static constexpr std::size_t RSB_PTR_MASK = RSB_SIZE - 1;
|
||||
|
||||
std::array<u32, 16> Reg{}; // Current register file.
|
||||
// TODO: Mode-specific register sets unimplemented.
|
||||
|
||||
@@ -36,8 +39,9 @@ struct A32JitState {
|
||||
u32 cpsr_q = 0;
|
||||
u32 cpsr_nzcv = 0;
|
||||
u32 cpsr_jaifm = 0;
|
||||
u32 Cpsr() const;
|
||||
void SetCpsr(u32 cpsr);
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpsr_nzcv = 0;
|
||||
|
||||
alignas(16) std::array<u32, 64> ExtReg{}; // Extension registers.
|
||||
|
||||
@@ -49,21 +53,19 @@ struct A32JitState {
|
||||
// Exclusive state
|
||||
u32 exclusive_state = 0;
|
||||
|
||||
static constexpr std::size_t RSBSize = 8; // MUST be a power of 2.
|
||||
static constexpr std::size_t RSBPtrMask = RSBSize - 1;
|
||||
u32 rsb_ptr = 0;
|
||||
std::array<u64, RSBSize> rsb_location_descriptors;
|
||||
std::array<u64, RSBSize> rsb_codeptrs;
|
||||
void ResetRSB();
|
||||
std::array<u64, RSB_SIZE> rsb_location_descriptors;
|
||||
std::array<u64, RSB_SIZE> rsb_codeptrs;
|
||||
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpsr_nzcv = 0;
|
||||
u32 Cpsr() const;
|
||||
void SetCpsr(u32 cpsr);
|
||||
|
||||
void ResetRSB();
|
||||
u32 Fpscr() const;
|
||||
void SetFpscr(u32 FPSCR);
|
||||
|
||||
u64 GetUniqueHash() const noexcept {
|
||||
return (static_cast<u64>(upper_location_descriptor) << 32) | (static_cast<u64>(Reg[15]));
|
||||
return (u64(upper_location_descriptor) << 32) | (u64(Reg[15]));
|
||||
}
|
||||
|
||||
void TransferJitState(const A32JitState& src, bool reset_rsb) {
|
||||
|
||||
@@ -208,7 +208,7 @@ void A64EmitX64::GenTerminalHandlers() {
|
||||
calculate_location_descriptor();
|
||||
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)]);
|
||||
code.sub(eax, 1);
|
||||
code.and_(eax, u32(A64JitState::RSBPtrMask));
|
||||
code.and_(eax, u32(A64JitState::RSB_PTR_MASK));
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
|
||||
@@ -33,9 +33,9 @@ using namespace Backend::X64;
|
||||
|
||||
static RunCodeCallbacks GenRunCodeCallbacks(A64::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A64::UserConfig& conf) {
|
||||
return RunCodeCallbacks{
|
||||
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
ArgCallback(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)),
|
||||
ArgCallback(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
conf.enable_cycle_counting,
|
||||
};
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
// TODO: Check code alignment
|
||||
const CodePtr current_code_ptr = [this] {
|
||||
// RSB optimization
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask;
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSB_PTR_MASK;
|
||||
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
|
||||
jit_state.rsb_ptr = new_rsb_ptr;
|
||||
return CodePtr(jit_state.rsb_codeptrs[new_rsb_ptr]);
|
||||
|
||||
@@ -29,18 +29,19 @@ struct A64JitState {
|
||||
|
||||
A64JitState() { ResetRSB(); }
|
||||
|
||||
// Exclusive state stuff
|
||||
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
|
||||
// Return stack buffer
|
||||
static constexpr size_t RSB_SIZE = 8; // MUST be a power of 2.
|
||||
static constexpr size_t RSB_PTR_MASK = RSB_SIZE - 1;
|
||||
|
||||
std::array<u64, 31> reg{};
|
||||
u64 sp = 0;
|
||||
u64 pc = 0;
|
||||
|
||||
u32 cpsr_nzcv = 0;
|
||||
|
||||
u32 GetPstate() const {
|
||||
return NZCV::FromX64(cpsr_nzcv);
|
||||
}
|
||||
void SetPstate(u32 new_pstate) {
|
||||
cpsr_nzcv = NZCV::ToX64(new_pstate);
|
||||
}
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpcr = 0;
|
||||
|
||||
alignas(16) std::array<u64, 64> vec{}; // Extension registers.
|
||||
|
||||
@@ -50,29 +51,31 @@ struct A64JitState {
|
||||
volatile u32 halt_reason = 0;
|
||||
|
||||
// Exclusive state
|
||||
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
|
||||
u8 exclusive_state = 0;
|
||||
|
||||
static constexpr size_t RSBSize = 8; // MUST be a power of 2.
|
||||
static constexpr size_t RSBPtrMask = RSBSize - 1;
|
||||
u32 rsb_ptr = 0;
|
||||
std::array<u64, RSBSize> rsb_location_descriptors;
|
||||
std::array<u64, RSBSize> rsb_codeptrs;
|
||||
std::array<u64, RSB_SIZE> rsb_location_descriptors;
|
||||
std::array<u64, RSB_SIZE> rsb_codeptrs;
|
||||
|
||||
u32 GetPstate() const {
|
||||
return NZCV::FromX64(cpsr_nzcv);
|
||||
}
|
||||
|
||||
void SetPstate(u32 new_pstate) {
|
||||
cpsr_nzcv = NZCV::ToX64(new_pstate);
|
||||
}
|
||||
|
||||
void ResetRSB() {
|
||||
rsb_location_descriptors.fill(0xFFFFFFFFFFFFFFFFull);
|
||||
rsb_codeptrs.fill(0);
|
||||
}
|
||||
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpcr = 0;
|
||||
u32 GetFpcr() const;
|
||||
u32 GetFpsr() const;
|
||||
void SetFpcr(u32 value);
|
||||
void SetFpsr(u32 value);
|
||||
|
||||
u64 GetUniqueHash() const noexcept {
|
||||
const u64 fpcr_u64 = static_cast<u64>(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift;
|
||||
const u64 fpcr_u64 = u64(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift;
|
||||
const u64 pc_u64 = pc & A64::LocationDescriptor::pc_mask;
|
||||
return pc_u64 | fpcr_u64;
|
||||
}
|
||||
|
||||
@@ -61,73 +61,6 @@ namespace {
|
||||
constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024;
|
||||
constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
class CustomXbyakAllocator : public Xbyak::Allocator {
|
||||
public:
|
||||
#ifdef _WIN32
|
||||
uint8_t* alloc(size_t size) override {
|
||||
void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
||||
if (p == nullptr) {
|
||||
using Xbyak::Error;
|
||||
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
|
||||
}
|
||||
return static_cast<uint8_t*>(p);
|
||||
}
|
||||
|
||||
void free(uint8_t* p) override {
|
||||
VirtualFree(static_cast<void*>(p), 0, MEM_RELEASE);
|
||||
}
|
||||
|
||||
bool useProtect() const override { return false; }
|
||||
#else
|
||||
static constexpr size_t DYNARMIC_PAGE_SIZE = 4096;
|
||||
|
||||
// Can't subclass Xbyak::MmapAllocator because it is not a pure interface
|
||||
// and doesn't expose its construtor
|
||||
uint8_t* alloc(size_t size) override {
|
||||
// Waste a page to store the size
|
||||
size += DYNARMIC_PAGE_SIZE;
|
||||
|
||||
int mode = MAP_PRIVATE;
|
||||
#if defined(MAP_ANONYMOUS)
|
||||
mode |= MAP_ANONYMOUS;
|
||||
#elif defined(MAP_ANON)
|
||||
mode |= MAP_ANON;
|
||||
#else
|
||||
# error "not supported"
|
||||
#endif
|
||||
#ifdef MAP_JIT
|
||||
mode |= MAP_JIT;
|
||||
#endif
|
||||
int prot = PROT_READ | PROT_WRITE;
|
||||
#ifdef PROT_MPROTECT
|
||||
// https://man.netbsd.org/mprotect.2 specifies that an mprotect() that is LESS
|
||||
// restrictive than the original mapping MUST fail
|
||||
prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC);
|
||||
#endif
|
||||
void* p = mmap(nullptr, size, prot, mode, -1, 0);
|
||||
if (p == MAP_FAILED) {
|
||||
using Xbyak::Error;
|
||||
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
|
||||
}
|
||||
std::memcpy(p, &size, sizeof(size_t));
|
||||
return static_cast<uint8_t*>(p) + DYNARMIC_PAGE_SIZE;
|
||||
}
|
||||
|
||||
void free(uint8_t* p) override {
|
||||
size_t size;
|
||||
std::memcpy(&size, p - DYNARMIC_PAGE_SIZE, sizeof(size_t));
|
||||
munmap(p - DYNARMIC_PAGE_SIZE, size);
|
||||
}
|
||||
|
||||
# ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
bool useProtect() const override { return false; }
|
||||
# endif
|
||||
#endif
|
||||
};
|
||||
|
||||
// This is threadsafe as Xbyak::Allocator does not contain any state; it is a pure interface.
|
||||
CustomXbyakAllocator s_allocator;
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
void ProtectMemory(const void* base, size_t size, bool is_executable) {
|
||||
# ifdef _WIN32
|
||||
@@ -145,11 +78,9 @@ void ProtectMemory(const void* base, size_t size, bool is_executable) {
|
||||
|
||||
HostFeature GetHostFeatures() {
|
||||
HostFeature features = {};
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION
|
||||
using Cpu = Xbyak::util::Cpu;
|
||||
Xbyak::util::Cpu cpu_info;
|
||||
|
||||
Xbyak::util::Cpu cpu_info{};
|
||||
if (cpu_info.has(Cpu::tSSSE3))
|
||||
features |= HostFeature::SSSE3;
|
||||
if (cpu_info.has(Cpu::tSSE41))
|
||||
@@ -196,7 +127,6 @@ HostFeature GetHostFeatures() {
|
||||
features |= HostFeature::GFNI;
|
||||
if (cpu_info.has(Cpu::tWAITPKG))
|
||||
features |= HostFeature::WAITPKG;
|
||||
|
||||
if (cpu_info.has(Cpu::tBMI2)) {
|
||||
// BMI2 instructions such as pdep and pext have been very slow up until Zen 3.
|
||||
// Check for Zen 3 or newer by its family (0x19).
|
||||
@@ -214,7 +144,6 @@ HostFeature GetHostFeatures() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -233,23 +162,27 @@ bool IsUnderRosetta() {
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
static const auto default_cg_mode = Xbyak::DontSetProtectRWE;
|
||||
#else
|
||||
static const auto default_cg_mode = nullptr; //Allow RWE
|
||||
#endif
|
||||
|
||||
BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function<void(BlockOfCode&)> rcp)
|
||||
: Xbyak::CodeGenerator(total_code_size, default_cg_mode, &s_allocator)
|
||||
, cb(std::move(cb))
|
||||
, jsi(jsi)
|
||||
, constant_pool(*this, CONSTANT_POOL_SIZE)
|
||||
, host_features(GetHostFeatures()) {
|
||||
: Xbyak::CodeGenerator(total_code_size
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
, Xbyak::DontSetProtectRWE
|
||||
#else
|
||||
, nullptr //Allow RWE
|
||||
#endif
|
||||
, nullptr)
|
||||
, constant_pool(*this, CONSTANT_POOL_SIZE)
|
||||
, jsi(jsi)
|
||||
, cb(std::move(cb))
|
||||
{
|
||||
EnableWriting();
|
||||
EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE);
|
||||
GenRunCode(rcp);
|
||||
}
|
||||
|
||||
bool BlockOfCode::HasHostFeature(HostFeature feature) const noexcept {
|
||||
return (GetHostFeatures() & feature) == feature;
|
||||
}
|
||||
|
||||
void BlockOfCode::PreludeComplete() {
|
||||
prelude_complete = true;
|
||||
code_begin = getCurr();
|
||||
@@ -341,7 +274,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
mov(rbx, ABI_PARAM2); // save temporarily in non-volatile register
|
||||
|
||||
if (cb.enable_cycle_counting) {
|
||||
cb.GetTicksRemaining->EmitCall(*this);
|
||||
cb.GetTicksRemaining.EmitCall(*this);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
|
||||
}
|
||||
@@ -388,7 +321,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
|
||||
jng(return_to_caller);
|
||||
}
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
jmp(ABI_RETURN);
|
||||
|
||||
align();
|
||||
@@ -401,7 +334,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
jng(return_to_caller_mxcsr_already_exited);
|
||||
}
|
||||
SwitchMxcsrOnEntry();
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
jmp(ABI_RETURN);
|
||||
|
||||
align();
|
||||
@@ -415,7 +348,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
L(return_to_caller_mxcsr_already_exited);
|
||||
|
||||
if (cb.enable_cycle_counting) {
|
||||
cb.AddTicks->EmitCall(*this, [this](RegList param) {
|
||||
cb.AddTicks.EmitCall(*this, [this](RegList param) {
|
||||
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
|
||||
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
|
||||
});
|
||||
@@ -455,18 +388,18 @@ void BlockOfCode::UpdateTicks() {
|
||||
return;
|
||||
}
|
||||
|
||||
cb.AddTicks->EmitCall(*this, [this](RegList param) {
|
||||
cb.AddTicks.EmitCall(*this, [this](RegList param) {
|
||||
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
|
||||
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
|
||||
});
|
||||
|
||||
cb.GetTicksRemaining->EmitCall(*this);
|
||||
cb.GetTicksRemaining.EmitCall(*this);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
|
||||
}
|
||||
|
||||
void BlockOfCode::LookupBlock() {
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
}
|
||||
|
||||
void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
|
||||
@@ -520,7 +453,7 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
|
||||
}
|
||||
|
||||
Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
return constant_pool.GetConstant(frame, lower, upper);
|
||||
return constant_pool.GetConstant(*this, frame, lower, upper);
|
||||
}
|
||||
|
||||
CodePtr BlockOfCode::GetCodeBegin() const {
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace Dynarmic::Backend::X64 {
|
||||
using CodePtr = const void*;
|
||||
|
||||
struct RunCodeCallbacks {
|
||||
std::unique_ptr<Callback> LookupBlock;
|
||||
std::unique_ptr<Callback> AddTicks;
|
||||
std::unique_ptr<Callback> GetTicksRemaining;
|
||||
ArgCallback LookupBlock;
|
||||
ArgCallback AddTicks;
|
||||
ArgCallback GetTicksRemaining;
|
||||
bool enable_cycle_counting;
|
||||
};
|
||||
|
||||
@@ -166,27 +166,24 @@ public:
|
||||
|
||||
JitStateInfo GetJitStateInfo() const { return jsi; }
|
||||
|
||||
bool HasHostFeature(HostFeature feature) const {
|
||||
return (host_features & feature) == feature;
|
||||
}
|
||||
bool HasHostFeature(HostFeature feature) const noexcept;
|
||||
|
||||
private:
|
||||
using RunCodeFuncType = HaltReason (*)(void*, CodePtr);
|
||||
static constexpr size_t MXCSR_ALREADY_EXITED = 1 << 0;
|
||||
static constexpr size_t FORCE_RETURN = 1 << 1;
|
||||
|
||||
RunCodeCallbacks cb;
|
||||
ConstantPool constant_pool;
|
||||
JitStateInfo jsi;
|
||||
std::array<const void*, 4> return_from_run_code;
|
||||
RunCodeFuncType run_code = nullptr;
|
||||
RunCodeFuncType step_code = nullptr;
|
||||
RunCodeCallbacks cb;
|
||||
CodePtr code_begin = nullptr;
|
||||
#ifdef _WIN32
|
||||
size_t committed_size = 0;
|
||||
#endif
|
||||
ConstantPool constant_pool;
|
||||
RunCodeFuncType run_code = nullptr;
|
||||
RunCodeFuncType step_code = nullptr;
|
||||
std::array<const void*, 4> return_from_run_code;
|
||||
bool prelude_complete = false;
|
||||
const HostFeature host_features;
|
||||
|
||||
void GenRunCode(std::function<void(BlockOfCode&)> rcp);
|
||||
};
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
namespace Dynarmic::Backend::X64 {
|
||||
|
||||
ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
|
||||
: code(code)
|
||||
, insertion_point(0)
|
||||
: insertion_point(0)
|
||||
{
|
||||
code.EnsureMemoryCommitted(align_size + size);
|
||||
code.int3();
|
||||
@@ -25,17 +24,17 @@ ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
|
||||
pool = std::span<ConstantT>(reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
|
||||
}
|
||||
|
||||
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
Xbyak::Address ConstantPool::GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
const auto constant = ConstantT(lower, upper);
|
||||
auto iter = constant_info.find(constant);
|
||||
if (iter == constant_info.end()) {
|
||||
auto it = constant_info.find(constant);
|
||||
if (it == constant_info.end()) {
|
||||
ASSERT(insertion_point < pool.size());
|
||||
ConstantT& target_constant = pool[insertion_point];
|
||||
target_constant = constant;
|
||||
iter = constant_info.insert({constant, &target_constant}).first;
|
||||
it = constant_info.insert({constant, &target_constant}).first;
|
||||
++insertion_point;
|
||||
}
|
||||
return frame[code.rip + iter->second];
|
||||
return frame[code.rip + it->second];
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::X64
|
||||
|
||||
@@ -29,7 +29,7 @@ class ConstantPool final {
|
||||
public:
|
||||
ConstantPool(BlockOfCode& code, size_t size);
|
||||
|
||||
Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
Xbyak::Address GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
|
||||
private:
|
||||
static constexpr size_t align_size = 16; // bytes
|
||||
@@ -45,7 +45,6 @@ private:
|
||||
|
||||
ankerl::unordered_dense::map<ConstantT, void*, ConstantHash> constant_info;
|
||||
std::span<ConstantT> pool;
|
||||
BlockOfCode& code;
|
||||
std::size_t insertion_point;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Dynarmic::Backend::X64 {
|
||||
|
||||
enum class HostFeature : u64 {
|
||||
enum class HostFeature : u32 {
|
||||
SSSE3 = 1ULL << 0,
|
||||
SSE41 = 1ULL << 1,
|
||||
SSE42 = 1ULL << 2,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2016 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -15,7 +18,7 @@ struct JitStateInfo {
|
||||
: offsetof_guest_MXCSR(offsetof(JitStateType, guest_MXCSR))
|
||||
, offsetof_asimd_MXCSR(offsetof(JitStateType, asimd_MXCSR))
|
||||
, offsetof_rsb_ptr(offsetof(JitStateType, rsb_ptr))
|
||||
, rsb_ptr_mask(JitStateType::RSBPtrMask)
|
||||
, rsb_ptr_mask(JitStateType::RSB_PTR_MASK)
|
||||
, offsetof_rsb_location_descriptors(offsetof(JitStateType, rsb_location_descriptors))
|
||||
, offsetof_rsb_codeptrs(offsetof(JitStateType, rsb_codeptrs))
|
||||
, offsetof_cpsr_nzcv(offsetof(JitStateType, cpsr_nzcv))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
@@ -56,14 +56,13 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro
|
||||
*/
|
||||
inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
|
||||
size_t count{};
|
||||
const auto application_id = FileSys::GetBaseTitleID(program_id);
|
||||
const auto& fs_controller = system.GetFileSystemController();
|
||||
const auto dlc_entries = system.GetContentProvider().ListEntriesFilter(
|
||||
FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::vector<u64> program_dlc_entries;
|
||||
|
||||
for (const auto& entry : dlc_entries) {
|
||||
if (FileSys::GetBaseTitleID(entry.title_id) == application_id) {
|
||||
if (FileSys::GetBaseTitleID(entry.title_id) == program_id) {
|
||||
program_dlc_entries.push_back(entry.title_id);
|
||||
}
|
||||
}
|
||||
@@ -84,17 +83,9 @@ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) {
|
||||
*/
|
||||
inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller,
|
||||
const u64 program_id) {
|
||||
const auto remove_update = [&fs_controller](u64 update_id) {
|
||||
return fs_controller.GetUserNANDContents()->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);
|
||||
const auto update_id = program_id | 0x800;
|
||||
return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
|
||||
fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,26 +111,15 @@ inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& f
|
||||
inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller,
|
||||
const u64 program_id, const std::string& mod_name) {
|
||||
// Check general Mods (LayeredFS and IPS)
|
||||
const auto remove_from_root = [&mod_name](const auto& root) {
|
||||
return root != nullptr && root->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;
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id);
|
||||
if (mod_dir != nullptr) {
|
||||
return mod_dir->DeleteSubdirectoryRecursive(mod_name);
|
||||
}
|
||||
|
||||
// Check SDMC mod directory (RomFS LayeredFS)
|
||||
if (remove_from_root(fs_controller.GetSDMCModificationLoadRoot(program_id))) {
|
||||
return true;
|
||||
}
|
||||
if (FileSys::GetBaseTitleID(program_id) != program_id &&
|
||||
remove_from_root(
|
||||
fs_controller.GetSDMCModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) {
|
||||
return true;
|
||||
const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id);
|
||||
if (sdmc_mod_dir != nullptr) {
|
||||
return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "frontend_common/data_manager.h"
|
||||
#include "mod_manager.h"
|
||||
|
||||
@@ -41,7 +40,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) {
|
||||
const auto program_id_string = fmt::format("{:016X}", FileSys::GetBaseTitleID(program_id));
|
||||
const auto program_id_string = fmt::format("{:016X}", program_id);
|
||||
const auto mod_name = path.filename();
|
||||
const auto mod_dir =
|
||||
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/hle/service/am/am_types.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
@@ -306,7 +305,6 @@ void RemoveAllTransferableShaderCaches(u64 program_id) {
|
||||
}
|
||||
|
||||
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 config_file_name =
|
||||
program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <system_error>
|
||||
#include <JlCompress.h>
|
||||
#include "frontend_common/mod_manager.h"
|
||||
#include "mod.h"
|
||||
@@ -124,8 +125,8 @@ const QString ExtractMod(const QString& path) {
|
||||
fs::remove_all(tmp, ec);
|
||||
if (!fs::create_directories(tmp, ec)) {
|
||||
QtCommon::Frontend::Critical(tr("Mod Extract Failed"),
|
||||
tr("Failed to create temporary directory %1")
|
||||
.arg(QString::fromStdString(tmp.string())));
|
||||
tr("Failed to create temporary directory %1")
|
||||
.arg(QString::fromStdString(tmp.string())));
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1281,7 +1281,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *blit_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1313,7 +1313,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *blit_depth_stencil_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1366,7 +1366,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *clear_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1422,7 +1422,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *clear_stencil_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1465,7 +1465,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPip
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *blit_msaa_color_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1584,7 +1584,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPas
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1697,7 +1697,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipeline
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
}));
|
||||
}, device.StaticPipelineCache()));
|
||||
return *msaa_copy_pipelines.back();
|
||||
}
|
||||
|
||||
@@ -1817,7 +1817,7 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}, device.StaticPipelineCache());
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
|
||||
@@ -1860,7 +1860,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}, device.StaticPipelineCache());
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -521,7 +521,7 @@ static vk::Pipeline CreateWrappedPipelineImpl(
|
||||
.subpass = 0,
|
||||
.basePipelineHandle = 0,
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}, device.StaticPipelineCache());
|
||||
}
|
||||
|
||||
vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass,
|
||||
|
||||
@@ -268,7 +268,7 @@ ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, Descriptor
|
||||
.layout = *layout,
|
||||
.basePipelineHandle = {},
|
||||
.basePipelineIndex = 0,
|
||||
});
|
||||
}, device.StaticPipelineCache());
|
||||
}
|
||||
|
||||
ComputePass::~ComputePass() = default;
|
||||
|
||||
@@ -63,6 +63,8 @@ using VideoCommon::GenericEnvironment;
|
||||
using VideoCommon::GraphicsEnvironment;
|
||||
|
||||
constexpr u32 CACHE_VERSION = 18;
|
||||
constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128;
|
||||
constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30;
|
||||
constexpr std::array<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
|
||||
|
||||
template <typename Container>
|
||||
@@ -710,6 +712,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
if (use_vulkan_pipeline_cache) {
|
||||
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
|
||||
CACHE_VERSION);
|
||||
size_t size = 0;
|
||||
vulkan_pipeline_cache.Read(&size, nullptr);
|
||||
last_cache_size.store(size, std::memory_order_relaxed);
|
||||
last_flush = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
if (state.statistics) {
|
||||
@@ -717,6 +723,35 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::QueueVulkanPipelineCacheFlush() {
|
||||
if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) {
|
||||
return;
|
||||
}
|
||||
if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) {
|
||||
return;
|
||||
}
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto megabytes = last_cache_size.load(std::memory_order_relaxed) / (1024 * 1024);
|
||||
const std::chrono::seconds interval{
|
||||
std::max<size_t>(VULKAN_CACHE_FLUSH_MIN_SECONDS, megabytes)};
|
||||
if (last_flush.time_since_epoch().count() != 0 && now - last_flush < interval) {
|
||||
return;
|
||||
}
|
||||
if (flush_in_flight.exchange(true, std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
pipelines_since_flush = 0;
|
||||
last_flush = now;
|
||||
serialization_thread.QueueWork([this] {
|
||||
SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
|
||||
CACHE_VERSION);
|
||||
size_t size = 0;
|
||||
vulkan_pipeline_cache.Read(&size, nullptr);
|
||||
last_cache_size.store(size, std::memory_order_relaxed);
|
||||
flush_in_flight.store(false, std::memory_order_release);
|
||||
});
|
||||
}
|
||||
|
||||
GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() {
|
||||
const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
|
||||
auto& pipeline{pair->second};
|
||||
@@ -755,7 +790,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
std::span<Shader::Environment* const> envs, PipelineStatistics* statistics,
|
||||
bool build_in_parallel) try {
|
||||
auto hash = key.Hash();
|
||||
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
|
||||
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
|
||||
size_t env_index{0};
|
||||
std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
|
||||
const bool uses_vertex_a{key.unique_hashes[0] != 0};
|
||||
@@ -891,6 +926,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline() {
|
||||
}
|
||||
SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION);
|
||||
});
|
||||
QueueVulkanPipelineCacheFlush();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -910,6 +946,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env_},
|
||||
pipeline_cache_filename, CACHE_VERSION);
|
||||
});
|
||||
QueueVulkanPipelineCacheFlush();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -922,7 +959,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LOG_INFO(Render_Vulkan, "{:#016x}", hash);
|
||||
LOG_DEBUG(Render_Vulkan, "{:#016x}", hash);
|
||||
|
||||
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
@@ -144,6 +146,8 @@ private:
|
||||
vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename,
|
||||
u32 expected_cache_version);
|
||||
|
||||
void QueueVulkanPipelineCacheFlush();
|
||||
|
||||
const Device& device;
|
||||
Scheduler& scheduler;
|
||||
DescriptorPool& descriptor_pool;
|
||||
@@ -171,6 +175,10 @@ private:
|
||||
|
||||
std::filesystem::path vulkan_pipeline_cache_filename;
|
||||
vk::PipelineCache vulkan_pipeline_cache;
|
||||
size_t pipelines_since_flush{};
|
||||
std::chrono::steady_clock::time_point last_flush{};
|
||||
std::atomic<size_t> last_cache_size{};
|
||||
std::atomic_bool flush_in_flight{};
|
||||
|
||||
Common::ThreadWorker workers;
|
||||
Common::ThreadWorker serialization_thread;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -16,6 +18,8 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
#include <ranges>
|
||||
#include "common/settings.h"
|
||||
@@ -393,6 +397,17 @@ std::vector<const char*> ExtensionListForVulkan(
|
||||
return output;
|
||||
}
|
||||
|
||||
constexpr std::array<char, 8> STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'};
|
||||
constexpr u32 STATIC_CACHE_VERSION = 1;
|
||||
|
||||
std::filesystem::path StaticPipelineCacheFilename() {
|
||||
const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
if (!Common::FS::CreateDir(shader_dir)) {
|
||||
return {};
|
||||
}
|
||||
return shader_dir / "vulkan_static_pipelines.bin";
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void Device::RemoveExtension(bool& extension, const std::string& extension_name) {
|
||||
@@ -750,15 +765,100 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
|
||||
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
|
||||
|
||||
owns_static_pipeline_cache = surface != VkSurfaceKHR{};
|
||||
LoadStaticPipelineCache();
|
||||
|
||||
// Initialize GPU logging if enabled
|
||||
InitializeGPULogging();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
SaveStaticPipelineCache();
|
||||
ShutdownGPULogging();
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
void Device::LoadStaticPipelineCache() {
|
||||
const auto create = [this](size_t size, const void* data) {
|
||||
static_pipeline_cache = logical.CreatePipelineCache({
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.initialDataSize = size,
|
||||
.pInitialData = data,
|
||||
});
|
||||
};
|
||||
if (!owns_static_pipeline_cache) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
const auto filename = StaticPipelineCacheFilename();
|
||||
if (filename.empty()) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
std::vector<char> data;
|
||||
try {
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
||||
const size_t total = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::array<char, 8> magic{};
|
||||
u32 version{};
|
||||
if (total < magic.size() + sizeof(version)) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
file.read(magic.data(), magic.size())
|
||||
.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||
if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
data.resize(total - magic.size() - sizeof(version));
|
||||
file.read(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
create(0, nullptr);
|
||||
return;
|
||||
}
|
||||
create(data.size(), data.empty() ? nullptr : data.data());
|
||||
}
|
||||
|
||||
void Device::SaveStaticPipelineCache() const {
|
||||
if (!owns_static_pipeline_cache || !static_pipeline_cache) {
|
||||
return;
|
||||
}
|
||||
const auto filename = StaticPipelineCacheFilename();
|
||||
if (filename.empty()) {
|
||||
return;
|
||||
}
|
||||
size_t size = 0;
|
||||
std::vector<char> data;
|
||||
static_pipeline_cache.Read(&size, nullptr);
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
data.resize(size);
|
||||
static_pipeline_cache.Read(&size, data.data());
|
||||
try {
|
||||
std::ofstream file(filename, std::ios::binary | std::ios::trunc);
|
||||
file.exceptions(std::ofstream::failbit);
|
||||
if (!file.is_open()) {
|
||||
return;
|
||||
}
|
||||
file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size())
|
||||
.write(reinterpret_cast<const char*>(&STATIC_CACHE_VERSION),
|
||||
sizeof(STATIC_CACHE_VERSION))
|
||||
.write(data.data(), static_cast<std::streamsize>(size));
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
Common::FS::RemoveFile(filename);
|
||||
}
|
||||
}
|
||||
|
||||
VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage,
|
||||
FormatType format_type) const {
|
||||
if (IsFormatSupported(wanted_format, wanted_usage, format_type)) {
|
||||
|
||||
@@ -273,6 +273,10 @@ public:
|
||||
return physical;
|
||||
}
|
||||
|
||||
VkPipelineCache StaticPipelineCache() const noexcept {
|
||||
return *static_pipeline_cache;
|
||||
}
|
||||
|
||||
/// Returns the main graphics queue.
|
||||
vk::Queue GetGraphicsQueue() const {
|
||||
return graphics_queue;
|
||||
@@ -1127,6 +1131,9 @@ private:
|
||||
/// Returns true if the device natively supports blitting depth stencil images.
|
||||
bool TestDepthStencilBlits(VkFormat format) const;
|
||||
|
||||
void LoadStaticPipelineCache();
|
||||
void SaveStaticPipelineCache() const;
|
||||
|
||||
private:
|
||||
VkInstance instance; ///< Vulkan instance.
|
||||
VmaAllocator allocator; ///< VMA allocator.
|
||||
@@ -1135,6 +1142,8 @@ private:
|
||||
vk::Device logical; ///< Logical device.
|
||||
vk::Queue graphics_queue; ///< Main graphics queue.
|
||||
vk::Queue present_queue; ///< Main present queue.
|
||||
vk::PipelineCache static_pipeline_cache;
|
||||
bool owns_static_pipeline_cache{};
|
||||
u32 instance_version{}; ///< Vulkan instance version.
|
||||
u32 graphics_family{}; ///< Main graphics queue family index.
|
||||
u32 present_family{}; ///< Main present queue family index.
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "common/settings_input.h"
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
@@ -51,7 +50,7 @@
|
||||
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
|
||||
std::vector<VkDeviceInfo::Record>& vk_device_records,
|
||||
Core::System& system_)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{FileSys::GetBaseTitleID(title_id_)},
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
|
||||
system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "common/fs/path_util.h"
|
||||
#include "configuration/addon/mod_select_dialog.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/mod_manager.h"
|
||||
@@ -138,7 +137,7 @@ void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) {
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::SetTitleId(u64 id) {
|
||||
this->title_id = FileSys::GetBaseTitleID(id);
|
||||
this->title_id = id;
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::InstallMods(const QStringList& mods) {
|
||||
|
||||
@@ -1922,7 +1922,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
|
||||
std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
|
||||
const auto config_file_name = title_id == 0
|
||||
? Common::FS::PathToUTF8String(file_path.filename())
|
||||
: fmt::format("{:016X}", FileSys::GetBaseTitleID(title_id));
|
||||
: fmt::format("{:016X}", title_id);
|
||||
QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
|
||||
QtCommon::system->HIDCore().ReloadInputDevices();
|
||||
QtCommon::system->ApplySettings();
|
||||
@@ -2544,11 +2544,9 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat
|
||||
}
|
||||
|
||||
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 ||
|
||||
update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS ||
|
||||
(update_nca.GetTitleId() != selected_update_id && update_nca.GetTitleId() != application_update_id)) {
|
||||
update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) {
|
||||
packed_update_raw = {};
|
||||
}
|
||||
|
||||
@@ -4423,7 +4421,6 @@ void MainWindow::SetFPSSuffix() {
|
||||
|
||||
bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
|
||||
u64* selected_title_id, u8* selected_content_record_type) {
|
||||
program_id = FileSys::GetBaseTitleID(program_id);
|
||||
using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
|
||||
boost::container::flat_set<ContentInfo> available_title_ids;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user