Ryujinx/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs
Emmanuel Hansen deb99d2cae
Avalonia UI - Part 1 (#3270)
* avalonia part 1

* remove vulkan ui backend

* move ui common files to ui common project

* get name for oading screen from device

* rebase.

* review 1

* review 1.1

* review

* cleanup

* addressed review

* use cancellation token

* review

* review

* rebased

* cancel library loading when closing window

* remove star  image, use fonticon instead

* delete render control frame buffer when game ends. change position of fav star

* addressed @Thog review

* ensure the right ui is downloaded in updates

* fix crash when showing not supported dialog during controller request

* add prefix to artifact names

* Auto-format Avalonia project

* Fix input

* Fix build, simplify app disposal

* remove nv stutter thread

* addressed review

* add missing change

* maintain window size if new size is zero length

* add game, handheld, docked to local

* reverse scale main window

* Update de_DE.json

* Update de_DE.json

* Update de_DE.json

* Update italian json

* Update it_IT.json

* let render timer poll with no wait

* remove unused code

* more unused code

* enabled tiered compilation and trimming

* check if window event is not closed before signaling

* fix atmospher case

* locale fix

* locale fix

* remove explicit tiered compilation declarations

* Remove ) it_IT.json

* Remove ) de_DE.json

* Update it_IT.json

* Update pt_BR locale with latest strings

* Remove ')'

* add more strings to locale

* update locale

* remove extra slash

* remove extra slash

* set firmware version to 0 if key's not found

* fix

* revert timer changes

* lock  on object instead

* Update it_IT.json

* remove unused method

* add load screen text to locale

* drop swap event

* Update de_DE.json

* Update de_DE.json

* do null check when stopping emulator

* Update de_DE.json

* Create tr_TR.json

* Add tr_TR

* Add tr_TR + Turkish

* Update it_IT.json

* Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* addressed review

* Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs

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

* use avalonia's inbuilt renderer on linux

* removed whitespace

* workaround for queue render crash with vsync off

* drop custom backend

* format files

* fix not closing issue

* remove warnings

* rebase

* update avalonia library

* Reposition the Text and Button on About Page

* Assign build version

* Remove appveyor text

Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com>
Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
2022-05-15 13:30:15 +02:00

1449 lines
No EOL
47 KiB
C#

using ARMeilleure.Translation.PTC;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Ncm;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
namespace Ryujinx.Ava.Ui.ViewModels
{
public class MainWindowViewModel : BaseModel
{
private readonly MainWindow _owner;
private ObservableCollection<ApplicationData> _applications;
private string _aspectStatusText;
private string _loadHeading;
private string _cacheLoadStatus;
private string _searchText;
private string _dockedStatusText;
private string _fifoStatusText;
private string _gameStatusText;
private string _gpuStatusText;
private bool _isAmiiboRequested;
private bool _isGameRunning;
private bool _isLoading;
private int _progressMaximum;
private int _progressValue;
private bool _showLoadProgress;
private bool _showMenuAndStatusBar = true;
private bool _showStatusSeparator;
private Brush _progressBarForegroundColor;
private Brush _progressBarBackgroundColor;
private Brush _vsyncColor;
private byte[] _selectedIcon;
private bool _isAppletMenuActive;
private int _statusBarProgressMaximum;
private int _statusBarProgressValue;
private bool _isPaused;
private bool _showContent = true;
private bool _isLoadingIndeterminate = true;
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
public string TitleName { get; internal set; }
public MainWindowViewModel(MainWindow owner) : this()
{
_owner = owner;
}
public MainWindowViewModel()
{
Applications = new ObservableCollection<ApplicationData>();
Applications.ToObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out _appsObservableList).AsObservableList();
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());
Volume = ConfigurationState.Instance.System.AudioVolume;
}
}
public void Initialize()
{
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
Ptc.PtcStateChanged -= ProgressHandler;
Ptc.PtcStateChanged += ProgressHandler;
}
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
RefreshView();
}
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
{
get => _appsObservableList;
set
{
_appsObservableList = value;
OnPropertyChanged();
}
}
public bool IsPaused
{
get => _isPaused;
set
{
_isPaused = value;
OnPropertyChanged();
}
}
public bool EnableNonGameRunningControls => !IsGameRunning;
public bool ShowFirmwareStatus => !ShowLoadProgress;
public bool IsGameRunning
{
get => _isGameRunning;
set
{
_isGameRunning = value;
if (!value)
{
ShowMenuAndStatusBar = false;
}
OnPropertyChanged();
OnPropertyChanged(nameof(EnableNonGameRunningControls));
OnPropertyChanged(nameof(ShowFirmwareStatus));
}
}
public bool IsAmiiboRequested
{
get => _isAmiiboRequested && _isGameRunning;
set
{
_isAmiiboRequested = value;
OnPropertyChanged();
}
}
public bool ShowLoadProgress
{
get => _showLoadProgress;
set
{
_showLoadProgress = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowFirmwareStatus));
}
}
public string GameStatusText
{
get => _gameStatusText;
set
{
_gameStatusText = value;
OnPropertyChanged();
}
}
private string _showUikey = "F4";
private string _pauseKey = "F5";
private string _screenshotkey = "F8";
private float _volume;
public ApplicationData SelectedApplication
{
get
{
switch (Glyph)
{
case Glyph.List:
return _owner.GameList.SelectedApplication;
case Glyph.Grid:
return _owner.GameGrid.SelectedApplication;
default:
return null;
}
}
}
public string LoadHeading
{
get => _loadHeading;
set
{
_loadHeading = value;
OnPropertyChanged();
}
}
public string CacheLoadStatus
{
get => _cacheLoadStatus;
set
{
_cacheLoadStatus = value;
OnPropertyChanged();
}
}
public Brush ProgressBarBackgroundColor
{
get => _progressBarBackgroundColor;
set
{
_progressBarBackgroundColor = value;
OnPropertyChanged();
}
}
public Brush ProgressBarForegroundColor
{
get => _progressBarForegroundColor;
set
{
_progressBarForegroundColor = value;
OnPropertyChanged();
}
}
public Brush VsyncColor
{
get => _vsyncColor;
set
{
_vsyncColor = value;
OnPropertyChanged();
}
}
public byte[] SelectedIcon
{
get => _selectedIcon;
set
{
_selectedIcon = value;
OnPropertyChanged();
}
}
public int ProgressMaximum
{
get => _progressMaximum;
set
{
_progressMaximum = value;
OnPropertyChanged();
}
}
public int ProgressValue
{
get => _progressValue;
set
{
_progressValue = value;
OnPropertyChanged();
}
}
public int StatusBarProgressMaximum
{
get => _statusBarProgressMaximum;
set
{
_statusBarProgressMaximum = value;
OnPropertyChanged();
}
}
public int StatusBarProgressValue
{
get => _statusBarProgressValue;
set
{
_statusBarProgressValue = value;
OnPropertyChanged();
}
}
public string FifoStatusText
{
get => _fifoStatusText;
set
{
_fifoStatusText = value;
OnPropertyChanged();
}
}
public string GpuStatusText
{
get => _gpuStatusText;
set
{
_gpuStatusText = value;
OnPropertyChanged();
}
}
public string DockedStatusText
{
get => _dockedStatusText;
set
{
_dockedStatusText = value;
OnPropertyChanged();
}
}
public string AspectRatioStatusText
{
get => _aspectStatusText;
set
{
_aspectStatusText = value;
OnPropertyChanged();
}
}
public string VolumeStatusText
{
get
{
string icon = Volume == 0 ? "🔇" : "🔊";
return $"{icon} {(int)(Volume * 100)}%";
}
}
public bool VolumeMuted => _volume == 0;
public float Volume
{
get => _volume;
set
{
_volume = value;
if (_isGameRunning)
{
_owner.AppHost.Device.SetVolume(_volume);
}
OnPropertyChanged(nameof(VolumeStatusText));
OnPropertyChanged(nameof(VolumeMuted));
OnPropertyChanged();
}
}
public bool ShowStatusSeparator
{
get => _showStatusSeparator;
set
{
_showStatusSeparator = value;
OnPropertyChanged();
}
}
public Thickness GridItemPadding => ShowNames ? new Thickness() : new Thickness(5);
public bool ShowMenuAndStatusBar
{
get => _showMenuAndStatusBar;
set
{
_showMenuAndStatusBar = value;
OnPropertyChanged();
}
}
public bool IsLoadingIndeterminate
{
get => _isLoadingIndeterminate;
set
{
_isLoadingIndeterminate = value;
OnPropertyChanged();
}
}
public bool ShowContent
{
get => _showContent;
set
{
_showContent = value;
OnPropertyChanged();
}
}
public bool IsAppletMenuActive
{
get => _isAppletMenuActive && EnableNonGameRunningControls;
set
{
_isAppletMenuActive = value;
OnPropertyChanged();
}
}
public bool IsGrid => Glyph == Glyph.Grid;
public bool IsList => Glyph == Glyph.List;
internal void Sort(bool isAscending)
{
IsAscending = isAscending;
RefreshView();
}
internal void Sort(ApplicationSort sort)
{
SortMode = sort;
RefreshView();
}
private IComparer<ApplicationData> GetComparer()
{
switch (SortMode)
{
case ApplicationSort.LastPlayed:
return new Models.Generic.LastPlayedSortComparer(IsAscending);
case ApplicationSort.FileSize:
return new Models.Generic.FileSizeSortComparer(IsAscending);
case ApplicationSort.TotalTimePlayed:
return new Models.Generic.TimePlayedSortComparer(IsAscending);
case ApplicationSort.Title:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName);
case ApplicationSort.Favorite:
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite);
case ApplicationSort.Developer:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer);
case ApplicationSort.FileType:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension);
case ApplicationSort.Path:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
default:
return null;
}
}
private void RefreshView()
{
RefreshGrid();
}
private void RefreshGrid()
{
Applications.ToObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out _appsObservableList).AsObservableList();
OnPropertyChanged(nameof(AppsObservableList));
}
public bool StartGamesInFullscreen
{
get => ConfigurationState.Instance.Ui.StartFullscreen;
set
{
ConfigurationState.Instance.Ui.StartFullscreen.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
OnPropertyChanged();
}
}
public bool ShowConsole
{
get => ConfigurationState.Instance.Ui.ShowConsole;
set
{
ConfigurationState.Instance.Ui.ShowConsole.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
OnPropertyChanged();
}
}
public ObservableCollection<ApplicationData> Applications
{
get => _applications;
set
{
_applications = value;
OnPropertyChanged();
}
}
public Glyph Glyph
{
get => (Glyph)ConfigurationState.Instance.Ui.GameListViewMode.Value;
set
{
ConfigurationState.Instance.Ui.GameListViewMode.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsGrid));
OnPropertyChanged(nameof(IsList));
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public bool ShowNames
{
get => ConfigurationState.Instance.Ui.ShowNames && ConfigurationState.Instance.Ui.GridSize > 1; set
{
ConfigurationState.Instance.Ui.ShowNames.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(GridItemPadding));
OnPropertyChanged(nameof(GridSizeScale));
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
internal ApplicationSort SortMode
{
get => (ApplicationSort)ConfigurationState.Instance.Ui.ApplicationSort.Value;
private set
{
ConfigurationState.Instance.Ui.ApplicationSort.Value = (int)value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortName));
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public string SortName
{
get
{
switch (SortMode)
{
case ApplicationSort.Title:
return LocaleManager.Instance["GameListHeaderApplication"];
case ApplicationSort.Developer:
return LocaleManager.Instance["GameListHeaderDeveloper"];
case ApplicationSort.LastPlayed:
return LocaleManager.Instance["GameListHeaderLastPlayed"];
case ApplicationSort.TotalTimePlayed:
return LocaleManager.Instance["GameListHeaderTimePlayed"];
case ApplicationSort.FileType:
return LocaleManager.Instance["GameListHeaderFileExtension"];
case ApplicationSort.FileSize:
return LocaleManager.Instance["GameListHeaderFileSize"];
case ApplicationSort.Path:
return LocaleManager.Instance["GameListHeaderPath"];
case ApplicationSort.Favorite:
return LocaleManager.Instance["CommonFavorite"];
}
return string.Empty;
}
}
public bool IsAscending
{
get => ConfigurationState.Instance.Ui.IsAscendingOrder;
private set
{
ConfigurationState.Instance.Ui.IsAscendingOrder.Value = value;
OnPropertyChanged();
OnPropertyChanged(nameof(SortMode));
OnPropertyChanged(nameof(SortName));
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public KeyGesture ShowUiKey
{
get => KeyGesture.Parse(_showUikey); set
{
_showUikey = value.ToString();
OnPropertyChanged();
}
}
public KeyGesture ScreenshotKey
{
get => KeyGesture.Parse(_screenshotkey); set
{
_screenshotkey = value.ToString();
OnPropertyChanged();
}
}
public KeyGesture PauseKey
{
get => KeyGesture.Parse(_pauseKey); set
{
_pauseKey = value.ToString();
OnPropertyChanged();
}
}
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public int GridSizeScale
{
get => ConfigurationState.Instance.Ui.GridSize;
set
{
ConfigurationState.Instance.Ui.GridSize.Value = value;
if (value < 2)
{
ShowNames = false;
}
OnPropertyChanged();
OnPropertyChanged(nameof(IsGridSmall));
OnPropertyChanged(nameof(IsGridMedium));
OnPropertyChanged(nameof(IsGridLarge));
OnPropertyChanged(nameof(IsGridHuge));
OnPropertyChanged(nameof(ShowNames));
OnPropertyChanged(nameof(GridItemPadding));
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
}
public void OpenAmiiboWindow()
{
if (!_isAmiiboRequested)
{
return;
}
// TODO : Implement Amiibo window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void HandleShaderProgress(Switch emulationContext)
{
emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
}
private bool Filter(object arg)
{
if (arg is ApplicationData app)
{
return string.IsNullOrWhiteSpace(_searchText) || app.TitleName.ToLower().Contains(_searchText.ToLower());
}
return false;
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressMaximum = e.NumAppsFound;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
Dispatcher.UIThread.Post(() =>
{
if (e.NumAppsFound == 0)
{
_owner.LoadProgressBar.IsVisible = false;
}
});
}
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
Applications.Add(applicationData);
});
}
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
Applications.Clear();
_owner.LoadProgressBar.IsVisible = true;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
});
ReloadGameList();
}
private void ReloadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
Thread thread = new(() =>
{
_owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
_isLoading = false;
})
{ Name = "GUI.AppListLoadThread", Priority = ThreadPriority.AboveNormal };
thread.Start();
}
public async void OpenFile()
{
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance["OpenFileDialogTitle"]
};
dialog.Filters.Add(new FileDialogFilter
{
Name = LocaleManager.Instance["AllSupportedFormats"],
Extensions =
{
"nsp",
"pfs0",
"xci",
"nca",
"nro",
"nso"
}
});
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
string[] files = await dialog.ShowAsync(_owner);
if (files != null && files.Length > 0)
{
_owner.LoadApplication(files[0]);
}
}
public async void OpenFolder()
{
OpenFolderDialog dialog = new()
{
Title = LocaleManager.Instance["OpenFolderDialogTitle"]
};
string folder = await dialog.ShowAsync(_owner);
if (!string.IsNullOrWhiteSpace(folder) && Directory.Exists(folder))
{
_owner.LoadApplication(folder);
}
}
public void TakeScreenshot()
{
_owner.AppHost.ScreenshotRequested = true;
}
public void HideUi()
{
ShowMenuAndStatusBar = false;
}
public void SetListMode()
{
Glyph = Glyph.List;
}
public void SetGridMode()
{
Glyph = Glyph.Grid;
}
public void OpenMiiApplet()
{
string contentPath = _owner.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrWhiteSpace(contentPath))
{
_owner.LoadApplication(contentPath, false, "Mii Applet");
}
}
public static void OpenRyujinxFolder()
{
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
}
public static void OpenLogsFolder()
{
string logPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
new DirectoryInfo(logPath).Create();
OpenHelper.OpenFolder(logPath);
}
public void ToggleFullscreen()
{
WindowState state = _owner.WindowState;
if (state == WindowState.FullScreen)
{
_owner.WindowState = WindowState.Normal;
if (IsGameRunning)
{
ShowMenuAndStatusBar = true;
}
}
else
{
_owner.WindowState = WindowState.FullScreen;
if (IsGameRunning)
{
ShowMenuAndStatusBar = false;
}
}
OnPropertyChanged(nameof(IsFullScreen));
}
public bool IsFullScreen => _owner.WindowState == WindowState.FullScreen;
public void ToggleDockMode()
{
if (IsGameRunning)
{
ConfigurationState.Instance.System.EnableDockedMode.Value =
!ConfigurationState.Instance.System.EnableDockedMode.Value;
}
}
public async void ExitCurrentState()
{
if (_owner.WindowState == WindowState.FullScreen)
{
ToggleFullscreen();
}
else if (IsGameRunning)
{
await Task.Delay(100);
_owner.AppHost?.ShowExitPrompt();
}
}
public void OpenSettings()
{
// TODO : Implement Settings window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void ManageProfiles()
{
// TODO : Implement Profiles window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public async void OpenAboutWindow()
{
AboutWindow window = new();
await window.ShowDialog(_owner);
}
public void ChangeLanguage(object obj)
{
LocaleManager.Instance.LoadLanguage((string)obj);
}
private void ProgressHandler<T>(T state, int current, int total) where T : Enum
{
try
{
ProgressMaximum = total;
ProgressValue = current;
switch (state)
{
case PtcLoadingState ptcState:
CacheLoadStatus = $"{current} / {total}";
switch (ptcState)
{
case PtcLoadingState.Start:
case PtcLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
IsLoadingIndeterminate = false;
break;
case PtcLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
}
break;
case ShaderCacheLoadingState shaderCacheState:
CacheLoadStatus = $"{current} / {total}";
switch (shaderCacheState)
{
case ShaderCacheLoadingState.Start:
case ShaderCacheLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingShaders"];
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
break;
}
break;
default:
throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
}
}
catch (Exception) { }
}
public void OpenUserSaveDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
{
ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
return;
}
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
}
}
public void ToggleFavorite()
{
var selection = SelectedApplication;
if (selection != null)
{
selection.Favorite = !selection.Favorite;
RefreshView();
}
}
public void OpenModsDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public void OpenSdModsDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
}
public void OpenPtcDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
{
Directory.CreateDirectory(ptcDir);
Directory.CreateDirectory(mainPath);
Directory.CreateDirectory(backupPath);
}
OpenHelper.OpenFolder(ptcDir);
}
}
public async void PurgePtcCache()
{
var selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new();
if (mainDir.Exists)
{
cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache"));
}
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
}
if (cacheFiles.Count > 0 && result == UserResult.Yes)
{
foreach (FileInfo file in cacheFiles)
{
try
{
file.Delete();
}
catch (Exception e)
{
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
}
}
}
}
}
public void OpenShaderCacheDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{
Directory.CreateDirectory(shaderCacheDir);
}
OpenHelper.OpenFolder(shaderCacheDir);
}
}
public void SimulateWakeUpMessage()
{
_owner.AppHost.Device.System.SimulateWakeUpMessage();
}
public async void PurgeShaderCache()
{
var selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
List<FileInfo> newCacheFiles = new List<FileInfo>();
if (shaderCacheDir.Exists)
{
oldCacheDirectories.AddRange(shaderCacheDir.EnumerateDirectories("*"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.toc"));
newCacheFiles.AddRange(shaderCacheDir.GetFiles("*.data"));
}
if ((oldCacheDirectories.Count > 0 || newCacheFiles.Count > 0) && result == UserResult.Yes)
{
foreach (DirectoryInfo directory in oldCacheDirectories)
{
try
{
directory.Delete(true);
}
catch (Exception e)
{
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
}
}
}
foreach (FileInfo file in newCacheFiles)
{
try
{
file.Delete();
}
catch (Exception e)
{
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
}
}
}
}
public async void CheckForUpdates()
{
if (Updater.CanUpdate(true, _owner))
{
await Updater.BeginParse(_owner, true);
}
}
public void OpenTitleUpdateManager()
{
// TODO : Implement Update window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void OpenDlcManager()
{
// TODO : Implement Dlc window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void OpenCheatManager()
{
// TODO : Implement cheat window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void OpenCheatManagerForCurrentApp()
{
if (!IsGameRunning)
{
return;
}
// TODO : Implement cheat window
ContentDialogHelper.ShowNotAvailableMessage(_owner);
}
public void OpenDeviceSaveDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
{
ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Device, userId: default, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
}
}
public void OpenBcatSaveDirectory()
{
var selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
{
ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
return;
}
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
}
}
private void OpenSaveDirectory(in SaveDataFilter filter, ApplicationData data, ulong titleId)
{
ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName);
}
private void ExtractLogo()
{
var selection = SelectedApplication;
if (selection != null)
{
ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
}
}
private void ExtractRomFs()
{
var selection = SelectedApplication;
if (selection != null)
{
ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
}
}
private void ExtractExeFs()
{
var selection = SelectedApplication;
if (selection != null)
{
ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
}
}
public void CloseWindow()
{
_owner.Close();
}
private async void HandleFirmwareInstallation(string path)
{
try
{
string filename = path;
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null)
{
ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
return;
}
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
if (currentVersion != null)
{
dialogMessage += string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSubMessage"], currentVersion.VersionString);
}
dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
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"]);
if (result == UserResult.Yes)
{
Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
Thread thread = new(() =>
{
Dispatcher.UIThread.InvokeAsync(delegate
{
waitingDialog.Show();
});
try
{
_owner.ContentManager.InstallFirmware(filename);
Dispatcher.UIThread.InvokeAsync(async delegate
{
waitingDialog.Close();
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(_owner, dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists)
{
miiEditorCacheFolder.Delete(true);
}
});
}
catch (Exception ex)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
waitingDialog.Close();
ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
});
}
finally
{
_owner.RefreshFirmwareStatus();
}
});
thread.Name = "GUI.FirmwareInstallerThread";
thread.Start();
}
}
catch (LibHac.Common.Keys.MissingKeyException ex)
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
}
catch (Exception ex)
{
ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
}
}
public async void InstallFirmwareFromFile()
{
OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(_owner);
if (file != null && file.Length > 0)
{
HandleFirmwareInstallation(file[0]);
}
}
public async void InstallFirmwareFromFolder()
{
OpenFolderDialog dialog = new();
string folder = await dialog.ShowAsync(_owner);
if (!string.IsNullOrWhiteSpace(folder))
{
HandleFirmwareInstallation(folder);
}
}
}
}