Compare commits

...

9 Commits

Author SHA1 Message Date
55e97959b9 Fix Vi managed and stray layers open/close/destroy (#3438)
* Fix Vi managed and stray layers open/close/destroy

* OpenLayer should set the state to ManagedOpened
2022-07-06 13:37:36 -03:00
f7ef6364b7 Implement CPU FCVT Half <-> Double conversion variants (#3439)
* Half <-> Double conversion support

* Add tests, fast path and deduplicate SoftFloat code

* PPTC version
2022-07-06 13:40:31 +02:00
b46b63e06a Add support for alpha to coverage dithering (#3069)
* Add support for alpha to coverage dithering

* Shader cache version bump

* Fix wrong alpha register

* Ensure support buffer is cleared

* New shader specialization based approach
2022-07-05 19:58:36 -03:00
594246ea47 UI - Avalonia Part 2 (#3351)
* add settings windows and children views

* Expose hotkeys configuration on the UI

* Remove double spacing from locale JSON

* simplify button assigner

* add cemuhook buttons and title to locale

* move common button assigner to own class

* cancel button assigner when window is closed

* remove unused setting

* address review. fix controller profile not loading default when switching devices

* fix updater file name

* Input cleanup (#37)

* addressed review

* add device type to controller device checks

* change accessibility modifier of public classes to internal

* Update Ryujinx.Ava/Ui/ViewModels/ControllerSettingsViewModel.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update de_DE.json

* Update de_DE.json

* Update tr_TR.json

Translated newly added lines

* Update it_IT.json

* fix rebase

* update avalonia

* fix wrong key used for button text

* Align settings window elements

* Tabs to spaces

* Update brazilian portuguese translation

* Minor improvement on brazilian portuguese translation

* fix turkish translation

* remove unused text

* change view related classes to public

* unsubscribe from deferred event if dialog is closed

* Load the default language before loading any other when switching languages

* Make controller settings more compact

* increase default width of settings window, reduce profile buttons width

Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: MutantAura <44103205+MutantAura@users.noreply.github.com>
Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com>
Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com>
Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
2022-07-05 20:06:31 +02:00
d21b403886 Stub GetTemperature (#3429) 2022-07-03 10:17:24 +02:00
5afd521c5a Bindless elimination for constant sampler handle (#3424)
* Bindless elimination for constant sampler handle

* Shader cache version bump

* Update TextureHandle.ReadPackedId for new bindless elimination
2022-07-02 15:03:35 -03:00
0c66d71fe8 ui: Fix timezone abbreviation since #3361 (#3430)
As title say
2022-06-29 22:08:30 +02:00
bdc4fa81f2 Add Simplified Chinese to Avalonia (V2) (#3416)
* Add files via upload

* Update Ryujinx.Ava.csproj

* Update MainWindow.axaml
2022-06-25 17:03:48 +02:00
625f5fb88a Account for pool change on texture bindings cache (#3420)
* Account for pool change on texture bindings cache

* Reduce the number of checks needed
2022-06-25 16:52:38 +02:00
102 changed files with 6378 additions and 669 deletions

View File

@ -105,11 +105,48 @@ namespace ARMeilleure.Instructions
}
else if (op.Size == 1 && op.Opc == 3) // Double -> Half.
{
throw new NotImplementedException("Double-precision to half-precision.");
if (Optimizations.UseF16c)
{
Debug.Assert(!Optimizations.ForceLegacySse);
Operand n = GetVec(op.Rn);
Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n);
res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest)));
context.Copy(GetVec(op.Rd), res);
}
else
{
Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0);
Operand res = context.Call(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)), ne);
res = context.ZeroExtend16(OperandType.I64, res);
context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1));
}
}
else if (op.Size == 3 && op.Opc == 1) // Double -> Half.
else if (op.Size == 3 && op.Opc == 1) // Half -> Double.
{
throw new NotImplementedException("Half-precision to double-precision.");
if (Optimizations.UseF16c)
{
Operand n = GetVec(op.Rn);
Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res);
res = context.VectorZeroUpper64(res);
context.Copy(GetVec(op.Rd), res);
}
else
{
Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1);
Operand res = context.Call(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)), ne);
context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0));
}
}
else // Invalid encoding.
{

File diff suppressed because it is too large Load Diff

View File

@ -206,6 +206,7 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedSrcUnsignedDstSatQ)));
SetDelegateInfo(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)));
SetDelegateInfo(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)));
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAdd)));
SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAddFpscr))); // A32 only.
@ -294,6 +295,8 @@ namespace ARMeilleure.Translation
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStepFused)));
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSqrt)));
SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSub)));
SetDelegateInfo(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)));
}
}
}

View File

@ -27,7 +27,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 3362; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 3439; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";

View File

@ -2,12 +2,16 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Diagnostics;
using System.IO;
namespace Ryujinx.Ava
@ -45,7 +49,30 @@ namespace Ryujinx.Ava
private void ShowRestartDialog()
{
// TODO: Implement Restart Dialog when SettingsWindow is implemented.
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Dispatcher.UIThread.InvokeAsync(async () =>
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var result = await ContentDialogHelper.CreateConfirmationDialog(
(desktop.MainWindow as MainWindow).SettingsWindow,
LocaleManager.Instance["DialogThemeRestartMessage"],
LocaleManager.Instance["DialogThemeRestartSubMessage"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["DialogRestartRequiredMessage"]);
if (result == UserResult.Yes)
{
var path = Process.GetCurrentProcess().MainModule.FileName;
var info = new ProcessStartInfo() { FileName = path, UseShellExecute = false };
var proc = Process.Start(info);
desktop.Shutdown();
Environment.Exit(0);
}
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e)

View File

@ -50,7 +50,7 @@ using WindowState = Avalonia.Controls.WindowState;
namespace Ryujinx.Ava
{
public class AppHost
internal class AppHost
{
private const int CursorHideIdleTime = 8; // Hide Cursor seconds

View File

@ -20,7 +20,7 @@
"MenuBarOptionsManageUserProfiles": "_Profilverwaltung",
"MenuBarActions": "_Aktionen",
"MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht",
"MenuBarActionsScanAmiibo": "Amiibo scannen",
"MenuBarActionsScanAmiibo": "Virtualisiere Amiibo",
"MenuBarTools": "_Werkzeuge",
"MenuBarToolsInstallFirmware": "Firmware installieren",
"MenuBarFileToolsInstallFirmwareFromFile": "Installiere Firmware von einer XCI oder einer ZIP Datei",
@ -242,9 +242,7 @@
"ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten",
"ControllerSettingsMisc": "Verschiedenes",
"ControllerSettingsTriggerThreshold": "Empfindlichkeit:",
"ControllerSettingsMotion": "Bewegungssteuerung",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Aktiviere Bewegungssteuerung",
"ControllerSettingsMotion": "Bewegung",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung",
"ControllerSettingsMotionControllerSlot": "Controller Slot:",
"ControllerSettingsMotionMirrorInput": "Spiegele Eingabe",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Hell",
"SettingsTabGeneralThemeEnableCustomTheme": "Benutzerdefiniertes Thema",
"ButtonBrowse": "Durchsuchen",
"ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook Motion konfigurieren",
"ControllerSettingsConfigureGeneral": "Konfigurieren",
"ControllerSettingsRumble": "Vibration",
"ControllerSettingsRumbleEnable": "Aktiviere Vibration",
"ControllerSettingsRumbleStrongMultiplier": "Starke Vibration - Multiplikator",
"ControllerSettingsRumbleWeakMultiplier": "Schwache Vibration - Multiplikator",
"DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]",
@ -329,7 +326,7 @@
"DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!",
"DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!",
"DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden",
"DialogUpdaterDirtyBuildSubMessage": "Für eine unterstütze Version: Ryujinx bitte von hier herunterladen https://ryujinx.org/",
"DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/",
"DialogRestartRequiredMessage": "Neustart erforderlich",
"DialogThemeRestartMessage": "Das Thema wurde gespeichert. Ein Neustart ist erforderlich, um das Thema anzuwenden.",
"DialogThemeRestartSubMessage": "Jetzt neu starten?",
@ -485,15 +482,13 @@
"EnableInternetAccessTooltip": "Aktiviert den Gast-Internet-Zugang. Die Anwendung verhält sich so, als ob die emulierte Switch-Konsole mit dem Internet verbunden wäre. Beachte, dass in einigen Fällen Anwendungen auch bei deaktivierter Option auf das Internet zugreifen können",
"GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager",
"GameListContextMenuManageCheat": "Cheats verwalten",
"ControllerSettingsStickRange": "Bereich",
"ControllerSettingsStickRange": "Bereich:",
"DialogStopEmulationTitle": "Ryujinx - Beende Emulation",
"DialogStopEmulationMessage": "Emulation wirklich beenden?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Audio",
"SettingsTabNetwork": "Netzwerk",
"SettingsTabNetworkConnection": "Netwerkverbindung",
"SettingsTabGraphicsFrameRate": "Host Aktualisierungsrate:",
"SettingsTabGraphicsFrameRateTooltip": "Aktiviert die Host Aktualisierungsrate. Auf 0 setzen, um den Grenzwert aufzuheben.",
"SettingsTabCpuCache": "CPU-Cache",
"SettingsTabCpuMemory": "CPU-Speicher",
"DialogUpdaterFlatpakNotSupportedMessage": "Bitte Aktualisiere Ryujinx mit FlatHub",
@ -544,6 +539,22 @@
"LoadingHeading": "{0} wird gestartet",
"CompilingPPTC": "PTC wird kompiliert",
"CompilingShaders": "Shader werden kompiliert",
"AllKeyboards": "Alle Tastaturen",
"OpenFileDialogTitle": "Wähle eine unterstützte Datei",
"OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel",
"AllSupportedFormats": "Alle unterstützten Formate",
"RyujinxUpdater": "Ryujinx - Updater",
"SettingsTabHotkeys": "Tastatur Hotkeys",
"SettingsTabHotkeysHotkeys": "Tastatur Hotkeys",
"SettingsTabHotkeysToggleVsyncHotkey": "Aktiviert/Deaktiviert VSync:",
"SettingsTabHotkeysScreenshotHotkey": "Screenshot:",
"SettingsTabHotkeysShowUiHotkey": "Zeige UI:",
"SettingsTabHotkeysPauseHotkey": "Pausieren:",
"SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:",
"ControllerMotionTitle": "Bewegungssteuerung - Einstellungen",
"ControllerRumbleTitle": "Vibration - Einstellungen",
"SettingsSelectThemeFileDialogTitle" : "Wähle ein benutzerdefiniertes Thema",
"SettingsXamlThemeFile" : "Xaml Thema-Datei",
"SettingsTabGraphicsBackend" : "Grafik-Backend",
"GraphicsBackendTooltip" : "Ändert das Grafik-Backend"
}

View File

@ -243,8 +243,6 @@
"ControllerSettingsMisc": "Διάφορα",
"ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:",
"ControllerSettingsMotion": "Κίνηση",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Ενεργοποίηση Κίνησης",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook",
"ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:",
"ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό",
"SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος",
"ButtonBrowse": "Αναζήτηση",
"ControllerSettingsMotionConfigureCemuHookSettings": "Ρύθμιση Παραμέτρων Κίνησης CemuHook",
"ControllerSettingsConfigureGeneral": "Παραμέτρων",
"ControllerSettingsRumble": "Δόνηση",
"ControllerSettingsRumbleEnable": "Ενεργοποίηση Δόνησης",
"ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης",
"ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης",
"DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]",
@ -485,15 +482,15 @@
"EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη",
"GameListContextMenuManageCheatToolTip" : "Διαχείριση Κόλπων",
"GameListContextMenuManageCheat" : "Διαχείριση Κόλπων",
"ControllerSettingsStickRange" : "Εύρος",
"ControllerSettingsStickRange" : "Εύρος:",
"DialogStopEmulationTitle" : "Ryujinx - Διακοπή εξομοίωσης",
"DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;",
"SettingsTabCpu": "Επεξεργαστής",
"SettingsTabAudio": "Ήχος",
"SettingsTabNetwork": "Δίκτυο",
"SettingsTabNetworkConnection" : "Σύνδεση δικτύου",
"SettingsTabGraphicsFrameRate" : "Ρυθμός Ανανέωσης Υπολογιστή:",
"SettingsTabGraphicsFrameRateTooltip" : "Προκαθορίζει το ρυθμό ανανέωσης του υπολογιστή. Ορίστε το στο 0 για να αφαιρέσετε το όριο.",
"SettingsTabCpuCache" : "Προσωρινή Μνήμη CPU",
"SettingsTabCpuMemory" : "Μνήμη CPU"
"SettingsTabCpuMemory" : "Μνήμη CPU",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
}

View File

@ -243,8 +243,6 @@
"ControllerSettingsMisc": "Miscellaneous",
"ControllerSettingsTriggerThreshold": "Trigger Threshold:",
"ControllerSettingsMotion": "Motion",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Enable Motion Controls",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion",
"ControllerSettingsMotionControllerSlot": "Controller Slot:",
"ControllerSettingsMotionMirrorInput": "Mirror Input",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Light",
"SettingsTabGeneralThemeEnableCustomTheme": "Enable Custom Theme",
"ButtonBrowse": "Browse",
"ControllerSettingsMotionConfigureCemuHookSettings": "Configure CemuHook Motion",
"ControllerSettingsConfigureGeneral": "Configure",
"ControllerSettingsRumble": "Rumble",
"ControllerSettingsRumbleEnable": "Enable Rumble",
"ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier",
"ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier",
"DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]",
@ -485,15 +482,13 @@
"EnableInternetAccessTooltip": "Enables guest Internet access. If enabled, the application will behave as if the emulated Switch console was connected to the Internet. Note that in some cases, applications may still access the Internet even with this option disabled",
"GameListContextMenuManageCheatToolTip": "Manage Cheats",
"GameListContextMenuManageCheat": "Manage Cheats",
"ControllerSettingsStickRange": "Range",
"ControllerSettingsStickRange": "Range:",
"DialogStopEmulationTitle": "Ryujinx - Stop Emulation",
"DialogStopEmulationMessage": "Are you sure you want to stop emulation?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Audio",
"SettingsTabNetwork": "Network",
"SettingsTabNetworkConnection": "Network Connection",
"[REMOVE]SettingsTabGraphicsFrameRate": "Host Refresh Rate:",
"[REMOVE]SettingsTabGraphicsFrameRateTooltip": "Sets host refresh rate. Set to 0 to remove limit.",
"SettingsTabCpuCache": "CPU Cache",
"SettingsTabCpuMemory": "CPU Memory",
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
@ -548,5 +543,16 @@
"OpenFileDialogTitle": "Select a supported file to open",
"OpenFolderDialogTitle": "Select a folder with an unpacked game",
"AllSupportedFormats": "All Supported Formats",
"RyujinxUpdater": "Ryujinx Updater"
"RyujinxUpdater": "Ryujinx Updater",
"SettingsTabHotkeys": "Keyboard Hotkeys",
"SettingsTabHotkeysHotkeys": "Keyboard Hotkeys",
"SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:",
"SettingsTabHotkeysScreenshotHotkey": "Screenshot:",
"SettingsTabHotkeysShowUiHotkey": "Show UI:",
"SettingsTabHotkeysPauseHotkey": "Pause:",
"SettingsTabHotkeysToggleMuteHotkey": "Mute:",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings",
"SettingsSelectThemeFileDialogTitle" : "Select Theme File",
"SettingsXamlThemeFile" : "Xaml Theme File"
}

View File

@ -242,8 +242,6 @@
"ControllerSettingsMisc": "Misceláneo",
"ControllerSettingsTriggerThreshold": "Límite de gatillos:",
"ControllerSettingsMotion": "Movimiento",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Habilitar controles por movimiento",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook",
"ControllerSettingsMotionControllerSlot": "Puerto del mando:",
"ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo",
@ -289,9 +287,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Claro",
"SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado",
"ButtonBrowse": "Buscar",
"ControllerSettingsMotionConfigureCemuHookSettings": "Configurar controles por movimiento de CemuHook",
"ControllerSettingsConfigureGeneral": "Configurar",
"ControllerSettingsRumble": "Vibración",
"ControllerSettingsRumbleEnable": "Habilitar vibraciones",
"ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes",
"ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles",
"DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]",
@ -483,15 +480,15 @@
"EnableInternetAccessTooltip": "Activa el acceso a Internet del guest. Cuando esté activo, la aplicación actuará como si la Nintendo Switch emulada estuviese conectada a Internet. Ten en cuenta que algunas aplicaciones pueden intentar acceder a Internet incluso con esta opción desactivada.",
"GameListContextMenuManageCheatToolTip" : "Activa o desactiva los cheats",
"GameListContextMenuManageCheat" : "Administrar cheats",
"ControllerSettingsStickRange" : "Alcance",
"ControllerSettingsStickRange" : "Alcance:",
"DialogStopEmulationTitle" : "Ryujinx - Detener emulación",
"DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Audio",
"SettingsTabNetwork": "Red",
"SettingsTabNetworkConnection" : "Conexión de red",
"SettingsTabGraphicsFrameRate" : "Velocidad máxima de fotogramas:",
"SettingsTabGraphicsFrameRateTooltip" : "Fija el límite de fotogramas del host. Elige 0 para deshabilitar el límite.",
"SettingsTabCpuCache" : "Caché de CPU",
"SettingsTabCpuMemory" : "Memoria de CPU"
"SettingsTabCpuMemory" : "Memoria de CPU",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
}

View File

@ -235,8 +235,7 @@
"ControllerSettingsMisc": "Divers",
"ControllerSettingsTriggerThreshold": "Seuil de gachettes:",
"ControllerSettingsMotion": "Mouvements",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Activer le capteur de mouvements",
"ControllerSettingsConfigureGeneral": "Configurer",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook",
"ControllerSettingsMotionControllerSlot": "Contrôleur ID:",
"ControllerSettingsMotionMirrorInput": "Inverser les contrôles",
@ -266,5 +265,7 @@
"InputDialogAddNewProfileSubtext": "(Longueur max.: {0})",
"AvatarChoose": "Choisir",
"AvatarSetBackgroundColor": "Choisir une couleur de fond",
"AvatarClose": "Fermer"
"AvatarClose": "Fermer",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
}

View File

@ -243,8 +243,6 @@
"ControllerSettingsMisc": "Miscellanee",
"ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:",
"ControllerSettingsMotion": "Movimento",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Attiva sensore di movimento",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook",
"ControllerSettingsMotionControllerSlot": "Slot del controller:",
"ControllerSettingsMotionMirrorInput": "Input specchiato",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Chiara",
"SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato",
"ButtonBrowse": "Sfoglia",
"ControllerSettingsMotionConfigureCemuHookSettings": "Configura movimento CemuHook",
"ControllerSettingsConfigureGeneral": "Configura",
"ControllerSettingsRumble": "Vibrazione",
"ControllerSettingsRumbleEnable": "Attiva vibrazione",
"ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte",
"ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole",
"DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]",
@ -461,7 +458,7 @@
"TraceLogTooltip": "Attiva messaggi trace log",
"GuestLogTooltip": "Attiva messaggi guest log",
"FileAccessLogTooltip": "Attiva messaggi file access log",
"FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le mpdalità possibili sono 0-3",
"FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3",
"DeveloperOptionTooltip": "Usa con attenzione",
"OpenGlLogLevel": "Richiede livelli di log appropriati abilitati",
"DebugLogTooltip": "Attiva messaggi debug log",
@ -485,15 +482,13 @@
"EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata",
"GameListContextMenuManageCheatToolTip": "Gestisci Cheats",
"GameListContextMenuManageCheat": "Gestisci Cheats",
"ControllerSettingsStickRange": "Raggio",
"ControllerSettingsStickRange": "Raggio:",
"DialogStopEmulationTitle": "Ryujinx - Ferma emulazione",
"DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Audio",
"SettingsTabNetwork": "Rete",
"SettingsTabNetworkConnection": "Connessione di rete",
"SettingsTabGraphicsFrameRate": "Frequenza di aggiornamento dell'host:",
"SettingsTabGraphicsFrameRateTooltip": "Imposta la frequenza di aggiornamento dell'host. Imposta a 0 per rimuovere il limite.",
"SettingsTabCpuCache": "Cache CPU",
"SettingsTabCpuMemory": "Memoria CPU",
"DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.",
@ -543,5 +538,21 @@
"ApiError": "Errore dell'API.",
"LoadingHeading": "Caricamento di {0}",
"CompilingPPTC": "Compilazione PTC",
"CompilingShaders": "Compilazione Shaders"
"CompilingShaders": "Compilazione Shaders",
"AllKeyboards": "Tutte le tastiere",
"OpenFileDialogTitle": "Seleziona un file supportato da aprire",
"OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto",
"AllSupportedFormats": "Tutti i formati supportati",
"RyujinxUpdater": "Ryujinx Updater",
"SettingsTabHotkeys": "Tasti di scelta rapida",
"SettingsTabHotkeysHotkeys": "Tasti di scelta rapida",
"SettingsTabHotkeysToggleVsyncHotkey": "VSync:",
"SettingsTabHotkeysScreenshotHotkey": "Screenshot:",
"SettingsTabHotkeysShowUiHotkey": "Mostra UI:",
"SettingsTabHotkeysPauseHotkey": "Metti in pausa:",
"SettingsTabHotkeysToggleMuteHotkey": "Muta:",
"ControllerMotionTitle": "Impostazioni dei sensori di movimento",
"ControllerRumbleTitle": "Impostazioni di vibrazione",
"SettingsSelectThemeFileDialogTitle" : "Seleziona file del tema",
"SettingsXamlThemeFile" : "File del tema xaml"
}

View File

@ -242,8 +242,6 @@
"ControllerSettingsMisc": "여러 가지 잡다한",
"ControllerSettingsTriggerThreshold": "트리거 임계값 :",
"ControllerSettingsMotion": "운동",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "모션 컨트롤 활성화",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용",
"ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯 :",
"ControllerSettingsMotionMirrorInput": "미러 입력",
@ -289,9 +287,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "빛",
"SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화",
"ButtonBrowse": "검색",
"ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook 모션 구성",
"ControllerSettingsConfigureGeneral": "션 구성",
"ControllerSettingsRumble": "하인 좌석",
"ControllerSettingsRumbleEnable": "럼블을 활성화",
"ControllerSettingsRumbleStrongMultiplier": "강력한 럼블 배율기",
"ControllerSettingsRumbleWeakMultiplier": "약한 럼블 승수",
"DialogMessageSaveNotAvailableMessage": "에 대한 세이브 데이터가 없습니다 {0} [{1:x16}]",
@ -483,15 +480,15 @@
"EnableInternetAccessTooltip": "게스트 인터넷 액세스를 활성화합니다. 활성화된 경우 응용 프로그램은 에뮬레이트된 스위치 콘솔이 인터넷에 연결된 것처럼 작동합니다. 경우에 따라 이 옵션이 비활성화된 경우에도 응용 프로그램이 인터넷에 계속 액세스할 수 있습니다",
"GameListContextMenuManageCheatToolTip" : "치트 관리",
"GameListContextMenuManageCheat" : "치트 관리",
"ControllerSettingsStickRange" : "범위",
"ControllerSettingsStickRange" : "범위:",
"DialogStopEmulationTitle" : "Ryujinx - 에뮬레이션 중지",
"DialogStopEmulationMessage": "에뮬레이션을 중지하시겠습니까?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "오디오",
"SettingsTabNetwork": "회로망",
"SettingsTabNetworkConnection" : "네트워크 연결",
"SettingsTabGraphicsFrameRate" : "호스트 새로 고침 빈도 :",
"SettingsTabGraphicsFrameRateTooltip" : "호스트 새로 고침 빈도를 설정합니다. 제한을 제거하려면 0으로 설정하십시오.",
"SettingsTabCpuCache" : "CPU 캐시",
"SettingsTabCpuMemory" : "CPU 메모리"
"SettingsTabCpuMemory" : "CPU 메모리",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
}

View File

@ -243,8 +243,6 @@
"ControllerSettingsMisc": "Miscelâneas",
"ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:",
"ControllerSettingsMotion": "Sensor de movimento",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Habilitar sensor de movimento",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook",
"ControllerSettingsMotionControllerSlot": "Slot do controle:",
"ControllerSettingsMotionMirrorInput": "Espelhar movimento",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Claro",
"SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado",
"ButtonBrowse": "Procurar",
"ControllerSettingsMotionConfigureCemuHookSettings": "Configurar sensor de movimento CemuHook",
"ControllerSettingsConfigureGeneral": "Configurar",
"ControllerSettingsRumble": "Vibração",
"ControllerSettingsRumbleEnable": "Habilitar vibração",
"ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte",
"ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca",
"DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]",
@ -442,7 +439,7 @@
"MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.",
"MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.",
"MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.",
"MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o endereço ao espaço de endereço do sistema convidado antes de acessar. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.",
"MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.",
"DRamTooltip": "Expande a memória do sistema emulado de 4GB para 6GB",
"IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados",
"GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico",
@ -485,15 +482,13 @@
"EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada",
"GameListContextMenuManageCheatToolTip": "Gerenciar Cheats",
"GameListContextMenuManageCheat": "Gerenciar Cheats",
"ControllerSettingsStickRange": "Intervalo",
"ControllerSettingsStickRange": "Intervalo:",
"DialogStopEmulationTitle": "Ryujinx - Parar emulação",
"DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Áudio",
"SettingsTabNetwork": "Rede",
"SettingsTabNetworkConnection": "Conexão de rede",
"SettingsTabGraphicsFrameRate": "Taxa de atualização do hóspede:",
"SettingsTabGraphicsFrameRateTooltip": "Define a taxa de atualização do hóspede. Coloque em 0 para remover o limite.",
"SettingsTabCpuCache": "Cache da CPU",
"SettingsTabCpuMemory": "Memória da CPU",
"DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.",
@ -540,5 +535,24 @@
"Handheld": "Portátil",
"ConnectionError": "Erro de conexão.",
"AboutPageDeveloperListMore": "{0} e mais...",
"ApiError": "Erro de API."
"ApiError": "Erro de API.",
"LoadingHeading": "Carregando {0}",
"CompilingPPTC": "Compilando PTC",
"CompilingShaders": "Compilando Shaders",
"AllKeyboards": "Todos os teclados",
"OpenFileDialogTitle": "Selecione um arquivo suportado para abrir",
"OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído",
"AllSupportedFormats": "Todos os formatos suportados",
"RyujinxUpdater": "Atualizador do Ryujinx",
"SettingsTabHotkeys": "Atalhos do teclado",
"SettingsTabHotkeysHotkeys": "Atalhos do teclado",
"SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:",
"SettingsTabHotkeysScreenshotHotkey": "Captura de tela:",
"SettingsTabHotkeysShowUiHotkey": "Exibir UI:",
"SettingsTabHotkeysPauseHotkey": "Pausar:",
"SettingsTabHotkeysToggleMuteHotkey": "Mudo:",
"ControllerMotionTitle": "Configurações do controle de movimento",
"ControllerRumbleTitle": "Configurações de vibração",
"SettingsSelectThemeFileDialogTitle" : "Selecionar arquivo do tema",
"SettingsXamlThemeFile" : "Arquivo de tema Xaml"
}

View File

@ -242,8 +242,6 @@
"ControllerSettingsMisc": "Разное",
"ControllerSettingsTriggerThreshold": "Порог срабатывания:",
"ControllerSettingsMotion": "Движение",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Включить управление движением",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook",
"ControllerSettingsMotionControllerSlot": "Слот контроллера:",
"ControllerSettingsMotionMirrorInput": "Зеркальный ввод",
@ -289,9 +287,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Светлая",
"SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему",
"ButtonBrowse": "Обзор",
"ControllerSettingsMotionConfigureCemuHookSettings": "Настройка движения CemuHook",
"ControllerSettingsConfigureGeneral": "Настройка",
"ControllerSettingsRumble": "Вибрация",
"ControllerSettingsRumbleEnable": "Включить вибрацию",
"ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации",
"ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации",
"DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]",
@ -483,15 +480,15 @@
"EnableInternetAccessTooltip": "Включает гостевой доступ в Интернет. Если этот параметр включен, приложение будет вести себя так, как если бы эмулированная консоль Switch была подключена к Интернету. Обратите внимание, что в некоторых случаях приложения могут по-прежнему получать доступ к Интернету, даже если эта опция отключена.",
"GameListContextMenuManageCheatToolTip" : "Управление читами",
"GameListContextMenuManageCheat" : "Управление читами",
"ControllerSettingsStickRange" : "Диапазон",
"ControllerSettingsStickRange" : "Диапазон:",
"DialogStopEmulationTitle" : "Ryujinx - Остановить эмуляцию",
"DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?",
"SettingsTabCpu": "ЦП",
"SettingsTabAudio": "Аудио",
"SettingsTabNetwork": "Сеть",
"SettingsTabNetworkConnection" : "Подключение к сети",
"SettingsTabGraphicsFrameRate" : "Частота обновления хоста:",
"SettingsTabGraphicsFrameRateTooltip" : "Устанавливает частоту обновления хоста. Установите на 0, чтобы снять ограничение.",
"SettingsTabCpuCache" : "Кэш ЦП",
"SettingsTabCpuMemory" : "Память ЦП"
"SettingsTabCpuMemory" : "Память ЦП",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
}

View File

@ -243,8 +243,6 @@
"ControllerSettingsMisc": "Misc.",
"ControllerSettingsTriggerThreshold": "Tetik Eşiği:",
"ControllerSettingsMotion": "Hareket",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "Hareket Kontrollerini Etkinleştir",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook Uyumlu Hareket",
"ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:",
"ControllerSettingsMotionMirrorInput": "Girişi Aynala",
@ -290,9 +288,8 @@
"SettingsTabGeneralThemeBaseStyleLight": "Aydınlık",
"SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir",
"ButtonBrowse": "Göz At",
"ControllerSettingsMotionConfigureCemuHookSettings": "CemuHook Hareket Ayarla",
"ControllerSettingsConfigureGeneral": "Ayarla",
"ControllerSettingsRumble": "Titreşim",
"ControllerSettingsRumbleEnable": "Titreşimi Etkinleştir",
"ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çarpanı",
"ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Çarpanı",
"DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi yok",
@ -485,15 +482,13 @@
"EnableInternetAccessTooltip": "Guest internet erişimini etkinleştirir. Etkinleştirilmişse, uygulama emüle edilen Switch konsolu internete bağlıymış gibi davranır. Not: Bazı durumlarda uygulamalar bu seçenek devre dışı olmasına rağmen internete erişebilir",
"GameListContextMenuManageCheatToolTip" : "Hileleri Yönet",
"GameListContextMenuManageCheat" : "Hileleri Yönet",
"ControllerSettingsStickRange" : "Bölge (Range)",
"ControllerSettingsStickRange" : "Bölge (Range):",
"DialogStopEmulationTitle" : "Ryujinx - Emülasyonu Durdur",
"DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Ses",
"SettingsTabNetwork": "Ağ",
"SettingsTabNetworkConnection" : "Ağ Bağlantısı",
"SettingsTabGraphicsFrameRate" : "Host Yenileme Hızı:",
"SettingsTabGraphicsFrameRateTooltip" : "Host yenileme hızını ayarlar. Limiti kaldırmak için 0'ı seçin.",
"SettingsTabCpuCache" : "CPU Cache",
"SettingsTabCpuMemory" : "CPU Hafızası",
"DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.",
@ -543,5 +538,21 @@
"ApiError": "API Hatası.",
"LoadingHeading": "Yükleniyor {0}",
"CompilingPPTC": "PTC derleniyor",
"CompilingShaders": "Shaderlar derleniyor"
"CompilingShaders": "Shaderlar derleniyor",
"AllKeyboards": "Tüm klavyeler",
"OpenFileDialogTitle": "Açılacak desteklenen bir dosya seçin",
"OpenFolderDialogTitle": "Sıkıştırılmamış oyun içeren klasör seçin",
"AllSupportedFormats": "Tüm Desteklenen Formatlar",
"RyujinxUpdater": "Ryujinx Güncelleyicisi",
"SettingsTabHotkeys": "Klavye Kısayolları",
"SettingsTabHotkeysHotkeys": "Klavye Kısayolları",
"SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir:",
"SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:",
"SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:",
"SettingsTabHotkeysPauseHotkey": "Duraklat:",
"SettingsTabHotkeysToggleMuteHotkey": "Sustur:",
"ControllerMotionTitle": "Hareket Kontrol Seçenekleri",
"ControllerRumbleTitle": "Titreşim Seçenekleri",
"SettingsSelectThemeFileDialogTitle" : "Tema Dosyası Seçin",
"SettingsXamlThemeFile" : "Xaml Tema Dosyası"
}

View File

@ -0,0 +1,550 @@
{
"MenuBarFileOpenApplet": "打开小程序",
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的Mii小程序",
"SettingsTabInputDirectMouseAccess": "直通鼠标操作",
"SettingsTabSystemMemoryManagerMode": "内存管理模式:",
"SettingsTabSystemMemoryManagerModeSoftware": "软件",
"SettingsTabSystemMemoryManagerModeHost": "本机 (快速)",
"SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快速)",
"MenuBarFile": "文件(_F)",
"MenuBarFileOpenFromFile": "加载文件中的程序(_L)",
"MenuBarFileOpenUnpacked": "加载解包后的游戏(_U)",
"MenuBarFileOpenEmuFolder": "打开Ryujinx文件夹",
"MenuBarFileOpenLogsFolder": "打开日志文件夹",
"MenuBarFileExit": "退出(_E)",
"MenuBarOptions": "选项",
"MenuBarOptionsToggleFullscreen": "切换全屏",
"MenuBarOptionsStartGamesInFullscreen": "以全屏模式启动游戏",
"MenuBarOptionsStopEmulation": "中止模拟",
"MenuBarOptionsSettings": "设置(_S)",
"MenuBarOptionsManageUserProfiles": "管理用户账户(_M)",
"MenuBarActions": "行动(_A)",
"MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息",
"MenuBarActionsScanAmiibo": "扫描Amiibo",
"MenuBarTools": "工具(_T)",
"MenuBarToolsInstallFirmware": "安装固件",
"MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件",
"MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件",
"MenuBarHelp": "帮助",
"MenuBarHelpCheckForUpdates": "检查更新",
"MenuBarHelpAbout": "关于",
"MenuSearch": "搜索...",
"GameListHeaderFavorite": "收藏",
"GameListHeaderIcon": "图标",
"GameListHeaderApplication": "名称",
"GameListHeaderDeveloper": "制作商",
"GameListHeaderVersion": "版本",
"GameListHeaderTimePlayed": "游玩时间",
"GameListHeaderLastPlayed": "上次游玩",
"GameListHeaderFileExtension": "扩展名",
"GameListHeaderFileSize": "大小",
"GameListHeaderPath": "路径",
"GameListContextMenuOpenUserSaveDirectory": "打开应用存档目录",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录",
"GameListContextMenuOpenUserDeviceDirectory": "打开应用系统目录",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "打开包含游戏系统设置的目录",
"GameListContextMenuOpenUserBcatDirectory": "打开BCAT目录",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "打开包含游戏BCAT数据的目录",
"GameListContextMenuManageTitleUpdates": "管理游戏更新",
"GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理窗口",
"GameListContextMenuManageDlc": "管理DLC",
"GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口",
"GameListContextMenuOpenModsDirectory": "打开MOD目录",
"GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏MOD的目录",
"GameListContextMenuCacheManagement": "缓存管理",
"GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 缓存",
"GameListContextMenuCacheManagementPurgePptcToolTip": "删除游戏的 PPTC 缓存",
"GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存",
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存",
"GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录",
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录",
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录",
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含应用程序着色器缓存的目录",
"GameListContextMenuExtractData": "提取数据",
"GameListContextMenuExtractDataExeFS": "ExeFS",
"GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区(包括更新)",
"GameListContextMenuExtractDataRomFS": "RomFS",
"GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区(包括更新)",
"GameListContextMenuExtractDataLogo": "图标",
"GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标(包括更新)",
"StatusBarGamesLoaded": "{0}/{1} 游戏加载完成",
"StatusBarSystemVersion": "系统版本: {0}",
"Settings": "设置",
"SettingsTabGeneral": "用户界面",
"SettingsTabGeneralGeneral": "常规",
"SettingsTabGeneralEnableDiscordRichPresence": "启用Discord在线状态展示",
"SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新",
"SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框",
"SettingsTabGeneralHideCursorOnIdle": "空闲时隐藏鼠标",
"SettingsTabGeneralGameDirectories": "游戏目录",
"SettingsTabGeneralAdd": "添加",
"SettingsTabGeneralRemove": "删除",
"SettingsTabSystem": "系统",
"SettingsTabSystemCore": "核心",
"SettingsTabSystemSystemRegion": "系统区域:",
"SettingsTabSystemSystemRegionJapan": "日本",
"SettingsTabSystemSystemRegionUSA": "美国",
"SettingsTabSystemSystemRegionEurope": "欧洲",
"SettingsTabSystemSystemRegionAustralia": "澳大利亚",
"SettingsTabSystemSystemRegionChina": "中国",
"SettingsTabSystemSystemRegionKorea": "韩国",
"SettingsTabSystemSystemRegionTaiwan": "台湾地区",
"SettingsTabSystemSystemLanguage": "系统语言:",
"SettingsTabSystemSystemLanguageJapanese": "日语",
"SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语",
"SettingsTabSystemSystemLanguageFrench": "法语",
"SettingsTabSystemSystemLanguageGerman": "德语",
"SettingsTabSystemSystemLanguageItalian": "意大利语",
"SettingsTabSystemSystemLanguageSpanish": "西班牙语",
"SettingsTabSystemSystemLanguageChinese": "中文(简体)",
"SettingsTabSystemSystemLanguageKorean": "韩语",
"SettingsTabSystemSystemLanguageDutch": "荷兰语",
"SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语",
"SettingsTabSystemSystemLanguageRussian": "俄语",
"SettingsTabSystemSystemLanguageTaiwanese": "中文(繁体)",
"SettingsTabSystemSystemLanguageBritishEnglish": "英式英语",
"SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语",
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语",
"SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)",
"SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)",
"SettingsTabSystemSystemTimeZone": "系统时区:",
"SettingsTabSystemSystemTime": "系统时钟:",
"SettingsTabSystemEnableVsync": "开启 VSync",
"SettingsTabSystemEnablePptc": "开启 PPTC 缓存",
"SettingsTabSystemEnableFsIntegrityChecks": "开启文件系统完整性检查",
"SettingsTabSystemAudioBackend": "音频后端:",
"SettingsTabSystemAudioBackendDummy": "无",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "修正",
"SettingsTabSystemHacksNote": " - 会引起模拟器不稳定",
"SettingsTabSystemExpandDramSize": "将模拟RAM大小扩展至 6GB",
"SettingsTabSystemIgnoreMissingServices": "忽略缺少的服务",
"SettingsTabGraphics": "图像",
"SettingsTabGraphicsEnhancements": "增强",
"SettingsTabGraphicsEnableShaderCache": "启用着色器缓存",
"SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:",
"SettingsTabGraphicsAnisotropicFilteringAuto": "自动",
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
"SettingsTabGraphicsAnisotropicFiltering4x": "4x",
"SettingsTabGraphicsAnisotropicFiltering8x": "8x",
"SettingsTabGraphicsAnisotropicFiltering16x": "16x",
"SettingsTabGraphicsResolutionScale": "分辨率缩放:",
"SettingsTabGraphicsResolutionScaleCustom": "自定义 (不推荐)",
"SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)",
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
"SettingsTabGraphicsAspectRatio": "宽高比:",
"SettingsTabGraphicsAspectRatio4x3": "4:3",
"SettingsTabGraphicsAspectRatio16x9": "16:9",
"SettingsTabGraphicsAspectRatio16x10": "16:10",
"SettingsTabGraphicsAspectRatio21x9": "21:9",
"SettingsTabGraphicsAspectRatio32x9": "32:9",
"SettingsTabGraphicsAspectRatioStretch": "拉伸至屏幕",
"SettingsTabGraphicsDeveloperOptions": "开发者选项",
"SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:",
"SettingsTabLogging": "日志",
"SettingsTabLoggingLogging": "日志",
"SettingsTabLoggingEnableLoggingToFile": "保存日志为文件",
"SettingsTabLoggingEnableStubLogs": "记录Stub",
"SettingsTabLoggingEnableInfoLogs": "记录Info",
"SettingsTabLoggingEnableWarningLogs": "记录Warning",
"SettingsTabLoggingEnableErrorLogs": "记录Error",
"SettingsTabLoggingEnableTraceLogs": "记录Trace",
"SettingsTabLoggingEnableGuestLogs": "记录Guest",
"SettingsTabLoggingEnableFsAccessLogs": "记录文件访问",
"SettingsTabLoggingFsGlobalAccessLogMode": "记录全局文件访问模式:",
"SettingsTabLoggingDeveloperOptions": "开发者选项 (警告: 会降低性能)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL日志级别:",
"SettingsTabLoggingOpenglLogLevelNone": "无",
"SettingsTabLoggingOpenglLogLevelError": "错误",
"SettingsTabLoggingOpenglLogLevelPerformance": "减速",
"SettingsTabLoggingOpenglLogLevelAll": "全部",
"SettingsTabLoggingEnableDebugLogs": "启用调试日志",
"SettingsTabInput": "输入",
"SettingsTabInputEnableDockedMode": "主机模式",
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
"SettingsButtonSave": "保存",
"SettingsButtonClose": "关闭",
"SettingsButtonApply": "应用",
"ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1",
"ControllerSettingsPlayer2": "玩家 2",
"ControllerSettingsPlayer3": "玩家 3",
"ControllerSettingsPlayer4": "玩家 4",
"ControllerSettingsPlayer5": "玩家 5",
"ControllerSettingsPlayer6": "玩家 6",
"ControllerSettingsPlayer7": "玩家 7",
"ControllerSettingsPlayer8": "玩家 8",
"ControllerSettingsHandheld": "掌机模式",
"ControllerSettingsInputDevice": "输入设备",
"ControllerSettingsRefresh": "刷新",
"ControllerSettingsDeviceDisabled": "关闭",
"ControllerSettingsControllerType": "手柄类型",
"ControllerSettingsControllerTypeHandheld": "掌机",
"ControllerSettingsControllerTypeProController": "Pro手柄",
"ControllerSettingsControllerTypeJoyConPair": "JoyCon",
"ControllerSettingsControllerTypeJoyConLeft": "左JoyCon",
"ControllerSettingsControllerTypeJoyConRight": "右JoyCon",
"ControllerSettingsProfile": "预设",
"ControllerSettingsProfileDefault": "默认",
"ControllerSettingsLoad": "加载",
"ControllerSettingsAdd": "新建",
"ControllerSettingsRemove": "删除",
"ControllerSettingsButtons": "按钮",
"ControllerSettingsButtonA": "A",
"ControllerSettingsButtonB": "B",
"ControllerSettingsButtonX": "X",
"ControllerSettingsButtonY": "Y",
"ControllerSettingsButtonPlus": "+",
"ControllerSettingsButtonMinus": "-",
"ControllerSettingsDPad": "方向键",
"ControllerSettingsDPadUp": "上",
"ControllerSettingsDPadDown": "下",
"ControllerSettingsDPadLeft": "左",
"ControllerSettingsDPadRight": "右",
"ControllerSettingsLStick": "左摇杆",
"ControllerSettingsLStickButton": "按下",
"ControllerSettingsLStickUp": "上",
"ControllerSettingsLStickDown": "下",
"ControllerSettingsLStickLeft": "左",
"ControllerSettingsLStickRight": "右",
"ControllerSettingsLStickStick": "杆",
"ControllerSettingsLStickInvertXAxis": "反转 X 方向",
"ControllerSettingsLStickInvertYAxis": "反转 Y 方向",
"ControllerSettingsLStickDeadzone": "死区:",
"ControllerSettingsRStick": "右摇杆",
"ControllerSettingsRStickButton": "按下",
"ControllerSettingsRStickUp": "上",
"ControllerSettingsRStickDown": "下",
"ControllerSettingsRStickLeft": "左",
"ControllerSettingsRStickRight": "右",
"ControllerSettingsRStickStick": "杆",
"ControllerSettingsRStickInvertXAxis": "反转 X 方向",
"ControllerSettingsRStickInvertYAxis": "反转 Y 方向",
"ControllerSettingsRStickDeadzone": "死区:",
"ControllerSettingsTriggersLeft": "左扳机",
"ControllerSettingsTriggersRight": "右扳机",
"ControllerSettingsTriggersButtonsLeft": "左扳机键",
"ControllerSettingsTriggersButtonsRight": "右扳机键",
"ControllerSettingsTriggers": "扳机",
"ControllerSettingsTriggerL": "L",
"ControllerSettingsTriggerR": "R",
"ControllerSettingsTriggerZL": "ZL",
"ControllerSettingsTriggerZR": "ZR",
"ControllerSettingsLeftSL": "SL",
"ControllerSettingsLeftSR": "SR",
"ControllerSettingsRightSL": "SL",
"ControllerSettingsRightSR": "SR",
"ControllerSettingsExtraButtonsLeft": "左按键",
"ControllerSettingsExtraButtonsRight": "右按键",
"ControllerSettingsMisc": "其他",
"ControllerSettingsTriggerThreshold": "扳机阈值:",
"ControllerSettingsMotion": "体感",
"ControllerSettingsCemuHook": "CemuHook",
"ControllerSettingsMotionEnableMotionControls": "启用体感操作",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用CemuHook体感协议",
"ControllerSettingsMotionControllerSlot": "手柄:",
"ControllerSettingsMotionMirrorInput": "镜像操作",
"ControllerSettingsMotionRightJoyConSlot": "右JoyCon:",
"ControllerSettingsMotionServerHost": "服务器Host:",
"ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:",
"ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:",
"ControllerSettingsSave": "保存",
"ControllerSettingsClose": "关闭",
"UserProfilesSelectedUserProfile": "选择用户账户:",
"UserProfilesSaveProfileName": "保存账户名",
"UserProfilesChangeProfileImage": "更换头像",
"UserProfilesAvailableUserProfiles": "现有的账户:",
"UserProfilesAddNewProfile": "新建账户",
"UserProfilesDeleteSelectedProfile": "删除选择的账户",
"UserProfilesClose": "关闭",
"ProfileImageSelectionTitle": "头像选择",
"ProfileImageSelectionHeader": "选择合适的头像图片",
"ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像",
"ProfileImageSelectionImportImage": "导入图像文件",
"ProfileImageSelectionSelectAvatar": "选择系统头像",
"InputDialogTitle": "输入对话框",
"InputDialogOk": "完成",
"InputDialogCancel": "取消",
"InputDialogAddNewProfileTitle": "选择用户名称",
"InputDialogAddNewProfileHeader": "请输入账户名称",
"InputDialogAddNewProfileSubtext": "(最大长度: {0})",
"AvatarChoose": "选择",
"AvatarSetBackgroundColor": "设置背景色",
"AvatarClose": "关闭",
"ControllerSettingsLoadProfileToolTip": "加载预设",
"ControllerSettingsAddProfileToolTip": "新增预设",
"ControllerSettingsRemoveProfileToolTip": "删除预设",
"ControllerSettingsSaveProfileToolTip": "保存预设",
"MenuBarFileToolsTakeScreenshot": "保存截图",
"MenuBarFileToolsHideUi": "隐藏UI",
"GameListContextMenuToggleFavorite": "标记为收藏",
"GameListContextMenuToggleFavoriteToolTip": "启用或取消收藏标记",
"SettingsTabGeneralTheme": "主题",
"SettingsTabGeneralThemeCustomTheme": "自选主题路径",
"SettingsTabGeneralThemeBaseStyle": "主题色调",
"SettingsTabGeneralThemeBaseStyleDark": "暗黑",
"SettingsTabGeneralThemeBaseStyleLight": "浅色",
"SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面",
"ButtonBrowse": "浏览",
"ControllerSettingsMotionConfigureCemuHookSettings": "配置CemuHook体感",
"ControllerSettingsRumble": "震动",
"ControllerSettingsRumbleEnable": "启用震动",
"ControllerSettingsRumbleStrongMultiplier": "强震动调节",
"ControllerSettingsRumbleWeakMultiplier": "弱震动调节",
"DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档",
"DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?",
"DialogConfirmationTitle": "Ryujinx - 设置",
"DialogUpdaterTitle": "Ryujinx - 更新",
"DialogErrorTitle": "Ryujinx - 错误",
"DialogWarningTitle": "Ryujinx - 警告",
"DialogExitTitle": "Ryujinx - 关闭",
"DialogErrorMessage": "Ryujinx遇到了错误",
"DialogExitMessage": "是否关闭Ryujinx",
"DialogExitSubMessage": "所有未保存的进度会丢失!",
"DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错: {0}",
"DialogMessageFindSaveErrorMessage": "查找特定的存档时出错: {0}",
"FolderDialogExtractTitle": "选择要解压到的文件夹",
"DialogNcaExtractionMessage": "提取{1}的{0}分区...",
"DialogNcaExtractionTitle": "Ryujinx - NCA分区提取",
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件",
"DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。",
"DialogNcaExtractionSuccessMessage": "提取成功。",
"DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。",
"DialogUpdaterCancelUpdateMessage": "更新取消!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的Ryujinx是最新版本。",
"DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。可能由于 GitHub Actions 正在编译新版本。请过几分钟重试。",
"DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本转换。",
"DialogUpdaterDownloadingMessage": "下载新版本中...",
"DialogUpdaterExtractionMessage": "正在提取更新...",
"DialogUpdaterRenamingMessage": "正在删除旧文件...",
"DialogUpdaterAddingFilesMessage": "安装更新中...",
"DialogUpdaterCompleteMessage": "更新成功!",
"DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?",
"DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!",
"DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)",
"DialogUpdaterNoInternetMessage": "没有连接到互联网",
"DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。",
"DialogUpdaterDirtyBuildMessage": "不能更新第三方版本的 Ryujinx",
"DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。",
"DialogRestartRequiredMessage": "需要重启模拟器",
"DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。",
"DialogThemeRestartSubMessage": "您是否要重启?",
"DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})",
"DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\\n模拟器现在可以运行。",
"DialogFirmwareNoFirmwareInstalledMessage": "未安装固件",
"DialogFirmwareInstalledMessage": "已安装固件{0}",
"DialogOpenSettingsWindowLabel": "打开设置窗口",
"DialogControllerAppletTitle": "控制器小窗口",
"DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错: {0}",
"DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错: {0}",
"DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错: {0}",
"DialogUserErrorDialogMessage": "{0}: {1}",
"DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息可以遵循我们的设置指南。",
"DialogUserErrorDialogTitle": "Ryujinx错误 ({0})",
"DialogAmiiboApiTitle": "Amiibo API",
"DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。",
"DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有网络连接。",
"DialogProfileInvalidProfileErrorMessage": "预设{0} 与当前输入配置系统不兼容。",
"DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设无法被覆盖",
"DialogProfileDeleteProfileTitle": "删除预设",
"DialogProfileDeleteProfileMessage": "删除后不可恢复,确定吗?",
"DialogWarning": "警告",
"DialogPPTCDeletionMessage": "您即将删除:\n\n{0}的 PPTC 缓存\n\n确定吗",
"DialogPPTCDeletionErrorMessage": "清除位于{0}的 PPTC 缓存时出错: {1}",
"DialogShaderDeletionMessage": "您即将删除:\n\n{0}的着色器缓存\n\n确定吗",
"DialogShaderDeletionErrorMessage": "清除位于{0}的着色器缓存时出错: {1}",
"DialogRyujinxErrorMessage": "Ryujinx遇到错误",
"DialogInvalidTitleIdErrorMessage": "UI 错误:所选游戏没有有效的标题ID",
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径{0}找不到有效的系统固件。",
"DialogFirmwareInstallerFirmwareInstallTitle": "安装固件{0}",
"DialogFirmwareInstallerFirmwareInstallMessage": "将安装{0}版本的系统。",
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n这将替换当前系统版本{0}。",
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n确认进行?",
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...",
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本{0}。",
"DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户",
"DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户",
"DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新",
"DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?",
"DialogDlcLoadNcaErrorMessage": "{0}. 错误的文件: {1}",
"DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC",
"DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?",
"DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。",
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?",
"DialogLoadAppGameAlreadyLoadedMessage": "当前已加载有游戏",
"DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。",
"DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!",
"DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程",
"DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。根据您的硬件您开启该选项时可能需要手动禁用驱动程序本身的GL多线程。",
"SettingsTabGraphicsFeaturesOptions": "功能",
"SettingsTabGraphicsBackendMultithreading": "后端多线程:",
"CommonAuto": "自动(推荐)",
"CommonOff": "关闭",
"CommonOn": "打开",
"InputDialogYes": "是",
"InputDialogNo": "否",
"DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。",
"MenuBarOptionsPauseEmulation": "暂停",
"MenuBarOptionsResumeEmulation": "继续",
"AboutUrlTooltipMessage": "在浏览器中打开Ryujinx的官网。",
"AboutDisclaimerMessage": "Ryujinx以任何方式都与Nintendo™以及任何商业伙伴没有关联",
"AboutAmiiboDisclaimerMessage": "我们的Amiibo模拟使用了\nAmiiboAPI (www.amiiboapi.com) ",
"AboutPatreonUrlTooltipMessage": "在浏览器中打开Ryujinx的Patreon赞助页。",
"AboutGithubUrlTooltipMessage": "在浏览器中打开Ryujinx的GitHub代码库。",
"AboutDiscordUrlTooltipMessage": "在浏览器中打开Ryujinx的Discord邀请链接。",
"AboutTwitterUrlTooltipMessage": "在浏览器中打开Ryujinx的Twitter主页。",
"AboutRyujinxAboutTitle": "关于:",
"AboutRyujinxAboutContent": "Ryujinx是Nintendo Switch™的模拟器.\n您可以在Patreon上支持Ryujinx。\n关注Twitter或者Discord可以获取模拟器最新动态。\n如果您对开发感兴趣欢迎来GitHub和Discord加入我们",
"AboutRyujinxMaintainersTitle": "由以下作者维护:",
"AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者的网页",
"AboutRyujinxSupprtersTitle": "感谢Patreon的赞助者:",
"AmiiboSeriesLabel": "Amiibo系列",
"AmiiboCharacterLabel": "角色",
"AmiiboScanButtonLabel": "扫描",
"AmiiboOptionsShowAllLabel": "显示所有 Amiibo",
"AmiiboOptionsUsRandomTagLabel": "修正: 使用随机标记的Uuid",
"DlcManagerTableHeadingEnabledLabel": "启用",
"DlcManagerTableHeadingTitleIdLabel": "游戏ID",
"DlcManagerTableHeadingContainerPathLabel": "文件夹路径",
"DlcManagerTableHeadingFullPathLabel": "完整路径",
"DlcManagerRemoveAllButton": "全部删除",
"MenuBarOptionsChangeLanguage": "更改语言",
"CommonSort": "排序",
"CommonShowNames": "显示名称",
"CommonFavorite": "收藏",
"OrderAscending": "从小到大",
"OrderDescending": "从大到小",
"SettingsTabGraphicsFeatures": "额外功能",
"ErrorWindowTitle": "错误窗口",
"ToggleDiscordTooltip": "启用或关闭Discord详细在线状态展示",
"AddGameDirBoxTooltip": "输入要添加的游戏目录",
"AddGameDirTooltip": "添加游戏目录到列表中",
"RemoveGameDirTooltip": "移除选中的目录",
"CustomThemeCheckTooltip": "启用或关闭自定义主题",
"CustomThemePathTooltip": "自定义主题的目录",
"CustomThemeBrowseTooltip": "查找自定义主题",
"DockModeToggleTooltip": "是否开启Swith的主机模式",
"DirectKeyboardTooltip": "是否开启\"直连键盘访问(HID)支持\" (部分游戏可以使用您的键盘输入文字)",
"DirectMouseTooltip": "是否开启\"直连鼠标访问(HID)支持\" (部分游戏可以使用您的鼠标导航)",
"RegionTooltip": "更改系统区域",
"LanguageTooltip": "更改系统语言",
"TimezoneTooltip": "更改系统时区",
"TimeTooltip": "更改系统时钟",
"VSyncToggleTooltip": "开启可以消除帧撕裂,关闭可以提高性能",
"PptcToggleTooltip": "开启以后减少游戏启动时间",
"FsIntegrityToggleTooltip": "是否检查游戏文件内容的完整性",
"AudioBackendTooltip": "默认推荐SDL但每种音频后端对各类游戏兼容性可能不同",
"MemoryManagerTooltip": "改变Switch内存映射到电脑内存的方式会影响CPU性能消耗",
"MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢",
"MemoryManagerHostTooltip": "直接映射内存页到电脑内存JIT效率很高",
"MemoryManagerUnsafeTooltip": "直接映射内存页但是不检查内存溢出JIT效率最高。Ryujinx可以访问任何位置的内存所以相对不安全。此模式下只应运行您信任的游戏或软件即官方游戏",
"DRamTooltip": "扩展模拟的Switch内存为6GB某些高清纹理MOD或4K MOD需要此选项",
"IgnoreMissingServicesTooltip": "忽略某些未实现的系统服务,少部分游戏需要此选项才能启动",
"GraphicsBackendThreadingTooltip": "启用后端多线程",
"GalThreadingTooltip": "使用模拟器自带的多线程调度,减少着色器编译的卡顿,并提高驱动程序的性能(尤其是缺失多线程的AMD)。NVIDIA用户需要重启模拟器才能禁用驱动本身的多线程否则您需手动执行禁用获得最佳性能",
"ShaderCacheToggleTooltip": "开启后缓存着色器到硬盘,减少画面卡顿",
"ResolutionScaleTooltip": "缩放渲染的分辨率",
"ResolutionScaleEntryTooltip": "尽量使用如1.5的浮点倍数。非整数的倍率易引起错误",
"AnisotropyTooltip": "各向异性过滤等级。能提高倾斜视角纹理的清晰度('自动'使用游戏默认指定的等级)",
"AspectRatioTooltip": "模拟器渲染窗口的宽高比",
"ShaderDumpPathTooltip": "转储图形着色器的路径",
"FileLogTooltip": "是否保存日志文件到硬盘",
"StubLogTooltip": "记录stub消息",
"InfoLogTooltip": "记录info消息",
"WarnLogTooltip": "记录warning消息",
"ErrorLogTooltip": "记录error消息",
"TraceLogTooltip": "记录trace消息",
"GuestLogTooltip": "记录guest消息",
"FileAccessLogTooltip": "记录文件访问消息",
"FSAccessLogModeTooltip": "记录FS访问消息输出到控制台。可选的模式是0-3",
"DeveloperOptionTooltip": "使用请谨慎",
"OpenGlLogLevel": "需要打开适当的日志等级",
"DebugLogTooltip": "记录debug消息",
"LoadApplicationFileTooltip": "选择Switch格式的游戏并加载",
"LoadApplicationFolderTooltip": "选择一个解包后格式的Switch游戏并加载",
"OpenRyujinxFolderTooltip": "打开Ryujinx系统目录",
"OpenRyujinxLogsTooltip": "打开日志存放的目录",
"ExitTooltip": "关闭Ryujinx",
"OpenSettingsTooltip": "打开设置窗口",
"OpenProfileManagerTooltip": "打开用户账号管理器",
"StopEmulationTooltip": "停止运行当前游戏并回到选择界面",
"CheckUpdatesTooltip": "检查新版本Ryujinx",
"OpenAboutTooltip": "打开'关于'窗口",
"GridSize": "网格尺寸",
"GridSizeTooltip": "调整网格模式的大小",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语",
"AboutRyujinxContributorsButtonHeader": "查看所有参与者",
"SettingsTabSystemAudioVolume": "音量: ",
"AudioVolumeTooltip": "调节音量",
"SettingsTabSystemEnableInternetAccess": "启用网络连接",
"EnableInternetAccessTooltip": "开启互联网访问。此选项打开后效果类似于Switch连接到互联网的状态。注意即使此选项关闭应用程序也偶尔有可能连接到网络",
"GameListContextMenuManageCheatToolTip": "管理金手指",
"GameListContextMenuManageCheat": "管理金手指",
"ControllerSettingsStickRange": "范围",
"DialogStopEmulationTitle": "Ryujinx - 停止模拟",
"DialogStopEmulationMessage": "是否确定停止模拟?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "音频",
"SettingsTabNetwork": "网络",
"SettingsTabNetworkConnection": "网络连接",
"SettingsTabCpuCache": "CPU 缓存",
"SettingsTabCpuMemory": "CPU 内存",
"DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。",
"UpdaterDisabledWarningTitle": "更新已禁用!",
"GameListContextMenuOpenSdModsDirectory": "打开Atmosphere MOD目录",
"GameListContextMenuOpenSdModsDirectoryToolTip": "打开包含应用程序MOD的其他Atmosphere SD卡目录",
"ControllerSettingsRotate90": "顺时针旋转 90°",
"IconSize": "图标尺寸",
"IconSizeTooltip": "更改游戏图标大小",
"MenuBarOptionsShowConsole": "显示控制台",
"ShaderCachePurgeError": "清除着色器缓存时出错: {0}: {1}",
"UserErrorNoKeys": "找不到密钥",
"UserErrorNoFirmware": "找不到固件",
"UserErrorFirmwareParsingFailed": "固件解析错误",
"UserErrorApplicationNotFound": "找不到应用程序",
"UserErrorUnknown": "未知错误",
"UserErrorUndefined": "未定义错误",
"UserErrorNoKeysDescription": "Ryujinx找不到 'prod.keys' 文件",
"UserErrorNoFirmwareDescription": "Ryujinx找不到任何已安装的固件",
"UserErrorFirmwareParsingFailedDescription": "Ryujinx无法解密选择的固件。通常是由于过旧的密钥。",
"UserErrorApplicationNotFoundDescription": "Ryujinx在选中路径找不到有效的应用程序。",
"UserErrorUnknownDescription": "发生未知错误!",
"UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!",
"OpenSetupGuideMessage": "打开设置教程",
"NoUpdate": "没有新版",
"TitleUpdateVersionLabel": "版本 {0} - {1}",
"RyujinxInfo": "Ryujinx - 信息",
"RyujinxConfirm": "Ryujinx - 确认",
"FileDialogAllTypes": "全部类型",
"Never": "从不",
"SwkbdMinCharacters": "至少应为 {0} 个字长",
"SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
"SoftwareKeyboard": "软件键盘",
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家()持有:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}请打开设置界面,配置手柄;或者关闭窗口。",
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家()持有 with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}请打开设置界面,配置手柄;或者关闭窗口。",
"DialogControllerAppletDockModeSet": "现在处于主机模式,无法使用掌机操作方式\n\n",
"UpdaterRenaming": "正在删除旧文件...",
"UpdaterRenameFailed": "更新过程中无法重命名文件: {0}",
"UpdaterAddingFiles": "安装更新中...",
"UpdaterExtracting": "正在提取更新...",
"UpdaterDownloading": "下载新版本中...",
"Game": "游戏",
"Docked": "主机模式",
"Handheld": "掌机模式",
"ConnectionError": "连接错误。",
"AboutPageDeveloperListMore": "{0} 以及等人...",
"ApiError": "API错误。",
"LoadingHeading": "正在加载 {0}",
"CompilingPPTC": "编译PPTC中",
"CompilingShaders": "编译着色器中",
"AllKeyboards": "所有键盘",
"OpenFileDialogTitle": "选择支持的文件格式",
"OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹",
"AllSupportedFormats": "全部支持的格式",
"RyujinxUpdater": "Ryujinx 更新程序"
}

View File

@ -161,11 +161,11 @@
<Setter Property="Background" Value="{DynamicResource ThemeControlBorderColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeControlBorderColor}" />
</Style>
<Style Selector="TabItem &gt; ScrollViewer">
<Style Selector="TabItem > ScrollViewer">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="Margin" Value="0,-5,0,0" />
</Style>
<Style Selector="TabItem &gt; ScrollViewer &gt; Border">
<Style Selector="TabItem > ScrollViewer > Border">
<Setter Property="BorderThickness" Value="0,1,0,0" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource HighlightBrush}" />

View File

@ -28,7 +28,7 @@ using Path = System.IO.Path;
namespace Ryujinx.Ava.Common
{
public static class ApplicationHelper
internal static class ApplicationHelper
{
private static HorizonClient _horizonClient;
private static AccountManager _accountManager;

View File

@ -5,7 +5,7 @@ using System;
namespace Ryujinx.Ava.Common.Locale
{
public class LocaleExtension : MarkupExtension
internal class LocaleExtension : MarkupExtension
{
public LocaleExtension(string key)
{

View File

@ -40,7 +40,7 @@ namespace Ryujinx.Ava.Common.Locale
}
// Load english first, if the target language translation is incomplete, we default to english.
LoadLanguage(DefaultLanguageCode);
LoadDefaultLanguage();
if (localeLanguageCode != DefaultLanguageCode)
{
@ -79,6 +79,11 @@ namespace Ryujinx.Ava.Common.Locale
OnPropertyChanged("Item");
}
public void LoadDefaultLanguage()
{
LoadLanguage(DefaultLanguageCode);
}
public void LoadLanguage(string languageCode)
{
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");

View File

@ -10,7 +10,7 @@ using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input
{
public class AvaloniaKeyboard : IKeyboard
internal class AvaloniaKeyboard : IKeyboard
{
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver;

View File

@ -10,7 +10,7 @@ using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input
{
public class AvaloniaKeyboardDriver : IGamepadDriver
internal class AvaloniaKeyboardDriver : IGamepadDriver
{
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
private readonly Control _control;

View File

@ -5,7 +5,7 @@ using AvaKey = Avalonia.Input.Key;
namespace Ryujinx.Ava.Input
{
public static class AvaloniaMappingHelper
internal static class AvaloniaMappingHelper
{
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
{

View File

@ -6,7 +6,7 @@ using System.Numerics;
namespace Ryujinx.Ava.Input
{
public class AvaloniaMouse : IMouse
internal class AvaloniaMouse : IMouse
{
private AvaloniaMouseDriver _driver;

View File

@ -9,7 +9,7 @@ using Size = System.Drawing.Size;
namespace Ryujinx.Ava.Input
{
public class AvaloniaMouseDriver : IGamepadDriver
internal class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;

View File

@ -23,7 +23,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Modules
{
public static class Updater
internal static class Updater
{
private const string GitHubApiURL = "https://api.github.com";
internal static bool Running;

View File

@ -185,7 +185,7 @@ namespace Ryujinx.Ava
}
}
private static void ReloadConfig()
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");

View File

@ -18,16 +18,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.14" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.14" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.14" />
<PackageReference Include="Avalonia.Svg" Version="0.10.13" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.13" />
<PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.15" />
<PackageReference Include="Avalonia.Svg" Version="0.10.14" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.7.14" />
<PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
<PackageReference Include="DynamicData" Version="7.9.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
@ -123,6 +123,7 @@
<None Remove="Assets\Locales\pt_BR.json" />
<None Remove="Assets\Locales\ru_RU.json" />
<None Remove="Assets\Locales\tr_TR.json" />
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\BaseDark.xaml" />
<None Remove="Assets\Styles\BaseLight.xaml" />
@ -139,6 +140,7 @@
<EmbeddedResource Include="Assets\Locales\pt_BR.json" />
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
@ -45,13 +46,61 @@ namespace Ryujinx.Ava.Ui.Applet
public bool DisplayMessageDialog(string title, string message)
{
// TODO : Show controller applet. Needs settings window to be implemented.
Dispatcher.UIThread.InvokeAsync(() =>
ManualResetEvent dialogCloseEvent = new(false);
bool okPressed = false;
Dispatcher.UIThread.InvokeAsync(async () =>
{
ContentDialogHelper.ShowNotAvailableMessage(_parent);
try
{
ManualResetEvent deferEvent = new(false);
bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"",
LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important,
deferEvent,
async (window) =>
{
if (opened)
{
return;
}
opened = true;
_parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window);
opened = false;
});
if (response == UserResult.Ok)
{
okPressed = true;
}
dialogCloseEvent.Set();
}
catch (Exception ex)
{
ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
dialogCloseEvent.Set();
}
});
return true;
dialogCloseEvent.WaitOne();
return okPressed;
}
public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText)

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Applet
{
public class ErrorAppletWindow : StyleableWindow
internal class ErrorAppletWindow : StyleableWindow
{
private readonly Window _owner;
private object _buttonResponse;

View File

@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
public class SwkbdAppletDialog : UserControl
internal class SwkbdAppletDialog : UserControl
{
private Predicate<int> _checkLength;
private int _inputMax;

View File

@ -5,7 +5,7 @@ using System.Runtime.Versioning;
namespace Ryujinx.Ava.Ui.Controls
{
[SupportedOSPlatform("linux")]
public class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
{
public AvaloniaGlxContext(IntPtr handle)
: base(FramebufferFormat.Default, 0, 0, 0, false, null)

View File

@ -5,7 +5,7 @@ using System.Runtime.Versioning;
namespace Ryujinx.Ava.Ui.Controls
{
[SupportedOSPlatform("windows")]
public class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
{
public AvaloniaWglContext(IntPtr handle)
: base(FramebufferFormat.Default, 0, 0, 0, false, null)

View File

@ -7,7 +7,7 @@ using System.IO;
namespace Ryujinx.Ava.Ui.Controls
{
public class BitmapArrayValueConverter : IValueConverter
internal class BitmapArrayValueConverter : IValueConverter
{
public static BitmapArrayValueConverter Instance = new();

View File

@ -0,0 +1,118 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
internal class ButtonKeyAssigner
{
internal class ButtonAssignedEventArgs : EventArgs
{
public ToggleButton Button { get; }
public bool IsAssigned { get; }
public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned)
{
Button = button;
IsAssigned = isAssigned;
}
}
public ToggleButton ToggledButton { get; set; }
private bool _isWaitingForInput;
private bool _shouldUnbind;
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
public ButtonKeyAssigner(ToggleButton toggleButton)
{
ToggledButton = toggleButton;
}
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
{
Dispatcher.UIThread.Post(() =>
{
ToggledButton.IsChecked = true;
});
if (_isWaitingForInput)
{
Dispatcher.UIThread.Post(() =>
{
Cancel();
});
return;
}
_isWaitingForInput = true;
assigner.Initialize();
await Task.Run(async () =>
{
while (true)
{
if (!_isWaitingForInput)
{
return;
}
await Task.Delay(10);
assigner.ReadInput();
if (assigner.HasAnyButtonPressed() || assigner.ShouldCancel() || (keyboard != null && keyboard.IsPressed(Key.Escape)))
{
break;
}
}
});
await Dispatcher.UIThread.InvokeAsync(() =>
{
string pressedButton = assigner.GetPressedButton();
if (_shouldUnbind)
{
SetButtonText(ToggledButton, "Unbound");
}
else if (pressedButton != "")
{
SetButtonText(ToggledButton, pressedButton);
}
_shouldUnbind = false;
_isWaitingForInput = false;
ToggledButton.IsChecked = false;
ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null));
static void SetButtonText(ToggleButton button, string text)
{
ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock);
if (textBlock != null && textBlock is TextBlock block)
{
block.Text = text;
}
}
});
}
public void Cancel(bool shouldUnbind = false)
{
_isWaitingForInput = false;
ToggledButton.IsChecked = false;
_shouldUnbind = shouldUnbind;
}
}
}

View File

@ -97,10 +97,12 @@ namespace Ryujinx.Ava.Ui.Controls
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
@ -115,6 +117,8 @@ namespace Ryujinx.Ava.Ui.Controls
return;
}
contentDialog.PrimaryButtonClick -= DeferClose;
startedDeferring = true;
var deferral = args.GetDeferral();

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Ava.Ui.Controls
{
public static class IGlContextExtension
internal static class IGlContextExtension
{
public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context)
{

View File

@ -6,7 +6,7 @@ using System.Globalization;
namespace Ryujinx.Ava.Ui.Controls
{
public class KeyValueConverter : IValueConverter
internal class KeyValueConverter : IValueConverter
{
public static KeyValueConverter Instance = new();

View File

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Ava.Ui.Controls
{
public class OpenToolkitBindingsContext : IBindingsContext
internal class OpenToolkitBindingsContext : IBindingsContext
{
private readonly Func<string, IntPtr> _getProcAddress;

View File

@ -18,7 +18,7 @@ using System;
namespace Ryujinx.Ava.Ui.Controls
{
public class RendererControl : Control
internal class RendererControl : Control
{
private int _image;

View File

@ -0,0 +1,6 @@
using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.Ui.Models
{
internal record ControllerModel(ControllerType Type, string Name);
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.Ava.Ui.Models
{
public enum DeviceType
{
None,
Keyboard,
Controller
}
}

View File

@ -3,7 +3,7 @@ using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
public class FileSizeSortComparer : IComparer
internal class FileSizeSortComparer : IComparer
{
public int Compare(object x, object y)
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
public class FileSizeSortComparer : IComparer<ApplicationData>
internal class FileSizeSortComparer : IComparer<ApplicationData>
{
public FileSizeSortComparer() { }
public FileSizeSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
public class LastPlayedSortComparer : IComparer<ApplicationData>
internal class LastPlayedSortComparer : IComparer<ApplicationData>
{
public LastPlayedSortComparer() { }
public LastPlayedSortComparer(bool isAscending) { IsAscending = isAscending; }

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
public class TimePlayedSortComparer : IComparer<ApplicationData>
internal class TimePlayedSortComparer : IComparer<ApplicationData>
{
public TimePlayedSortComparer() { }
public TimePlayedSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }

View File

@ -7,7 +7,7 @@ using System;
namespace Ryujinx.Ava.Ui.Models
{
public class InputConfiguration<Key, Stick> : BaseModel
internal class InputConfiguration<Key, Stick> : BaseModel
{
private float _deadzoneRight;
private float _triggerThreshold;

View File

@ -4,7 +4,7 @@ using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
public class LastPlayedSortComparer : IComparer
internal class LastPlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{

View File

@ -0,0 +1,6 @@
using Ryujinx.Common.Configuration.Hid;
namespace Ryujinx.Ava.Ui.Models
{
public record PlayerModel(PlayerIndex Id, string Name);
}

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Ava.Ui.Models
{
public class ProfileImageModel
internal class ProfileImageModel
{
public ProfileImageModel(string name, byte[] data)
{

View File

@ -2,7 +2,7 @@
namespace Ryujinx.Ava.Ui.Models
{
public class StatusUpdatedEventArgs : EventArgs
internal class StatusUpdatedEventArgs : EventArgs
{
public bool VSyncEnabled { get; }
public float Volume { get; }

View File

@ -3,7 +3,7 @@ using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
public class TimePlayedSortComparer : IComparer
internal class TimePlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Ava.Ui.Windows
namespace Ryujinx.Ava.Ui.Models
{
public class TimeZone
internal class TimeZone
{
public TimeZone(string utcDifference, string location, string abbreviation)
{

View File

@ -3,7 +3,7 @@ using Ryujinx.Ava.Common.Locale;
namespace Ryujinx.Ava.Ui.Models
{
public class TitleUpdateModel
internal class TitleUpdateModel
{
public bool IsEnabled { get; set; }
public bool IsNoUpdate { get; }

View File

@ -0,0 +1,887 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Svg.Skia;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Input;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.Ui.ViewModels
{
public class ControllerSettingsViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
private const string JoyConPairResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg";
private const string JoyConLeftResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg";
private const string JoyConRightResource = "Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg";
private const string KeyboardString = "keyboard";
private const string ControllerString = "controller";
private readonly MainWindow _mainWindow;
private PlayerIndex _playerId;
private int _controller;
private string _controllerImage;
private int _device;
private object _configuration;
private string _profileName;
private bool _isLoaded;
private readonly UserControl _owner;
public IGamepadDriver AvaloniaKeyboardDriver { get; }
public IGamepad SelectedGamepad { get; private set; }
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
internal ObservableCollection<ControllerModel> Controllers { get; set; }
public AvaloniaList<string> ProfilesList { get; set; }
public AvaloniaList<string> DeviceList { get; set; }
// XAML Flags
public bool ShowSettings => _device > 0;
public bool IsController => _device > 1;
public bool IsKeyboard => !IsController;
public bool IsRight { get; set; }
public bool IsLeft { get; set; }
public bool IsModified { get; set; }
public object Configuration
{
get => _configuration;
set
{
_configuration = value;
OnPropertyChanged();
}
}
public PlayerIndex PlayerId
{
get => _playerId;
set
{
if (IsModified)
{
return;
}
IsModified = false;
_playerId = value;
if (!Enum.IsDefined(typeof(PlayerIndex), _playerId))
{
_playerId = PlayerIndex.Player1;
}
LoadConfiguration();
LoadDevice();
LoadProfiles();
_isLoaded = true;
OnPropertyChanged();
}
}
public int Controller
{
get => _controller;
set
{
_controller = value;
if (_controller == -1)
{
_controller = 0;
}
if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1)
{
ControllerType controller = Controllers[_controller].Type;
IsLeft = true;
IsRight = true;
switch (controller)
{
case ControllerType.Handheld:
ControllerImage = JoyConPairResource;
break;
case ControllerType.ProController:
ControllerImage = ProControllerResource;
break;
case ControllerType.JoyconPair:
ControllerImage = JoyConPairResource;
break;
case ControllerType.JoyconLeft:
ControllerImage = JoyConLeftResource;
IsRight = false;
break;
case ControllerType.JoyconRight:
ControllerImage = JoyConRightResource;
IsLeft = false;
break;
}
LoadInputDriver();
LoadProfiles();
}
OnPropertyChanged();
NotifyChanges();
}
}
public string ControllerImage
{
get => _controllerImage;
set
{
_controllerImage = value;
OnPropertyChanged();
OnPropertyChanged(nameof(Image));
}
}
public SvgImage Image
{
get
{
SvgImage image = new SvgImage();
if (!string.IsNullOrWhiteSpace(_controllerImage))
{
SvgSource source = new SvgSource();
source.Load(EmbeddedResources.GetStream(_controllerImage));
image.Source = source;
}
return image;
}
}
public string ProfileName
{
get => _profileName; set
{
_profileName = value;
OnPropertyChanged();
}
}
public int Device
{
get => _device;
set
{
_device = value < 0 ? 0 : value;
if (_device >= Devices.Count)
{
return;
}
var selected = Devices[_device].Type;
if (selected != DeviceType.None)
{
LoadControllers();
if (_isLoaded)
{
LoadConfiguration(LoadDefaultConfiguration());
}
}
OnPropertyChanged();
NotifyChanges();
}
}
public InputConfig Config { get; set; }
public ControllerSettingsViewModel(UserControl owner) : this()
{
_owner = owner;
if (Program.PreviewerDetached)
{
_mainWindow =
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Avalonia.Application.Current
.ApplicationLifetime).MainWindow;
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
if (_mainWindow.AppHost != null)
{
_mainWindow.AppHost.NpadManager.BlockInputUpdates();
}
_isLoaded = false;
LoadDevices();
PlayerId = PlayerIndex.Player1;
}
}
public ControllerSettingsViewModel()
{
PlayerIndexes = new ObservableCollection<PlayerModel>();
Controllers = new ObservableCollection<ControllerModel>();
Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>();
ProfilesList = new AvaloniaList<string>();
DeviceList = new AvaloniaList<string>();
ControllerImage = ProControllerResource;
PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance["ControllerSettingsPlayer1"]));
PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance["ControllerSettingsPlayer2"]));
PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance["ControllerSettingsPlayer3"]));
PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance["ControllerSettingsPlayer4"]));
PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance["ControllerSettingsPlayer5"]));
PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance["ControllerSettingsPlayer6"]));
PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance["ControllerSettingsPlayer7"]));
PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance["ControllerSettingsPlayer8"]));
PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance["ControllerSettingsHandheld"]));
}
private void LoadConfiguration(InputConfig inputConfig = null)
{
Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId);
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{
Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig);
}
if (Config is StandardControllerInputConfig controllerInputConfig)
{
Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig);
}
}
public void LoadDevice()
{
if (Config == null || Config.Backend == InputBackendType.Invalid)
{
Device = 0;
}
else
{
var type = DeviceType.None;
if (Config is StandardKeyboardInputConfig)
{
type = DeviceType.Keyboard;
}
if (Config is StandardControllerInputConfig)
{
type = DeviceType.Controller;
}
var item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id);
if (item != default)
{
Device = Devices.ToList().FindIndex(x => x.Id == item.Id);
}
else
{
Device = 0;
}
}
}
public async void ShowMotionConfig()
{
await MotionSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
}
public async void ShowRumbleConfig()
{
await RumbleSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
}
private void LoadInputDriver()
{
if (_device < 0)
{
return;
}
string id = GetCurrentGamepadId();
var type = Devices[Device].Type;
if (type == DeviceType.None)
{
return;
}
else if (type == DeviceType.Keyboard)
{
if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver)
{
// NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused...
SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id);
}
else
{
SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
}
}
else
{
SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
}
}
private void HandleOnGamepadDisconnected(string id)
{
Dispatcher.UIThread.Post(() =>
{
LoadDevices();
});
}
private void HandleOnGamepadConnected(string id)
{
Dispatcher.UIThread.Post(() =>
{
LoadDevices();
});
}
private string GetCurrentGamepadId()
{
if (_device < 0)
{
return string.Empty;
}
var device = Devices[Device];
if (device.Type == DeviceType.None)
{
return null;
}
return device.Id.Split(" ")[0];
}
public void LoadControllers()
{
Controllers.Clear();
if (_playerId == PlayerIndex.Handheld)
{
Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance["ControllerSettingsControllerTypeHandheld"]));
Controller = 0;
}
else
{
Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance["ControllerSettingsControllerTypeProController"]));
Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance["ControllerSettingsControllerTypeJoyConPair"]));
Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance["ControllerSettingsControllerTypeJoyConLeft"]));
Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance["ControllerSettingsControllerTypeJoyConRight"]));
if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1)
{
Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
}
else
{
Controller = 0;
}
}
}
private static string GetShortGamepadName(string str)
{
const string Ellipsis = "...";
const int MaxSize = 50;
if (str.Length > MaxSize)
{
return str.Substring(0, MaxSize - Ellipsis.Length) + Ellipsis;
}
return str;
}
public void LoadDevices()
{
lock (Devices)
{
Devices.Clear();
DeviceList.Clear();
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance["ControllerSettingsDeviceDisabled"]));
foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
{
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
if (gamepad != null)
{
Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)} ({id})"));
}
}
foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
{
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
if (gamepad != null)
{
Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({id})"));
}
}
DeviceList.AddRange(Devices.Select(x => x.Name));
Device = Math.Min(Device, DeviceList.Count);
}
}
private string GetProfileBasePath()
{
string path = AppDataManager.ProfilesDirPath;
var type = Devices[Device == -1 ? 0 : Device].Type;
if (type == DeviceType.Keyboard)
{
path = Path.Combine(path, KeyboardString);
}
else if (type == DeviceType.Controller)
{
path = Path.Combine(path, ControllerString);
}
return path;
}
private void LoadProfiles()
{
ProfilesList.Clear();
string basePath = GetProfileBasePath();
if (!Directory.Exists(basePath))
{
Directory.CreateDirectory(basePath);
}
ProfilesList.Add((LocaleManager.Instance["ControllerSettingsProfileDefault"]));
foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories))
{
ProfilesList.Add(Path.GetFileNameWithoutExtension(profile));
}
if (string.IsNullOrWhiteSpace(ProfileName))
{
ProfileName = LocaleManager.Instance["ControllerSettingsProfileDefault"];
}
}
public InputConfig LoadDefaultConfiguration()
{
var activeDevice = Devices.FirstOrDefault();
if (Devices.Count > 0 && Device < Devices.Count && Device >= 0)
{
activeDevice = Devices[Device];
}
InputConfig config;
if (activeDevice.Type == DeviceType.Keyboard)
{
string id = activeDevice.Id;
config = new StandardKeyboardInputConfig
{
Version = Ryujinx.Common.Configuration.Hid.InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = id,
ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = Key.Up,
DpadDown = Key.Down,
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound
},
LeftJoyconStick =
new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.W,
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = Key.Z,
ButtonB = Key.X,
ButtonX = Key.C,
ButtonY = Key.V,
ButtonPlus = Key.Plus,
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.I,
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H
}
};
}
else if (activeDevice.Type == DeviceType.Controller)
{
bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo");
string id = activeDevice.Id.Split(" ")[0];
config = new StandardControllerInputConfig
{
Version = Ryujinx.Common.Configuration.Hid.InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = id,
ControllerType = ControllerType.ProController,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
DpadUp = ConfigGamepadInputId.DpadUp,
DpadDown = ConfigGamepadInputId.DpadDown,
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false
}
};
}
else
{
config = new InputConfig();
}
config.PlayerIndex = _playerId;
return config;
}
public void LoadProfile()
{
if (Device == 0)
{
return;
}
InputConfig config = null;
if (string.IsNullOrWhiteSpace(ProfileName))
{
return;
}
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
{
config = LoadDefaultConfiguration();
}
else
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
if (!File.Exists(path))
{
var index = ProfilesList.IndexOf(ProfileName);
if (index != -1)
{
ProfilesList.RemoveAt(index);
}
return;
}
try
{
using (Stream stream = File.OpenRead(path))
{
config = JsonHelper.Deserialize<InputConfig>(stream);
}
}
catch (JsonException) { }
catch (InvalidOperationException)
{
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
return;
}
}
if (config != null)
{
_isLoaded = false;
LoadConfiguration(config);
LoadDevice();
_isLoaded = true;
NotifyChanges();
}
}
public async void SaveProfile()
{
if (Device == 0)
{
return;
}
if (Configuration == null)
{
return;
}
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
{
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
return;
}
else
{
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
}
else if (IsController)
{
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, true);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
}
}
}
public async void RemoveProfile()
{
if (Device == 0 || ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"] || ProfilesList.IndexOf(ProfileName) == -1)
{
return;
}
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogProfileDeleteProfileTitle"],
LocaleManager.Instance["DialogProfileDeleteProfileMessage"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result == UserResult.Yes)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
if (File.Exists(path))
{
File.Delete(path);
}
LoadProfiles();
}
}
public void Save()
{
IsModified = false;
List<InputConfig> newConfig = new();
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
newConfig.Remove(newConfig.Find(x => x == null));
if (Device == 0)
{
newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId));
}
else
{
var device = Devices[Device];
if (device.Type == DeviceType.Keyboard)
{
var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>;
inputConfig.Id = device.Id;
}
else
{
var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>;
inputConfig.Id = device.Id.Split(" ")[0];
}
var config = !IsController
? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig()
: (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId;
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
if (i == -1)
{
newConfig.Add(config);
}
else
{
newConfig[i] = config;
}
}
_mainWindow.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
public void NotifyChange(string property)
{
OnPropertyChanged(property);
}
public void NotifyChanges()
{
OnPropertyChanged(nameof(Configuration));
OnPropertyChanged(nameof(IsController));
OnPropertyChanged(nameof(ShowSettings));
OnPropertyChanged(nameof(IsKeyboard));
OnPropertyChanged(nameof(IsRight));
OnPropertyChanged(nameof(IsLeft));
}
public void Dispose()
{
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose();
AvaloniaKeyboardDriver.Dispose();
}
}
}

View File

@ -11,6 +11,7 @@ using LibHac.FsSystem;
using LibHac.Ncm;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
@ -35,7 +36,7 @@ using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
namespace Ryujinx.Ava.Ui.ViewModels
{
public class MainWindowViewModel : BaseModel
internal class MainWindowViewModel : BaseModel
{
private readonly MainWindow _owner;
private ObservableCollection<ApplicationData> _applications;
@ -86,9 +87,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (Program.PreviewerDetached)
{
ShowUiKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi.ToString());
ScreenshotKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot.ToString());
PauseKey = KeyGesture.Parse(ConfigurationState.Instance.Hid.Hotkeys.Value.Pause.ToString());
LoadConfigurableHotKeys();
Volume = ConfigurationState.Instance.System.AudioVolume;
}
@ -836,6 +835,22 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public void LoadConfigurableHotKeys()
{
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
{
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
}
}
public void TakeScreenshot()
{
_owner.AppHost.ScreenshotRequested = true;
@ -930,10 +945,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public void OpenSettings()
public async void OpenSettings()
{
// TODO : Implement Settings window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
await _owner.SettingsWindow.ShowDialog(_owner);
LoadConfigurableHotKeys();
}
public void ManageProfiles()
@ -951,6 +968,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ChangeLanguage(object obj)
{
LocaleManager.Instance.LoadDefaultLanguage();
LocaleManager.Instance.LoadLanguage((string)obj);
}
@ -1350,7 +1368,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, dialogTitle, dialogMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner,
dialogTitle,
dialogMessage,
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
UpdateWaitWindow waitingDialog = ContentDialogHelper.CreateWaitingDialog(dialogTitle, LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallWaitMessage"]);

View File

@ -0,0 +1,403 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Threading;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.Input;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using System;
using System.Collections.Generic;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.ViewModels
{
internal class SettingsViewModel : BaseModel
{
private readonly VirtualFileSystem _virtualFileSystem;
private readonly ContentManager _contentManager;
private readonly StyleableWindow _owner;
private TimeZoneContentManager _timeZoneContentManager;
private readonly List<string> _validTzRegions;
private float _customResolutionScale;
private int _resolutionScale;
private int _graphicsBackendMultithreadingIndex;
private float _previousVolumeLevel;
private float _volume;
public int ResolutionScale
{
get => _resolutionScale;
set
{
_resolutionScale = value;
OnPropertyChanged(nameof(CustomResolutionScale));
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
}
}
public int GraphicsBackendMultithreadingIndex
{
get => _graphicsBackendMultithreadingIndex;
set
{
_graphicsBackendMultithreadingIndex = value;
if (_owner != null)
{
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateInfoDialog(_owner,
LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
"",
"",
LocaleManager.Instance["InputDialogOk"],
LocaleManager.Instance["DialogSettingsBackendThreadingWarningTitle"]);
});
}
}
OnPropertyChanged();
}
}
public float CustomResolutionScale
{
get => _customResolutionScale;
set
{
_customResolutionScale = MathF.Round(value, 1);
OnPropertyChanged();
}
}
public bool EnableDiscordIntegration { get; set; }
public bool CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; }
public bool HideCursorOnIdle { get; set; }
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
public bool EnableVsync { get; set; }
public bool EnablePptc { get; set; }
public bool EnableInternetAccess { get; set; }
public bool EnableFsIntegrityChecks { get; set; }
public bool IgnoreMissingServices { get; set; }
public bool ExpandDramSize { get; set; }
public bool EnableShaderCache { get; set; }
public bool EnableFileLog { get; set; }
public bool EnableStub { get; set; }
public bool EnableInfo { get; set; }
public bool EnableWarn { get; set; }
public bool EnableError { get; set; }
public bool EnableTrace { get; set; }
public bool EnableGuest { get; set; }
public bool EnableFsAccessLog { get; set; }
public bool EnableDebug { get; set; }
public bool IsOpenAlEnabled { get; set; }
public bool IsSoundIoEnabled { get; set; }
public bool IsSDL2Enabled { get; set; }
public bool EnableCustomTheme { get; set; }
public bool IsCustomResolutionScaleActive => _resolutionScale == 0;
public string TimeZone { get; set; }
public string ShaderDumpPath { get; set; }
public string CustomThemePath { get; set; }
public int Language { get; set; }
public int Region { get; set; }
public int FsGlobalAccessLogMode { get; set; }
public int AudioBackend { get; set; }
public int MaxAnisotropy { get; set; }
public int AspectRatio { get; set; }
public int OpenglDebugLevel { get; set; }
public int MemoryMode { get; set; }
public int BaseStyleIndex { get; set; }
public float Volume
{
get => _volume;
set
{
_volume = value;
ConfigurationState.Instance.System.AudioVolume.Value = (float)(_volume / 100);
OnPropertyChanged();
}
}
public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; }
public AvaloniaList<TimeZone> TimeZones { get; set; }
public AvaloniaList<string> GameDirectories { get; set; }
private KeyboardHotkeys _keyboardHotkeys;
public KeyboardHotkeys KeyboardHotkeys
{
get => _keyboardHotkeys;
set
{
_keyboardHotkeys = value;
OnPropertyChanged();
}
}
public IGamepadDriver AvaloniaKeyboardDriver { get; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager, StyleableWindow owner) : this()
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
_owner = owner;
if (Program.PreviewerDetached)
{
LoadTimeZones();
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
}
}
public SettingsViewModel()
{
GameDirectories = new AvaloniaList<string>();
TimeZones = new AvaloniaList<TimeZone>();
_validTzRegions = new List<string>();
CheckSoundBackends();
if (Program.PreviewerDetached)
{
LoadCurrentConfiguration();
}
}
public void CheckSoundBackends()
{
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
}
public void LoadTimeZones()
{
_timeZoneContentManager = new TimeZoneContentManager();
_timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None);
foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets())
{
int hours = Math.DivRem(offset, 3600, out int seconds);
int minutes = Math.Abs(seconds) / 60;
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
_validTzRegions.Add(location);
}
}
public void ValidateAndSetTimeZone(string location)
{
if (_validTzRegions.Contains(location))
{
TimeZone = location;
OnPropertyChanged(nameof(TimeZone));
}
}
public async void BrowseTheme()
{
var dialog = new OpenFileDialog()
{
Title = LocaleManager.Instance["SettingsSelectThemeFileDialogTitle"],
AllowMultiple = false
};
dialog.Filters.Add(new FileDialogFilter() { Extensions = { "xaml" }, Name = LocaleManager.Instance["SettingsXamlThemeFile"] });
var file = await dialog.ShowAsync(_owner);
if (file != null && file.Length > 0)
{
CustomThemePath = file[0];
OnPropertyChanged(nameof(CustomThemePath));
}
}
public void LoadCurrentConfiguration()
{
ConfigurationState config = ConfigurationState.Instance;
GameDirectories.Clear();
GameDirectories.AddRange(config.Ui.GameDirs.Value);
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
HideCursorOnIdle = config.HideCursorOnIdle;
EnableDockedMode = config.System.EnableDockedMode;
EnableKeyboard = config.Hid.EnableKeyboard;
EnableMouse = config.Hid.EnableMouse;
EnableVsync = config.Graphics.EnableVsync;
EnablePptc = config.System.EnablePtc;
EnableInternetAccess = config.System.EnableInternetAccess;
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
IgnoreMissingServices = config.System.IgnoreMissingServices;
ExpandDramSize = config.System.ExpandRam;
EnableShaderCache = config.Graphics.EnableShaderCache;
EnableFileLog = config.Logger.EnableFileLog;
EnableStub = config.Logger.EnableStub;
EnableInfo = config.Logger.EnableInfo;
EnableWarn = config.Logger.EnableWarn;
EnableError = config.Logger.EnableError;
EnableTrace = config.Logger.EnableTrace;
EnableGuest = config.Logger.EnableGuest;
EnableDebug = config.Logger.EnableDebug;
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
EnableCustomTheme = config.Ui.EnableCustomTheme;
Volume = config.System.AudioVolume * 100;
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
TimeZone = config.System.TimeZone;
ShaderDumpPath = config.Graphics.ShadersDumpPath;
CustomThemePath = config.Ui.CustomThemePath;
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
Language = (int)config.System.Language.Value;
Region = (int)config.System.Region.Value;
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
AudioBackend = (int)config.System.AudioBackend.Value;
MemoryMode = (int)config.System.MemoryManagerMode.Value;
float anisotropy = config.Graphics.MaxAnisotropy;
MaxAnisotropy = anisotropy == -1 ? 0 : (int)(MathF.Log2(anisotropy));
AspectRatio = (int)config.Graphics.AspectRatio.Value;
int resolution = config.Graphics.ResScale;
ResolutionScale = resolution == -1 ? 0 : resolution;
CustomResolutionScale = config.Graphics.ResScaleCustom;
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
DateOffset = dateTimeOffset.Date;
TimeOffset = dateTimeOffset.TimeOfDay;
KeyboardHotkeys = config.Hid.Hotkeys.Value;
_previousVolumeLevel = Volume;
}
public void SaveSettings()
{
List<string> gameDirs = new List<string>(GameDirectories);
ConfigurationState config = ConfigurationState.Instance;
if (_validTzRegions.Contains(TimeZone))
{
config.System.TimeZone.Value = TimeZone;
}
config.Logger.EnableError.Value = EnableError;
config.Logger.EnableTrace.Value = EnableTrace;
config.Logger.EnableWarn.Value = EnableWarn;
config.Logger.EnableInfo.Value = EnableInfo;
config.Logger.EnableStub.Value = EnableStub;
config.Logger.EnableDebug.Value = EnableDebug;
config.Logger.EnableGuest.Value = EnableGuest;
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
config.Logger.EnableFileLog.Value = EnableFileLog;
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
config.System.EnableDockedMode.Value = EnableDockedMode;
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.HideCursorOnIdle.Value = HideCursorOnIdle;
config.Graphics.EnableVsync.Value = EnableVsync;
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
config.System.EnablePtc.Value = EnablePptc;
config.System.EnableInternetAccess.Value = EnableInternetAccess;
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
config.System.ExpandRam.Value = ExpandDramSize;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
config.Hid.EnableMouse.Value = EnableMouse;
config.Ui.CustomThemePath.Value = CustomThemePath;
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
config.System.Language.Value = (Language)Language;
config.System.Region.Value = (Region)Region;
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
{
DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off);
}
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
config.Ui.GameDirs.Value = gameDirs;
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
float anisotropy = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
config.Graphics.MaxAnisotropy.Value = anisotropy;
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
config.Graphics.ResScale.Value = ResolutionScale == 0 ? -1 : ResolutionScale;
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
config.System.AudioVolume.Value = Volume / 100;
AudioBackend audioBackend = (AudioBackend)AudioBackend;
if (audioBackend != config.System.AudioBackend.Value)
{
config.System.AudioBackend.Value = audioBackend;
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
}
config.Hid.Hotkeys.Value = KeyboardHotkeys;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
_previousVolumeLevel = Volume;
}
public void RevertIfNotSaved()
{
Program.ReloadConfig();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,200 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Ui.Windows
{
public class ControllerSettingsWindow : UserControl
{
private bool _dialogOpen;
public Grid SettingButtons { get; set; }
private ButtonKeyAssigner _currentAssigner;
internal ControllerSettingsViewModel ViewModel { get; set; }
public ControllerSettingsWindow()
{
DataContext = ViewModel = new ControllerSettingsViewModel(this);
InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button && !(visual is CheckBox))
{
button.Checked += Button_Checked;
button.Unchecked += Button_Unchecked;
}
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
SettingButtons = this.FindControl<Grid>("SettingButtons");
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver)
{
_currentAssigner.Cancel();
}
}
private void Button_Checked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.IsAssigned)
{
ViewModel.IsModified = true;
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
}
public void SaveCurrentProfile()
{
ViewModel.Save();
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
var device = ViewModel.Devices[ViewModel.Device];
if (device.Type == Models.DeviceType.Keyboard)
{
assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad);
}
else if (device.Type == Models.DeviceType.Controller)
{
InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.Id == ViewModel.SelectedGamepad.Id);
assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (config as StandardControllerInputConfig).TriggerThreshold, forStick);
}
else
{
throw new Exception("Controller not supported");
}
return assigner;
}
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.IsModified && !_dialogOpen)
{
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog(
this.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"],
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result == UserResult.Yes)
{
ViewModel.Save();
}
_dialogOpen = false;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
(PlayerIndex key, _) = (KeyValuePair<PlayerIndex, string>)e.AddedItems[0];
ViewModel.PlayerId = key;
}
}
}
public void Dispose()
{
_currentAssigner?.Cancel();
_currentAssigner = null;
ViewModel.Dispose();
}
}
}

View File

@ -175,6 +175,10 @@
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Turkish" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="Simplified Chinese" />
</MenuItem>
<Separator />
<MenuItem
@ -738,4 +742,4 @@
</Grid>
</Grid>
</Grid>
</window:StyleableWindow>
</window:StyleableWindow>

View File

@ -58,10 +58,10 @@ namespace Ryujinx.Ava.Ui.Windows
public LibHacHorizonManager LibHacHorizonManager { get; private set; }
public AppHost AppHost { get; private set; }
internal AppHost AppHost { get; private set; }
public InputManager InputManager { get; private set; }
public RendererControl GlRenderer { get; private set; }
internal RendererControl GlRenderer { get; private set; }
public ContentControl ContentFrame { get; private set; }
public TextBlock LoadStatus { get; private set; }
public TextBlock FirmwareStatus { get; private set; }
@ -78,7 +78,8 @@ namespace Ryujinx.Ava.Ui.Windows
public HotKeyControl DockToggleHotKey { get; private set; }
public HotKeyControl ExitHotKey { get; private set; }
public ToggleSplitButton VolumeStatus { get; set; }
public MainWindowViewModel ViewModel { get; private set; }
internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; }
public bool CanUpdate
{

View File

@ -0,0 +1,140 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.Ui.Windows.MotionSettingsWindow">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
<Slider
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
Maximum="100"
Minimum="0"
Value="{Binding Sensitivity, Mode=TwoWay}" />
<TextBlock HorizontalAlignment="Center"
Margin="5, 0"
Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock
Margin="0"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
<Slider
Margin="0,-5,0,-5"
Width="150"
MaxWidth="150"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
Maximum="100"
Minimum="0"
Value="{Binding GyroDeadzone, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Margin="5, 0"
Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
</StackPanel>
<Separator Height="1" Margin="0,5" />
<CheckBox Margin="5" IsChecked="{Binding EnableCemuHookMotion}">
<TextBlock Margin="0,3,0,0" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
</CheckBox>
</StackPanel>
<Border Grid.Row="1"
Padding="20,5"
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
HorizontalAlignment="Stretch">
<Grid VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionServerHost}" />
<TextBox
Height="30"
MinWidth="100"
MaxWidth="100"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding DsuServerHost, Mode=TwoWay}" />
<TextBlock
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text=":" />
<TextBox
Height="30"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding DsuServerPort, Mode=TwoWay}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Margin="0,10,0,0" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionControllerSlot}" />
<ui:NumberBox Grid.Row="0" Grid.Column="1"
Name="CemuHookSlotUpDown"
SmallChange="1"
LargeChange="1"
Maximum="4"
Minimum="0"
Value="{Binding Slot}" />
<TextBlock Margin="0,10,0,0" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" />
<ui:NumberBox Grid.Row="1" Grid.Column="1"
Name="CemuHookRightJoyConSlotUpDown"
SmallChange="1"
LargeChange="1"
Maximum="4"
Minimum="0"
Value="{Binding AltSlot}" />
</Grid>
</StackPanel>
<CheckBox HorizontalAlignment="Center"
IsChecked="{Binding MirrorInput, Mode=TwoWay}">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" />
</CheckBox>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,81 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows
{
public class MotionSettingsWindow : UserControl
{
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public MotionSettingsWindow()
{
InitializeComponent();
}
public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
{
Slot = config.Slot,
AltSlot = config.AltSlot,
DsuServerHost = config.DsuServerHost,
DsuServerPort = config.DsuServerPort,
MirrorInput = config.MirrorInput,
EnableMotion = config.EnableMotion,
Sensitivity = config.Sensitivity,
GyroDeadzone = config.GyroDeadzone,
EnableCemuHookMotion = config.EnableCemuHookMotion
};
InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
}
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
{
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
if (contentDialog != null)
{
contentDialog.Title = LocaleManager.Instance["ControllerMotionTitle"];
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
contentDialog.SecondaryButtonText = "";
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
contentDialog.Content = content;
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewmodel.Slot;
config.EnableMotion = content._viewmodel.EnableMotion;
config.Sensitivity = content._viewmodel.Sensitivity;
config.GyroDeadzone = content._viewmodel.GyroDeadzone;
config.AltSlot = content._viewmodel.AltSlot;
config.DsuServerHost = content._viewmodel.DsuServerHost;
config.DsuServerPort = content._viewmodel.DsuServerPort;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
config.MirrorInput = content._viewmodel.MirrorInput;
};
await contentDialog.ShowAsync();
}
}
}
}

View File

@ -0,0 +1,57 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.Ui.Windows.RumbleSettingsWindow">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="100"
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
<Slider
Margin="0,-5,0,-5"
Width="200"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
Maximum="10"
Minimum="0"
Value="{Binding StrongRumble, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Margin="5,0"
Text="{Binding StrongRumble, StringFormat=\{0:0.00\}}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock
Width="100"
TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
<Slider
Margin="0,-5,0,-5"
Width="200"
MaxWidth="200"
Maximum="10"
TickFrequency="0.01"
IsSnapToTickEnabled="True"
Minimum="0"
Value="{Binding WeakRumble, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Margin="5,0"
Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,67 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows
{
public class RumbleSettingsWindow : UserControl
{
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public RumbleSettingsWindow()
{
InitializeComponent();
}
public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
{
StrongRumble = config.StrongRumble,
WeakRumble = config.WeakRumble
};
InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
}
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
{
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
if (contentDialog != null)
{
contentDialog.Title = LocaleManager.Instance["ControllerRumbleTitle"];
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
contentDialog.SecondaryButtonText = "";
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
contentDialog.Content = content;
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewmodel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble;
};
await contentDialog.ShowAsync();
}
}
}
}

View File

@ -0,0 +1,897 @@
<window:StyleableWindow
x:Class="Ryujinx.Ava.Ui.Windows.SettingsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="1100"
Height="768"
d:DesignWidth="800"
d:DesignHeight="950"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:CompileBindings="True"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Window.Resources>
<controls:KeyValueConverter x:Key="Key" />
</Window.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl
Grid.Row="1"
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<ScrollViewer Name="UiPage"
Margin="0,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ToggleDiscordTooltip}"
Text="{locale:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</CheckBox>
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
<TextBlock Text="{locale:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
</CheckBox>
<CheckBox IsChecked="{Binding ShowConfirmExit}">
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
</CheckBox>
<CheckBox IsChecked="{Binding HideCursorOnIdle}">
<TextBlock Text="{locale:Locale SettingsTabGeneralHideCursorOnIdle}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<ListBox
Name="GameList"
MinHeight="150"
Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Name="PathBox"
Margin="0"
ToolTip.Tip="{locale:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale AddGameDirTooltip}"
Click="AddButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralAdd}" />
</Button>
<Button
Name="RemoveButton"
Grid.Column="2"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale RemoveGameDirTooltip}"
Click="RemoveButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{locale:Locale SettingsTabGeneralRemove}" />
</Button>
</Grid>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGeneralTheme}" />
<Grid Margin="10,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox IsChecked="{Binding EnableCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
</CheckBox>
<TextBlock VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Row="1"
Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
<TextBox Margin="0,10,0,0"
Grid.Row="1"
Grid.Column="1"
Text="{Binding CustomThemePath}" />
<Button Grid.Row="1"
Grid.Column="2"
Margin="10,10,0,0"
Command="{ReflectionBinding BrowseTheme}"
ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
Content="{locale:Locale ButtonBrowse}" />
<TextBlock VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Row="2"
Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
<ComboBox VerticalAlignment="Center"
Margin="0,10,0,0"
Grid.Column="1"
Grid.Row="2"
MinWidth="100"
SelectedIndex="{Binding BaseStyleIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
</ComboBoxItem>
</ComboBox>
</Grid>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="InputPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Padding="0,0,2,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel Margin="4" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
IsChecked="{Binding EnableDockedMode}">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
IsChecked="{Binding EnableKeyboard}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
</CheckBox>
<CheckBox Margin="5,0"
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
IsChecked="{Binding EnableMouse}">
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
</CheckBox>
</StackPanel>
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0,0,0,0" MinHeight="600" />
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="HotkeysPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel Margin="10,5" Orientation="Vertical" Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabHotkeysHotkeys}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleVsyncHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleVsync, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysScreenshotHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Screenshot, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysShowUiHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ShowUi, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysPauseHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.Pause, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysToggleMuteHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ToggleMute, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer Name="SystemPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabSystemCore}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemRegion}"
Width="250" />
<ComboBox SelectedIndex="{Binding Region}"
ToolTip.Tip="{locale:Locale RegionTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionUSA}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionEurope}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionAustralia}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionChina}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionKorea}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionTaiwan}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding Language}"
ToolTip.Tip="{locale:Locale LanguageTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageGerman}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageItalian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageKorean}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageDutch}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguagePortuguese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageRussian}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
Width="250" />
<AutoCompleteBox
Name="TimeZoneBox"
Width="350"
FilterMode="Contains"
Items="{Binding TimeZones}"
SelectionChanged="TimeZoneBox_OnSelectionChanged"
Text="{Binding Path=TimeZone, Mode=OneWay}"
TextChanged="TimeZoneBox_OnTextChanged"
ValueMemberBinding="{ReflectionBinding TzMultiBinding}"
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
</StackPanel>
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemSystemTime}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="250"/>
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
ToolTip.Tip="{locale:Locale TimeTooltip}"
Width="350" />
</StackPanel>
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
<TimePicker
VerticalAlignment="Center"
ClockIdentifier="24HourClock"
SelectedTime="{Binding TimeOffset}"
Width="350"
ToolTip.Tip="{locale:Locale TimeTooltip}" />
</StackPanel>
<CheckBox IsChecked="{Binding EnableVsync}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabSystemHacks}" />
<TextBlock Text="{locale:Locale SettingsTabSystemHacksNote}" />
</StackPanel>
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding ExpandDramSize}"
ToolTip.Tip="{locale:Locale DRamTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
</CheckBox>
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="CpuPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabCpuCache}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding EnablePptc}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnablePptc}"
ToolTip.Tip="{locale:Locale PptcToggleTooltip}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabCpuMemory}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemMemoryManagerMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding MemoryMode}"
ToolTip.Tip="{locale:Locale MemoryManagerTooltip}"
HorizontalContentAlignment="Left"
Width="350">
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerSoftwareTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeSoftware}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerHostTooltip}">
<TextBlock Text="{locale:Locale SettingsTabSystemMemoryManagerModeHost}" />
</ComboBoxItem>
<ComboBoxItem
ToolTip.Tip="{locale:Locale MemoryManagerUnsafeTooltip}">
<TextBlock
Text="{locale:Locale SettingsTabSystemMemoryManagerModeHostUnchecked}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="GraphicsPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10, 5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGraphicsEnhancements}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical" Spacing="10">
<CheckBox IsChecked="{Binding EnableShaderCache}"
ToolTip.Tip="{locale:Locale ShaderCacheToggleTooltip}">
<TextBlock Text="{locale:Locale SettingsTabGraphicsEnableShaderCache}" />
</CheckBox>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering}"
Width="250" />
<ComboBox SelectedIndex="{Binding MaxAnisotropy}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AnisotropyTooltip}">
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFilteringAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering4x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering8x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabGraphicsAnisotropicFiltering16x}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}"
Text="{locale:Locale SettingsTabGraphicsResolutionScale}"
Width="250" />
<ComboBox SelectedIndex="{Binding ResolutionScale}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale ResolutionScaleTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleCustom}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScaleNative}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale2x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale3x}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsResolutionScale4x}" />
</ComboBoxItem>
</ComboBox>
<ui:NumberBox
Margin="10,0,0,0"
ToolTip.Tip="{locale:Locale ResolutionScaleEntryTooltip}"
MinWidth="150"
SmallChange="0.1"
LargeChange="1"
SimpleNumberFormat="F2"
SpinButtonPlacementMode="Inline"
IsVisible="{Binding IsCustomResolutionScaleActive}"
Maximum="100"
Minimum="0.1"
Value="{Binding CustomResolutionScale}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}"
Text="{locale:Locale SettingsTabGraphicsAspectRatio}"
Width="250" />
<ComboBox SelectedIndex="{Binding AspectRatio}"
Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale AspectRatioTooltip}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio4x3}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio16x10}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio21x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatio32x9}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabGraphicsAspectRatioStretch}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGraphicsFeatures}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale GraphicsBackendThreadingTooltip}"
Text="{locale:Locale SettingsTabGraphicsBackendMultithreading}"
Width="250" />
<ComboBox Width="350"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOff}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale CommonOn}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabGraphicsDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}"
Text="{locale:Locale SettingsTabGraphicsShaderDumpPath}"
Width="250" />
<TextBox Text="{Binding ShaderDumpPath}"
Width="350"
ToolTip.Tip="{locale:Locale ShaderDumpPathTooltip}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="AudioPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabAudio}" />
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioBackend}"
ToolTip.Tip="{locale:Locale AudioBackendTooltip}"
Width="250" />
<ComboBox SelectedIndex="{Binding AudioBackend}"
Width="350"
HorizontalContentAlignment="Left">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendDummy}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsOpenAlEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendOpenAL}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSoundIoEnabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
</ComboBoxItem>
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
<TextBlock Text="{locale:Locale SettingsTabSystemAudioBackendSDL2}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabSystemAudioVolume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="250" />
<ui:NumberBox Value="{Binding Volume}"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Width="350"
SmallChange="1"
LargeChange="10"
SimpleNumberFormat="F0"
SpinButtonPlacementMode="Inline"
Minimum="0"
Maximum="100" />
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}"
Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0"
Maximum="100"
SmallChange="5"
TickFrequency="5"
IsSnapToTickEnabled="True"
LargeChange="10"
Width="350" />
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="NetworkPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabNetworkConnection}" />
<CheckBox Margin="10,0,0,0" IsChecked="{Binding EnableInternetAccess}">
<TextBlock Text="{locale:Locale SettingsTabSystemEnableInternetAccess}"
ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" />
</CheckBox>
</StackPanel>
</Border>
</ScrollViewer>
<ScrollViewer
Name="LoggingPage"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Border>
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabLoggingLogging}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableFileLog}"
ToolTip.Tip="{locale:Locale FileLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableLoggingToFile}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableStub}"
ToolTip.Tip="{locale:Locale StubLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableStubLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableInfo}"
ToolTip.Tip="{locale:Locale InfoLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableInfoLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableWarn}"
ToolTip.Tip="{locale:Locale WarnLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableWarningLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableError}"
ToolTip.Tip="{locale:Locale ErrorLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableErrorLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableTrace}"
ToolTip.Tip="{locale:Locale TraceLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableTraceLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableGuest}"
ToolTip.Tip="{locale:Locale GuestLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableGuestLogs}" />
</CheckBox>
</StackPanel>
<Separator Height="1" />
<TextBlock FontWeight="Bold" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical"
Spacing="10">
<StackPanel Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDebug}"
ToolTip.Tip="{locale:Locale DebugLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableDebugLogs}" />
</CheckBox>
<CheckBox IsChecked="{Binding EnableFsAccessLog}"
ToolTip.Tip="{locale:Locale FileAccessLogTooltip}">
<TextBlock Text="{locale:Locale SettingsTabLoggingEnableFsAccessLogs}" />
</CheckBox>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale FSAccessLogModeTooltip}"
Text="{locale:Locale SettingsTabLoggingFsGlobalAccessLogMode}"
Width="285" />
<ui:NumberBox
Maximum="3"
Minimum="0"
Width="150"
SpinButtonPlacementMode="Inline"
SmallChange="1"
LargeChange="1"
Value="{Binding FsGlobalAccessLogMode}" />
</StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{locale:Locale SettingsTabLoggingOpenglLogLevel}"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}"
Width="285" />
<ComboBox SelectedIndex="{Binding OpenglDebugLevel}"
Width="150"
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale OpenGlLogLevel}">
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingOpenglLogLevelNone}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingOpenglLogLevelError}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
Text="{locale:Locale SettingsTabLoggingOpenglLogLevelPerformance}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{locale:Locale SettingsTabLoggingOpenglLogLevelAll}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</Grid>
<ui:NavigationView Grid.Row="1" IsSettingsVisible="False" Name="NavPanel" IsBackEnabled="False"
PaneDisplayMode="LeftCompact"
Margin="2,10,10,0"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem IsSelected="True"
Content="{locale:Locale SettingsTabGeneral}"
Tag="UiPage"
Icon="New" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabInput}"
Tag="InputPage"
Icon="Games" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabHotkeys}"
Tag="HotkeysPage"
Icon="Keyboard" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabSystem}"
Tag="SystemPage"
Icon="Settings" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.Icon>
<ui:FontIcon FontFamily="avares://Ryujinx.Ava/Assets/Fonts#Segoe Fluent Icons"
Glyph="{controls:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
Icon="Image" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabAudio}"
Icon="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabNetwork}"
Tag="NetworkPage"
Icon="Globe" />
<ui:NavigationViewItem
Content="{locale:Locale SettingsTabLogging}"
Tag="LoggingPage"
Icon="Document" />
</ui:NavigationView.MenuItems>
</ui:NavigationView>
<StackPanel
Grid.Row="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="{locale:Locale SettingsButtonSave}" Click="SaveButton_Clicked" />
<Button Content="{locale:Locale SettingsButtonClose}" Click="CloseButton_Clicked" />
<Button Content="{locale:Locale SettingsButtonApply}"
Click="ApplyButton_Clicked" />
</StackPanel>
</Grid>
</window:StyleableWindow>

View File

@ -0,0 +1,298 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows
{
public class SettingsWindow : StyleableWindow
{
private ListBox _gameList;
private TextBox _pathBox;
private AutoCompleteBox _timeZoneBox;
private ControllerSettingsWindow _controllerSettings;
// Pages
private Control _uiPage;
private Control _inputPage;
private Control _hotkeysPage;
private Control _systemPage;
private Control _cpuPage;
private Control _graphicsPage;
private Control _audioPage;
private Control _networkPage;
private Control _loggingPage;
private NavigationView _navPanel;
private ButtonKeyAssigner _currentAssigner;
internal SettingsViewModel ViewModel { get; set; }
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
{
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
DataContext = ViewModel;
InitializeComponent();
AttachDebugDevTools();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()));
MultiBinding tzMultiBinding = new() { Converter = converter };
tzMultiBinding.Bindings.Add(new Binding("UtcDifference"));
tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
_timeZoneBox.ValueMemberBinding = tzMultiBinding;
}
public SettingsWindow()
{
ViewModel = new SettingsViewModel();
DataContext = ViewModel;
InitializeComponent();
AttachDebugDevTools();
}
[Conditional("DEBUG")]
private void AttachDebugDevTools()
{
this.AttachDevTools();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_pathBox = this.FindControl<TextBox>("PathBox");
_gameList = this.FindControl<ListBox>("GameList");
_timeZoneBox = this.FindControl<AutoCompleteBox>("TimeZoneBox");
_controllerSettings = this.FindControl<ControllerSettingsWindow>("ControllerSettings");
_uiPage = this.FindControl<Control>("UiPage");
_inputPage = this.FindControl<Control>("InputPage");
_hotkeysPage = this.FindControl<Control>("HotkeysPage");
_systemPage = this.FindControl<Control>("SystemPage");
_cpuPage = this.FindControl<Control>("CpuPage");
_graphicsPage = this.FindControl<Control>("GraphicsPage");
_audioPage = this.FindControl<Control>("AudioPage");
_networkPage = this.FindControl<Control>("NetworkPage");
_loggingPage = this.FindControl<Control>("LoggingPage");
var pageGrid = this.FindControl<Grid>("Pages");
pageGrid.Children.Clear();
_navPanel = this.FindControl<NavigationView>("NavPanel");
_navPanel.SelectionChanged += NavPanelOnSelectionChanged;
_navPanel.SelectedItem = _navPanel.MenuItems.ElementAt(0);
}
private void Button_Checked(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
FocusManager.Instance.Focus(this, NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
}
private void Button_Unchecked(object sender, RoutedEventArgs e)
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navitem)
{
switch (navitem.Tag.ToString())
{
case "UiPage":
_navPanel.Content = _uiPage;
break;
case "InputPage":
_navPanel.Content = _inputPage;
break;
case "HotkeysPage":
_navPanel.Content = _hotkeysPage;
break;
case "SystemPage":
_navPanel.Content = _systemPage;
break;
case "CpuPage":
_navPanel.Content = _cpuPage;
break;
case "GraphicsPage":
_navPanel.Content = _graphicsPage;
break;
case "AudioPage":
_navPanel.Content = _audioPage;
break;
case "NetworkPage":
_navPanel.Content = _networkPage;
break;
case "LoggingPage":
_navPanel.Content = _loggingPage;
break;
}
}
}
private async void AddButton_OnClick(object sender, RoutedEventArgs e)
{
string path = _pathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{
ViewModel.GameDirectories.Add(path);
}
else
{
path = await new OpenFolderDialog().ShowAsync(this);
if (!string.IsNullOrWhiteSpace(path))
{
ViewModel.GameDirectories.Add(path);
}
}
}
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{
List<string> selected = new(_gameList.SelectedItems.Cast<string>());
foreach (string path in selected)
{
ViewModel.GameDirectories.Remove(path);
}
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
if (e.AddedItems[0] is TimeZone timeZone)
{
e.Handled = true;
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
private void TimeZoneBox_OnTextChanged(object sender, EventArgs e)
{
if (sender is AutoCompleteBox box)
{
if (box.SelectedItem != null && box.SelectedItem is TimeZone timeZone)
{
ViewModel.ValidateAndSetTimeZone(timeZone.Location);
}
}
}
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
Close();
}
private void CloseButton_Clicked(object sender, RoutedEventArgs e)
{
ViewModel.RevertIfNotSaved();
Close();
}
private void ApplyButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void SaveSettings()
{
ViewModel.SaveSettings();
_controllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window)
{
window.ViewModel.LoadApplications();
}
}
protected override void OnClosed(EventArgs e)
{
_controllerSettings.Dispose();
_currentAssigner?.Cancel();
_currentAssigner = null;
base.OnClosed(e);
}
}
}

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (_restartQuery)
{
string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
string ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
string ryuArg = string.Join(" ", Environment.GetCommandLineArgs().AsEnumerable().Skip(1).ToArray());

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Common.Configuration.Hid
{
public struct KeyboardHotkeys
public class KeyboardHotkeys
{
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }

View File

@ -60,6 +60,8 @@ namespace Ryujinx.Graphics.GAL
void SetLogicOpState(bool enable, LogicalOp op);
void SetMultisampleState(MultisampleDescriptor multisample);
void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel);
void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin);

View File

@ -0,0 +1,19 @@
namespace Ryujinx.Graphics.GAL
{
public struct MultisampleDescriptor
{
public bool AlphaToCoverageEnable { get; }
public bool AlphaToCoverageDitherEnable { get; }
public bool AlphaToOneEnable { get; }
public MultisampleDescriptor(
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable,
bool alphaToOneEnable)
{
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
AlphaToOneEnable = alphaToOneEnable;
}
}
}

View File

@ -179,6 +179,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetLogicOpState] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetMultisampleState] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetMultisampleStateCommand.Run(ref GetCommand<SetMultisampleStateCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPatchParameters] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPointParameters] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>

View File

@ -71,6 +71,7 @@
SetIndexBuffer,
SetLineParameters,
SetLogicOpState,
SetMultisampleState,
SetPatchParameters,
SetPointParameters,
SetPolygonMode,

View File

@ -0,0 +1,18 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetMultisampleStateCommand : IGALCommand
{
public CommandType CommandType => CommandType.SetMultisampleState;
private MultisampleDescriptor _multisample;
public void Set(MultisampleDescriptor multisample)
{
_multisample = multisample;
}
public static void Run(ref SetMultisampleStateCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetMultisampleState(command._multisample);
}
}
}

View File

@ -184,6 +184,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
_renderer.New<SetMultisampleStateCommand>().Set(multisample);
_renderer.QueueCommand();
}
public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
{
_renderer.New<SetPatchParametersCommand>().Set(vertices, defaultOuterLevel, defaultInnerLevel);

View File

@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.GAL
{
_renderer = renderer;
Handle = renderer.CreateBuffer(SupportBuffer.RequiredSize);
renderer.Pipeline.ClearBuffer(Handle, 0, SupportBuffer.RequiredSize, 0);
}
private void MarkDirty(int startOffset, int byteSize)

View File

@ -166,7 +166,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.BlendEnable),
nameof(ThreedClassState.BlendState)),
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState))
new StateUpdateCallbackEntry(UpdateLogicOpState, nameof(ThreedClassState.LogicOpState)),
new StateUpdateCallbackEntry(UpdateMultisampleState,
nameof(ThreedClassState.AlphaToCoverageDitherEnable),
nameof(ThreedClassState.MultisampleControl))
});
}
@ -1092,6 +1096,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
}
/// <summary>
/// Updates multisample state, based on guest state.
/// </summary>
private void UpdateMultisampleState()
{
bool alphaToCoverageEnable = (_state.State.MultisampleControl & 1) != 0;
bool alphaToOneEnable = (_state.State.MultisampleControl & 0x10) != 0;
_context.Renderer.Pipeline.SetMultisampleState(new MultisampleDescriptor(
alphaToCoverageEnable,
_state.State.AlphaToCoverageDitherEnable,
alphaToOneEnable));
}
/// <summary>
/// Updates host shaders based on the guest GPU state.
/// </summary>
@ -1231,7 +1249,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_state.State.EarlyZForce,
_drawState.Topology,
_state.State.TessMode,
_state.State.ViewportTransformEnable == 0);
_state.State.ViewportTransformEnable == 0,
(_state.State.MultisampleControl & 1) != 0,
_state.State.AlphaToCoverageDitherEnable);
}
/// <summary>

View File

@ -767,7 +767,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public SamplerIndex SamplerIndex;
public fixed uint Reserved1238[37];
public Boolean32 DepthTestEnable;
public fixed uint Reserved12D0[5];
public fixed uint Reserved12D0[4];
public Boolean32 AlphaToCoverageDitherEnable;
public Boolean32 BlendIndependent;
public Boolean32 DepthWriteEnable;
public Boolean32 AlphaTestEnable;
@ -802,9 +803,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
public Boolean32 PointSpriteEnable;
public fixed uint Reserved1524[3];
public uint ResetCounter;
public uint Reserved1534;
public Boolean32 MultisampleEnable;
public Boolean32 RtDepthStencilEnable;
public fixed uint Reserved153C[5];
public uint MultisampleControl;
public fixed uint Reserved1540[4];
public GpuVa RenderEnableAddress;
public Condition RenderEnableCondition;
public PoolState SamplerPoolState;

View File

@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly GpuChannel _channel;
private readonly TexturePoolCache _texturePoolCache;
private TexturePool _cachedTexturePool;
private SamplerPool _cachedSamplerPool;
private readonly TextureBindingInfo[][] _textureBindings;
private readonly TextureBindingInfo[][] _imageBindings;
@ -343,9 +346,14 @@ namespace Ryujinx.Graphics.Gpu.Image
? _texturePoolCache.FindOrCreate(_channel, texturePoolAddress, _texturePoolMaximumId)
: null;
SamplerPool samplerPool = _samplerPool;
// Check if the texture pool has been modified since bindings were last committed.
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
bool poolModified = false;
bool poolModified = _cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool;
_cachedTexturePool = texturePool;
_cachedSamplerPool = samplerPool;
if (texturePool != null)
{
@ -358,9 +366,9 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (_samplerPool != null)
if (samplerPool != null)
{
int samplerPoolSequence = _samplerPool.CheckModified();
int samplerPoolSequence = samplerPool.CheckModified();
if (_samplerPoolSequence != samplerPoolSequence)
{
@ -784,13 +792,23 @@ namespace Ryujinx.Graphics.Gpu.Image
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
if (handleType != TextureHandleType.CombinedSampler)
{
ulong samplerBufferAddress = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
int samplerHandle;
int samplerHandle = _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4);
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{
ulong samplerBufferAddress = _isCompute
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
if (handleType == TextureHandleType.SeparateSamplerId)
samplerHandle = _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4);
}
else
{
samplerHandle = samplerWordOffset;
}
if (handleType == TextureHandleType.SeparateSamplerId ||
handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
samplerHandle <<= 20;
}

View File

@ -167,6 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce),
topology,
tessMode,
false,
false,
false);
TransformFeedbackDescriptor[] tfdNew = null;

View File

@ -67,6 +67,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
return MemoryMarshal.Cast<byte, ulong>(_data.Span.Slice((int)address));
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _oldSpecState.GraphicsState.AlphaToCoverageEnable && _oldSpecState.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public int QueryBindingConstantBuffer(int index)
{

View File

@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 1;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 1;
private const uint CodeGenVersion = 3069;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -66,6 +66,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
return MemoryMarshal.Cast<byte, ulong>(_channel.MemoryManager.GetSpan(address, size));
}
/// <inheritdoc/>
public bool QueryAlphaToCoverageDitherEnable()
{
return _state.GraphicsState.AlphaToCoverageEnable && _state.GraphicsState.AlphaToCoverageDitherEnable;
}
/// <inheritdoc/>
public int QueryBindingConstantBuffer(int index)
{

View File

@ -30,6 +30,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// </summary>
public readonly bool ViewportTransformDisable;
/// <summary>
/// Indicates whenever alpha-to-coverage is enabled.
/// </summary>
public readonly bool AlphaToCoverageEnable;
/// <summary>
/// Indicates whenever alpha-to-coverage dithering is enabled.
/// </summary>
public readonly bool AlphaToCoverageDitherEnable;
/// <summary>
/// Creates a new GPU graphics state.
/// </summary>
@ -37,12 +47,22 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// <param name="topology">Primitive topology</param>
/// <param name="tessellationMode">Tessellation mode</param>
/// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param>
public GpuChannelGraphicsState(bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, bool viewportTransformDisable)
/// <param name="alphaToCoverageEnable">Indicates whenever alpha-to-coverage is enabled</param>
/// <param name="alphaToCoverageDitherEnable">Indicates whenever alpha-to-coverage dithering is enabled</param>
public GpuChannelGraphicsState(
bool earlyZForce,
PrimitiveTopology topology,
TessMode tessellationMode,
bool viewportTransformDisable,
bool alphaToCoverageEnable,
bool alphaToCoverageDitherEnable)
{
EarlyZForce = earlyZForce;
Topology = topology;
TessellationMode = tessellationMode;
ViewportTransformDisable = viewportTransformDisable;
AlphaToCoverageEnable = alphaToCoverageEnable;
AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable;
}
}
}

View File

@ -455,6 +455,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
return false;
}
bool thisA2cDitherEnable = GraphicsState.AlphaToCoverageEnable && GraphicsState.AlphaToCoverageDitherEnable;
bool otherA2cDitherEnable = graphicsState.AlphaToCoverageEnable && graphicsState.AlphaToCoverageDitherEnable;
if (otherA2cDitherEnable != thisA2cDitherEnable)
{
return false;
}
return Matches(channel, poolState, checkTextures, isCompute: false);
}

View File

@ -5,19 +5,20 @@ namespace Ryujinx.Graphics.OpenGL
{
static class HwCapabilities
{
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
private static readonly Lazy<bool> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsTextureShadowLod = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
private static readonly Lazy<bool> _supportsAlphaToCoverageDitherControl = new Lazy<bool>(() => HasExtension("GL_NV_alpha_to_coverage_dither_control"));
private static readonly Lazy<bool> _supportsAstcCompression = new Lazy<bool>(() => HasExtension("GL_KHR_texture_compression_astc_ldr"));
private static readonly Lazy<bool> _supportsDrawTexture = new Lazy<bool>(() => HasExtension("GL_NV_draw_texture"));
private static readonly Lazy<bool> _supportsFragmentShaderInterlock = new Lazy<bool>(() => HasExtension("GL_ARB_fragment_shader_interlock"));
private static readonly Lazy<bool> _supportsFragmentShaderOrdering = new Lazy<bool>(() => HasExtension("GL_INTEL_fragment_shader_ordering"));
private static readonly Lazy<bool> _supportsImageLoadFormatted = new Lazy<bool>(() => HasExtension("GL_EXT_shader_image_load_formatted"));
private static readonly Lazy<bool> _supportsIndirectParameters = new Lazy<bool>(() => HasExtension("GL_ARB_indirect_parameters"));
private static readonly Lazy<bool> _supportsParallelShaderCompile = new Lazy<bool>(() => HasExtension("GL_ARB_parallel_shader_compile"));
private static readonly Lazy<bool> _supportsPolygonOffsetClamp = new Lazy<bool>(() => HasExtension("GL_EXT_polygon_offset_clamp"));
private static readonly Lazy<bool> _supportsQuads = new Lazy<bool>(SupportsQuadsCheck);
private static readonly Lazy<bool> _supportsSeamlessCubemapPerTexture = new Lazy<bool>(() => HasExtension("GL_ARB_seamless_cubemap_per_texture"));
private static readonly Lazy<bool> _supportsShaderBallot = new Lazy<bool>(() => HasExtension("GL_ARB_shader_ballot"));
private static readonly Lazy<bool> _supportsTextureShadowLod = new Lazy<bool>(() => HasExtension("GL_EXT_texture_shadow_lod"));
private static readonly Lazy<bool> _supportsViewportSwizzle = new Lazy<bool>(() => HasExtension("GL_NV_viewport_swizzle"));
private static readonly Lazy<int> _maximumComputeSharedMemorySize = new Lazy<int>(() => GetLimit(All.MaxComputeSharedMemorySize));
private static readonly Lazy<int> _storageBufferOffsetAlignment = new Lazy<int>(() => GetLimit(All.ShaderStorageBufferOffsetAlignment));
@ -43,19 +44,20 @@ namespace Ryujinx.Graphics.OpenGL
public static bool UsePersistentBufferForFlush => _gpuVendor.Value == GpuVendor.AmdWindows || _gpuVendor.Value == GpuVendor.Nvidia;
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
public static bool SupportsQuads => _supportsQuads.Value;
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value;
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
public static bool SupportsAlphaToCoverageDitherControl => _supportsAlphaToCoverageDitherControl.Value;
public static bool SupportsAstcCompression => _supportsAstcCompression.Value;
public static bool SupportsDrawTexture => _supportsDrawTexture.Value;
public static bool SupportsFragmentShaderInterlock => _supportsFragmentShaderInterlock.Value;
public static bool SupportsFragmentShaderOrdering => _supportsFragmentShaderOrdering.Value;
public static bool SupportsImageLoadFormatted => _supportsImageLoadFormatted.Value;
public static bool SupportsIndirectParameters => _supportsIndirectParameters.Value;
public static bool SupportsParallelShaderCompile => _supportsParallelShaderCompile.Value;
public static bool SupportsPolygonOffsetClamp => _supportsPolygonOffsetClamp.Value;
public static bool SupportsQuads => _supportsQuads.Value;
public static bool SupportsSeamlessCubemapPerTexture => _supportsSeamlessCubemapPerTexture.Value;
public static bool SupportsShaderBallot => _supportsShaderBallot.Value;
public static bool SupportsTextureShadowLod => _supportsTextureShadowLod.Value;
public static bool SupportsViewportSwizzle => _supportsViewportSwizzle.Value;
public static bool SupportsMismatchingViewFormat => _gpuVendor.Value != GpuVendor.AmdWindows && _gpuVendor.Value != GpuVendor.IntelWindows;
public static bool SupportsNonConstantTextureOffset => _gpuVendor.Value == GpuVendor.Nvidia;

View File

@ -918,6 +918,34 @@ namespace Ryujinx.Graphics.OpenGL
}
}
public void SetMultisampleState(MultisampleDescriptor multisample)
{
if (multisample.AlphaToCoverageEnable)
{
GL.Enable(EnableCap.SampleAlphaToCoverage);
if (multisample.AlphaToOneEnable)
{
GL.Enable(EnableCap.SampleAlphaToOne);
}
else
{
GL.Disable(EnableCap.SampleAlphaToOne);
}
if (HwCapabilities.SupportsAlphaToCoverageDitherControl)
{
GL.NV.AlphaToCoverageDitherControl(multisample.AlphaToCoverageDitherEnable
? NvAlphaToCoverageDitherControl.AlphaToCoverageDitherEnableNv
: NvAlphaToCoverageDitherControl.AlphaToCoverageDitherDisableNv);
}
}
else
{
GL.Disable(EnableCap.SampleAlphaToCoverage);
}
}
public void SetLineParameters(float width, bool smooth)
{
if (smooth)

View File

@ -615,7 +615,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
{
bool needsSupportBlock = stage == ShaderStage.Fragment ||
bool needsSupportBlock = stage == ShaderStage.Fragment ||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
if (!needsSupportBlock && scaleElements == 0)

View File

@ -34,6 +34,15 @@ namespace Ryujinx.Graphics.Shader
/// <returns>Span of the memory location</returns>
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
/// <summary>
/// Queries whenever the alpha-to-coverage dithering feature is enabled.
/// </summary>
/// <returns>True if the feature is enabled, false otherwise</returns>
bool QueryAlphaToCoverageDitherEnable()
{
return false;
}
/// <summary>
/// Queries the binding number of a constant buffer.
/// </summary>
@ -237,7 +246,7 @@ namespace Ryujinx.Graphics.Shader
/// <returns>True if the coordinates are normalized, false otherwise</returns>
bool QueryTextureCoordNormalized(int handle, int cbufSlot = -1)
{
return false;
return true;
}
/// <summary>

View File

@ -7,7 +7,8 @@ namespace Ryujinx.Graphics.Shader
{
CombinedSampler = 0, // Must be 0.
SeparateSamplerHandle = 1,
SeparateSamplerId = 2
SeparateSamplerId = 2,
SeparateConstantSamplerHandle = 3
}
public static class TextureHandle
@ -97,9 +98,19 @@ namespace Ryujinx.Graphics.Shader
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
if (handleType != TextureHandleType.CombinedSampler)
{
int samplerHandle = cachedSamplerBuffer[samplerWordOffset];
int samplerHandle;
if (handleType == TextureHandleType.SeparateSamplerId)
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
{
samplerHandle = cachedSamplerBuffer[samplerWordOffset];
}
else
{
samplerHandle = samplerWordOffset;
}
if (handleType == TextureHandleType.SeparateSamplerId ||
handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
samplerHandle <<= 20;
}

View File

@ -205,6 +205,8 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (Config.Stage == ShaderStage.Fragment)
{
GenerateAlphaToCoverageDitherDiscard();
if (Config.OmapDepth)
{
Operand dest = Attribute(AttributeConsts.FragmentOutputDepth);
@ -266,6 +268,35 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
private void GenerateAlphaToCoverageDitherDiscard()
{
// If the feature is disabled, or alpha is not written, then we're done.
if (!Config.GpuAccessor.QueryAlphaToCoverageDitherEnable() || (Config.OmapTargets & 8) == 0)
{
return;
}
// 11 11 11 10 10 10 10 00
// 11 01 01 01 01 00 00 00
Operand ditherMask = Const(unchecked((int)0xfbb99110u));
Operand x = this.BitwiseAnd(this.FP32ConvertToU32(Attribute(AttributeConsts.PositionX)), Const(1));
Operand y = this.BitwiseAnd(this.FP32ConvertToU32(Attribute(AttributeConsts.PositionY)), Const(1));
Operand xy = this.BitwiseOr(x, this.ShiftLeft(y, Const(1)));
Operand alpha = Register(3, RegisterType.Gpr);
Operand scaledAlpha = this.FPMultiply(this.FPSaturate(alpha), ConstF(8));
Operand quantizedAlpha = this.IMinimumU32(this.FP32ConvertToU32(scaledAlpha), Const(7));
Operand shift = this.BitwiseOr(this.ShiftLeft(quantizedAlpha, Const(2)), xy);
Operand opaque = this.BitwiseAnd(this.ShiftRightU32(ditherMask, shift), Const(1));
Operand a2cDitherEndLabel = Label();
this.BranchIfTrue(a2cDitherEndLabel, opaque);
this.Discard();
this.MarkLabel(a2cDitherEndLabel);
}
public Operation[] GetOperations()
{
return _operations.ToArray();

View File

@ -51,16 +51,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand src0 = Utils.FindLastOperation(handleCombineOp.GetSource(0), block);
Operand src1 = Utils.FindLastOperation(handleCombineOp.GetSource(1), block);
// For cases where we have a constant, ensure that the constant is always
// the second operand.
// Since this is a commutative operation, both are fine,
// and having a "canonical" representation simplifies some checks below.
if (src0.Type == OperandType.Constant && src1.Type != OperandType.Constant)
{
Operand temp = src1;
src1 = src0;
src0 = temp;
}
TextureHandleType handleType = TextureHandleType.SeparateSamplerHandle;
// Try to match masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// where samplerHandle and textureHandle comes from a constant buffer, and shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// where samplerId and textureHandle comes from a constant buffer.
// Try to match the following patterns:
// Masked pattern:
// - samplerHandle = samplerHandle & 0xFFF00000;
// - textureHandle = textureHandle & 0xFFFFF;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerHandle and textureHandle comes from a constant buffer.
// Shifted pattern:
// - samplerHandle = samplerId << 20;
// - combinedHandle = samplerHandle | textureHandle;
// Where samplerId and textureHandle comes from a constant buffer.
// Constant pattern:
// - combinedHandle = samplerHandleConstant | textureHandle;
// Where samplerHandleConstant is a constant value, and textureHandle comes from a constant buffer.
if (src0.AsgOp is Operation src0AsgOp)
{
if (src1.AsgOp is Operation src1AsgOp &&
@ -104,18 +120,34 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
handleType = TextureHandleType.SeparateSamplerId;
}
}
else if (src1.Type == OperandType.Constant && (src1.Value & 0xfffff) == 0)
{
handleType = TextureHandleType.SeparateConstantSamplerHandle;
}
if (src0.Type != OperandType.ConstantBuffer || src1.Type != OperandType.ConstantBuffer)
if (src0.Type != OperandType.ConstantBuffer)
{
continue;
}
SetHandle(
config,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType);
if (handleType == TextureHandleType.SeparateConstantSamplerHandle)
{
SetHandle(
config,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType);
}
else if (src1.Type == OperandType.ConstantBuffer)
{
SetHandle(
config,
texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), src1.GetCbufOffset(), handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), src1.GetCbufSlot()),
rewriteSamplerType);
}
}
else if (texOp.Inst == Instruction.ImageLoad ||
texOp.Inst == Instruction.ImageStore ||

View File

@ -217,7 +217,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
// CreateManagedDisplayLayer() -> u64
public ResultCode CreateManagedDisplayLayer(ServiceCtx context)
{
context.Device.System.SurfaceFlinger.CreateLayer(_pid, out long layerId);
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid);
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
context.ResponseData.Write(layerId);
@ -238,8 +238,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
// CreateManagedDisplaySeparableLayer() -> (u64, u64)
public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context)
{
context.Device.System.SurfaceFlinger.CreateLayer(_pid, out long displayLayerId);
context.Device.System.SurfaceFlinger.CreateLayer(_pid, out long recordingLayerId);
context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid);
context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid);
context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId);
context.ResponseData.Write(displayLayerId);

View File

@ -6,13 +6,13 @@ namespace Ryujinx.HLE.HOS.Services.Ptm.Ts
[Service("ts")]
class IMeasurementServer : IpcService
{
private const uint DefaultTemperature = 42000u;
private const uint DefaultTemperature = 42u;
public IMeasurementServer(ServiceCtx context) { }
[CommandHipc(3)]
// GetTemperatureMilliC(Location location) -> u32
public ResultCode GetTemperatureMilliC(ServiceCtx context)
[CommandHipc(1)]
// GetTemperature(Location location) -> u32
public ResultCode GetTemperature(ServiceCtx context)
{
Location location = (Location)context.RequestData.ReadByte();
@ -22,5 +22,18 @@ namespace Ryujinx.HLE.HOS.Services.Ptm.Ts
return ResultCode.Success;
}
[CommandHipc(3)]
// GetTemperatureMilliC(Location location) -> u32
public ResultCode GetTemperatureMilliC(ServiceCtx context)
{
Location location = (Location)context.RequestData.ReadByte();
Logger.Stub?.PrintStub(LogClass.ServicePtm, new { location });
context.ResponseData.Write(DefaultTemperature * 1000);
return ResultCode.Success;
}
}
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
enum LayerState
{
NotInitialized,
ManagedClosed,
ManagedOpened,
Stray
}
}

View File

@ -11,6 +11,8 @@ using System.Threading;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{
using ResultCode = Ryujinx.HLE.HOS.Services.Vi.ResultCode;
class SurfaceFlinger : IConsumerListener, IDisposable
{
private const int TargetFps = 60;
@ -45,6 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
public BufferItemConsumer Consumer;
public BufferQueueCore Core;
public ulong Owner;
public LayerState State;
}
private class TextureCallbackInformation
@ -92,24 +95,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
}
}
public IGraphicBufferProducer OpenLayer(ulong pid, long layerId)
{
bool needCreate;
lock (Lock)
{
needCreate = GetLayerByIdLocked(layerId) == null;
}
if (needCreate)
{
CreateLayerFromId(pid, layerId);
}
return GetProducerByLayerId(layerId);
}
public IGraphicBufferProducer CreateLayer(ulong pid, out long layerId)
public IGraphicBufferProducer CreateLayer(out long layerId, ulong pid, LayerState initialState = LayerState.ManagedClosed)
{
layerId = 1;
@ -124,12 +110,12 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
}
}
CreateLayerFromId(pid, layerId);
CreateLayerFromId(pid, layerId, initialState);
return GetProducerByLayerId(layerId);
}
private void CreateLayerFromId(ulong pid, long layerId)
private void CreateLayerFromId(ulong pid, long layerId, LayerState initialState)
{
lock (Lock)
{
@ -148,39 +134,129 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
Producer = producer,
Consumer = new BufferItemConsumer(_device, consumer, 0, -1, false, this),
Core = core,
Owner = pid
Owner = pid,
State = initialState
});
}
}
public bool CloseLayer(long layerId)
public ResultCode OpenLayer(ulong pid, long layerId, out IBinder producer)
{
Layer layer = GetLayerByIdLocked(layerId);
if (layer == null || layer.State != LayerState.ManagedClosed)
{
producer = null;
return ResultCode.InvalidArguments;
}
layer.State = LayerState.ManagedOpened;
producer = layer.Producer;
return ResultCode.Success;
}
public ResultCode CloseLayer(long layerId)
{
lock (Lock)
{
Layer layer = GetLayerByIdLocked(layerId);
if (layer != null)
if (layer == null)
{
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to close layer {layerId}");
return ResultCode.InvalidValue;
}
bool removed = _layers.Remove(layerId);
CloseLayer(layerId, layer);
// If the layer was removed and the current in use, we need to change the current layer in use.
if (removed && RenderLayerId == layerId)
return ResultCode.Success;
}
}
public ResultCode DestroyManagedLayer(long layerId)
{
lock (Lock)
{
Layer layer = GetLayerByIdLocked(layerId);
if (layer == null)
{
// If no layer is availaible, reset to default value.
if (_layers.Count == 0)
{
SetRenderLayer(0);
}
else
{
SetRenderLayer(_layers.Last().Key);
}
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (not found)");
return ResultCode.InvalidValue;
}
return removed;
if (layer.State != LayerState.ManagedClosed && layer.State != LayerState.ManagedOpened)
{
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy managed layer {layerId} (permission denied)");
return ResultCode.PermissionDenied;
}
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
if (_layers.Remove(layerId) && layer.State == LayerState.ManagedOpened)
{
CloseLayer(layerId, layer);
}
return ResultCode.Success;
}
}
public ResultCode DestroyStrayLayer(long layerId)
{
lock (Lock)
{
Layer layer = GetLayerByIdLocked(layerId);
if (layer == null)
{
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (not found)");
return ResultCode.InvalidValue;
}
if (layer.State != LayerState.Stray)
{
Logger.Error?.Print(LogClass.SurfaceFlinger, $"Failed to destroy stray layer {layerId} (permission denied)");
return ResultCode.PermissionDenied;
}
HOSBinderDriverServer.UnregisterBinderObject(layer.ProducerBinderId);
if (_layers.Remove(layerId))
{
CloseLayer(layerId, layer);
}
return ResultCode.Success;
}
}
private void CloseLayer(long layerId, Layer layer)
{
// If the layer was removed and the current in use, we need to change the current layer in use.
if (RenderLayerId == layerId)
{
// If no layer is availaible, reset to default value.
if (_layers.Count == 0)
{
SetRenderLayer(0);
}
else
{
SetRenderLayer(_layers.Last().Key);
}
}
if (layer.State == LayerState.ManagedOpened)
{
layer.State = LayerState.ManagedClosed;
}
}

View File

@ -15,7 +15,7 @@ using Ryujinx.HLE.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using TimeZoneRuleBox = Ryujinx.Common.Memory.Box<Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule>;
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
@ -181,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..];
int abbrEnd = abbrStart.IndexOf((byte)0);
outList.Add((ttInfo.GmtOffset, locName, abbrStart[..abbrEnd].ToString()));
outList.Add((ttInfo.GmtOffset, locName, Encoding.UTF8.GetString(abbrStart[..abbrEnd])));
}
}

View File

@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
ulong pid = context.Device.System.AppletState.AppletResourceUserIds.GetData<ulong>((int)appletResourceUserId);
context.Device.System.SurfaceFlinger.CreateLayer(pid, out long layerId);
context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, pid);
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
context.ResponseData.Write(layerId);
@ -49,9 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
{
long layerId = context.RequestData.ReadInt64();
context.Device.System.SurfaceFlinger.CloseLayer(layerId);
return ResultCode.Success;
return context.Device.System.SurfaceFlinger.DestroyManagedLayer(layerId);
}
[CommandHipc(2012)] // 7.0.0+

View File

@ -237,7 +237,12 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
long userId = context.RequestData.ReadInt64();
ulong parcelPtr = context.Request.ReceiveBuff[0].Position;
IBinder producer = context.Device.System.SurfaceFlinger.OpenLayer(context.Request.HandleDesc.PId, layerId);
ResultCode result = context.Device.System.SurfaceFlinger.OpenLayer(context.Request.HandleDesc.PId, layerId, out IBinder producer);
if (result != ResultCode.Success)
{
return result;
}
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
@ -260,9 +265,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
long layerId = context.RequestData.ReadInt64();
context.Device.System.SurfaceFlinger.CloseLayer(layerId);
return ResultCode.Success;
return context.Device.System.SurfaceFlinger.CloseLayer(layerId);
}
[CommandHipc(2030)]
@ -275,7 +278,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
ulong parcelPtr = context.Request.ReceiveBuff[0].Position;
// TODO: support multi display.
IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(0, out long layerId);
IBinder producer = context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, 0, LayerState.Stray);
context.Device.System.SurfaceFlinger.SetRenderLayer(layerId);
@ -299,9 +302,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
{
long layerId = context.RequestData.ReadInt64();
context.Device.System.SurfaceFlinger.CloseLayer(layerId);
return ResultCode.Success;
return context.Device.System.SurfaceFlinger.DestroyStrayLayer(layerId);
}
[CommandHipc(2101)]

Some files were not shown because too many files have changed in this diff Show More