diff --git a/.editorconfig b/.editorconfig index 8a3054287..9e00e3bae 100644 --- a/.editorconfig +++ b/.editorconfig @@ -63,10 +63,6 @@ dotnet_code_quality_unused_parameters = all:suggestion #### C# Coding Conventions #### -# Namespace preferences -csharp_style_namespace_declarations = block_scoped:warning -resharper_csharp_namespace_body = block_scoped - # var preferences csharp_style_var_elsewhere = false:silent csharp_style_var_for_built_in_types = false:silent diff --git a/ARMeilleure/Decoders/IOpCode32Exception.cs b/ARMeilleure/Decoders/IOpCode32Exception.cs index 8f0fb81a0..82819bddd 100644 --- a/ARMeilleure/Decoders/IOpCode32Exception.cs +++ b/ARMeilleure/Decoders/IOpCode32Exception.cs @@ -1,7 +1,6 @@ -namespace ARMeilleure.Decoders +namespace ARMeilleure.Decoders; + +interface IOpCode32Exception { - interface IOpCode32Exception - { - int Id { get; } - } + int Id { get; } } \ No newline at end of file diff --git a/Ryujinx.Ava/Common/Locale/LocaleManager.cs b/Ryujinx.Ava/Common/Locale/LocaleManager.cs index 464ab780d..1374bfee1 100644 --- a/Ryujinx.Ava/Common/Locale/LocaleManager.cs +++ b/Ryujinx.Ava/Common/Locale/LocaleManager.cs @@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale { var localeStrings = new Dictionary(); string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); - var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); + var strings = JsonHelper.Deserialize>(languageJson); foreach (var item in strings) { diff --git a/Ryujinx.Ava/Modules/Updater/Updater.cs b/Ryujinx.Ava/Modules/Updater/Updater.cs index c58575284..e89abd1da 100644 --- a/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -4,14 +4,13 @@ using FluentAvalonia.UI.Controls; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json.Linq; using Ryujinx.Ava; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using Ryujinx.Ui.Common.Helper; -using Ryujinx.Ui.Common.Models.Github; using System; using System.Collections.Generic; using System.Diagnostics; @@ -32,7 +31,6 @@ namespace Ryujinx.Modules internal static class Updater { private const string GitHubApiURL = "https://api.github.com"; - private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); @@ -101,16 +99,22 @@ namespace Ryujinx.Modules string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); - var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse); - _buildVer = fetched.Name; + JObject jsonRoot = JObject.Parse(fetchedJson); + JToken assets = jsonRoot["assets"]; - foreach (var asset in fetched.Assets) + _buildVer = (string)jsonRoot["name"]; + + foreach (JToken asset in assets) { - if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt)) - { - _buildUrl = asset.BrowserDownloadUrl; + string assetName = (string)asset["name"]; + string assetState = (string)asset["state"]; + string downloadURL = (string)asset["browser_download_url"]; - if (asset.State != "uploaded") + if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt)) + { + _buildUrl = downloadURL; + + if (assetState != "uploaded") { if (showVersionUpToDate) { diff --git a/Ryujinx.Ava/UI/Models/Amiibo.cs b/Ryujinx.Ava/UI/Models/Amiibo.cs new file mode 100644 index 000000000..d0ccafd08 --- /dev/null +++ b/Ryujinx.Ava/UI/Models/Amiibo.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Ava.UI.Models +{ + public class Amiibo + { + public struct AmiiboJson + { + [JsonPropertyName("amiibo")] public List Amiibo { get; set; } + [JsonPropertyName("lastUpdated")] public DateTime LastUpdated { get; set; } + } + + public struct AmiiboApi + { + [JsonPropertyName("name")] public string Name { get; set; } + [JsonPropertyName("head")] public string Head { get; set; } + [JsonPropertyName("tail")] public string Tail { get; set; } + [JsonPropertyName("image")] public string Image { get; set; } + [JsonPropertyName("amiiboSeries")] public string AmiiboSeries { get; set; } + [JsonPropertyName("character")] public string Character { get; set; } + [JsonPropertyName("gameSeries")] public string GameSeries { get; set; } + [JsonPropertyName("type")] public string Type { get; set; } + + [JsonPropertyName("release")] public Dictionary Release { get; set; } + + [JsonPropertyName("gamesSwitch")] public List GamesSwitch { get; set; } + + public override string ToString() + { + return Name; + } + + public string GetId() + { + return Head + Tail; + } + + public override bool Equals(object obj) + { + if (obj is AmiiboApi amiibo) + { + return amiibo.Head + amiibo.Tail == Head + Tail; + } + + return false; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + public class AmiiboApiGamesSwitch + { + [JsonPropertyName("amiiboUsage")] public List AmiiboUsage { get; set; } + + [JsonPropertyName("gameID")] public List GameId { get; set; } + + [JsonPropertyName("gameName")] public string GameName { get; set; } + } + + public class AmiiboApiUsage + { + [JsonPropertyName("Usage")] public string Usage { get; set; } + + [JsonPropertyName("write")] public bool Write { get; set; } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs index 9b5422adf..872c1a37f 100644 --- a/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs @@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels { string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); - Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray) + "\n\n"); + Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString)) + "\n\n"; } catch { diff --git a/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs index 090c13a97..5311318c5 100644 --- a/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs @@ -4,11 +4,11 @@ using Avalonia.Media.Imaging; using Avalonia.Threading; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Utilities; -using Ryujinx.Ui.Common.Models.Amiibo; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -17,7 +17,6 @@ using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext; namespace Ryujinx.Ava.UI.ViewModels { @@ -32,8 +31,8 @@ namespace Ryujinx.Ava.UI.ViewModels private readonly StyleableWindow _owner; private Bitmap _amiiboImage; - private List _amiiboList; - private AvaloniaList _amiibos; + private List _amiiboList; + private AvaloniaList _amiibos; private ObservableCollection _amiiboSeries; private int _amiiboSelectedIndex; @@ -42,8 +41,6 @@ namespace Ryujinx.Ava.UI.ViewModels private bool _showAllAmiibo; private bool _useRandomUuid; private string _usage; - - private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId) { @@ -55,9 +52,9 @@ namespace Ryujinx.Ava.UI.ViewModels Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); - _amiiboList = new List(); + _amiiboList = new List(); _amiiboSeries = new ObservableCollection(); - _amiibos = new AvaloniaList(); + _amiibos = new AvaloniaList(); _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); @@ -97,7 +94,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public AvaloniaList AmiiboList + public AvaloniaList AmiiboList { get => _amiibos; set @@ -190,9 +187,9 @@ namespace Ryujinx.Ava.UI.ViewModels if (File.Exists(_amiiboJsonPath)) { - amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath); + amiiboJsonString = File.ReadAllText(_amiiboJsonPath); - if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated)) + if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString).LastUpdated)) { amiiboJsonString = await DownloadAmiiboJson(); } @@ -209,7 +206,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - _amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo; + _amiiboList = JsonHelper.Deserialize(amiiboJsonString).Amiibo; _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); ParseAmiiboData(); @@ -226,7 +223,7 @@ namespace Ryujinx.Ava.UI.ViewModels { if (!ShowAllAmiibo) { - foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) + foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) { if (game != null) { @@ -258,7 +255,7 @@ namespace Ryujinx.Ava.UI.ViewModels private void SelectLastScannedAmiibo() { - AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId); + Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId); SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries); AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); @@ -273,7 +270,7 @@ namespace Ryujinx.Ava.UI.ViewModels return; } - List amiiboSortedList = _amiiboList + List amiiboSortedList = _amiiboList .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) .OrderBy(amiibo => amiibo.Name).ToList(); @@ -283,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels { if (!_showAllAmiibo) { - foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) + foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) { if (game != null) { @@ -317,7 +314,7 @@ namespace Ryujinx.Ava.UI.ViewModels return; } - AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; + Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image; @@ -329,11 +326,11 @@ namespace Ryujinx.Ava.UI.ViewModels { bool writable = false; - foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) + foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) { if (item.GameId.Contains(TitleId)) { - foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) + foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage) { usageString += Environment.NewLine + $"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"; diff --git a/Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs b/Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs index dd261b103..35256b3b5 100644 --- a/Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs @@ -51,8 +51,6 @@ namespace Ryujinx.Ava.UI.ViewModels private bool _isLoaded; private readonly UserControl _owner; - private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepad SelectedGamepad { get; private set; } @@ -708,7 +706,10 @@ namespace Ryujinx.Ava.UI.ViewModels try { - config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); + using (Stream stream = File.OpenRead(path)) + { + config = JsonHelper.Deserialize(stream); + } } catch (JsonException) { } catch (InvalidOperationException) @@ -774,7 +775,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.ControllerType = Controllers[_controller].Type; - string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig); + string jsonString = JsonHelper.Serialize(config, true); await File.WriteAllTextAsync(path, jsonString); diff --git a/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs index 1d7da9a40..e5e4f66b5 100644 --- a/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Path = System.IO.Path; @@ -40,8 +41,6 @@ namespace Ryujinx.Ava.UI.ViewModels private ulong _titleId; private string _titleName; - private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public AvaloniaList DownloadableContents { get => _downloadableContents; @@ -101,7 +100,7 @@ namespace Ryujinx.Ava.UI.ViewModels try { - _downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer); + _downloadableContentContainerList = JsonHelper.DeserializeFromFile>(_downloadableContentJsonPath); } catch { @@ -331,7 +330,10 @@ namespace Ryujinx.Ava.UI.ViewModels _downloadableContentContainerList.Add(container); } - JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer); + using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough)) + { + downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true))); + } } } diff --git a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs index ed5b5eacf..dd9e1b961 100644 --- a/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs @@ -25,228 +25,226 @@ using System.Text; using Path = System.IO.Path; using SpanHelpers = LibHac.Common.SpanHelpers; -namespace Ryujinx.Ava.UI.ViewModels +namespace Ryujinx.Ava.UI.ViewModels; + +public class TitleUpdateViewModel : BaseModel { - public class TitleUpdateViewModel : BaseModel + public TitleUpdateMetadata _titleUpdateWindowData; + public readonly string _titleUpdateJsonPath; + private VirtualFileSystem _virtualFileSystem { get; } + private ulong _titleId { get; } + private string _titleName { get; } + + private AvaloniaList _titleUpdates = new(); + private AvaloniaList _views = new(); + private object _selectedUpdate; + + public AvaloniaList TitleUpdates { - public TitleUpdateMetadata _titleUpdateWindowData; - public readonly string _titleUpdateJsonPath; - private VirtualFileSystem _virtualFileSystem { get; } - private ulong _titleId { get; } - private string _titleName { get; } - - private AvaloniaList _titleUpdates = new(); - private AvaloniaList _views = new(); - private object _selectedUpdate; - - private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - - public AvaloniaList TitleUpdates + get => _titleUpdates; + set { - get => _titleUpdates; - set - { - _titleUpdates = value; - OnPropertyChanged(); - } + _titleUpdates = value; + OnPropertyChanged(); } + } - public AvaloniaList Views + public AvaloniaList Views + { + get => _views; + set { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } + _views = value; + OnPropertyChanged(); } + } - public object SelectedUpdate + public object SelectedUpdate + { + get => _selectedUpdate; + set { - get => _selectedUpdate; - set - { - _selectedUpdate = value; - OnPropertyChanged(); - } + _selectedUpdate = value; + OnPropertyChanged(); } + } - public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) + { + _virtualFileSystem = virtualFileSystem; + + _titleId = titleId; + _titleName = titleName; + + _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); + + try { - _virtualFileSystem = virtualFileSystem; - - _titleId = titleId; - _titleName = titleName; - - _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json"); - - try - { - _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata); - } - catch - { - Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}"); - - _titleUpdateWindowData = new TitleUpdateMetadata - { - Selected = "", - Paths = new List() - }; - - Save(); - } - - LoadUpdates(); + _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath); } - - private void LoadUpdates() + catch { - foreach (string path in _titleUpdateWindowData.Paths) + Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}"); + + _titleUpdateWindowData = new TitleUpdateMetadata { - AddUpdate(path); - } + Selected = "", + Paths = new List() + }; - TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null); - - SelectedUpdate = selected; - - // NOTE: Save the list again to remove leftovers. Save(); - SortUpdates(); } - public void SortUpdates() + LoadUpdates(); + } + + private void LoadUpdates() + { + foreach (string path in _titleUpdateWindowData.Paths) { - var list = TitleUpdates.ToList(); + AddUpdate(path); + } - list.Sort((first, second) => + TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null); + + SelectedUpdate = selected; + + // NOTE: Save the list again to remove leftovers. + Save(); + + SortUpdates(); + } + + public void SortUpdates() + { + var list = TitleUpdates.ToList(); + + list.Sort((first, second) => + { + if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) { - if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString())) - { - return -1; - } - else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) - { - return 1; - } + return -1; + } + else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) + { + return 1; + } - return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; - }); + return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; + }); - Views.Clear(); - Views.Add(new BaseModel()); - Views.AddRange(list); + Views.Clear(); + Views.Add(new BaseModel()); + Views.AddRange(list); - if (SelectedUpdate == null) + if (SelectedUpdate == null) + { + SelectedUpdate = Views[0]; + } + else if (!TitleUpdates.Contains(SelectedUpdate)) + { + if (Views.Count > 1) + { + SelectedUpdate = Views[1]; + } + else { SelectedUpdate = Views[0]; } - else if (!TitleUpdates.Contains(SelectedUpdate)) + } + } + + private void AddUpdate(string path) + { + if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) + { + using FileStream file = new(path, FileMode.Open, FileAccess.Read); + + try { - if (Views.Count > 1) + (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); + + if (controlNca != null && patchNca != null) { - SelectedUpdate = Views[1]; + ApplicationControlProperty controlData = new(); + + using UniqueRef nacpFile = new(); + + controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); + nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); + + TitleUpdates.Add(new TitleUpdateModel(controlData, path)); } else - { - SelectedUpdate = Views[0]; - } - } - } - - private void AddUpdate(string path) - { - if (File.Exists(path) && TitleUpdates.All(x => x.Path != path)) - { - using FileStream file = new(path, FileMode.Open, FileAccess.Read); - - try - { - (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0); - - if (controlNca != null && patchNca != null) - { - ApplicationControlProperty controlData = new(); - - using UniqueRef nacpFile = new(); - - controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); - - TitleUpdates.Add(new TitleUpdateModel(controlData, path)); - } - else - { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); - }); - } - } - catch (Exception ex) { Dispatcher.UIThread.Post(async () => { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)); + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); }); } } - } - - public void RemoveUpdate(TitleUpdateModel update) - { - TitleUpdates.Remove(update); - - SortUpdates(); - } - - public async void Add() - { - OpenFileDialog dialog = new() + catch (Exception ex) { - Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], - AllowMultiple = true - }; - - dialog.Filters.Add(new FileDialogFilter - { - Name = "NSP", - Extensions = { "nsp" } - }); - - if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - string[] files = await dialog.ShowAsync(desktop.MainWindow); - - if (files != null) + Dispatcher.UIThread.Post(async () => { - foreach (string file in files) - { - AddUpdate(file); - } - } + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)); + }); } - - SortUpdates(); - } - - public void Save() - { - _titleUpdateWindowData.Paths.Clear(); - _titleUpdateWindowData.Selected = ""; - - foreach (TitleUpdateModel update in TitleUpdates) - { - _titleUpdateWindowData.Paths.Add(update.Path); - - if (update == SelectedUpdate) - { - _titleUpdateWindowData.Selected = update.Path; - } - } - - JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata); } } + + public void RemoveUpdate(TitleUpdateModel update) + { + TitleUpdates.Remove(update); + + SortUpdates(); + } + + public async void Add() + { + OpenFileDialog dialog = new() + { + Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], + AllowMultiple = true + }; + + dialog.Filters.Add(new FileDialogFilter + { + Name = "NSP", + Extensions = { "nsp" } + }); + + if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + string[] files = await dialog.ShowAsync(desktop.MainWindow); + + if (files != null) + { + foreach (string file in files) + { + AddUpdate(file); + } + } + } + + SortUpdates(); + } + + public void Save() + { + _titleUpdateWindowData.Paths.Clear(); + _titleUpdateWindowData.Selected = ""; + + foreach (TitleUpdateModel update in TitleUpdates) + { + _titleUpdateWindowData.Paths.Add(update.Path); + + if (update == SelectedUpdate) + { + _titleUpdateWindowData.Selected = update.Path; + } + } + + File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + } } \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs index 51c71c378..1c6f4265c 100644 --- a/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs @@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Main { string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); - var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); + var strings = JsonHelper.Deserialize>(languageJson); if (!strings.TryGetValue("Language", out string languageName)) { diff --git a/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs index 206d0a7ea..5368a1333 100644 --- a/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs +++ b/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs @@ -1,7 +1,7 @@ using Avalonia.Interactivity; using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.ViewModels; -using Ryujinx.Ui.Common.Models.Amiibo; namespace Ryujinx.Ava.UI.Windows { @@ -35,14 +35,14 @@ namespace Ryujinx.Ava.UI.Windows } public bool IsScanned { get; set; } - public AmiiboApi ScannedAmiibo { get; set; } + public Amiibo.AmiiboApi ScannedAmiibo { get; set; } public AmiiboWindowViewModel ViewModel { get; set; } private void ScanButton_Click(object sender, RoutedEventArgs e) { if (ViewModel.AmiiboSelectedIndex > -1) { - AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; + Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; ScannedAmiibo = amiibo; IsScanned = true; Close(); diff --git a/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs b/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs index 153ce95d2..1b50c46f3 100644 --- a/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs +++ b/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs @@ -6,8 +6,11 @@ using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.Ui.Common.Helper; +using System.IO; +using System.Text; using System.Threading.Tasks; using Button = Avalonia.Controls.Button; diff --git a/Ryujinx.Common/Configuration/AspectRatioExtensions.cs b/Ryujinx.Common/Configuration/AspectRatioExtensions.cs index 5e97ed19c..3d0be88e9 100644 --- a/Ryujinx.Common/Configuration/AspectRatioExtensions.cs +++ b/Ryujinx.Common/Configuration/AspectRatioExtensions.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration +namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum AspectRatio { Fixed4x3, diff --git a/Ryujinx.Common/Configuration/BackendThreading.cs b/Ryujinx.Common/Configuration/BackendThreading.cs index 8833b3f07..cfc089146 100644 --- a/Ryujinx.Common/Configuration/BackendThreading.cs +++ b/Ryujinx.Common/Configuration/BackendThreading.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration +namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum BackendThreading { Auto, diff --git a/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs b/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs deleted file mode 100644 index 132c45a44..000000000 --- a/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(List))] - public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/GraphicsBackend.cs b/Ryujinx.Common/Configuration/GraphicsBackend.cs index d74dd6e19..26e4a28a9 100644 --- a/Ryujinx.Common/Configuration/GraphicsBackend.cs +++ b/Ryujinx.Common/Configuration/GraphicsBackend.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration +namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum GraphicsBackend { Vulkan, diff --git a/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs b/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs index ad12302a6..556af689a 100644 --- a/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs +++ b/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum GraphicsDebugLevel { None, diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs index 2b9e0af42..d1c2e4e81 100644 --- a/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs +++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs @@ -1,5 +1,4 @@ -using Ryujinx.Common.Utilities; -using System; +using System; using System.Text.Json; using System.Text.Json.Serialization; @@ -7,8 +6,6 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion { class JsonMotionConfigControllerConverter : JsonConverter { - private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader) { // Temporary reader to get the backend type @@ -55,8 +52,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion return motionBackendType switch { - MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController), - MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController), + MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options), + MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options), _ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"), }; } @@ -66,10 +63,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion switch (value.MotionBackend) { case MotionInputBackendType.GamepadDriver: - JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController); + JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options); break; case MotionInputBackendType.CemuHook: - JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController); + JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options); break; default: throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}"); diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs index 7636aa414..832aae0d1 100644 --- a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs +++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs @@ -1,8 +1,5 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion { - [JsonConverter(typeof(JsonMotionConfigControllerConverter))] public class MotionConfigController { public MotionInputBackendType MotionBackend { get; set; } diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs deleted file mode 100644 index 5cd9e452b..000000000 --- a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration.Hid.Controller.Motion -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(MotionConfigController))] - [JsonSerializable(typeof(CemuHookMotionConfigController))] - [JsonSerializable(typeof(StandardMotionConfigController))] - public partial class MotionConfigJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs index c65510478..45d654edc 100644 --- a/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs +++ b/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration.Hid.Controller.Motion +namespace Ryujinx.Common.Configuration.Hid.Controller.Motion { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum MotionInputBackendType : byte { Invalid, diff --git a/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/Ryujinx.Common/Configuration/Hid/ControllerType.cs index 70f811c89..0ad01bbb6 100644 --- a/Ryujinx.Common/Configuration/Hid/ControllerType.cs +++ b/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -1,12 +1,9 @@ -using Ryujinx.Common.Utilities; using System; -using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { - // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical [Flags] - [JsonConverter(typeof(TypedStringEnumConverter))] + // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical public enum ControllerType : int { None, diff --git a/Ryujinx.Common/Configuration/Hid/InputBackendType.cs b/Ryujinx.Common/Configuration/Hid/InputBackendType.cs index 1db3f5703..9e944f9e8 100644 --- a/Ryujinx.Common/Configuration/Hid/InputBackendType.cs +++ b/Ryujinx.Common/Configuration/Hid/InputBackendType.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration.Hid +namespace Ryujinx.Common.Configuration.Hid { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum InputBackendType { Invalid, diff --git a/Ryujinx.Common/Configuration/Hid/InputConfig.cs b/Ryujinx.Common/Configuration/Hid/InputConfig.cs index 16c8f8e32..3364e35fa 100644 --- a/Ryujinx.Common/Configuration/Hid/InputConfig.cs +++ b/Ryujinx.Common/Configuration/Hid/InputConfig.cs @@ -1,10 +1,8 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { - [JsonConverter(typeof(JsonInputConfigConverter))] public class InputConfig : INotifyPropertyChanged { /// diff --git a/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs b/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs deleted file mode 100644 index 254c4feb4..000000000 --- a/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Ryujinx.Common.Configuration.Hid.Controller; -using Ryujinx.Common.Configuration.Hid.Keyboard; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration.Hid -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(InputConfig))] - [JsonSerializable(typeof(StandardKeyboardInputConfig))] - [JsonSerializable(typeof(StandardControllerInputConfig))] - public partial class InputConfigJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs b/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs index 08bbcbf17..7223ad451 100644 --- a/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs +++ b/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs @@ -1,16 +1,13 @@ using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Keyboard; -using Ryujinx.Common.Utilities; using System; using System.Text.Json; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { - public class JsonInputConfigConverter : JsonConverter + class JsonInputConfigConverter : JsonConverter { - private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader) { // Temporary reader to get the backend type @@ -57,8 +54,8 @@ namespace Ryujinx.Common.Configuration.Hid return backendType switch { - InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig), - InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig), + InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options), + InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options), _ => throw new InvalidOperationException($"Unknown backend type {backendType}"), }; } @@ -68,10 +65,10 @@ namespace Ryujinx.Common.Configuration.Hid switch (value.Backend) { case InputBackendType.WindowKeyboard: - JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig); + JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options); break; case InputBackendType.GamepadSDL2: - JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig); + JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options); break; default: throw new ArgumentException($"Unknown backend type {value.Backend}"); diff --git a/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs b/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs index dd6495d4d..2e34cb96c 100644 --- a/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs +++ b/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs @@ -1,10 +1,6 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - namespace Ryujinx.Common.Configuration.Hid { // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical - [JsonConverter(typeof(TypedStringEnumConverter))] public enum PlayerIndex : int { Player1 = 0, diff --git a/Ryujinx.Common/Configuration/MemoryManagerMode.cs b/Ryujinx.Common/Configuration/MemoryManagerMode.cs index f10fd6f1b..ad6c2a346 100644 --- a/Ryujinx.Common/Configuration/MemoryManagerMode.cs +++ b/Ryujinx.Common/Configuration/MemoryManagerMode.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration +namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum MemoryManagerMode : byte { SoftwarePageTable, diff --git a/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs b/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs deleted file mode 100644 index 5b661b878..000000000 --- a/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Configuration -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(TitleUpdateMetadata))] - public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs index 28a7d5461..b9a08323e 100644 --- a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs +++ b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs @@ -1,20 +1,22 @@ -using System.Text; +using System; +using System.Reflection; +using System.Text; namespace Ryujinx.Common.Logging { internal class DefaultLogFormatter : ILogFormatter { - private static readonly ObjectPool StringBuilderPool = SharedPools.Default(); + private static readonly ObjectPool _stringBuilderPool = SharedPools.Default(); public string Format(LogEventArgs args) { - StringBuilder sb = StringBuilderPool.Allocate(); + StringBuilder sb = _stringBuilderPool.Allocate(); try { sb.Clear(); - sb.Append($@"{args.Time:hh\:mm\:ss\.fff}"); + sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time); sb.Append($" |{args.Level.ToString()[0]}| "); if (args.ThreadName != null) @@ -25,17 +27,53 @@ namespace Ryujinx.Common.Logging sb.Append(args.Message); - if (args.Data is not null) + if (args.Data != null) { - sb.Append(' '); - DynamicObjectFormatter.Format(sb, args.Data); + PropertyInfo[] props = args.Data.GetType().GetProperties(); + + sb.Append(" {"); + + foreach (var prop in props) + { + sb.Append(prop.Name); + sb.Append(": "); + + if (typeof(Array).IsAssignableFrom(prop.PropertyType)) + { + Array array = (Array)prop.GetValue(args.Data); + foreach (var item in array) + { + sb.Append(item.ToString()); + sb.Append(", "); + } + + if (array.Length > 0) + { + sb.Remove(sb.Length - 2, 2); + } + } + else + { + sb.Append(prop.GetValue(args.Data)); + } + + sb.Append(" ; "); + } + + // We remove the final ';' from the string + if (props.Length > 0) + { + sb.Remove(sb.Length - 3, 3); + } + + sb.Append('}'); } return sb.ToString(); } finally { - StringBuilderPool.Release(sb); + _stringBuilderPool.Release(sb); } } } diff --git a/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs b/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs deleted file mode 100644 index 5f15cc2a6..000000000 --- a/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs +++ /dev/null @@ -1,84 +0,0 @@ -#nullable enable -using System; -using System.Reflection; -using System.Text; - -namespace Ryujinx.Common.Logging -{ - internal class DynamicObjectFormatter - { - private static readonly ObjectPool StringBuilderPool = SharedPools.Default(); - - public static string? Format(object? dynamicObject) - { - if (dynamicObject is null) - { - return null; - } - - StringBuilder sb = StringBuilderPool.Allocate(); - - try - { - Format(sb, dynamicObject); - - return sb.ToString(); - } - finally - { - StringBuilderPool.Release(sb); - } - } - - public static void Format(StringBuilder sb, object? dynamicObject) - { - if (dynamicObject is null) - { - return; - } - - PropertyInfo[] props = dynamicObject.GetType().GetProperties(); - - sb.Append('{'); - - foreach (var prop in props) - { - sb.Append(prop.Name); - sb.Append(": "); - - if (typeof(Array).IsAssignableFrom(prop.PropertyType)) - { - Array? array = (Array?) prop.GetValue(dynamicObject); - - if (array is not null) - { - foreach (var item in array) - { - sb.Append(item); - sb.Append(", "); - } - - if (array.Length > 0) - { - sb.Remove(sb.Length - 2, 2); - } - } - } - else - { - sb.Append(prop.GetValue(dynamicObject)); - } - - sb.Append(" ; "); - } - - // We remove the final ';' from the string - if (props.Length > 0) - { - sb.Remove(sb.Length - 3, 3); - } - - sb.Append('}'); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index e62676cd3..7e53c972b 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - namespace Ryujinx.Common.Logging { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum LogClass { Application, diff --git a/Ryujinx.Common/Logging/LogEventArgs.cs b/Ryujinx.Common/Logging/LogEventArgs.cs index a27af7809..511c8e6e2 100644 --- a/Ryujinx.Common/Logging/LogEventArgs.cs +++ b/Ryujinx.Common/Logging/LogEventArgs.cs @@ -11,7 +11,15 @@ namespace Ryujinx.Common.Logging public readonly string Message; public readonly object Data; - public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null) + public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message) + { + Level = level; + Time = time; + ThreadName = threadName; + Message = message; + } + + public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data) { Level = level; Time = time; diff --git a/Ryujinx.Common/Logging/LogEventArgsJson.cs b/Ryujinx.Common/Logging/LogEventArgsJson.cs deleted file mode 100644 index 425b97662..000000000 --- a/Ryujinx.Common/Logging/LogEventArgsJson.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Logging -{ - internal class LogEventArgsJson - { - public LogLevel Level { get; } - public TimeSpan Time { get; } - public string ThreadName { get; } - - public string Message { get; } - public string Data { get; } - - [JsonConstructor] - public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null) - { - Level = level; - Time = time; - ThreadName = threadName; - Message = message; - Data = data; - } - - public static LogEventArgsJson FromLogEventArgs(LogEventArgs args) - { - return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs b/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs deleted file mode 100644 index da21f11e8..000000000 --- a/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Logging -{ - [JsonSerializable(typeof(LogEventArgsJson))] - internal partial class LogEventJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Logging/LogLevel.cs b/Ryujinx.Common/Logging/LogLevel.cs index 3786c7561..8857fb45a 100644 --- a/Ryujinx.Common/Logging/LogLevel.cs +++ b/Ryujinx.Common/Logging/LogLevel.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - namespace Ryujinx.Common.Logging { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum LogLevel { Debug, diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs index 06976433e..95f96576c 100644 --- a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs +++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs @@ -1,5 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.IO; +using System.IO; +using System.Text.Json; namespace Ryujinx.Common.Logging { @@ -25,8 +25,12 @@ namespace Ryujinx.Common.Logging public void Log(object sender, LogEventArgs e) { - var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e); - JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson); + string text = JsonSerializer.Serialize(e); + + using (BinaryWriter writer = new BinaryWriter(_stream)) + { + writer.Write(text); + } } public void Dispose() diff --git a/Ryujinx.Common/Utilities/CommonJsonContext.cs b/Ryujinx.Common/Utilities/CommonJsonContext.cs deleted file mode 100644 index d7b3f78cd..000000000 --- a/Ryujinx.Common/Utilities/CommonJsonContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Utilities -{ - [JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")] - [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "StringDictionary")] - public partial class CommonJsonContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Common/Utilities/JsonHelper.cs b/Ryujinx.Common/Utilities/JsonHelper.cs index 9a2d6f181..36f391149 100644 --- a/Ryujinx.Common/Utilities/JsonHelper.cs +++ b/Ryujinx.Common/Utilities/JsonHelper.cs @@ -1,62 +1,15 @@ -using System.IO; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller.Motion; +using System.IO; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; +using System.Text.Json.Serialization; namespace Ryujinx.Common.Utilities { public class JsonHelper { - private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy(); - private const int DefaultFileWriteBufferSize = 4096; - - /// - /// Creates new serializer options with default settings. - /// - /// - /// It is REQUIRED for you to save returned options statically or as a part of static serializer context - /// in order to avoid performance issues. You can safely modify returned options for your case before storing. - /// - public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) - { - JsonSerializerOptions options = new() - { - DictionaryKeyPolicy = SnakeCasePolicy, - PropertyNamingPolicy = SnakeCasePolicy, - WriteIndented = indented, - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip - }; - - return options; - } - - public static string Serialize(T value, JsonTypeInfo typeInfo) - { - return JsonSerializer.Serialize(value, typeInfo); - } - - public static T Deserialize(string value, JsonTypeInfo typeInfo) - { - return JsonSerializer.Deserialize(value, typeInfo); - } - - public static void SerializeToFile(string filePath, T value, JsonTypeInfo typeInfo) - { - using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough); - JsonSerializer.Serialize(file, value, typeInfo); - } - - public static T DeserializeFromFile(string filePath, JsonTypeInfo typeInfo) - { - using FileStream file = File.OpenRead(filePath); - return JsonSerializer.Deserialize(file, typeInfo); - } - - public static void SerializeToStream(Stream stream, T value, JsonTypeInfo typeInfo) - { - JsonSerializer.Serialize(stream, value, typeInfo); - } + public static JsonNamingPolicy SnakeCase { get; } private class SnakeCaseNamingPolicy : JsonNamingPolicy { @@ -67,7 +20,7 @@ namespace Ryujinx.Common.Utilities return name; } - StringBuilder builder = new(); + StringBuilder builder = new StringBuilder(); for (int i = 0; i < name.Length; i++) { @@ -81,7 +34,7 @@ namespace Ryujinx.Common.Utilities } else { - builder.Append('_'); + builder.Append("_"); builder.Append(char.ToLowerInvariant(c)); } } @@ -94,5 +47,64 @@ namespace Ryujinx.Common.Utilities return builder.ToString(); } } + + static JsonHelper() + { + SnakeCase = new SnakeCaseNamingPolicy(); + } + + public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false) + { + JsonSerializerOptions options = new JsonSerializerOptions + { + DictionaryKeyPolicy = SnakeCase, + PropertyNamingPolicy = SnakeCase, + WriteIndented = prettyPrint, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonInputConfigConverter()); + options.Converters.Add(new JsonMotionConfigControllerConverter()); + + return options; + } + + public static T Deserialize(Stream stream) + { + using (BinaryReader reader = new BinaryReader(stream)) + { + return JsonSerializer.Deserialize(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions()); + } + } + + public static T DeserializeFromFile(string path) + { + return Deserialize(File.ReadAllText(path)); + } + + public static T Deserialize(string json) + { + return JsonSerializer.Deserialize(json, GetDefaultSerializerOptions()); + } + + public static void Serialize(Stream stream, TValue obj, bool prettyPrint = false) + { + using (BinaryWriter writer = new BinaryWriter(stream)) + { + writer.Write(SerializeToUtf8Bytes(obj, prettyPrint)); + } + } + + public static string Serialize(TValue obj, bool prettyPrint = false) + { + return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint)); + } + + public static byte[] SerializeToUtf8Bytes(T obj, bool prettyPrint = false) + { + return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint)); + } } -} \ No newline at end of file +} diff --git a/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs b/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs deleted file mode 100644 index c0127dc4a..000000000 --- a/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -#nullable enable -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Utilities -{ - /// - /// Specifies that value of will be serialized as string in JSONs - /// - /// - /// Trimming friendly alternative to . - /// Get rid of this converter if dotnet supports similar functionality out of the box. - /// - /// Type of enum to serialize - public sealed class TypedStringEnumConverter : JsonConverter where TEnum : struct, Enum - { - public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var enumValue = reader.GetString(); - if (string.IsNullOrEmpty(enumValue)) - { - return default; - } - - return Enum.Parse(enumValue); - } - - public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } -} diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs index 7d06e5eb7..82bd9b312 100644 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs @@ -13,7 +13,6 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.Loaders.Executables; @@ -25,13 +24,14 @@ using System.IO; using System.Linq; using System.Reflection; using System.Text; -using System.Text.Json; using static Ryujinx.HLE.HOS.ModLoader; using ApplicationId = LibHac.Ncm.ApplicationId; using Path = System.IO.Path; namespace Ryujinx.HLE.HOS { + using JsonHelper = Common.Utilities.JsonHelper; + public class ApplicationLoader { // Binaries from exefs are loaded into mem in this order. Do not change. @@ -57,10 +57,6 @@ namespace Ryujinx.HLE.HOS private string _displayVersion; private BlitStruct _controlData; - private static readonly JsonSerializerOptions SerializerOptions = JsonHelper.GetDefaultSerializerOptions(); - private static readonly DownloadableContentJsonSerializerContext ContentSerializerContext = new(SerializerOptions); - private static readonly TitleUpdateMetadataJsonSerializerContext TitleSerializerContext = new(SerializerOptions); - public BlitStruct ControlData => _controlData; public string TitleName => _titleName; public string DisplayVersion => _displayVersion; @@ -201,7 +197,7 @@ namespace Ryujinx.HLE.HOS if (File.Exists(titleUpdateMetadataPath)) { - updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, TitleSerializerContext.TitleUpdateMetadata).Selected; + updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath).Selected; if (File.Exists(updatePath)) { @@ -415,7 +411,7 @@ namespace Ryujinx.HLE.HOS if (File.Exists(titleAocMetadataPath)) { - List dlcContainerList = JsonHelper.DeserializeFromFile(titleAocMetadataPath, ContentSerializerContext.ListDownloadableContentContainer); + List dlcContainerList = JsonHelper.DeserializeFromFile>(titleAocMetadataPath); foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) { diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs index 535779d2e..ec0b0a10b 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs @@ -1,11 +1,11 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; -using Ryujinx.HLE.HOS.Services.Account.Acc.Types; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Text.Json.Serialization; namespace Ryujinx.HLE.HOS.Services.Account.Acc { @@ -13,7 +13,29 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc { private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json"); - private static readonly ProfilesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + private struct ProfilesJson + { + [JsonPropertyName("profiles")] + public List Profiles { get; set; } + [JsonPropertyName("last_opened")] + public string LastOpened { get; set; } + } + + private struct UserProfileJson + { + [JsonPropertyName("user_id")] + public string UserId { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("account_state")] + public AccountState AccountState { get; set; } + [JsonPropertyName("online_play_state")] + public AccountState OnlinePlayState { get; set; } + [JsonPropertyName("last_modified_timestamp")] + public long LastModifiedTimestamp { get; set; } + [JsonPropertyName("image")] + public byte[] Image { get; set; } + } public UserId LastOpened { get; set; } @@ -25,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc { try { - ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, SerializerContext.ProfilesJson); + ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath); foreach (var profile in profilesJson.Profiles) { @@ -70,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc }); } - JsonHelper.SerializeToFile(_profilesJsonPath, profilesJson, SerializerContext.ProfilesJson); + File.WriteAllText(_profilesJsonPath, JsonHelper.Serialize(profilesJson, true)); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs deleted file mode 100644 index 6b54898e5..000000000 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc.Types; -using System.Text.Json.Serialization; - -namespace Ryujinx.HLE.HOS.Services.Account.Acc -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(ProfilesJson))] - internal partial class ProfilesJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs index 1699abfbd..2382a2554 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - namespace Ryujinx.HLE.HOS.Services.Account.Acc { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum AccountState { Closed, diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs deleted file mode 100644 index 09f9d1421..000000000 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types -{ - internal struct ProfilesJson - { - public List Profiles { get; set; } - public string LastOpened { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs deleted file mode 100644 index 06ff4833f..000000000 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types -{ - internal struct UserProfileJson - { - public string UserId { get; set; } - public string Name { get; set; } - public AccountState AccountState { get; set; } - public AccountState OnlinePlayState { get; set; } - public long LastModifiedTimestamp { get; set; } - public byte[] Image { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs deleted file mode 100644 index e75f62004..000000000 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; -using System.Text.Json.Serialization; - -namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp -{ - [JsonSerializable(typeof(VirtualAmiiboFile))] - internal partial class AmiiboJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs index 9166e87fa..4fdeadcb2 100644 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -1,6 +1,5 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.Memory; -using Ryujinx.Common.Utilities; using Ryujinx.Cpu; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii.Types; @@ -9,6 +8,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using System.Text.Json; namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { @@ -16,8 +17,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { private static uint _openedApplicationAreaId; - private static readonly AmiiboJsonSerializerContext SerializerContext = AmiiboJsonSerializerContext.Default; - public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) { if (useRandomUuid) @@ -174,7 +173,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp if (File.Exists(filePath)) { - virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, SerializerContext.VirtualAmiiboFile); + virtualAmiiboFile = JsonSerializer.Deserialize(File.ReadAllText(filePath), new JsonSerializerOptions(JsonSerializerDefaults.General)); } else { @@ -198,7 +197,8 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) { string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); - JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, SerializerContext.VirtualAmiiboFile); + + File.WriteAllText(filePath, JsonSerializer.Serialize(virtualAmiiboFile)); } } } diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index 40eec4a73..f618e38d6 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/Ryujinx.Headless.SDL2/Program.cs @@ -56,8 +56,6 @@ namespace Ryujinx.Headless.SDL2 private static bool _enableKeyboard; private static bool _enableMouse; - private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - static void Main(string[] args) { Version = ReleaseInformation.GetVersion(); @@ -287,7 +285,10 @@ namespace Ryujinx.Headless.SDL2 try { - config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); + using (Stream stream = File.OpenRead(path)) + { + config = JsonHelper.Deserialize(stream); + } } catch (JsonException) { diff --git a/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs b/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs deleted file mode 100644 index f81121c28..000000000 --- a/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.App.Common -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(ApplicationMetadata))] - internal partial class ApplicationJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs index add6dad3f..43510d5ec 100644 --- a/Ryujinx.Ui.Common/App/ApplicationLibrary.cs +++ b/Ryujinx.Ui.Common/App/ApplicationLibrary.cs @@ -10,7 +10,6 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.SystemState; @@ -23,6 +22,7 @@ using System.Reflection; using System.Text; using System.Text.Json; using System.Threading; +using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; using Path = System.IO.Path; namespace Ryujinx.Ui.App.Common @@ -42,8 +42,6 @@ namespace Ryujinx.Ui.App.Common private Language _desiredTitleLanguage; private CancellationTokenSource _cancellationToken; - private static readonly ApplicationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public ApplicationLibrary(VirtualFileSystem virtualFileSystem) { _virtualFileSystem = virtualFileSystem; @@ -492,12 +490,14 @@ namespace Ryujinx.Ui.App.Common appMetadata = new ApplicationMetadata(); - JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata); + using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough); + + JsonHelper.Serialize(stream, appMetadata, true); } try { - appMetadata = JsonHelper.DeserializeFromFile(metadataFile, SerializerContext.ApplicationMetadata); + appMetadata = JsonHelper.DeserializeFromFile(metadataFile); } catch (JsonException) { @@ -510,7 +510,9 @@ namespace Ryujinx.Ui.App.Common { modifyFunction(appMetadata); - JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata); + using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough); + + JsonHelper.Serialize(stream, appMetadata, true); } return appMetadata; diff --git a/Ryujinx.Ui.Common/Configuration/AudioBackend.cs b/Ryujinx.Ui.Common/Configuration/AudioBackend.cs index 1f9bd0baf..99111ea64 100644 --- a/Ryujinx.Ui.Common/Configuration/AudioBackend.cs +++ b/Ryujinx.Ui.Common/Configuration/AudioBackend.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Configuration +namespace Ryujinx.Ui.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum AudioBackend { Dummy, diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs index 14c03957a..e9aec04b2 100644 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs @@ -5,7 +5,7 @@ using Ryujinx.Common.Utilities; using Ryujinx.Ui.Common.Configuration.System; using Ryujinx.Ui.Common.Configuration.Ui; using System.Collections.Generic; -using System.Text.Json.Nodes; +using System.IO; namespace Ryujinx.Ui.Common.Configuration { @@ -321,14 +321,14 @@ namespace Ryujinx.Ui.Common.Configuration /// /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions) /// TODO: Remove this when those older versions aren't in use anymore. - public List KeyboardConfig { get; set; } + public List KeyboardConfig { get; set; } /// /// Legacy controller control bindings /// /// Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions) /// TODO: Remove this when those older versions aren't in use anymore. - public List ControllerConfig { get; set; } + public List ControllerConfig { get; set; } /// /// Input configurations @@ -354,12 +354,11 @@ namespace Ryujinx.Ui.Common.Configuration /// Loads a configuration file from disk /// /// The path to the JSON configuration file - /// Parsed configuration file public static bool TryLoad(string path, out ConfigurationFileFormat configurationFileFormat) { try { - configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + configurationFileFormat = JsonHelper.DeserializeFromFile(path); return configurationFileFormat.Version != 0; } @@ -377,7 +376,8 @@ namespace Ryujinx.Ui.Common.Configuration /// The path to the JSON configuration file public void SaveConfig(string path) { - JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); + using FileStream fileStream = File.Create(path, 4096, FileOptions.WriteThrough); + JsonHelper.Serialize(fileStream, this, true); } } } diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs deleted file mode 100644 index 6ce2ef01a..000000000 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Ryujinx.Common.Utilities; - -namespace Ryujinx.Ui.Common.Configuration -{ - internal static class ConfigurationFileFormatSettings - { - public static readonly ConfigurationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs deleted file mode 100644 index bb8dfb499..000000000 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Configuration -{ - [JsonSourceGenerationOptions(WriteIndented = true)] - [JsonSerializable(typeof(ConfigurationFileFormat))] - internal partial class ConfigurationJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs b/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs index 82a331c16..bcdd2e70a 100644 --- a/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs +++ b/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs @@ -9,7 +9,6 @@ using Ryujinx.Ui.Common.Configuration.Ui; using Ryujinx.Ui.Common.Helper; using System; using System.Collections.Generic; -using System.Text.Json.Nodes; namespace Ryujinx.Ui.Common.Configuration { @@ -632,8 +631,8 @@ namespace Ryujinx.Ui.Common.Configuration EnableKeyboard = Hid.EnableKeyboard, EnableMouse = Hid.EnableMouse, Hotkeys = Hid.Hotkeys, - KeyboardConfig = new List(), - ControllerConfig = new List(), + KeyboardConfig = new List(), + ControllerConfig = new List(), InputConfig = Hid.InputConfig, GraphicsBackend = Graphics.GraphicsBackend, PreferredGpu = Graphics.PreferredGpu diff --git a/Ryujinx.Ui.Common/Configuration/System/Language.cs b/Ryujinx.Ui.Common/Configuration/System/Language.cs index 404f8063d..3d2dc9914 100644 --- a/Ryujinx.Ui.Common/Configuration/System/Language.cs +++ b/Ryujinx.Ui.Common/Configuration/System/Language.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Configuration.System +namespace Ryujinx.Ui.Common.Configuration.System { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum Language { Japanese, diff --git a/Ryujinx.Ui.Common/Configuration/System/Region.cs b/Ryujinx.Ui.Common/Configuration/System/Region.cs index 7dfac6388..fb51e08e5 100644 --- a/Ryujinx.Ui.Common/Configuration/System/Region.cs +++ b/Ryujinx.Ui.Common/Configuration/System/Region.cs @@ -1,9 +1,5 @@ -using Ryujinx.Common.Utilities; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Configuration.System +namespace Ryujinx.Ui.Common.Configuration.System { - [JsonConverter(typeof(TypedStringEnumConverter))] public enum Region { Japan, diff --git a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs b/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs deleted file mode 100644 index f412b9504..000000000 --- a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Amiibo -{ - public struct AmiiboApi : IEquatable - { - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("head")] - public string Head { get; set; } - [JsonPropertyName("tail")] - public string Tail { get; set; } - [JsonPropertyName("image")] - public string Image { get; set; } - [JsonPropertyName("amiiboSeries")] - public string AmiiboSeries { get; set; } - [JsonPropertyName("character")] - public string Character { get; set; } - [JsonPropertyName("gameSeries")] - public string GameSeries { get; set; } - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("release")] - public Dictionary Release { get; set; } - - [JsonPropertyName("gamesSwitch")] - public List GamesSwitch { get; set; } - - public override string ToString() - { - return Name; - } - - public string GetId() - { - return Head + Tail; - } - - public bool Equals(AmiiboApi other) - { - return Head + Tail == other.Head + other.Tail; - } - - public override bool Equals(object obj) - { - return obj is AmiiboApi other && Equals(other); - } - - public override int GetHashCode() - { - return HashCode.Combine(Head, Tail); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs b/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs deleted file mode 100644 index def7d1bcc..000000000 --- a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Amiibo -{ - public class AmiiboApiGamesSwitch - { - [JsonPropertyName("amiiboUsage")] - public List AmiiboUsage { get; set; } - [JsonPropertyName("gameID")] - public List GameId { get; set; } - [JsonPropertyName("gameName")] - public string GameName { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs b/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs deleted file mode 100644 index 814573c22..000000000 --- a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Amiibo -{ - public class AmiiboApiUsage - { - [JsonPropertyName("Usage")] - public string Usage { get; set; } - [JsonPropertyName("write")] - public bool Write { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs b/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs deleted file mode 100644 index feb7993c1..000000000 --- a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Amiibo -{ - public struct AmiiboJson - { - [JsonPropertyName("amiibo")] - public List Amiibo { get; set; } - [JsonPropertyName("lastUpdated")] - public DateTime LastUpdated { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs b/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs deleted file mode 100644 index 4cbb5a7b4..000000000 --- a/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Amiibo -{ - [JsonSerializable(typeof(AmiiboJson))] - public partial class AmiiboJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs b/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs deleted file mode 100644 index 10d014783..000000000 --- a/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Ryujinx.Ui.Common.Models.Github -{ - public class GithubReleaseAssetJsonResponse - { - public string Name { get; set; } - public string State { get; set; } - public string BrowserDownloadUrl { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs b/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs deleted file mode 100644 index 954d03e31..000000000 --- a/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Ryujinx.Ui.Common.Models.Github -{ - public class GithubReleasesJsonResponse - { - public string Name { get; set; } - public List Assets { get; set; } - } -} \ No newline at end of file diff --git a/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs b/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs deleted file mode 100644 index e5fd9d094..000000000 --- a/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Ryujinx.Ui.Common.Models.Github -{ - [JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)] - public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext - { - } -} \ No newline at end of file diff --git a/Ryujinx/Modules/Updater/Updater.cs b/Ryujinx/Modules/Updater/Updater.cs index 3f186ce6b..5ad5924e8 100644 --- a/Ryujinx/Modules/Updater/Updater.cs +++ b/Ryujinx/Modules/Updater/Updater.cs @@ -2,14 +2,14 @@ using Gtk; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json.Linq; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Common.Utilities; using Ryujinx.Ui; -using Ryujinx.Ui.Common.Models.Github; using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -38,8 +38,6 @@ namespace Ryujinx.Modules private static string _buildUrl; private static long _buildSize; - private static readonly GithubReleasesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates. private static readonly string[] WindowsDependencyDirs = new string[] { "bin", "etc", "lib", "share" }; @@ -109,16 +107,22 @@ namespace Ryujinx.Modules // Fetch latest build information string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); - var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse); - _buildVer = fetched.Name; + JObject jsonRoot = JObject.Parse(fetchedJson); + JToken assets = jsonRoot["assets"]; - foreach (var asset in fetched.Assets) + _buildVer = (string)jsonRoot["name"]; + + foreach (JToken asset in assets) { - if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt)) - { - _buildUrl = asset.BrowserDownloadUrl; + string assetName = (string)asset["name"]; + string assetState = (string)asset["state"]; + string downloadURL = (string)asset["browser_download_url"]; - if (asset.State != "uploaded") + if (assetName.StartsWith("ryujinx") && assetName.EndsWith(_platformExt)) + { + _buildUrl = downloadURL; + + if (assetState != "uploaded") { if (showVersionUpToDate) { diff --git a/Ryujinx/Ui/Windows/AboutWindow.cs b/Ryujinx/Ui/Windows/AboutWindow.cs index 41cf9c013..ea827a92f 100644 --- a/Ryujinx/Ui/Windows/AboutWindow.cs +++ b/Ryujinx/Ui/Windows/AboutWindow.cs @@ -31,7 +31,7 @@ namespace Ryujinx.Ui.Windows { string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); - _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray)); + _patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString)); } catch { diff --git a/Ryujinx/Ui/Windows/AmiiboWindow.cs b/Ryujinx/Ui/Windows/AmiiboWindow.cs index 470032373..9140a14e9 100644 --- a/Ryujinx/Ui/Windows/AmiiboWindow.cs +++ b/Ryujinx/Ui/Windows/AmiiboWindow.cs @@ -3,7 +3,6 @@ using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Utilities; using Ryujinx.Ui.Common.Configuration; -using Ryujinx.Ui.Common.Models.Amiibo; using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; @@ -12,15 +11,65 @@ using System.Linq; using System.Net.Http; using System.Reflection; using System.Text; -using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; -using AmiiboApi = Ryujinx.Ui.Common.Models.Amiibo.AmiiboApi; -using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext; namespace Ryujinx.Ui.Windows { public partial class AmiiboWindow : Window { + private struct AmiiboJson + { + [JsonPropertyName("amiibo")] + public List Amiibo { get; set; } + [JsonPropertyName("lastUpdated")] + public DateTime LastUpdated { get; set; } + } + + private struct AmiiboApi + { + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("head")] + public string Head { get; set; } + [JsonPropertyName("tail")] + public string Tail { get; set; } + [JsonPropertyName("image")] + public string Image { get; set; } + [JsonPropertyName("amiiboSeries")] + public string AmiiboSeries { get; set; } + [JsonPropertyName("character")] + public string Character { get; set; } + [JsonPropertyName("gameSeries")] + public string GameSeries { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("release")] + public Dictionary Release { get; set; } + + [JsonPropertyName("gamesSwitch")] + public List GamesSwitch { get; set; } + } + + private class AmiiboApiGamesSwitch + { + [JsonPropertyName("amiiboUsage")] + public List AmiiboUsage { get; set; } + [JsonPropertyName("gameID")] + public List GameId { get; set; } + [JsonPropertyName("gameName")] + public string GameName { get; set; } + } + + private class AmiiboApiUsage + { + [JsonPropertyName("Usage")] + public string Usage { get; set; } + [JsonPropertyName("write")] + public bool Write { get; set; } + } + private const string DEFAULT_JSON = "{ \"amiibo\": [] }"; public string AmiiboId { get; private set; } @@ -47,8 +96,6 @@ namespace Ryujinx.Ui.Windows private List _amiiboList; - private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") { Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); @@ -80,9 +127,9 @@ namespace Ryujinx.Ui.Windows if (File.Exists(_amiiboJsonPath)) { - amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath); + amiiboJsonString = File.ReadAllText(_amiiboJsonPath); - if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated)) + if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString).LastUpdated)) { amiiboJsonString = await DownloadAmiiboJson(); } @@ -101,7 +148,7 @@ namespace Ryujinx.Ui.Windows } } - _amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo; + _amiiboList = JsonHelper.Deserialize(amiiboJsonString).Amiibo; _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); if (LastScannedAmiiboShowAll) diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs index 9b4befd85..0f0fba0b8 100644 --- a/Ryujinx/Ui/Windows/ControllerWindow.cs +++ b/Ryujinx/Ui/Windows/ControllerWindow.cs @@ -115,8 +115,6 @@ namespace Ryujinx.Ui.Windows private bool _mousePressed; private bool _middleMousePressed; - private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { } private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) @@ -1122,7 +1120,10 @@ namespace Ryujinx.Ui.Windows try { - config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); + using (Stream stream = File.OpenRead(path)) + { + config = JsonHelper.Deserialize(stream); + } } catch (JsonException) { } } @@ -1144,7 +1145,9 @@ namespace Ryujinx.Ui.Windows if (profileDialog.Run() == (int)ResponseType.Ok) { string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); - string jsonString = JsonHelper.Serialize(inputConfig, SerializerContext.InputConfig); + string jsonString; + + jsonString = JsonHelper.Serialize(inputConfig, true); File.WriteAllText(path, jsonString); } diff --git a/Ryujinx/Ui/Windows/DlcWindow.cs b/Ryujinx/Ui/Windows/DlcWindow.cs index b22f15932..9fccec195 100644 --- a/Ryujinx/Ui/Windows/DlcWindow.cs +++ b/Ryujinx/Ui/Windows/DlcWindow.cs @@ -7,13 +7,15 @@ using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.Ui.Widgets; using System; using System.Collections.Generic; using System.IO; -using GUI = Gtk.Builder.ObjectAttribute; +using System.Text; + +using GUI = Gtk.Builder.ObjectAttribute; +using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; namespace Ryujinx.Ui.Windows { @@ -24,8 +26,6 @@ namespace Ryujinx.Ui.Windows private readonly string _dlcJsonPath; private readonly List _dlcContainerList; - private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); - #pragma warning disable CS0649, IDE0044 [GUI] Label _baseTitleInfoLabel; [GUI] TreeView _dlcTreeView; @@ -45,7 +45,7 @@ namespace Ryujinx.Ui.Windows try { - _dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, SerializerContext.ListDownloadableContentContainer); + _dlcContainerList = JsonHelper.DeserializeFromFile>(_dlcJsonPath); } catch { @@ -260,7 +260,10 @@ namespace Ryujinx.Ui.Windows while (_dlcTreeView.Model.IterNext(ref parentIter)); } - JsonHelper.SerializeToFile(_dlcJsonPath, _dlcContainerList, SerializerContext.ListDownloadableContentContainer); + using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough)) + { + dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true))); + } Dispose(); } diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs index 226473fca..4aea58955 100644 --- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs +++ b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs @@ -7,7 +7,6 @@ using LibHac.Ns; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common.Configuration; -using Ryujinx.Common.Utilities; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.Ui.Widgets; @@ -15,8 +14,10 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using GUI = Gtk.Builder.ObjectAttribute; -using SpanHelpers = LibHac.Common.SpanHelpers; +using System.Text; + +using GUI = Gtk.Builder.ObjectAttribute; +using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; namespace Ryujinx.Ui.Windows { @@ -30,7 +31,6 @@ namespace Ryujinx.Ui.Windows private TitleUpdateMetadata _titleUpdateWindowData; private readonly Dictionary _radioButtonToPathDictionary; - private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); #pragma warning disable CS0649, IDE0044 [GUI] Label _baseTitleInfoLabel; @@ -53,7 +53,7 @@ namespace Ryujinx.Ui.Windows try { - _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, SerializerContext.TitleUpdateMetadata); + _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath); } catch { @@ -192,7 +192,10 @@ namespace Ryujinx.Ui.Windows } } - JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata); + using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough)) + { + dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true))); + } _parent.UpdateGameTable();