mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-08 12:09:06 +00:00
Ayyyy, welcome to the UI: Actions Menu → Amiibo & Skylanders PR!
WOOOOO!!!!
This is the third PR in a series aimed at delivering the largest overhaul and improvements to the `Actions` menu yet.
This PR introduces visual and functionality improvements to the two `Scan Amiibo` options, as well as improving `Scan Skylanders`.
### GENERAL
* **Renamed:** Open Mii Applet → Open Mii Editor in `MainMenuBarView.axaml.cs`
### LOCALISATION
* **Fractured:** More locales
* `Amiibo.json` - all UI-related locales to the `Scan Amiibo` window
* `Common_Buttons.json` - all locales related to buttons
* `Common_FilePicker.json` - all locales related to the file picker that are implemented in many places
* `Dialog_Amiibo.json` - all Amiibo dialogs
* `Dialog_Skylanders.json` - all Skylander dialogs
* **Removed:** Some weird duplicated locales (they appeared after some sort of rebase, but they were few)
**NOTE:** `Common_Buttons.json` and `Common_FilePicker.json` were not fully populated, but many more locales will be added and cleaned up in future PRs.
### SCAN AMIIBO (WINDOW)
* **Improved:** The UI
* The UI now more cleanly utilises both Normal and Flush Controls.
* All buttons have been moved to the top row and features a whole lot more updates (read below).
* **Reworded:** `Hack: Random UUID Tag` → `Use Random UUID`
* The option is inherently already a hack/workaround, therefore, having "Hack" is not necessary. It is also implied that it's a tag.
* **Implemented:** [Feature Request #271](https://github.com/Ryubing/Issues/issues/271)
* The feature request is for the ability to pause emulation while navigating the `Scan Amiibo` window (so as to reduce/remove timeouts) and for an Amiibo "name" sorting feature. You asked, we deliver!
* **Pause Emulation:**
* The top row now features a "setting" button dropdown, which houses `Use Random UUID` and `Pause Emulation While Scanning`
* Note: Both options were moved to a button dropdown so as to not have to increase window size by a 1000x
* `Pause Emulation While Scanning` is a dynamic, real-time implementation.
* By default, the checkbox is not checked.
* When it's checked, emulation _instantly_ pauses for the duration while the window is open. Pressing "Scan Amiibo" or "Cancel" resumes emulation. Reopening the window pauses the emulation instantly as well.
* This setting persists both while in-game and upon restarting Ryujinx, so users don't have to constantly go back and check the option again.
* Additionally, this feature has been propagated to `Scan Amiibo (BIN)`. Perhaps some users would also want emulation to pause while scanning via BIN, so if the checkbox is checked, it'll pause emulation when the file picker is open as well.
* **Sorting & Search:**
* Additionally, implemented a simple "sort by name", in ascending and descending order.
* Added a search bar (related to the change below) to help with searching through the long Amiibo list.
* Search is semi-alphabetical, semi-series based. If I want to search for Samus Amiibo, then if I type "Samus", it'll first show Dark Samus, as it appears in a series earlier in the list. This behaviour can be modified to just be alphabetical, but it is also more categorisation.
* **Modified & Renamed:** `Show Incompatible Amiibo` → `Show All Amiibo`
* Pressing `Show Incompatible Amiibo` would display all Amiibo available, so the option was renamed.
* The option now forces all Amiibo to appear in a list, without a preselected series. Unchecked the option restores original behaviour by breaking down Amiibo by their series (and only those directly compatible with the game).
* **Removed:** Series label
* It's clear that it's an Amiibo series.
* **Fixed:** Amiibo Usage visual structure
* When an Amiibo had multiple in-game usages (multiple purposes), they would all be squished together with no spaces in between them, reducing readability.
* Now, they are all separated and given an extra line between each usage.
* Additionally, fixed "Unknown" text position as well by giving it another extra empty line.
* **Renamed:** Writeable to Usage (Can Save Data)
* Usage and Usage (Can Save Data) are separate strings because some languages must add space before or after the colon
* **Fixed:** Sometimes incorrect Amiibo images could be shown when pressing `Show Incompatible Amiibo`/`Show All Amiibo` in certain instances.
* Sometimes = pressing the checkbox 10 or 20...maybe 30 times in a row (I pressed it a lot!)
* **Reworded:** Cabinet Dialog strings
* Cabinet Dialog is the dialog that appears when you want to rename your Amiibo. Previously, the options didn't reflect the actual window and what they were asking the user, but now clearly tell the user that they are renaming their Amiibo, and when the dialog prompts the user to scan their amiibo again.
### SCAN AMIIBO (BIN)
* **Renamed:** `Scan Amiibo (.BIN)` to `Scan Amiibo (BIN)` to be similar to `Install Keys` > `KEYS` and similar menu items
* **Adjusted:** File picker title when loading Amiibo from BIN to...literally tell the user what to select and what will happen (similar to `Install Firmware` and `Install Keys` file picker titles)
### SCAN/REMOVE SKYLANDER
* **Merged:** `Scan Skylander` and `Remove Skylander`
* Instead of having two separate buttons, let's have one button that actively updates state. Instead of using two shortcuts, it now uses one, and the label updates based on whether the game is asking to `Scan Skylander` or whether you can `Remove Skylander`.
* **Fixed:** `Scan Skylander`/`Remove Skylander` were not visible properly
* Initial implementation only displayed the menu options/buttons when a particular Skylander Imaginators game was being played (i.e. specific ID)
* While there is only one Skylander game on the Switch, the game has been distributed in multiple regions under different game IDs and has different languages. People who owned Skylander Imaginators with a 0100ccc0002e6000 ID could use this feature and _nobody else._
* This has now been fixed by allowing those options to be visible at all times, regardless of the game
* For Skylander games, this is not an issue because the backend implementation for scanning/removing a skylander is the same (tested)
* For other games, then it doesn't matter since the feature is not used, but it will let users know the feature exists, in case they wished to previously play the game, but couldn't due to the missing backend.
* **Renamed:** Skylander functions and methods
* You don't open a window (like Scan Amiibo) but you open a file picker.
* **Fixed:** Inconsistent button state and shortcut update
* This was a bug in the previous implementation as well - you couldn't scan/remove skylander with the keyboard shortcut unless you opened the `Actions` menu (and only then would the shortcut register). This has been fixed.
As such...Skylanders Imaginators are now **Playable**! Updated the entry and added another game ID.
_If there are any features or changes that you wish to be implemented, please comment down below and I'll be happy to accommodate!_
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/158
993 lines
41 KiB
C#
993 lines
41 KiB
C#
using ARMeilleure;
|
|
using Gommon;
|
|
using LibHac.Tools.FsSystem;
|
|
using Ryujinx.Ava.Systems.Configuration.System;
|
|
using Ryujinx.Ava.Systems.Configuration.UI;
|
|
using Ryujinx.Common;
|
|
using Ryujinx.Common.Configuration;
|
|
using Ryujinx.Common.Configuration.Hid;
|
|
using Ryujinx.Common.Configuration.Multiplayer;
|
|
using Ryujinx.Common.Helper;
|
|
using Ryujinx.Common.Logging;
|
|
using Ryujinx.Common.Utilities;
|
|
using Ryujinx.HLE;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
|
|
|
namespace Ryujinx.Ava.Systems.Configuration
|
|
{
|
|
public partial class ConfigurationState
|
|
{
|
|
/// <summary>
|
|
/// UI configuration section
|
|
/// </summary>
|
|
public class UISection
|
|
{
|
|
public class Columns
|
|
{
|
|
public ReactiveObject<bool> FavColumn { get; private set; }
|
|
public ReactiveObject<bool> IconColumn { get; private set; }
|
|
public ReactiveObject<bool> AppColumn { get; private set; }
|
|
public ReactiveObject<bool> DevColumn { get; private set; }
|
|
public ReactiveObject<bool> VersionColumn { get; private set; }
|
|
public ReactiveObject<bool> LdnInfoColumn { get; private set; }
|
|
public ReactiveObject<bool> TimePlayedColumn { get; private set; }
|
|
public ReactiveObject<bool> LastPlayedColumn { get; private set; }
|
|
public ReactiveObject<bool> FileExtColumn { get; private set; }
|
|
public ReactiveObject<bool> FileSizeColumn { get; private set; }
|
|
public ReactiveObject<bool> PathColumn { get; private set; }
|
|
|
|
public Columns()
|
|
{
|
|
FavColumn = new ReactiveObject<bool>();
|
|
IconColumn = new ReactiveObject<bool>();
|
|
AppColumn = new ReactiveObject<bool>();
|
|
DevColumn = new ReactiveObject<bool>();
|
|
VersionColumn = new ReactiveObject<bool>();
|
|
LdnInfoColumn = new ReactiveObject<bool>();
|
|
TimePlayedColumn = new ReactiveObject<bool>();
|
|
LastPlayedColumn = new ReactiveObject<bool>();
|
|
FileExtColumn = new ReactiveObject<bool>();
|
|
FileSizeColumn = new ReactiveObject<bool>();
|
|
PathColumn = new ReactiveObject<bool>();
|
|
}
|
|
}
|
|
|
|
public class ColumnSortSettings
|
|
{
|
|
public ReactiveObject<int> SortColumnId { get; private set; }
|
|
public ReactiveObject<bool> SortAscending { get; private set; }
|
|
|
|
public ColumnSortSettings()
|
|
{
|
|
SortColumnId = new ReactiveObject<int>();
|
|
SortAscending = new ReactiveObject<bool>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used to toggle which file types are shown in the UI
|
|
/// </summary>
|
|
public class ShownFileTypeSettings
|
|
{
|
|
public ReactiveObject<bool> NSP { get; private set; }
|
|
public ReactiveObject<bool> PFS0 { get; private set; }
|
|
public ReactiveObject<bool> XCI { get; private set; }
|
|
public ReactiveObject<bool> NCA { get; private set; }
|
|
public ReactiveObject<bool> NRO { get; private set; }
|
|
public ReactiveObject<bool> NSO { get; private set; }
|
|
|
|
public ShownFileTypeSettings()
|
|
{
|
|
NSP = new ReactiveObject<bool>();
|
|
PFS0 = new ReactiveObject<bool>();
|
|
XCI = new ReactiveObject<bool>();
|
|
NCA = new ReactiveObject<bool>();
|
|
NRO = new ReactiveObject<bool>();
|
|
NSO = new ReactiveObject<bool>();
|
|
}
|
|
}
|
|
|
|
// <summary>
|
|
/// Determines main window start-up position, size and state
|
|
///<summary>
|
|
public class WindowStartupSettings
|
|
{
|
|
public ReactiveObject<int> WindowSizeWidth { get; private set; }
|
|
public ReactiveObject<int> WindowSizeHeight { get; private set; }
|
|
public ReactiveObject<int> WindowPositionX { get; private set; }
|
|
public ReactiveObject<int> WindowPositionY { get; private set; }
|
|
public ReactiveObject<bool> WindowMaximized { get; private set; }
|
|
|
|
public WindowStartupSettings()
|
|
{
|
|
WindowSizeWidth = new ReactiveObject<int>();
|
|
WindowSizeHeight = new ReactiveObject<int>();
|
|
WindowPositionX = new ReactiveObject<int>();
|
|
WindowPositionY = new ReactiveObject<int>();
|
|
WindowMaximized = new ReactiveObject<bool>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used to toggle columns in the GUI
|
|
/// </summary>
|
|
public Columns GuiColumns { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Used to configure column sort settings in the GUI
|
|
/// </summary>
|
|
public ColumnSortSettings ColumnSort { get; private set; }
|
|
|
|
/// <summary>
|
|
/// A list of directories containing games to be used to load games into the games list
|
|
/// </summary>
|
|
public ReactiveObject<List<string>> GameDirs { get; private set; }
|
|
|
|
/// <summary>
|
|
/// A list of directories containing DLC/updates the user wants to autoload during library refreshes
|
|
/// </summary>
|
|
public ReactiveObject<List<string>> AutoloadDirs { get; private set; }
|
|
|
|
/// <summary>
|
|
/// A list of file types to be hidden in the games List
|
|
/// </summary>
|
|
public ShownFileTypeSettings ShownFileTypes { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Determines main window start-up position, size and state
|
|
/// </summary>
|
|
public WindowStartupSettings WindowStartup { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Language Code for the UI
|
|
/// </summary>
|
|
public ReactiveObject<string> LanguageCode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Selects the base style
|
|
/// </summary>
|
|
public ReactiveObject<string> BaseStyle { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Start games in fullscreen mode
|
|
/// </summary>
|
|
public ReactiveObject<bool> StartFullscreen { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Start games with UI hidden
|
|
/// </summary>
|
|
public ReactiveObject<bool> StartNoUI { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Hide / Show Console Window
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowConsole { get; private set; }
|
|
|
|
/// <summary>
|
|
/// View Mode of the Game list
|
|
/// </summary>
|
|
public ReactiveObject<int> GameListViewMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Show application name in Grid Mode
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowNames { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Sets App Icon Size in Grid Mode
|
|
/// </summary>
|
|
public ReactiveObject<int> GridSize { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Sorts Apps in Grid Mode
|
|
/// </summary>
|
|
public ReactiveObject<int> ApplicationSort { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Sets if Grid is ordered in Ascending Order
|
|
/// </summary>
|
|
public ReactiveObject<bool> IsAscendingOrder { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Show Dynamic Input Swap first-use warning
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowDynamicInputSwapWarning { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Pause emulation while scanning Amiibo
|
|
/// </summary>
|
|
public ReactiveObject<bool> PauseEmulationWhileScanningAmiibo { get; private set; }
|
|
|
|
public UISection()
|
|
{
|
|
GuiColumns = new Columns();
|
|
ColumnSort = new ColumnSortSettings();
|
|
GameDirs = new ReactiveObject<List<string>>();
|
|
AutoloadDirs = new ReactiveObject<List<string>>();
|
|
ShownFileTypes = new ShownFileTypeSettings();
|
|
WindowStartup = new WindowStartupSettings();
|
|
BaseStyle = new ReactiveObject<string>();
|
|
StartFullscreen = new ReactiveObject<bool>();
|
|
StartNoUI = new ReactiveObject<bool>();
|
|
GameListViewMode = new ReactiveObject<int>();
|
|
ShowNames = new ReactiveObject<bool>();
|
|
GridSize = new ReactiveObject<int>();
|
|
ApplicationSort = new ReactiveObject<int>();
|
|
IsAscendingOrder = new ReactiveObject<bool>();
|
|
LanguageCode = new ReactiveObject<string>();
|
|
ShowConsole = new ReactiveObject<bool>();
|
|
ShowConsole.Event += static (_, e) => ConsoleHelper.SetConsoleWindowState(e.NewValue);
|
|
ShowDynamicInputSwapWarning = new ReactiveObject<bool>();
|
|
ShowDynamicInputSwapWarning.Value = true;
|
|
PauseEmulationWhileScanningAmiibo = new ReactiveObject<bool>();
|
|
PauseEmulationWhileScanningAmiibo.Value = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logger configuration section
|
|
/// </summary>
|
|
public class LoggerSection
|
|
{
|
|
/// <summary>
|
|
/// Enables printing debug log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableDebug { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing stub log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableStub { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing info log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableInfo { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing warning log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableWarn { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing error log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableError { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing trace log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableTrace { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing guest log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableGuest { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing FS access log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables printing network log messages
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableNetLog { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables log messages from Avalonia
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableAvaloniaLog { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Controls which log messages are written to the log targets
|
|
/// </summary>
|
|
public ReactiveObject<LogClass[]> FilteredClasses { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables logging to a file on disk
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableFileLog { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Controls which OpenGL log messages are recorded in the log
|
|
/// </summary>
|
|
public ReactiveObject<GraphicsDebugLevel> GraphicsDebugLevel { get; private set; }
|
|
|
|
public LoggerSection()
|
|
{
|
|
EnableDebug = new ReactiveObject<bool>();
|
|
EnableDebug.LogChangesToValue(nameof(EnableDebug));
|
|
EnableStub = new ReactiveObject<bool>();
|
|
EnableInfo = new ReactiveObject<bool>();
|
|
EnableWarn = new ReactiveObject<bool>();
|
|
EnableError = new ReactiveObject<bool>();
|
|
EnableTrace = new ReactiveObject<bool>();
|
|
EnableGuest = new ReactiveObject<bool>();
|
|
EnableFsAccessLog = new ReactiveObject<bool>();
|
|
EnableNetLog = new ReactiveObject<bool>();
|
|
EnableAvaloniaLog = new ReactiveObject<bool>();
|
|
FilteredClasses = new ReactiveObject<LogClass[]>();
|
|
EnableFileLog = new ReactiveObject<bool>();
|
|
EnableFileLog.LogChangesToValue(nameof(EnableFileLog));
|
|
GraphicsDebugLevel = new ReactiveObject<GraphicsDebugLevel>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// System configuration section
|
|
/// </summary>
|
|
public class SystemSection
|
|
{
|
|
/// <summary>
|
|
/// Change System Language
|
|
/// </summary>
|
|
public ReactiveObject<Language> Language { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Change System Region
|
|
/// </summary>
|
|
public ReactiveObject<Region> Region { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Change System TimeZone
|
|
/// </summary>
|
|
public ReactiveObject<string> TimeZone { get; private set; }
|
|
|
|
/// <summary>
|
|
/// System Time Offset in Seconds
|
|
/// </summary>
|
|
public ReactiveObject<long> SystemTimeOffset { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Instead of setting the time via configuration, use the values provided by the system.
|
|
/// </summary>
|
|
public ReactiveObject<bool> MatchSystemTime { get; private set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Enable or disable use global input config (Independent from controllers binding)
|
|
/// </summary>
|
|
public ReactiveObject<bool> UseInputGlobalConfig { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables Docked Mode
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableDockedMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables persistent profiled translation cache
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnablePtc { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Clock tick scalar, in percent points (100 = 1.0).
|
|
/// </summary>
|
|
public ReactiveObject<long> TickScalar { get; set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables low-power persistent profiled translation cache loading
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableLowPowerPtc { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables guest Internet access
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableInternetAccess { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables integrity checks on Game content files
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableFsIntegrityChecks { get; private set; }
|
|
|
|
public IntegrityCheckLevel IntegrityCheckLevel => EnableFsIntegrityChecks
|
|
? IntegrityCheckLevel.ErrorOnInvalid
|
|
: IntegrityCheckLevel.None;
|
|
|
|
/// <summary>
|
|
/// Enables FS access log output to the console. Possible modes are 0-3
|
|
/// </summary>
|
|
public ReactiveObject<int> FsGlobalAccessLogMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The selected audio backend
|
|
/// </summary>
|
|
public ReactiveObject<AudioBackend> AudioBackend { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The audio backend volume
|
|
/// </summary>
|
|
public ReactiveObject<float> AudioVolume { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The selected memory manager mode
|
|
/// </summary>
|
|
public ReactiveObject<MemoryManagerMode> MemoryManagerMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Defines the amount of RAM available on the emulated system, and how it is distributed
|
|
/// </summary>
|
|
public ReactiveObject<MemoryConfiguration> DramSize { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enable or disable ignoring missing services
|
|
/// </summary>
|
|
public ReactiveObject<bool> IgnoreMissingServices { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Ignore Controller Applet
|
|
/// </summary>
|
|
public ReactiveObject<bool> IgnoreControllerApplet { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Skip User Profiles Manager
|
|
/// </summary>
|
|
public ReactiveObject<bool> SkipUserProfilesManager { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Uses Hypervisor over JIT if available
|
|
/// </summary>
|
|
public ReactiveObject<bool> UseHypervisor { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enable or disable low-latency garbage collection
|
|
/// </summary>
|
|
public ReactiveObject<bool> GCLowLatency { get; private set; }
|
|
|
|
public SystemSection()
|
|
{
|
|
Language = new ReactiveObject<Language>();
|
|
Language.LogChangesToValue(nameof(Language));
|
|
Region = new ReactiveObject<Region>();
|
|
Region.LogChangesToValue(nameof(Region));
|
|
TimeZone = new ReactiveObject<string>();
|
|
TimeZone.LogChangesToValue(nameof(TimeZone));
|
|
SystemTimeOffset = new ReactiveObject<long>();
|
|
SystemTimeOffset.LogChangesToValue(nameof(SystemTimeOffset));
|
|
MatchSystemTime = new ReactiveObject<bool>();
|
|
MatchSystemTime.LogChangesToValue(nameof(MatchSystemTime));
|
|
UseInputGlobalConfig = new ReactiveObject<bool>();
|
|
UseInputGlobalConfig.LogChangesToValue(nameof(UseInputGlobalConfig));
|
|
EnableDockedMode = new ReactiveObject<bool>();
|
|
EnableDockedMode.LogChangesToValue(nameof(EnableDockedMode));
|
|
EnablePtc = new ReactiveObject<bool>();
|
|
EnablePtc.LogChangesToValue(nameof(EnablePtc));
|
|
EnableLowPowerPtc = new ReactiveObject<bool>();
|
|
EnableLowPowerPtc.LogChangesToValue(nameof(EnableLowPowerPtc));
|
|
EnableLowPowerPtc.Event += (_, evnt)
|
|
=> Optimizations.LowPower = evnt.NewValue;
|
|
TickScalar = new ReactiveObject<long>();
|
|
TickScalar.LogChangesToValue(nameof(TickScalar));
|
|
TickScalar.Event += (_, evnt) =>
|
|
{
|
|
if (Switch.Shared is null)
|
|
return;
|
|
|
|
Switch.Shared.Configuration.TickScalar = evnt.NewValue;
|
|
};
|
|
EnableInternetAccess = new ReactiveObject<bool>();
|
|
EnableInternetAccess.LogChangesToValue(nameof(EnableInternetAccess));
|
|
EnableFsIntegrityChecks = new ReactiveObject<bool>();
|
|
EnableFsIntegrityChecks.LogChangesToValue(nameof(EnableFsIntegrityChecks));
|
|
FsGlobalAccessLogMode = new ReactiveObject<int>();
|
|
FsGlobalAccessLogMode.LogChangesToValue(nameof(FsGlobalAccessLogMode));
|
|
AudioBackend = new ReactiveObject<AudioBackend>();
|
|
AudioBackend.LogChangesToValue(nameof(AudioBackend));
|
|
MemoryManagerMode = new ReactiveObject<MemoryManagerMode>();
|
|
MemoryManagerMode.LogChangesToValue(nameof(MemoryManagerMode));
|
|
DramSize = new ReactiveObject<MemoryConfiguration>();
|
|
DramSize.LogChangesToValue(nameof(DramSize));
|
|
IgnoreMissingServices = new ReactiveObject<bool>();
|
|
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
|
IgnoreControllerApplet = new ReactiveObject<bool>();
|
|
IgnoreControllerApplet.LogChangesToValue(nameof(IgnoreControllerApplet));
|
|
SkipUserProfilesManager = new ReactiveObject<bool>();
|
|
SkipUserProfilesManager.LogChangesToValue(nameof(SkipUserProfilesManager));
|
|
AudioVolume = new ReactiveObject<float>();
|
|
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
|
UseHypervisor = new ReactiveObject<bool>();
|
|
UseHypervisor.LogChangesToValue(nameof(UseHypervisor));
|
|
GCLowLatency = new ReactiveObject<bool>();
|
|
GCLowLatency.LogChangesToValue(nameof(GCLowLatency));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hid configuration section
|
|
/// </summary>
|
|
public class HidSection
|
|
{
|
|
/// <summary>
|
|
/// Enable or disable keyboard support (Independent from controllers binding)
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableKeyboard { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enable or disable mouse support (Independent from controllers binding)
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableMouse { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
|
|
/// </summary>
|
|
public ReactiveObject<bool> DisableInputWhenOutOfFocus { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Hotkey Keyboard Bindings
|
|
/// </summary>
|
|
public ReactiveObject<KeyboardHotkeys> Hotkeys { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Input device configuration.
|
|
/// NOTE: This ReactiveObject won't issue an event when the List has elements added or removed.
|
|
/// TODO: Implement a ReactiveList class.
|
|
/// </summary>
|
|
public ReactiveObject<List<InputConfig>> InputConfig { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Player-level input routing assignments.
|
|
/// NOTE: This keeps dynamic input swap and multi-device ownership attached to the player,
|
|
/// not to the currently edited keyboard/controller profile.
|
|
/// </summary>
|
|
public ReactiveObject<List<PlayerInputAssignment>> PlayerInputAssignments { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Whether to allow mapping the same input device to multiple players.
|
|
/// This is a global setting shared across all players.
|
|
/// </summary>
|
|
public ReactiveObject<bool> AllowDuplicateDeviceAssignment { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The speed of spectrum cycling for the Rainbow LED feature.
|
|
/// </summary>
|
|
public ReactiveObject<float> RainbowSpeed { get; }
|
|
|
|
public HidSection()
|
|
{
|
|
EnableKeyboard = new ReactiveObject<bool>();
|
|
EnableMouse = new ReactiveObject<bool>();
|
|
DisableInputWhenOutOfFocus = new ReactiveObject<bool>();
|
|
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
|
InputConfig = new ReactiveObject<List<InputConfig>>();
|
|
PlayerInputAssignments = new ReactiveObject<List<PlayerInputAssignment>>();
|
|
AllowDuplicateDeviceAssignment = new ReactiveObject<bool>();
|
|
RainbowSpeed = new ReactiveObject<float>();
|
|
RainbowSpeed.Event += (_, args) => Rainbow.Speed = args.NewValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Graphics configuration section
|
|
/// </summary>
|
|
public class GraphicsSection
|
|
{
|
|
/// <summary>
|
|
/// Whether or not backend threading is enabled. The "Auto" setting will determine whether threading should be enabled at runtime.
|
|
/// </summary>
|
|
public ReactiveObject<BackendThreading> BackendThreading { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
|
|
/// </summary>
|
|
public ReactiveObject<float> MaxAnisotropy { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Aspect Ratio applied to the renderer window.
|
|
/// </summary>
|
|
public ReactiveObject<AspectRatio> AspectRatio { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Resolution Scale. An integer scale applied to applicable render targets. Values 1-4, or -1 to use a custom floating point scale instead.
|
|
/// </summary>
|
|
public ReactiveObject<int> ResScale { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Custom Resolution Scale. A custom floating point scale applied to applicable render targets. Only active when Resolution Scale is -1.
|
|
/// </summary>
|
|
public ReactiveObject<float> ResScaleCustom { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Dumps shaders in this local directory
|
|
/// </summary>
|
|
public ReactiveObject<string> ShadersDumpPath { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Toggles the present interval mode. Options are Switch (60Hz), Unbounded (previously Vsync off), and Custom, if enabled.
|
|
/// </summary>
|
|
public ReactiveObject<VSyncMode> VSyncMode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables the custom present interval mode.
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableCustomVSyncInterval { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Changes the custom present interval.
|
|
/// </summary>
|
|
public ReactiveObject<int> CustomVSyncInterval { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables Shader cache
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableShaderCache { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables texture recompression
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableTextureRecompression { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables Macro high-level emulation
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableMacroHLE { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables color space passthrough, if available.
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableColorSpacePassthrough { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Graphics backend
|
|
/// </summary>
|
|
public ReactiveObject<GraphicsBackend> GraphicsBackend { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Applies anti-aliasing to the renderer.
|
|
/// </summary>
|
|
public ReactiveObject<AntiAliasing> AntiAliasing { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Sets the framebuffer upscaling type.
|
|
/// </summary>
|
|
public ReactiveObject<ScalingFilter> ScalingFilter { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Sets the framebuffer upscaling level.
|
|
/// </summary>
|
|
public ReactiveObject<int> ScalingFilterLevel { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Preferred GPU
|
|
/// </summary>
|
|
public ReactiveObject<string> PreferredGpu { get; private set; }
|
|
|
|
public GraphicsSection()
|
|
{
|
|
BackendThreading = new ReactiveObject<BackendThreading>();
|
|
BackendThreading.LogChangesToValue(nameof(BackendThreading));
|
|
ResScale = new ReactiveObject<int>();
|
|
ResScale.LogChangesToValue(nameof(ResScale));
|
|
ResScaleCustom = new ReactiveObject<float>();
|
|
ResScaleCustom.LogChangesToValue(nameof(ResScaleCustom));
|
|
MaxAnisotropy = new ReactiveObject<float>();
|
|
MaxAnisotropy.LogChangesToValue(nameof(MaxAnisotropy));
|
|
AspectRatio = new ReactiveObject<AspectRatio>();
|
|
AspectRatio.LogChangesToValue(nameof(AspectRatio));
|
|
ShadersDumpPath = new ReactiveObject<string>();
|
|
VSyncMode = new ReactiveObject<VSyncMode>();
|
|
VSyncMode.LogChangesToValue(nameof(VSyncMode));
|
|
EnableCustomVSyncInterval = new ReactiveObject<bool>();
|
|
EnableCustomVSyncInterval.LogChangesToValue(nameof(EnableCustomVSyncInterval));
|
|
CustomVSyncInterval = new ReactiveObject<int>();
|
|
CustomVSyncInterval.LogChangesToValue(nameof(CustomVSyncInterval));
|
|
EnableShaderCache = new ReactiveObject<bool>();
|
|
EnableShaderCache.LogChangesToValue(nameof(EnableShaderCache));
|
|
EnableTextureRecompression = new ReactiveObject<bool>();
|
|
EnableTextureRecompression.LogChangesToValue(nameof(EnableTextureRecompression));
|
|
GraphicsBackend = new ReactiveObject<GraphicsBackend>();
|
|
GraphicsBackend.LogChangesToValue(nameof(GraphicsBackend));
|
|
PreferredGpu = new ReactiveObject<string>();
|
|
PreferredGpu.LogChangesToValue(nameof(PreferredGpu));
|
|
EnableMacroHLE = new ReactiveObject<bool>();
|
|
EnableMacroHLE.LogChangesToValue(nameof(EnableMacroHLE));
|
|
EnableColorSpacePassthrough = new ReactiveObject<bool>();
|
|
EnableColorSpacePassthrough.LogChangesToValue(nameof(EnableColorSpacePassthrough));
|
|
AntiAliasing = new ReactiveObject<AntiAliasing>();
|
|
AntiAliasing.LogChangesToValue(nameof(AntiAliasing));
|
|
ScalingFilter = new ReactiveObject<ScalingFilter>();
|
|
ScalingFilter.LogChangesToValue(nameof(ScalingFilter));
|
|
ScalingFilterLevel = new ReactiveObject<int>();
|
|
ScalingFilterLevel.LogChangesToValue(nameof(ScalingFilterLevel));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Multiplayer configuration section
|
|
/// </summary>
|
|
public class MultiplayerSection
|
|
{
|
|
/// <summary>
|
|
/// GUID for the network interface used by LAN (or 0 for default)
|
|
/// </summary>
|
|
public ReactiveObject<string> LanInterfaceId { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Multiplayer Mode
|
|
/// </summary>
|
|
public ReactiveObject<MultiplayerMode> Mode { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Disable P2P
|
|
/// </summary>
|
|
public ReactiveObject<bool> DisableP2p { get; private set; }
|
|
|
|
/// <summary>
|
|
/// LDN PassPhrase
|
|
/// </summary>
|
|
public ReactiveObject<string> LdnPassphrase { get; private set; }
|
|
|
|
/// <summary>
|
|
/// LDN Server
|
|
/// </summary>
|
|
public ReactiveObject<string> LdnServer { get; private set; }
|
|
|
|
public string GetLdnServer()
|
|
{
|
|
string ldnServer = LdnServer;
|
|
return string.IsNullOrEmpty(ldnServer)
|
|
? SharedConstants.DefaultLanPlayHost
|
|
: ldnServer;
|
|
}
|
|
|
|
public string GetLdnWebServer()
|
|
{
|
|
if (Environment.GetEnvironmentVariable("RYULDN_WEB_HOST") is not { } ldnWebServer)
|
|
ldnWebServer = LdnServer;
|
|
|
|
return string.IsNullOrEmpty(ldnWebServer)
|
|
? SharedConstants.DefaultLanPlayWebHost
|
|
: ldnWebServer;
|
|
}
|
|
|
|
public MultiplayerSection()
|
|
{
|
|
LanInterfaceId = new ReactiveObject<string>();
|
|
Mode = new ReactiveObject<MultiplayerMode>();
|
|
Mode.LogChangesToValue(nameof(MultiplayerMode));
|
|
DisableP2p = new ReactiveObject<bool>();
|
|
DisableP2p.LogChangesToValue(nameof(DisableP2p));
|
|
LdnPassphrase = new ReactiveObject<string>();
|
|
LdnPassphrase.LogChangesToValue(nameof(LdnPassphrase));
|
|
LdnServer = new ReactiveObject<string>();
|
|
LdnServer.LogChangesToValue(nameof(LdnServer));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Debug configuration section
|
|
/// </summary>
|
|
public class DebugSection
|
|
{
|
|
/// <summary>
|
|
/// Enables or disables the GDB stub
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableGdbStub { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Which TCP port should the GDB stub listen on
|
|
/// </summary>
|
|
public ReactiveObject<ushort> GdbStubPort { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Suspend execution when starting an application
|
|
/// </summary>
|
|
public ReactiveObject<bool> DebuggerSuspendOnStart { get; private set; }
|
|
|
|
public DebugSection()
|
|
{
|
|
EnableGdbStub = new ReactiveObject<bool>();
|
|
EnableGdbStub.LogChangesToValue(nameof(EnableGdbStub));
|
|
GdbStubPort = new ReactiveObject<ushort>();
|
|
GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
|
|
DebuggerSuspendOnStart = new ReactiveObject<bool>();
|
|
DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
|
|
}
|
|
}
|
|
|
|
public class HacksSection
|
|
{
|
|
/// <summary>
|
|
/// Show toggles for dirty hacks in the UI.
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowDirtyHacks { get; private set; }
|
|
|
|
public ReactiveObject<bool> Xc2MenuSoftlockFix { get; private set; }
|
|
|
|
public ReactiveObject<bool> DisableNifmIsAnyInternetRequestAccepted { get; private set; }
|
|
|
|
public HacksSection()
|
|
{
|
|
ShowDirtyHacks = new ReactiveObject<bool>();
|
|
Xc2MenuSoftlockFix = new ReactiveObject<bool>();
|
|
Xc2MenuSoftlockFix.Event += HackChanged;
|
|
DisableNifmIsAnyInternetRequestAccepted = new ReactiveObject<bool>();
|
|
DisableNifmIsAnyInternetRequestAccepted.Event += HackChanged;
|
|
}
|
|
|
|
private void HackChanged(object sender, ReactiveEventArgs<bool> rxe)
|
|
{
|
|
if (!ShowDirtyHacks)
|
|
return;
|
|
|
|
string newHacks = EnabledHacks.Select(x => x.Hack)
|
|
.JoinToString(", ");
|
|
|
|
if (newHacks != _lastHackCollection)
|
|
{
|
|
RyuLogger.Info?.Print(LogClass.Configuration,
|
|
$"EnabledDirtyHacks set to: [{newHacks}]", "LogValueChange");
|
|
|
|
_lastHackCollection = newHacks;
|
|
}
|
|
}
|
|
|
|
private static string _lastHackCollection;
|
|
|
|
public EnabledDirtyHack[] EnabledHacks
|
|
{
|
|
get
|
|
{
|
|
List<EnabledDirtyHack> enabledHacks = [];
|
|
|
|
if (Xc2MenuSoftlockFix)
|
|
Apply(DirtyHack.Xc2MenuSoftlockFix);
|
|
|
|
if (DisableNifmIsAnyInternetRequestAccepted)
|
|
Apply(DirtyHack.NifmServiceDisableIsAnyInternetRequestAccepted);
|
|
|
|
return enabledHacks.ToArray();
|
|
|
|
void Apply(DirtyHack hack, int value = 0)
|
|
{
|
|
enabledHacks.Add(new EnabledDirtyHack(hack, value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The default configuration instance
|
|
/// </summary>
|
|
public static ConfigurationState Instance { get; private set; }
|
|
|
|
public static ConfigurationState InstanceExtra{ get; private set; }
|
|
|
|
/// <summary>
|
|
/// The UI section
|
|
/// </summary>
|
|
public UISection UI { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Logger section
|
|
/// </summary>
|
|
public LoggerSection Logger { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The System section
|
|
/// </summary>
|
|
public SystemSection System { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Graphics section
|
|
/// </summary>
|
|
public GraphicsSection Graphics { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Hid section
|
|
/// </summary>
|
|
public HidSection Hid { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Multiplayer section
|
|
/// </summary>
|
|
public MultiplayerSection Multiplayer { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Debug
|
|
/// </summary>
|
|
public DebugSection Debug { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The Dirty Hacks section
|
|
/// </summary>
|
|
public HacksSection Hacks { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables Discord Rich Presence
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableDiscordIntegration { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
|
|
/// </summary>
|
|
public ReactiveObject<UpdaterType> UpdateCheckerType { get; private set; }
|
|
|
|
/// <summary>
|
|
/// How the emulator should behave when you click off/on the window.
|
|
/// </summary>
|
|
public ReactiveObject<FocusLostType> FocusLostActionType { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Show "Confirm Exit" Dialog
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowConfirmExit { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables or disables save window size, position and state on close.
|
|
/// </summary>
|
|
public ReactiveObject<bool> RememberWindowState { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Disable the new title bar layout & window layout changes.
|
|
/// </summary>
|
|
public ReactiveObject<bool> ShowOldUI { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Enables hardware-accelerated rendering for Avalonia
|
|
/// </summary>
|
|
public ReactiveObject<bool> EnableHardwareAcceleration { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Hide Cursor on Idle
|
|
/// </summary>
|
|
public ReactiveObject<HideCursorMode> HideCursor { get; private set; }
|
|
|
|
private ConfigurationState()
|
|
{
|
|
UI = new UISection();
|
|
Logger = new LoggerSection();
|
|
System = new SystemSection();
|
|
Graphics = new GraphicsSection();
|
|
Hid = new HidSection();
|
|
Multiplayer = new MultiplayerSection();
|
|
Debug = new DebugSection();
|
|
Hacks = new HacksSection();
|
|
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
|
FocusLostActionType = new ReactiveObject<FocusLostType>();
|
|
HideCursor = new ReactiveObject<HideCursorMode>();
|
|
EnableDiscordIntegration = new ReactiveObject<bool>();
|
|
ShowConfirmExit = new ReactiveObject<bool>();
|
|
RememberWindowState = new ReactiveObject<bool>();
|
|
ShowOldUI = new ReactiveObject<bool>();
|
|
EnableHardwareAcceleration = new ReactiveObject<bool>();
|
|
}
|
|
|
|
public HleConfiguration CreateHleConfiguration() =>
|
|
new(
|
|
System.DramSize,
|
|
System.Language.Value.Horizon,
|
|
System.Region.Value.Horizon,
|
|
Graphics.VSyncMode,
|
|
System.EnableDockedMode,
|
|
System.EnablePtc,
|
|
System.TickScalar,
|
|
System.EnableInternetAccess,
|
|
System.EnableFsIntegrityChecks
|
|
? IntegrityCheckLevel.ErrorOnInvalid
|
|
: IntegrityCheckLevel.None,
|
|
System.FsGlobalAccessLogMode,
|
|
System.MatchSystemTime
|
|
? 0
|
|
: System.SystemTimeOffset,
|
|
System.TimeZone,
|
|
System.MemoryManagerMode,
|
|
System.IgnoreMissingServices,
|
|
Graphics.AspectRatio,
|
|
System.AudioVolume,
|
|
System.UseHypervisor,
|
|
Multiplayer.LanInterfaceId,
|
|
Multiplayer.Mode,
|
|
Multiplayer.DisableP2p,
|
|
Multiplayer.LdnPassphrase,
|
|
Multiplayer.GetLdnServer(),
|
|
Debug.EnableGdbStub,
|
|
Debug.GdbStubPort,
|
|
Debug.DebuggerSuspendOnStart,
|
|
Graphics.CustomVSyncInterval,
|
|
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
|
|
}
|
|
}
|