Compare commits
30 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e3d4e3396 | ||
|
|
569f8d3b44 | ||
|
|
f4c383392d | ||
|
|
5fcc05a4b2 | ||
|
|
92d49ad652 | ||
|
|
f23a2b514b | ||
|
|
37e135d74d | ||
|
|
16fe64ad0c | ||
|
|
b6f2490288 | ||
|
|
ea716eb5cc | ||
|
|
6b898c6d69 | ||
|
|
fa5dfcb712 | ||
|
|
cb1fd1bad8 | ||
|
|
d8609eef89 | ||
|
|
f759ff3a5c | ||
|
|
72d9dc9a3f | ||
|
|
55b543f466 | ||
|
|
3cce51d25b | ||
|
|
4d395b3b72 | ||
|
|
5a0d4e1d38 | ||
|
|
b3e2c9f9f1 | ||
|
|
2a1acbfb4d | ||
|
|
a57150afbd | ||
|
|
60cc611f38 | ||
|
|
064bad6ddf | ||
|
|
74671186bf | ||
|
|
ace6c2318b | ||
|
|
6c34adb1de | ||
|
|
107aa52cdb | ||
|
|
a64ad8315f |
@@ -3,4 +3,4 @@
|
||||
|
||||
[codespell]
|
||||
skip = ./.git,./build,./dist,./Doxyfile,./externals,./LICENSES
|
||||
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
|
||||
ignore-words-list = aci,allright,ba,deques,froms,hda,inout,lod,masia,nam,nax,nd,optin,pullrequests,pullrequest,te,transfered,unstall,uscaled,zink
|
||||
|
||||
@@ -74,16 +74,7 @@ android {
|
||||
|
||||
// Signed by release key, allowing for upload to Play Store.
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
|
||||
register("relWithVersionCode") {
|
||||
resValue("string", "app_name_suffixed", "yuzu")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
@@ -96,6 +87,7 @@ android {
|
||||
// builds a release build that doesn't need signing
|
||||
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
||||
register("relWithDebInfo") {
|
||||
resValue("string", "app_name_suffixed", "yuzu Debug Release")
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = true
|
||||
@@ -103,16 +95,19 @@ android {
|
||||
getDefaultProguardFile("proguard-android.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
versionNameSuffix = "-debug"
|
||||
versionNameSuffix = "-relWithDebInfo"
|
||||
applicationIdSuffix = ".relWithDebInfo"
|
||||
isJniDebuggable = true
|
||||
}
|
||||
|
||||
// Signed by debug key disallowing distribution on Play Store.
|
||||
// Attaches 'debug' suffix to version and package name, allowing installation alongside the release build.
|
||||
debug {
|
||||
resValue("string", "app_name_suffixed", "yuzu Debug")
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
versionNameSuffix = "-debug"
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,19 +157,19 @@ dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.fragment:fragment-ktx:1.5.7")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.0")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("androidx.preference:preference:1.2.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||
implementation("io.coil-kt:coil:2.2.2")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.window:window:1.0.0")
|
||||
implementation("androidx.window:window:1.1.0")
|
||||
implementation("org.ini4j:ini4j:0.5.4")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
|
||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
<application
|
||||
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
||||
android:label="@string/app_name"
|
||||
android:label="@string/app_name_suffixed"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:allowBackup="true"
|
||||
android:hasFragileUserData="true"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -6,15 +6,12 @@ package org.yuzu.yuzu_emu.activities
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
@@ -23,7 +20,6 @@ import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
@@ -39,7 +35,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
@@ -148,11 +143,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
|
||||
NativeLibrary.notifyOrientationChange(
|
||||
EmulationMenuSettings.landscapeScreenLayout,
|
||||
getAdjustedRotation()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -258,23 +248,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
||||
|
||||
private fun getAdjustedRotation():Int {
|
||||
val rotation = getSystemService<DisplayManager>()!!.getDisplay(Display.DEFAULT_DISPLAY).rotation
|
||||
val config: Configuration = resources.configuration
|
||||
|
||||
if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
|
||||
(config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
|
||||
return rotation
|
||||
}
|
||||
when (rotation) {
|
||||
Surface.ROTATION_0 -> return Surface.ROTATION_90
|
||||
Surface.ROTATION_90 -> return Surface.ROTATION_0
|
||||
Surface.ROTATION_180 -> return Surface.ROTATION_270
|
||||
Surface.ROTATION_270 -> return Surface.ROTATION_180
|
||||
}
|
||||
return rotation
|
||||
}
|
||||
|
||||
private fun restoreState(savedInstanceState: Bundle) {
|
||||
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -284,10 +284,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
if (!FileUtil.hasExtension(result.toString(), "keys")) {
|
||||
if (!FileUtil.hasExtension(result, "keys")) {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_keys_failure_extension_description
|
||||
R.string.install_prod_keys_failure_extension_description
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
@@ -379,10 +379,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
if (!FileUtil.hasExtension(result.toString(), "bin")) {
|
||||
if (!FileUtil.hasExtension(result, "bin")) {
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_keys_failure_extension_description
|
||||
R.string.install_amiibo_keys_failure_extension_description
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
@@ -324,7 +326,25 @@ object FileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun hasExtension(path: String, extension: String): Boolean {
|
||||
return path.substring(path.lastIndexOf(".") + 1).contains(extension)
|
||||
fun hasExtension(path: String, extension: String): Boolean =
|
||||
path.substring(path.lastIndexOf(".") + 1).contains(extension)
|
||||
|
||||
fun hasExtension(uri: Uri, extension: String): Boolean {
|
||||
val fileName: String?
|
||||
val cursor = YuzuApplication.appContext.contentResolver.query(uri, null, null, null, null)
|
||||
val nameIndex = cursor?.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
cursor?.moveToFirst()
|
||||
|
||||
if (nameIndex == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
fileName = cursor.getString(nameIndex)
|
||||
cursor.close()
|
||||
|
||||
if (fileName == null) {
|
||||
return false
|
||||
}
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1).contains(extension)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -93,14 +94,6 @@ public:
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
u32 ScreenRotation() const {
|
||||
return m_screen_rotation;
|
||||
}
|
||||
|
||||
void SetScreenRotation(u32 screen_rotation) {
|
||||
m_screen_rotation = screen_rotation;
|
||||
}
|
||||
|
||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||
const std::string& custom_driver_name,
|
||||
const std::string& file_redirect_dir) {
|
||||
@@ -281,6 +274,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 +345,7 @@ private:
|
||||
struct RomMetadata {
|
||||
std::string title;
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
|
||||
RomMetadata GetRomMetadata(const std::string& path) {
|
||||
@@ -360,11 +358,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;
|
||||
|
||||
@@ -388,7 +392,6 @@ private:
|
||||
// Window management
|
||||
std::unique_ptr<EmuWindow_Android> m_window;
|
||||
ANativeWindow* m_native_window{};
|
||||
u32 m_screen_rotation{};
|
||||
|
||||
// Core emulation
|
||||
Core::System m_system;
|
||||
@@ -414,10 +417,6 @@ private:
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
u32 GetAndroidScreenRotation() {
|
||||
return EmulationSession::GetInstance().ScreenRotation();
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
@@ -461,13 +460,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
|
||||
EmulationSession::GetInstance().SurfaceChanged();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jint layout_option,
|
||||
jint rotation) {
|
||||
return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jstring j_directory) {
|
||||
@@ -662,6 +654,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.
|
||||
|
||||
@@ -65,11 +65,8 @@
|
||||
<string name="invalid_keys_file">Invalid keys file selected</string>
|
||||
<string name="install_keys_success">Keys successfully installed</string>
|
||||
<string name="reading_keys_failure">Error reading encryption keys</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. Verify your keys have the .keys extension.\n\n
|
||||
2. Keys must not be stored in the Downloads folder.\n\n
|
||||
Resolve the issue(s) and try again.
|
||||
</string>
|
||||
<string name="install_prod_keys_failure_extension_description">Verify your keys file has a .keys extension and try again.</string>
|
||||
<string name="install_amiibo_keys_failure_extension_description">Verify your keys file has a .bin extension and try again.</string>
|
||||
<string name="invalid_keys_error">Invalid encryption keys</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"};
|
||||
@@ -523,9 +524,16 @@ struct Values {
|
||||
Setting<bool> tas_loop{false, "tas_loop"};
|
||||
|
||||
Setting<bool> mouse_panning{false, "mouse_panning"};
|
||||
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_deadzone_x_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
|
||||
Setting<u8, true> mouse_panning_deadzone_y_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
|
||||
Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
|
||||
Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
|
||||
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
||||
Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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->WriteBackupData(data);
|
||||
result = device->GetTagInfo(tag_info, false);
|
||||
}
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device->WriteBackupData(tag_info.uuid, data);
|
||||
result = VerifyDeviceResult(device, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& nro_file);
|
||||
|
||||
bool IsHomebrew();
|
||||
|
||||
FileType GetFileType() const override {
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
@@ -76,9 +76,6 @@ void Mouse::UpdateThread(std::stop_token stop_token) {
|
||||
UpdateStickInput();
|
||||
UpdateMotionInput();
|
||||
|
||||
if (mouse_panning_timeout++ > 20) {
|
||||
StopPanning();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
||||
}
|
||||
}
|
||||
@@ -88,18 +85,45 @@ void Mouse::UpdateStickInput() {
|
||||
return;
|
||||
}
|
||||
|
||||
const float sensitivity =
|
||||
Settings::values.mouse_panning_sensitivity.GetValue() * default_stick_sensitivity;
|
||||
const float length = last_mouse_change.Length();
|
||||
|
||||
// Slow movement by 4%
|
||||
last_mouse_change *= 0.96f;
|
||||
SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
|
||||
SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
|
||||
// Prevent input from exceeding the max range (1.0f) too much,
|
||||
// but allow some room to make it easier to sustain
|
||||
if (length > 1.2f) {
|
||||
last_mouse_change /= length;
|
||||
last_mouse_change *= 1.2f;
|
||||
}
|
||||
|
||||
auto mouse_change = last_mouse_change;
|
||||
|
||||
// Bind the mouse change to [0 <= deadzone_counterweight <= 1,1]
|
||||
if (length < 1.0f) {
|
||||
const float deadzone_h_counterweight =
|
||||
Settings::values.mouse_panning_deadzone_x_counterweight.GetValue();
|
||||
const float deadzone_v_counterweight =
|
||||
Settings::values.mouse_panning_deadzone_y_counterweight.GetValue();
|
||||
mouse_change /= length;
|
||||
mouse_change.x *= length + (1 - length) * deadzone_h_counterweight * 0.01f;
|
||||
mouse_change.y *= length + (1 - length) * deadzone_v_counterweight * 0.01f;
|
||||
}
|
||||
|
||||
SetAxis(identifier, mouse_axis_x, mouse_change.x);
|
||||
SetAxis(identifier, mouse_axis_y, -mouse_change.y);
|
||||
|
||||
// Decay input over time
|
||||
const float clamped_length = std::min(1.0f, length);
|
||||
const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue();
|
||||
const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f;
|
||||
const float min_decay = Settings::values.mouse_panning_min_decay.GetValue();
|
||||
const float clamped_decay = std::min(1 - min_decay / 100.0f, decay);
|
||||
last_mouse_change *= clamped_decay;
|
||||
}
|
||||
|
||||
void Mouse::UpdateMotionInput() {
|
||||
const float sensitivity =
|
||||
Settings::values.mouse_panning_sensitivity.GetValue() * default_motion_sensitivity;
|
||||
// This may need its own sensitivity instead of using the average
|
||||
const float sensitivity = (Settings::values.mouse_panning_x_sensitivity.GetValue() +
|
||||
Settings::values.mouse_panning_y_sensitivity.GetValue()) /
|
||||
2.0f * default_motion_sensitivity;
|
||||
|
||||
const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x +
|
||||
last_motion_change.y * last_motion_change.y);
|
||||
@@ -131,49 +155,28 @@ void Mouse::UpdateMotionInput() {
|
||||
|
||||
void Mouse::Move(int x, int y, int center_x, int center_y) {
|
||||
if (Settings::values.mouse_panning) {
|
||||
mouse_panning_timeout = 0;
|
||||
|
||||
auto mouse_change =
|
||||
const auto mouse_change =
|
||||
(Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
|
||||
const float x_sensitivity =
|
||||
Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
|
||||
const float y_sensitivity =
|
||||
Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
|
||||
|
||||
last_motion_change += {-mouse_change.y, -mouse_change.x, 0};
|
||||
|
||||
const auto move_distance = mouse_change.Length();
|
||||
if (move_distance == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make slow movements at least 3 units on length
|
||||
if (move_distance < 3.0f) {
|
||||
// Normalize value
|
||||
mouse_change /= move_distance;
|
||||
mouse_change *= 3.0f;
|
||||
}
|
||||
|
||||
// Average mouse movements
|
||||
last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
|
||||
|
||||
const auto last_move_distance = last_mouse_change.Length();
|
||||
|
||||
// Make fast movements clamp to 8 units on length
|
||||
if (last_move_distance > 8.0f) {
|
||||
// Normalize value
|
||||
last_mouse_change /= last_move_distance;
|
||||
last_mouse_change *= 8.0f;
|
||||
}
|
||||
|
||||
// Ignore average if it's less than 1 unit and use current movement value
|
||||
if (last_move_distance < 1.0f) {
|
||||
last_mouse_change = mouse_change / mouse_change.Length();
|
||||
}
|
||||
last_mouse_change.x += mouse_change.x * x_sensitivity * 0.09f;
|
||||
last_mouse_change.y += mouse_change.y * y_sensitivity * 0.09f;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (button_pressed) {
|
||||
const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
|
||||
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
|
||||
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
|
||||
SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
|
||||
const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue();
|
||||
const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue();
|
||||
SetAxis(identifier, mouse_axis_x,
|
||||
static_cast<float>(mouse_move.x) * x_sensitivity * 0.0012f);
|
||||
SetAxis(identifier, mouse_axis_y,
|
||||
static_cast<float>(-mouse_move.y) * y_sensitivity * 0.0012f);
|
||||
|
||||
last_motion_change = {
|
||||
static_cast<float>(-mouse_move.y) / 50.0f,
|
||||
@@ -241,10 +244,6 @@ void Mouse::ReleaseAllButtons() {
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
void Mouse::StopPanning() {
|
||||
last_mouse_change = {};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
|
||||
@@ -98,7 +98,6 @@ private:
|
||||
void UpdateThread(std::stop_token stop_token);
|
||||
void UpdateStickInput();
|
||||
void UpdateMotionInput();
|
||||
void StopPanning();
|
||||
|
||||
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
@@ -108,7 +107,6 @@ private:
|
||||
Common::Vec3<float> last_motion_change;
|
||||
Common::Vec2<int> wheel_position;
|
||||
bool button_pressed;
|
||||
int mouse_panning_timeout{};
|
||||
std::jthread update_thread;
|
||||
};
|
||||
|
||||
|
||||
@@ -715,20 +715,38 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::BindHostVertexBuffers() {
|
||||
HostBindings host_bindings;
|
||||
bool any_valid{false};
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||
const Binding& binding = channel_state->vertex_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
|
||||
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||
continue;
|
||||
}
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
host_bindings.min_index = std::min(host_bindings.min_index, index);
|
||||
host_bindings.max_index = std::max(host_bindings.max_index, index);
|
||||
any_valid = true;
|
||||
}
|
||||
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||
runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
|
||||
if (any_valid) {
|
||||
host_bindings.max_index++;
|
||||
for (u32 index = host_bindings.min_index; index < host_bindings.max_index; index++) {
|
||||
flags[Dirty::VertexBuffer0 + index] = false;
|
||||
|
||||
const Binding& binding = channel_state->vertex_buffers[index];
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, binding.size);
|
||||
|
||||
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
|
||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||
|
||||
host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(binding.size);
|
||||
host_bindings.strides.push_back(stride);
|
||||
}
|
||||
runtime.BindVertexBuffers(host_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,15 +900,25 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
if (maxwell3d->regs.transform_feedback_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
HostBindings host_bindings;
|
||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||
const Binding& binding = channel_state->transform_feedback_buffers[index];
|
||||
if (maxwell3d->regs.transform_feedback.controls[index].varying_count == 0 &&
|
||||
maxwell3d->regs.transform_feedback.controls[index].stride == 0) {
|
||||
break;
|
||||
}
|
||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||
TouchBuffer(buffer, binding.buffer_id);
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||
runtime.BindTransformFeedbackBuffer(index, buffer, offset, size);
|
||||
host_bindings.buffers.push_back(reinterpret_cast<void*>(&buffer));
|
||||
host_bindings.offsets.push_back(offset);
|
||||
host_bindings.sizes.push_back(binding.size);
|
||||
}
|
||||
if (host_bindings.buffers.size() > 0) {
|
||||
runtime.BindTransformFeedbackBuffers(host_bindings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1616,6 +1644,8 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
||||
bool dirty_index{false};
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
|
||||
const auto scalar_replace = [buffer_id](Binding& binding) {
|
||||
if (binding.buffer_id == buffer_id) {
|
||||
binding.buffer_id = BufferId{};
|
||||
@@ -1624,8 +1654,19 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
||||
const auto replace = [scalar_replace](std::span<Binding> bindings) {
|
||||
std::ranges::for_each(bindings, scalar_replace);
|
||||
};
|
||||
scalar_replace(channel_state->index_buffer);
|
||||
replace(channel_state->vertex_buffers);
|
||||
|
||||
if (channel_state->index_buffer.buffer_id == buffer_id) {
|
||||
channel_state->index_buffer.buffer_id = BufferId{};
|
||||
dirty_index = true;
|
||||
}
|
||||
|
||||
for (u32 index = 0; index < channel_state->vertex_buffers.size(); index++) {
|
||||
auto& binding = channel_state->vertex_buffers[index];
|
||||
if (binding.buffer_id == buffer_id) {
|
||||
binding.buffer_id = BufferId{};
|
||||
dirty_vertex_buffers.push_back(index);
|
||||
}
|
||||
}
|
||||
std::ranges::for_each(channel_state->uniform_buffers, replace);
|
||||
std::ranges::for_each(channel_state->storage_buffers, replace);
|
||||
replace(channel_state->transform_feedback_buffers);
|
||||
@@ -1642,20 +1683,21 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
|
||||
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
|
||||
slot_buffers.erase(buffer_id);
|
||||
|
||||
NotifyBufferDeletion();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::NotifyBufferDeletion() {
|
||||
if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) {
|
||||
channel_state->dirty_uniform_buffers.fill(~u32{0});
|
||||
channel_state->uniform_buffer_binding_sizes.fill({});
|
||||
}
|
||||
|
||||
auto& flags = maxwell3d->dirty.flags;
|
||||
flags[Dirty::IndexBuffer] = true;
|
||||
flags[Dirty::VertexBuffers] = true;
|
||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||
flags[Dirty::VertexBuffer0 + index] = true;
|
||||
if (dirty_index) {
|
||||
flags[Dirty::IndexBuffer] = true;
|
||||
}
|
||||
|
||||
if (dirty_vertex_buffers.size() > 0) {
|
||||
flags[Dirty::VertexBuffers] = true;
|
||||
for (auto index : dirty_vertex_buffers) {
|
||||
flags[Dirty::VertexBuffer0 + index] = true;
|
||||
}
|
||||
}
|
||||
channel_state->has_deleted_buffers = true;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,15 @@ static constexpr Binding NULL_BINDING{
|
||||
.buffer_id = NULL_BUFFER_ID,
|
||||
};
|
||||
|
||||
struct HostBindings {
|
||||
boost::container::small_vector<void*, NUM_VERTEX_BUFFERS> buffers;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> offsets;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> sizes;
|
||||
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> strides;
|
||||
u32 min_index{NUM_VERTEX_BUFFERS};
|
||||
u32 max_index{0};
|
||||
};
|
||||
|
||||
class BufferCacheChannelInfo : public ChannelInfo {
|
||||
public:
|
||||
BufferCacheChannelInfo() = delete;
|
||||
@@ -519,8 +528,6 @@ private:
|
||||
|
||||
void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
|
||||
|
||||
void NotifyBufferDeletion();
|
||||
|
||||
[[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
|
||||
bool is_written) const;
|
||||
|
||||
|
||||
@@ -232,6 +232,15 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset,
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
|
||||
for (u32 index = 0; index < bindings.buffers.size(); index++) {
|
||||
BindVertexBuffer(
|
||||
bindings.min_index + index, *reinterpret_cast<Buffer*>(bindings.buffers[index]),
|
||||
static_cast<u32>(bindings.offsets[index]), static_cast<u32>(bindings.sizes[index]),
|
||||
static_cast<u32>(bindings.strides[index]));
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
|
||||
u32 offset, u32 size) {
|
||||
if (use_assembly_shaders) {
|
||||
@@ -320,6 +329,15 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer,
|
||||
static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
|
||||
for (u32 index = 0; index < bindings.buffers.size(); index++) {
|
||||
glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index,
|
||||
reinterpret_cast<Buffer*>(bindings.buffers[index])->Handle(),
|
||||
static_cast<GLintptr>(bindings.offsets[index]),
|
||||
static_cast<GLsizeiptr>(bindings.sizes[index]));
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
PixelFormat format) {
|
||||
*texture_handles++ = buffer.View(offset, size, format);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/buffer_cache/buffer_cache.h"
|
||||
#include "video_core/buffer_cache/buffer_cache_base.h"
|
||||
#include "video_core/buffer_cache/memory_tracker_base.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
@@ -87,6 +87,7 @@ public:
|
||||
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
|
||||
|
||||
void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
|
||||
void BindVertexBuffers(VideoCommon::HostBindings& bindings);
|
||||
|
||||
void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
|
||||
|
||||
@@ -99,6 +100,7 @@ public:
|
||||
bool is_written);
|
||||
|
||||
void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);
|
||||
void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
|
||||
|
||||
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
|
||||
VideoCore::Surface::PixelFormat format);
|
||||
|
||||
@@ -37,10 +37,6 @@
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
extern u32 GetAndroidScreenRotation();
|
||||
#endif
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
@@ -78,47 +74,6 @@ struct ScreenRectVertex {
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
constexpr u32 ROTATION_0 = 0;
|
||||
constexpr u32 ROTATION_90 = 1;
|
||||
constexpr u32 ROTATION_180 = 2;
|
||||
constexpr u32 ROTATION_270 = 3;
|
||||
|
||||
// clang-format off
|
||||
switch (GetAndroidScreenRotation()) {
|
||||
case ROTATION_0:
|
||||
// Desktop
|
||||
return { 2.f / width, 0.f, 0.f, 0.f,
|
||||
0.f, 2.f / height, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
-1.f, -1.f, 0.f, 1.f};
|
||||
case ROTATION_180:
|
||||
// Reverse desktop
|
||||
return {-2.f / width, 0.f, 0.f, 0.f,
|
||||
0.f, -2.f / height, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
1.f, 1.f, 0.f, 1.f};
|
||||
case ROTATION_270:
|
||||
// Reverse landscape
|
||||
return { 0.f, -2.f / width, 0.f, 0.f,
|
||||
2.f / height, 0.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
-1.f, 1.f, 0.f, 1.f};
|
||||
case ROTATION_90:
|
||||
default:
|
||||
// Landscape
|
||||
return { 0.f, 2.f / width, 0.f, 0.f,
|
||||
-2.f / height, 0.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
1.f, -1.f, 0.f, 1.f};
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
// clang-format off
|
||||
return { 2.f / width, 0.f, 0.f, 0.f,
|
||||
@@ -128,8 +83,6 @@ std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
|
||||
using namespace VideoCore::Surface;
|
||||
return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
|
||||
@@ -1159,7 +1112,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = GetFormat(framebuffer),
|
||||
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
|
||||
.extent =
|
||||
{
|
||||
.width = (up_scale * framebuffer.width) >> down_shift,
|
||||
@@ -1180,14 +1133,14 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
|
||||
const auto create_commit = [&](vk::Image& image) {
|
||||
return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
|
||||
};
|
||||
const auto create_image_view = [&](vk::Image& image) {
|
||||
const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
|
||||
return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = *image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = GetFormat(framebuffer),
|
||||
.format = used_on_framebuffer ? VK_FORMAT_R16G16B16A16_SFLOAT : GetFormat(framebuffer),
|
||||
.components =
|
||||
{
|
||||
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
@@ -1217,7 +1170,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
|
||||
const u32 down_shift = Settings::values.resolution_info.down_shift;
|
||||
aa_image = create_image(true, up_scale, down_shift);
|
||||
aa_commit = create_commit(aa_image);
|
||||
aa_image_view = create_image_view(aa_image);
|
||||
aa_image_view = create_image_view(aa_image, true);
|
||||
VkExtent2D size{
|
||||
.width = (up_scale * framebuffer.width) >> down_shift,
|
||||
.height = (up_scale * framebuffer.height) >> down_shift,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/buffer_cache/buffer_cache.h"
|
||||
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
@@ -502,6 +501,40 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings& bindings) {
|
||||
boost::container::small_vector<VkBuffer, 32> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); index++) {
|
||||
auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
|
||||
auto handle = buffer.Handle();
|
||||
if (handle == VK_NULL_HANDLE) {
|
||||
bindings.offsets[index] = 0;
|
||||
bindings.sizes[index] = VK_WHOLE_SIZE;
|
||||
if (!device.HasNullDescriptor()) {
|
||||
ReserveNullBuffer();
|
||||
handle = *null_buffer;
|
||||
}
|
||||
}
|
||||
buffer_handles.push_back(handle);
|
||||
}
|
||||
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||
scheduler.Record([bindings = bindings,
|
||||
buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers2EXT(
|
||||
bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.strides.data()));
|
||||
});
|
||||
} else {
|
||||
scheduler.Record([bindings = bindings,
|
||||
buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindVertexBuffers(
|
||||
bindings.min_index, bindings.max_index - bindings.min_index, buffer_handles.data(),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset,
|
||||
u32 size) {
|
||||
if (!device.IsExtTransformFeedbackSupported()) {
|
||||
@@ -523,6 +556,25 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings) {
|
||||
if (!device.IsExtTransformFeedbackSupported()) {
|
||||
// Already logged in the rasterizer
|
||||
return;
|
||||
}
|
||||
boost::container::small_vector<VkBuffer, 4> buffer_handles;
|
||||
for (u32 index = 0; index < bindings.buffers.size(); index++) {
|
||||
auto& buffer = *reinterpret_cast<Buffer*>(bindings.buffers[index]);
|
||||
buffer_handles.push_back(buffer.Handle());
|
||||
}
|
||||
scheduler.Record(
|
||||
[bindings = bindings, buffer_handles = buffer_handles](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.BindTransformFeedbackBuffersEXT(
|
||||
0, static_cast<u32>(buffer_handles.size()), buffer_handles.data(),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.offsets.data()),
|
||||
reinterpret_cast<const VkDeviceSize*>(bindings.sizes.data()));
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::ReserveNullBuffer() {
|
||||
if (null_buffer) {
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Vulkan {
|
||||
class Device;
|
||||
class DescriptorPool;
|
||||
class Scheduler;
|
||||
struct HostVertexBinding;
|
||||
|
||||
class BufferCacheRuntime;
|
||||
|
||||
@@ -96,8 +97,10 @@ public:
|
||||
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
|
||||
|
||||
void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
|
||||
void BindVertexBuffers(VideoCommon::HostBindings& bindings);
|
||||
|
||||
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
|
||||
void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings);
|
||||
|
||||
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
|
||||
[[maybe_unused]] u32 binding_index, u32 size) {
|
||||
|
||||
@@ -231,7 +231,12 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
#ifdef ANDROID
|
||||
// On Android, do not allow surface rotation to deviate from the frontend.
|
||||
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
|
||||
#else
|
||||
.preTransform = capabilities.currentTransform,
|
||||
#endif
|
||||
.compositeAlpha = alpha_flags,
|
||||
.presentMode = present_mode,
|
||||
.clipped = VK_FALSE,
|
||||
|
||||
@@ -98,6 +98,9 @@ add_executable(yuzu
|
||||
configuration/configure_input_profile_dialog.cpp
|
||||
configuration/configure_input_profile_dialog.h
|
||||
configuration/configure_input_profile_dialog.ui
|
||||
configuration/configure_mouse_panning.cpp
|
||||
configuration/configure_mouse_panning.h
|
||||
configuration/configure_mouse_panning.ui
|
||||
configuration/configure_motion_touch.cpp
|
||||
configuration/configure_motion_touch.h
|
||||
configuration/configure_motion_touch.ui
|
||||
|
||||
@@ -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
|
||||
@@ -345,6 +351,10 @@ void Config::ReadPlayerValue(std::size_t player_index) {
|
||||
player_motions = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
if (player_index == 0) {
|
||||
ReadMousePanningValues();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::ReadDebugValues() {
|
||||
@@ -465,6 +475,7 @@ void Config::ReadControlValues() {
|
||||
ReadKeyboardValues();
|
||||
ReadMouseValues();
|
||||
ReadTouchscreenValues();
|
||||
ReadMousePanningValues();
|
||||
ReadMotionTouchValues();
|
||||
ReadHidbusValues();
|
||||
ReadIrCameraValues();
|
||||
@@ -475,8 +486,6 @@ void Config::ReadControlValues() {
|
||||
Settings::values.enable_raw_input = false;
|
||||
#endif
|
||||
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
Settings::values.mouse_panning = false;
|
||||
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
ReadBasicSetting(Settings::values.enable_joycon_driver);
|
||||
ReadBasicSetting(Settings::values.enable_procon_driver);
|
||||
ReadBasicSetting(Settings::values.random_amiibo_id);
|
||||
@@ -490,6 +499,16 @@ void Config::ReadControlValues() {
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
void Config::ReadMousePanningValues() {
|
||||
ReadBasicSetting(Settings::values.mouse_panning);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_decay_strength);
|
||||
ReadBasicSetting(Settings::values.mouse_panning_min_decay);
|
||||
}
|
||||
|
||||
void Config::ReadMotionTouchValues() {
|
||||
int num_touch_from_button_maps =
|
||||
qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
|
||||
@@ -754,6 +773,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);
|
||||
@@ -1056,6 +1076,10 @@ void Config::SavePlayerValue(std::size_t player_index) {
|
||||
QString::fromStdString(player.motions[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
|
||||
if (player_index == 0) {
|
||||
SaveMousePanningValues();
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveDebugValues() {
|
||||
@@ -1092,6 +1116,16 @@ void Config::SaveTouchscreenValues() {
|
||||
WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
|
||||
}
|
||||
|
||||
void Config::SaveMousePanningValues() {
|
||||
// Don't overwrite values.mouse_panning
|
||||
WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_decay_strength);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_min_decay);
|
||||
}
|
||||
|
||||
void Config::SaveMotionTouchValues() {
|
||||
WriteBasicSetting(Settings::values.touch_device);
|
||||
WriteBasicSetting(Settings::values.touch_from_button_map_index);
|
||||
@@ -1178,6 +1212,7 @@ void Config::SaveControlValues() {
|
||||
SaveDebugValues();
|
||||
SaveMouseValues();
|
||||
SaveTouchscreenValues();
|
||||
SaveMousePanningValues();
|
||||
SaveMotionTouchValues();
|
||||
SaveHidbusValues();
|
||||
SaveIrCameraValues();
|
||||
@@ -1192,7 +1227,6 @@ void Config::SaveControlValues() {
|
||||
WriteBasicSetting(Settings::values.random_amiibo_id);
|
||||
WriteBasicSetting(Settings::values.keyboard_enabled);
|
||||
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||
WriteBasicSetting(Settings::values.controller_navigation);
|
||||
|
||||
WriteBasicSetting(Settings::values.tas_enable);
|
||||
@@ -1409,6 +1443,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);
|
||||
|
||||
@@ -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
|
||||
@@ -73,6 +74,7 @@ private:
|
||||
void ReadKeyboardValues();
|
||||
void ReadMouseValues();
|
||||
void ReadTouchscreenValues();
|
||||
void ReadMousePanningValues();
|
||||
void ReadMotionTouchValues();
|
||||
void ReadHidbusValues();
|
||||
void ReadIrCameraValues();
|
||||
@@ -103,6 +105,7 @@ private:
|
||||
void SaveDebugValues();
|
||||
void SaveMouseValues();
|
||||
void SaveTouchscreenValues();
|
||||
void SaveMousePanningValues();
|
||||
void SaveMotionTouchValues();
|
||||
void SaveHidbusValues();
|
||||
void SaveIrCameraValues();
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -191,6 +191,16 @@ Compute pipelines are always enabled on all other drivers.</string>
|
||||
</property>
|
||||
</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">
|
||||
|
||||
@@ -129,9 +129,6 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
|
||||
Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
|
||||
Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
|
||||
Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
|
||||
Settings::values.mouse_panning = ui->mouse_panning->isChecked();
|
||||
Settings::values.mouse_panning_sensitivity =
|
||||
static_cast<float>(ui->mouse_panning_sensitivity->value());
|
||||
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
|
||||
Settings::values.enable_raw_input = ui->enable_raw_input->isChecked();
|
||||
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
|
||||
@@ -167,8 +164,6 @@ void ConfigureInputAdvanced::LoadConfiguration() {
|
||||
ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue());
|
||||
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue());
|
||||
ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue());
|
||||
ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue());
|
||||
ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue());
|
||||
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
|
||||
ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
|
||||
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
|
||||
@@ -197,8 +192,6 @@ void ConfigureInputAdvanced::RetranslateUI() {
|
||||
void ConfigureInputAdvanced::UpdateUIEnabled() {
|
||||
ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
|
||||
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
|
||||
ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
|
||||
ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
|
||||
ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA)
|
||||
ui->enable_ir_sensor->setEnabled(false);
|
||||
|
||||
@@ -2744,48 +2744,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="mouse_panning">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>23</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable mouse panning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QSpinBox" name="mouse_panning_sensitivity">
|
||||
<property name="toolTip">
|
||||
<string>Mouse sensitivity</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="motion_touch">
|
||||
<property name="text">
|
||||
<string>Motion / Touch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<item row="8" column="2">
|
||||
<widget class="QPushButton" name="buttonMotionTouch">
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_input_player.h"
|
||||
#include "yuzu/configuration/configure_input_player_widget.h"
|
||||
#include "yuzu/configuration/configure_mouse_panning.h"
|
||||
#include "yuzu/configuration/input_profiles.h"
|
||||
#include "yuzu/util/limitable_input_dialog.h"
|
||||
|
||||
@@ -711,6 +712,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
||||
});
|
||||
}
|
||||
|
||||
if (player_index_ == 0) {
|
||||
connect(ui->mousePanningButton, &QPushButton::clicked, [this, input_subsystem_] {
|
||||
const auto right_stick_param =
|
||||
emulated_controller->GetStickParam(Settings::NativeAnalog::RStick);
|
||||
ConfigureMousePanning dialog(this, input_subsystem_,
|
||||
right_stick_param.Get("deadzone", 0.0f),
|
||||
right_stick_param.Get("range", 1.0f));
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
dialog.ApplyConfiguration();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui->mousePanningWidget->hide();
|
||||
}
|
||||
|
||||
// Player Connected checkbox
|
||||
connect(ui->groupConnectedController, &QGroupBox::toggled,
|
||||
[this](bool checked) { emit Connected(checked); });
|
||||
|
||||
@@ -3048,6 +3048,102 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="mousePanningWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="mousePanningHorizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="mousePanningHorizontalSpacerLeft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="mousePanningGroup">
|
||||
<property name="title">
|
||||
<string>Mouse panning</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mousePanningVerticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mousePanningButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">min-width: 68px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="mousePanningHorizontalSpacerRight">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
79
src/yuzu/configuration/configure_mouse_panning.cpp
Normal file
79
src/yuzu/configuration/configure_mouse_panning.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "ui_configure_mouse_panning.h"
|
||||
#include "yuzu/configuration/configure_mouse_panning.h"
|
||||
|
||||
ConfigureMousePanning::ConfigureMousePanning(QWidget* parent,
|
||||
InputCommon::InputSubsystem* input_subsystem_,
|
||||
float right_stick_deadzone, float right_stick_range)
|
||||
: QDialog(parent), input_subsystem{input_subsystem_},
|
||||
ui(std::make_unique<Ui::ConfigureMousePanning>()) {
|
||||
ui->setupUi(this);
|
||||
SetConfiguration(right_stick_deadzone, right_stick_range);
|
||||
ConnectEvents();
|
||||
}
|
||||
|
||||
ConfigureMousePanning::~ConfigureMousePanning() = default;
|
||||
|
||||
void ConfigureMousePanning::closeEvent(QCloseEvent* event) {
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void ConfigureMousePanning::SetConfiguration(float right_stick_deadzone, float right_stick_range) {
|
||||
ui->enable->setChecked(Settings::values.mouse_panning.GetValue());
|
||||
ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetValue());
|
||||
ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetValue());
|
||||
ui->deadzone_x_counterweight->setValue(
|
||||
Settings::values.mouse_panning_deadzone_x_counterweight.GetValue());
|
||||
ui->deadzone_y_counterweight->setValue(
|
||||
Settings::values.mouse_panning_deadzone_y_counterweight.GetValue());
|
||||
ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetValue());
|
||||
ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetValue());
|
||||
|
||||
if (right_stick_deadzone > 0.0f || right_stick_range != 1.0f) {
|
||||
ui->warning_label->setText(QString::fromStdString(
|
||||
"Mouse panning works better with a deadzone of 0% and a range of 100%.\n"
|
||||
"Current values are " +
|
||||
std::to_string(static_cast<int>(right_stick_deadzone * 100.0f)) + "% and " +
|
||||
std::to_string(static_cast<int>(right_stick_range * 100.0f)) + "% respectively."));
|
||||
} else {
|
||||
ui->warning_label->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureMousePanning::SetDefaultConfiguration() {
|
||||
ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetDefault());
|
||||
ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetDefault());
|
||||
ui->deadzone_x_counterweight->setValue(
|
||||
Settings::values.mouse_panning_deadzone_x_counterweight.GetDefault());
|
||||
ui->deadzone_y_counterweight->setValue(
|
||||
Settings::values.mouse_panning_deadzone_y_counterweight.GetDefault());
|
||||
ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetDefault());
|
||||
ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetDefault());
|
||||
}
|
||||
|
||||
void ConfigureMousePanning::ConnectEvents() {
|
||||
connect(ui->default_button, &QPushButton::clicked, this,
|
||||
&ConfigureMousePanning::SetDefaultConfiguration);
|
||||
connect(ui->button_box, &QDialogButtonBox::accepted, this,
|
||||
&ConfigureMousePanning::ApplyConfiguration);
|
||||
connect(ui->button_box, &QDialogButtonBox::rejected, this, [this] { reject(); });
|
||||
}
|
||||
|
||||
void ConfigureMousePanning::ApplyConfiguration() {
|
||||
Settings::values.mouse_panning = ui->enable->isChecked();
|
||||
Settings::values.mouse_panning_x_sensitivity = static_cast<float>(ui->x_sensitivity->value());
|
||||
Settings::values.mouse_panning_y_sensitivity = static_cast<float>(ui->y_sensitivity->value());
|
||||
Settings::values.mouse_panning_deadzone_x_counterweight =
|
||||
static_cast<float>(ui->deadzone_x_counterweight->value());
|
||||
Settings::values.mouse_panning_deadzone_y_counterweight =
|
||||
static_cast<float>(ui->deadzone_y_counterweight->value());
|
||||
Settings::values.mouse_panning_decay_strength = static_cast<float>(ui->decay_strength->value());
|
||||
Settings::values.mouse_panning_min_decay = static_cast<float>(ui->min_decay->value());
|
||||
|
||||
accept();
|
||||
}
|
||||
35
src/yuzu/configuration/configure_mouse_panning.h
Normal file
35
src/yuzu/configuration/configure_mouse_panning.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
|
||||
namespace InputCommon {
|
||||
class InputSubsystem;
|
||||
}
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureMousePanning;
|
||||
}
|
||||
|
||||
class ConfigureMousePanning : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfigureMousePanning(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_,
|
||||
float right_stick_deadzone, float right_stick_range);
|
||||
~ConfigureMousePanning() override;
|
||||
|
||||
public slots:
|
||||
void ApplyConfiguration();
|
||||
|
||||
private:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void SetConfiguration(float right_stick_deadzone, float right_stick_range);
|
||||
void SetDefaultConfiguration();
|
||||
void ConnectEvents();
|
||||
|
||||
InputCommon::InputSubsystem* input_subsystem;
|
||||
std::unique_ptr<Ui::ConfigureMousePanning> ui;
|
||||
};
|
||||
238
src/yuzu/configuration/configure_mouse_panning.ui
Normal file
238
src/yuzu/configuration/configure_mouse_panning.ui
Normal file
@@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureMousePanning</class>
|
||||
<widget class="QDialog" name="configure_mouse_panning">
|
||||
<property name="windowTitle">
|
||||
<string>Configure mouse panning</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Can be toggled via a hotkey</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="sensitivity_box">
|
||||
<property name="title">
|
||||
<string>Sensitivity</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="x_sensitivity_label">
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="x_sensitivity">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="y_sensitivity_label">
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="y_sensitivity">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="deadzone_counterweight_box">
|
||||
<property name="title">
|
||||
<string>Deadzone counterweight</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Counteracts a game's built-in deadzone</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="deadzone_x_counterweight_label">
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="deadzone_x_counterweight">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="deadzone_y_counterweight_label">
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="deadzone_y_counterweight">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="decay_box">
|
||||
<property name="title">
|
||||
<string>Stick decay</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="decay_strength_label">
|
||||
<property name="text">
|
||||
<string>Strength</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="decay_strength">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>22</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="min_decay_label">
|
||||
<property name="text">
|
||||
<string>Minimum</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="min_decay">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="warning_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="default_button">
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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() {
|
||||
|
||||
@@ -140,9 +140,29 @@ udp_input_servers =
|
||||
# 0 (default): Off, 1: On
|
||||
mouse_panning =
|
||||
|
||||
# Set mouse sensitivity.
|
||||
# Default: 1.0
|
||||
mouse_panning_sensitivity =
|
||||
# Set mouse panning horizontal sensitivity.
|
||||
# Default: 50.0
|
||||
mouse_panning_x_sensitivity =
|
||||
|
||||
# Set mouse panning vertical sensitivity.
|
||||
# Default: 50.0
|
||||
mouse_panning_y_sensitivity =
|
||||
|
||||
# Set mouse panning deadzone horizontal counterweight.
|
||||
# Default: 0.0
|
||||
mouse_panning_deadzone_x_counterweight =
|
||||
|
||||
# Set mouse panning deadzone vertical counterweight.
|
||||
# Default: 0.0
|
||||
mouse_panning_deadzone_y_counterweight =
|
||||
|
||||
# Set mouse panning stick decay strength.
|
||||
# Default: 22.0
|
||||
mouse_panning_decay_strength =
|
||||
|
||||
# Set mouse panning stick minimum decay.
|
||||
# Default: 5.0
|
||||
mouse_panning_minimum_decay =
|
||||
|
||||
# Emulate an analog control stick from keyboard inputs.
|
||||
# 0 (default): Disabled, 1: Enabled
|
||||
|
||||
Reference in New Issue
Block a user