summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt26
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/audio_core/audio_core.cpp8
-rw-r--r--src/audio_core/audio_core.h14
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp1
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/common/uuid.cpp2
-rw-r--r--src/core/core.cpp18
-rw-r--r--src/core/core.h3
-rw-r--r--src/core/hle/service/nfc/common/device.cpp160
-rw-r--r--src/core/hle/service/nfc/common/device.h10
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp14
-rw-r--r--src/core/hle/service/nfc/nfc_interface.cpp8
-rw-r--r--src/core/hle/service/nfc/nfc_result.h20
-rw-r--r--src/core/hle/service/nfp/nfp_interface.cpp6
-rw-r--r--src/core/hle/service/nfp/nfp_result.h2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp4
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp4
-rw-r--r--src/core/loader/nro.cpp13
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/yuzu/configuration/config.cpp8
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui10
-rw-r--r--src/yuzu/main.cpp8
35 files changed, 316 insertions, 91 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c11b6bc16..22af9e435 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -223,6 +223,8 @@ object NativeLibrary {
external fun getCompany(filename: String): String
+ external fun isHomebrew(filename: String): Boolean
+
external fun setAppDirectory(directory: String)
external fun initializeGpuDriver(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 94d5156cf..f4db61cb3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -263,7 +263,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val config: Configuration = resources.configuration
if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
- (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
+ (config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0 ||
+ (config.screenLayout and Configuration.SCREENLAYOUT_SIZE_SMALL) != 0) {
return rotation
}
when (rotation) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index ebc0f164a..adbe3696b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -127,13 +127,7 @@ class SearchFragment : Fragment() {
}
}
- R.id.chip_homebrew -> {
- baseList.filter {
- Log.error("Guh - ${it.path}")
- FileUtil.hasExtension(it.path, "nro")
- || FileUtil.hasExtension(it.path, "nso")
- }
- }
+ R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
R.id.chip_retail -> baseList.filter {
FileUtil.hasExtension(it.path, "xci")
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 2a17653b2..3d6782c49 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -16,7 +16,8 @@ class Game(
val regions: String,
val path: String,
val gameId: String,
- val company: String
+ val company: String,
+ val isHomebrew: Boolean
) : Parcelable {
val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
@@ -31,6 +32,7 @@ class Game(
&& path == other.path
&& gameId == other.gameId
&& company == other.company
+ && isHomebrew == other.isHomebrew
}
companion object {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 7059856f1..d9b301210 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.MissingFieldException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -20,6 +22,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.utils.GameHelper
import java.util.Locale
+@OptIn(ExperimentalSerializationApi::class)
class GamesViewModel : ViewModel() {
private val _games = MutableLiveData<List<Game>>(emptyList())
val games: LiveData<List<Game>> get() = _games
@@ -49,7 +52,13 @@ class GamesViewModel : ViewModel() {
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
- val game: Game = Json.decodeFromString(it)
+ val game: Game
+ try {
+ game = Json.decodeFromString(it)
+ } catch (e: MissingFieldException) {
+ return@forEach
+ }
+
val gameExists =
DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(game.path))
?.exists()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index c9f5797ac..aa424c768 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -765,18 +765,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
// If we have API access, calculate the safe area to draw the overlay
var cutoutLeft = 0
var cutoutBottom = 0
- val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
- if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
- insets.boundingRectTop.bottom.toFloat() else maxY
- if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
- insets.boundingRectRight.left.toFloat() else maxX
-
- minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
- minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
-
- cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
- cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
+ if (insets != null) {
+ if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
+ insets.boundingRectTop.bottom.toFloat() else maxY
+ if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
+ insets.boundingRectRight.left.toFloat() else maxX
+
+ minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
+ minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
+
+ cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
+ cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
+ }
}
// This makes sure that if we have an inset on one side of the screen, we mirror it on
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index ba6b5783e..42b207618 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
-import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
@@ -83,7 +82,8 @@ object GameHelper {
NativeLibrary.getRegions(filePath),
filePath,
gameId,
- NativeLibrary.getCompany(filePath)
+ NativeLibrary.getCompany(filePath),
+ NativeLibrary.isHomebrew(filePath)
)
val addedTime = preferences.getLong(newGame.keyAddedToLibraryTime, 0L)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b87e04b3d..03cb0b74b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -13,6 +13,7 @@
#include <android/api-level.h>
#include <android/native_window_jni.h>
+#include <core/loader/nro.h>
#include "common/detached_tasks.h"
#include "common/dynamic_library.h"
@@ -281,6 +282,10 @@ public:
return GetRomMetadata(path).icon;
}
+ bool GetIsHomebrew(const std::string& path) {
+ return GetRomMetadata(path).isHomebrew;
+ }
+
void ResetRomMetadata() {
m_rom_metadata_cache.clear();
}
@@ -348,6 +353,7 @@ private:
struct RomMetadata {
std::string title;
std::vector<u8> icon;
+ bool isHomebrew;
};
RomMetadata GetRomMetadata(const std::string& path) {
@@ -360,11 +366,17 @@ private:
RomMetadata CacheRomMetadata(const std::string& path) {
const auto file = Core::GetGameFileFromPath(m_vfs, path);
- const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
+ auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
RomMetadata entry;
loader->ReadTitle(entry.title);
loader->ReadIcon(entry.icon);
+ if (loader->GetFileType() == Loader::FileType::NRO) {
+ auto loader_nro = dynamic_cast<Loader::AppLoader_NRO*>(loader.get());
+ entry.isHomebrew = loader_nro->IsHomebrew();
+ } else {
+ entry.isHomebrew = false;
+ }
m_rom_metadata_cache[path] = entry;
@@ -662,6 +674,12 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany([[maybe_unused]] JNIEnv
return env->NewStringUTF("");
}
+jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew([[maybe_unused]] JNIEnv* env,
+ [[maybe_unused]] jclass clazz,
+ [[maybe_unused]] jstring j_filename) {
+ return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
+}
+
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation
[[maybe_unused]] (JNIEnv* env, [[maybe_unused]] jclass clazz) {
// Create the default config.ini.
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 07a679c32..703ef4494 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -47,12 +47,4 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
return *adsp;
}
-void AudioCore::SetNVDECActive(bool active) {
- nvdec_active = active;
-}
-
-bool AudioCore::IsNVDECActive() const {
- return nvdec_active;
-}
-
} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index e33e00a3e..ea047773e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -57,18 +57,6 @@ public:
*/
AudioRenderer::ADSP::ADSP& GetADSP();
- /**
- * Toggle NVDEC state, used to avoid stall in playback.
- *
- * @param active - Set true if nvdec is active, otherwise false.
- */
- void SetNVDECActive(bool active);
-
- /**
- * Get NVDEC state.
- */
- bool IsNVDECActive() const;
-
private:
/**
* Create the sinks on startup.
@@ -83,8 +71,6 @@ private:
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
- /// Is NVDec currently active?
- bool nvdec_active{false};
};
} // namespace AudioCore
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index c77c112f1..61bac9eba 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -10,6 +10,7 @@
// Sub-directories contained within a yuzu data directory
+#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
#define DUMP_DIR "dump"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index e026a13d9..d71cfacc6 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -114,6 +114,7 @@ public:
#endif
GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
+ GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 7cfe85b70..ba28964d0 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -12,6 +12,7 @@ namespace Common::FS {
enum class YuzuPath {
YuzuDir, // Where yuzu stores its data.
+ AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
DumpDir, // Where dumped data is stored.
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ff53e80bb..9ff3edabb 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -235,6 +235,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
values.enable_compute_pipelines.SetGlobal(true);
+ values.use_video_framerate.SetGlobal(true);
// System
values.language_index.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 7f865b2a7..9682281b0 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -482,6 +482,7 @@ struct Values {
SwitchableSetting<AstcRecompression, true> astc_recompression{
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
"astc_recompression"};
+ SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"};
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 89e1ed225..035df7fe0 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -48,7 +48,7 @@ std::array<u8, 0x10> ConstructFromRawString(std::string_view raw_string) {
}
std::array<u8, 0x10> ConstructFromFormattedString(std::string_view formatted_string) {
- std::array<u8, 0x10> uuid;
+ std::array<u8, 0x10> uuid{};
size_t i = 0;
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 4406ae30e..7ba704f18 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -216,6 +216,14 @@ struct System::Impl {
}
}
+ void SetNVDECActive(bool is_nvdec_active) {
+ nvdec_active = is_nvdec_active;
+ }
+
+ bool GetNVDECActive() {
+ return nvdec_active;
+ }
+
void InitializeDebugger(System& system, u16 port) {
debugger = std::make_unique<Debugger>(system, port);
}
@@ -485,6 +493,8 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool exit_lock = false;
+ bool nvdec_active{};
+
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
@@ -594,6 +604,14 @@ void System::UnstallApplication() {
impl->UnstallApplication();
}
+void System::SetNVDECActive(bool is_nvdec_active) {
+ impl->SetNVDECActive(is_nvdec_active);
+}
+
+bool System::GetNVDECActive() {
+ return impl->GetNVDECActive();
+}
+
void System::InitializeDebugger() {
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
}
diff --git a/src/core/core.h b/src/core/core.h
index 4f153154f..ff2e4bd30 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -189,6 +189,9 @@ public:
std::unique_lock<std::mutex> StallApplication();
void UnstallApplication();
+ void SetNVDECActive(bool is_nvdec_active);
+ [[nodiscard]] bool GetNVDECActive();
+
/**
* Initialize the debugger.
*/
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 0bd7900e1..b14f682b5 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -12,6 +12,11 @@
#pragma warning(pop)
#endif
+#include <fmt/format.h>
+
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
#include "common/input.h"
#include "common/logging/log.h"
#include "common/string_util.h"
@@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
LOG_INFO(Service_NFC, "Loading amiibo without keys");
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
- BuildAmiiboWithoutKeys();
+ BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
is_plain_amiibo = true;
is_write_protected = true;
return true;
@@ -366,16 +371,25 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
// The loaded amiibo is not encrypted
if (is_plain_amiibo) {
+ std::vector<u8> data(sizeof(NFP::NTAG215File));
+ memcpy(data.data(), &tag_data, sizeof(tag_data));
+ WriteBackupData(tag_data.uid, data);
+
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
}
if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
- LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
- return ResultCorruptedData;
+ bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
+ LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
+ return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
}
+ std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+ WriteBackupData(encrypted_tag_data.uuid.uid, data);
+
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
return ResultSuccess;
@@ -470,6 +484,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
if (is_plain_amiibo) {
memcpy(data.data(), &tag_data, sizeof(tag_data));
+ WriteBackupData(tag_data.uid, data);
} else {
if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Failed to encode data");
@@ -477,6 +492,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+ WriteBackupData(encrypted_tag_data.uuid.uid, data);
}
if (!npad_device->WriteNfc(data)) {
@@ -488,7 +504,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
}
Result NfcDevice::Restore() {
- if (device_state != DeviceState::TagMounted) {
+ if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ResultTagRemoved;
@@ -496,13 +512,59 @@ Result NfcDevice::Restore() {
return ResultWrongDeviceState;
}
- if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
- LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return ResultWrongDeviceState;
+ NFC::TagInfo tag_info{};
+ std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
+ Result result = GetTagInfo(tag_info, false);
+
+ if (result.IsError()) {
+ return result;
}
- // TODO: Load amiibo from backup on system
- LOG_ERROR(Service_NFP, "Not Implemented");
+ result = ReadBackupData(tag_info.uuid, data);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ NFP::NTAG215File temporary_tag_data{};
+ NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};
+
+ // Fallback for encrypted amiibos without keys
+ if (is_write_protected) {
+ return ResultWriteAmiiboFailed;
+ }
+
+ // Fallback for plain amiibos
+ if (is_plain_amiibo) {
+ LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
+ memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+ temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
+ }
+
+ if (!is_plain_amiibo) {
+ LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
+ temporary_tag_data = {};
+ memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+ }
+
+ if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
+ return ResultNotAnAmiibo;
+ }
+
+ if (!is_plain_amiibo) {
+ if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo");
+ return ResultCorruptedData;
+ }
+ }
+
+ // Overwrite tag contents with backup and mount the tag
+ tag_data = temporary_tag_data;
+ encrypted_tag_data = temporary_encrypted_tag_data;
+ device_state = DeviceState::TagMounted;
+ mount_target = NFP::MountTarget::All;
+ is_data_moddified = true;
+
return ResultSuccess;
}
@@ -1132,13 +1194,69 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
return FlushWithBreak(break_type);
}
-Result NfcDevice::ReadBackupData(std::span<u8> data) const {
- // Not implemented
+Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
+ constexpr auto backup_dir = "backup";
+ const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
+ const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
+
+ if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
+ return ResultUnableToAccessBackupFile;
+ }
+
return ResultSuccess;
}
-Result NfcDevice::WriteBackupData(std::span<const u8> data) {
- // Not implemented
+Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
+ constexpr auto backup_dir = "backup";
+ const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
+ const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
+
+ const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
+ Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ if (!keys_file.IsOpen()) {
+ LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
+ return ResultUnableToAccessBackupFile;
+ }
+
+ if (keys_file.Read(data) != data.size()) {
+ LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
+ return ResultUnableToAccessBackupFile;
+ }
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
+ constexpr auto backup_dir = "backup";
+ const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
+ const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
+
+ if (HasBackup(uid).IsError()) {
+ if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
+ return ResultBackupPathAlreadyExist;
+ }
+
+ if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
+ return ResultBackupPathAlreadyExist;
+ }
+ }
+
+ const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
+ Common::FS::FileAccessMode::ReadWrite,
+ Common::FS::FileType::BinaryFile};
+
+ if (!keys_file.IsOpen()) {
+ LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
+ return ResultUnableToAccessBackupFile;
+ }
+
+ if (keys_file.Write(data) != data.size()) {
+ LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
+ return ResultUnableToAccessBackupFile;
+ }
+
return ResultSuccess;
}
@@ -1177,7 +1295,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co
return amiibo_name;
}
-void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) {
+void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
+ const NFP::AmiiboName& amiibo_name) const {
std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
// Convert from utf8 to utf16
@@ -1258,22 +1377,23 @@ void NfcDevice::UpdateRegisterInfoCrc() {
tag_data.register_info_crc = crc.checksum();
}
-void NfcDevice::BuildAmiiboWithoutKeys() {
+void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
+ const NFP::EncryptedNTAG215File& encrypted_file) const {
Service::Mii::MiiManager manager;
- auto& settings = tag_data.settings;
+ auto& settings = stubbed_tag_data.settings;
- tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data);
+ stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
// Common info
- tag_data.write_counter = 0;
- tag_data.amiibo_version = 0;
+ stubbed_tag_data.write_counter = 0;
+ stubbed_tag_data.amiibo_version = 0;
settings.write_date = GetAmiiboDate(GetCurrentPosixTime());
// Register info
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
settings.settings.font_region.Assign(0);
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
- tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
+ stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0));
// Admin info
settings.settings.amiibo_initialized.Assign(1);
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 6a37e8458..6f049b687 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -86,8 +86,9 @@ public:
Result GetAll(NFP::NfpData& data) const;
Result SetAll(const NFP::NfpData& data);
Result BreakTag(NFP::BreakType break_type);
- Result ReadBackupData(std::span<u8> data) const;
- Result WriteBackupData(std::span<const u8> data);
+ Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
+ Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
+ Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
Result WriteNtf(std::span<const u8> data);
u64 GetHandle() const;
@@ -103,14 +104,15 @@ private:
void CloseNfcTag();
NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
- void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name);
+ void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
u64 GetCurrentPosixTime() const;
u64 RemoveVersionByte(u64 application_id) const;
void UpdateSettingsCrc();
void UpdateRegisterInfoCrc();
- void BuildAmiiboWithoutKeys();
+ void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
+ const NFP::EncryptedNTAG215File& encrypted_file) const;
bool is_controller_set{};
int callback_key;
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index d5deaaf27..cffd602df 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
+ NFC::TagInfo tag_info{};
if (result.IsSuccess()) {
- result = device->ReadBackupData(data);
+ result = device->GetTagInfo(tag_info, false);
+ }
+
+ if (result.IsSuccess()) {
+ result = device->ReadBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}
@@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device);
+ NFC::TagInfo tag_info{};
+
+ if (result.IsSuccess()) {
+ result = device->GetTagInfo(tag_info, false);
+ }
if (result.IsSuccess()) {
- result = device->WriteBackupData(data);
+ result = device->WriteBackupData(tag_info.uuid, data);
result = VerifyDeviceResult(device, result);
}
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 0fa29d398..198d0f2b9 100644
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -302,7 +302,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const {
return TranslateResultToNfp(result);
}
default:
- if (result != ResultUnknown216) {
+ if (result != ResultBackupPathAlreadyExist) {
return result;
}
return ResultUnknown74;
@@ -343,6 +343,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultApplicationAreaIsNotInitialized) {
return NFP::ResultApplicationAreaIsNotInitialized;
}
+ if (result == ResultCorruptedDataWithBackup) {
+ return NFP::ResultCorruptedDataWithBackup;
+ }
if (result == ResultCorruptedData) {
return NFP::ResultCorruptedData;
}
@@ -355,6 +358,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const {
if (result == ResultNotAnAmiibo) {
return NFP::ResultNotAnAmiibo;
}
+ if (result == ResultUnableToAccessBackupFile) {
+ return NFP::ResultUnableToAccessBackupFile;
+ }
LOG_WARNING(Service_NFC, "Result conversion not handled");
return result;
}
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h
index 917d79ef8..59a808740 100644
--- a/src/core/hle/service/nfc/nfc_result.h
+++ b/src/core/hle/service/nfc/nfc_result.h
@@ -9,20 +9,22 @@ namespace Service::NFC {
constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
-constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68);
constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
-constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
-constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
-constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
-constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
-constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
-constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
-constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
-constexpr Result ResultUnknown216(ErrorModule::NFC, 216);
+constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113);
+constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120);
+constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128);
+constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136);
+constexpr Result ResultCorruptedData(ErrorModule::NFC, 144);
+constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152);
+constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168);
+constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178);
+constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216);
} // namespace Service::NFC
diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp
index 21d159154..34ef9d82d 100644
--- a/src/core/hle/service/nfp/nfp_interface.cpp
+++ b/src/core/hle/service/nfp/nfp_interface.cpp
@@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) {
void Interface::Restore(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->Restore(device_handle);
result = TranslateResultToServiceError(result);
@@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) {
void Interface::ReadBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
std::vector<u8> backup_data{};
auto result = GetManager()->ReadBackupData(device_handle, backup_data);
@@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto backup_data_buffer{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
result = TranslateResultToServiceError(result);
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
index 4c126cd81..618533843 100644
--- a/src/core/hle/service/nfp/nfp_result.h
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136);
constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
+constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200);
} // namespace Service::NFP
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 0c7aee1b8..dc45169ad 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
void nvhost_nvdec::OnOpen(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
- system.AudioCore().SetNVDECActive(true);
+ system.SetNVDECActive(true);
}
void nvhost_nvdec::OnClose(DeviceFD fd) {
@@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
if (iter != host1x_file.fd_to_id.end()) {
system.GPU().ClearCdmaInstance(iter->second);
}
- system.AudioCore().SetNVDECActive(false);
+ system.SetNVDECActive(false);
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 4988e6e17..da2d5890f 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const {
speed_scale = 0.01f;
}
}
+ if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) {
+ // Run at intended presentation rate during video playback.
+ speed_scale = 1.f;
+ }
// As an extension, treat nonpositive swap interval as framerate multiplier.
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 73d04d7ee..7be6cf5f3 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -33,7 +33,8 @@ static_assert(sizeof(NroSegmentHeader) == 0x8, "NroSegmentHeader has incorrect s
struct NroHeader {
INSERT_PADDING_BYTES(0x4);
u32_le module_header_offset;
- INSERT_PADDING_BYTES(0x8);
+ u32 magic_ext1;
+ u32 magic_ext2;
u32_le magic;
INSERT_PADDING_BYTES(0x4);
u32_le file_size;
@@ -124,6 +125,16 @@ FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& nro_file) {
return FileType::Error;
}
+bool AppLoader_NRO::IsHomebrew() {
+ // Read NSO header
+ NroHeader nro_header{};
+ if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
+ return false;
+ }
+ return nro_header.magic_ext1 == Common::MakeMagic('H', 'O', 'M', 'E') &&
+ nro_header.magic_ext2 == Common::MakeMagic('B', 'R', 'E', 'W');
+}
+
static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index ccb77b581..8de6eebc6 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -38,6 +38,8 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
+ bool IsHomebrew();
+
FileType GetFileType() const override {
return IdentifyType(file);
}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 6288fef62..bac9dff90 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -101,6 +101,12 @@ const std::map<Settings::RendererBackend, QString> Config::renderer_backend_text
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
};
+const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
+ {Settings::ShaderBackend::GLSL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
+ {Settings::ShaderBackend::GLASM, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
+ {Settings::ShaderBackend::SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
+};
+
// This shouldn't have anything except static initializers (no functions). So
// QKeySequence(...).toString() is NOT ALLOWED HERE.
// This must be in alphabetical order according to action name as it must have the same order as
@@ -754,6 +760,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.use_fast_gpu_time);
ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
ReadGlobalSetting(Settings::values.enable_compute_pipelines);
+ ReadGlobalSetting(Settings::values.use_video_framerate);
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
@@ -1409,6 +1416,7 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.use_fast_gpu_time);
WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
WriteGlobalSetting(Settings::values.enable_compute_pipelines);
+ WriteGlobalSetting(Settings::values.use_video_framerate);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ad590ea9e..0fd4baf6b 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -54,6 +54,7 @@ public:
static const std::map<bool, QString> use_docked_mode_texts_map;
static const std::map<Settings::GPUAccuracy, QString> gpu_accuracy_texts_map;
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
+ static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
static constexpr UISettings::Theme default_theme{
#ifdef _WIN32
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 896863f87..0463ac8b9 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -42,6 +42,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
ui->enable_compute_pipelines_checkbox->setChecked(
Settings::values.enable_compute_pipelines.GetValue());
+ ui->use_video_framerate_checkbox->setChecked(Settings::values.use_video_framerate.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
@@ -91,6 +92,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
ui->enable_compute_pipelines_checkbox,
enable_compute_pipelines);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_video_framerate,
+ ui->use_video_framerate_checkbox, use_video_framerate);
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -125,6 +128,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
Settings::values.max_anisotropy.UsingGlobal());
ui->enable_compute_pipelines_checkbox->setEnabled(
Settings::values.enable_compute_pipelines.UsingGlobal());
+ ui->use_video_framerate_checkbox->setEnabled(
+ Settings::values.use_video_framerate.UsingGlobal());
return;
}
@@ -149,6 +154,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
Settings::values.enable_compute_pipelines,
enable_compute_pipelines);
+ ConfigurationShared::SetColoredTristate(ui->use_video_framerate_checkbox,
+ Settings::values.use_video_framerate,
+ use_video_framerate);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index 1c7b636b9..a4dc8ceb0 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -47,6 +47,7 @@ private:
ConfigurationShared::CheckState use_fast_gpu_time;
ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
ConfigurationShared::CheckState enable_compute_pipelines;
+ ConfigurationShared::CheckState use_video_framerate;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index 37757a918..e7f0ef6be 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -192,6 +192,16 @@ Compute pipelines are always enabled on all other drivers.</string>
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="use_video_framerate_checkbox">
+ <property name="toolTip">
+ <string>Run the game at normal speed during video playback, even when the framerate is unlocked.</string>
+ </property>
+ <property name="text">
+ <string>Sync to framerate of video playback</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QWidget" name="af_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<property name="leftMargin">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 145fea5f1..9d06b21b6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -4116,7 +4116,13 @@ void GMainWindow::UpdateDockedButton() {
void GMainWindow::UpdateAPIText() {
const auto api = Settings::values.renderer_backend.GetValue();
const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
- renderer_status_button->setText(renderer_status_text.toUpper());
+ renderer_status_button->setText(
+ api == Settings::RendererBackend::OpenGL
+ ? tr("%1 %2").arg(
+ renderer_status_text.toUpper(),
+ Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
+ ->second)
+ : renderer_status_text.toUpper());
}
void GMainWindow::UpdateFilterText() {