0
0
Fork 0

Revert "Use source generated json serializers in order to improve code trimming (#4094)" (#4576)

This reverts commit 4ce4299ca2.
This commit is contained in:
gdkchan 2023-03-21 20:14:46 -03:00 committed by GitHub
parent 4ce4299ca2
commit ba95ee54ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 608 additions and 886 deletions

View file

@ -63,10 +63,6 @@ dotnet_code_quality_unused_parameters = all:suggestion
#### C# Coding Conventions #### #### C# Coding Conventions ####
# Namespace preferences
csharp_style_namespace_declarations = block_scoped:warning
resharper_csharp_namespace_body = block_scoped
# var preferences # var preferences
csharp_style_var_elsewhere = false:silent csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent csharp_style_var_for_built_in_types = false:silent

View file

@ -1,7 +1,6 @@
namespace ARMeilleure.Decoders namespace ARMeilleure.Decoders;
interface IOpCode32Exception
{ {
interface IOpCode32Exception int Id { get; }
{
int Id { get; }
}
} }

View file

@ -130,7 +130,7 @@ namespace Ryujinx.Ava.Common.Locale
{ {
var localeStrings = new Dictionary<LocaleKeys, string>(); var localeStrings = new Dictionary<LocaleKeys, string>();
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
foreach (var item in strings) foreach (var item in strings)
{ {

View file

@ -4,14 +4,13 @@ using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Ava; using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.Models.Github;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -32,7 +31,6 @@ namespace Ryujinx.Modules
internal static class Updater internal static class Updater
{ {
private const string GitHubApiURL = "https://api.github.com"; 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 HomeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); 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 buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse); JObject jsonRoot = JObject.Parse(fetchedJson);
_buildVer = fetched.Name; 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)) string assetName = (string)asset["name"];
{ string assetState = (string)asset["state"];
_buildUrl = asset.BrowserDownloadUrl; 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) if (showVersionUpToDate)
{ {

View file

@ -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<AmiiboApi> 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<string, string> Release { get; set; }
[JsonPropertyName("gamesSwitch")] public List<AmiiboApiGamesSwitch> 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<AmiiboApiUsage> AmiiboUsage { get; set; }
[JsonPropertyName("gameID")] public List<string> 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; }
}
}
}

View file

@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); 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<string[]>(patreonJsonString)) + "\n\n";
} }
catch catch
{ {

View file

@ -4,11 +4,11 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Models.Amiibo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -17,7 +17,6 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@ -32,8 +31,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private readonly StyleableWindow _owner; private readonly StyleableWindow _owner;
private Bitmap _amiiboImage; private Bitmap _amiiboImage;
private List<AmiiboApi> _amiiboList; private List<Amiibo.AmiiboApi> _amiiboList;
private AvaloniaList<AmiiboApi> _amiibos; private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
private ObservableCollection<string> _amiiboSeries; private ObservableCollection<string> _amiiboSeries;
private int _amiiboSelectedIndex; private int _amiiboSelectedIndex;
@ -43,8 +42,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _useRandomUuid; private bool _useRandomUuid;
private string _usage; private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId) public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
{ {
_owner = owner; _owner = owner;
@ -55,9 +52,9 @@ namespace Ryujinx.Ava.UI.ViewModels
Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo")); Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
_amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json"); _amiiboJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
_amiiboList = new List<AmiiboApi>(); _amiiboList = new List<Amiibo.AmiiboApi>();
_amiiboSeries = new ObservableCollection<string>(); _amiiboSeries = new ObservableCollection<string>();
_amiibos = new AvaloniaList<AmiiboApi>(); _amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png"); _amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
@ -97,7 +94,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public AvaloniaList<AmiiboApi> AmiiboList public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
{ {
get => _amiibos; get => _amiibos;
set set
@ -190,9 +187,9 @@ namespace Ryujinx.Ava.UI.ViewModels
if (File.Exists(_amiiboJsonPath)) 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<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
{ {
amiiboJsonString = await DownloadAmiiboJson(); amiiboJsonString = await DownloadAmiiboJson();
} }
@ -209,7 +206,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo; _amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData(); ParseAmiiboData();
@ -226,7 +223,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (!ShowAllAmiibo) if (!ShowAllAmiibo)
{ {
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch) foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
{ {
if (game != null) if (game != null)
{ {
@ -258,7 +255,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void SelectLastScannedAmiibo() 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); SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned); AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
@ -273,7 +270,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return; return;
} }
List<AmiiboApi> amiiboSortedList = _amiiboList List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex]) .Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
.OrderBy(amiibo => amiibo.Name).ToList(); .OrderBy(amiibo => amiibo.Name).ToList();
@ -283,7 +280,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
if (!_showAllAmiibo) if (!_showAllAmiibo)
{ {
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch) foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
{ {
if (game != null) if (game != null)
{ {
@ -317,7 +314,7 @@ namespace Ryujinx.Ava.UI.ViewModels
return; return;
} }
AmiiboApi selected = _amiibos[_amiiboSelectedIndex]; Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image; string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
@ -329,11 +326,11 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
bool writable = false; bool writable = false;
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch) foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
{ {
if (item.GameId.Contains(TitleId)) if (item.GameId.Contains(TitleId))
{ {
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
{ {
usageString += Environment.NewLine + usageString += Environment.NewLine +
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"; $"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";

View file

@ -51,8 +51,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isLoaded; private bool _isLoaded;
private readonly UserControl _owner; private readonly UserControl _owner;
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepadDriver AvaloniaKeyboardDriver { get; }
public IGamepad SelectedGamepad { get; private set; } public IGamepad SelectedGamepad { get; private set; }
@ -708,7 +706,10 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); using (Stream stream = File.OpenRead(path))
{
config = JsonHelper.Deserialize<InputConfig>(stream);
}
} }
catch (JsonException) { } catch (JsonException) { }
catch (InvalidOperationException) catch (InvalidOperationException)
@ -774,7 +775,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ControllerType = Controllers[_controller].Type; config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig); string jsonString = JsonHelper.Serialize(config, true);
await File.WriteAllTextAsync(path, jsonString); await File.WriteAllTextAsync(path, jsonString);

View file

@ -21,6 +21,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
@ -40,8 +41,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private ulong _titleId; private ulong _titleId;
private string _titleName; private string _titleName;
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<DownloadableContentModel> DownloadableContents public AvaloniaList<DownloadableContentModel> DownloadableContents
{ {
get => _downloadableContents; get => _downloadableContents;
@ -101,7 +100,7 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer); _downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
} }
catch catch
{ {
@ -331,7 +330,10 @@ namespace Ryujinx.Ava.UI.ViewModels
_downloadableContentContainerList.Add(container); _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)));
}
} }
} }

View file

@ -25,228 +25,226 @@ using System.Text;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers; 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<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
public AvaloniaList<TitleUpdateModel> TitleUpdates
{ {
public TitleUpdateMetadata _titleUpdateWindowData; get => _titleUpdates;
public readonly string _titleUpdateJsonPath; set
private VirtualFileSystem _virtualFileSystem { get; }
private ulong _titleId { get; }
private string _titleName { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<TitleUpdateModel> TitleUpdates
{ {
get => _titleUpdates; _titleUpdates = value;
set OnPropertyChanged();
{
_titleUpdates = value;
OnPropertyChanged();
}
} }
}
public AvaloniaList<object> Views public AvaloniaList<object> Views
{
get => _views;
set
{ {
get => _views; _views = value;
set OnPropertyChanged();
{
_views = value;
OnPropertyChanged();
}
} }
}
public object SelectedUpdate public object SelectedUpdate
{
get => _selectedUpdate;
set
{ {
get => _selectedUpdate; _selectedUpdate = value;
set 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; _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
_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<string>()
};
Save();
}
LoadUpdates();
} }
catch
private void LoadUpdates()
{ {
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<string>()
};
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
SelectedUpdate = selected;
// NOTE: Save the list again to remove leftovers.
Save(); 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;
{ }
return -1; else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
} {
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString())) return 1;
{ }
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.Clear();
Views.Add(new BaseModel()); Views.Add(new BaseModel());
Views.AddRange(list); 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]; 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<IFile> 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 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<IFile> 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 () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
}); });
} }
} }
} catch (Exception ex)
public void RemoveUpdate(TitleUpdateModel update)
{
TitleUpdates.Remove(update);
SortUpdates();
}
public async void Add()
{
OpenFileDialog dialog = new()
{ {
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle], Dispatcher.UIThread.Post(async () =>
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) await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
{ });
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;
}
}
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)));
}
} }

View file

@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last(); string languageCode = Path.GetFileNameWithoutExtension(locale).Split('.').Last();
string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}"); string languageJson = EmbeddedResources.ReadAllText($"{localePath}/{languageCode}{localeExt}");
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary); var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
if (!strings.TryGetValue("Language", out string languageName)) if (!strings.TryGetValue("Language", out string languageName))
{ {

View file

@ -1,7 +1,7 @@
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Models.Amiibo;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
@ -35,14 +35,14 @@ namespace Ryujinx.Ava.UI.Windows
} }
public bool IsScanned { get; set; } public bool IsScanned { get; set; }
public AmiiboApi ScannedAmiibo { get; set; } public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
public AmiiboWindowViewModel ViewModel { get; set; } public AmiiboWindowViewModel ViewModel { get; set; }
private void ScanButton_Click(object sender, RoutedEventArgs e) private void ScanButton_Click(object sender, RoutedEventArgs e)
{ {
if (ViewModel.AmiiboSelectedIndex > -1) if (ViewModel.AmiiboSelectedIndex > -1)
{ {
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex]; Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
ScannedAmiibo = amiibo; ScannedAmiibo = amiibo;
IsScanned = true; IsScanned = true;
Close(); Close();

View file

@ -6,8 +6,11 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System.IO;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Button = Avalonia.Controls.Button; using Button = Avalonia.Controls.Button;

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
public enum AspectRatio public enum AspectRatio
{ {
Fixed4x3, Fixed4x3,

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
public enum BackendThreading public enum BackendThreading
{ {
Auto, Auto,

View file

@ -1,11 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend public enum GraphicsBackend
{ {
Vulkan, Vulkan,

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
public enum GraphicsDebugLevel public enum GraphicsDebugLevel
{ {
None, None,

View file

@ -1,5 +1,4 @@
using Ryujinx.Common.Utilities; using System;
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -7,8 +6,6 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{ {
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController> class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
{ {
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader) private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
{ {
// Temporary reader to get the backend type // Temporary reader to get the backend type
@ -55,8 +52,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
return motionBackendType switch return motionBackendType switch
{ {
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController), MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options),
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController), MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options),
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"), _ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
}; };
} }
@ -66,10 +63,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
switch (value.MotionBackend) switch (value.MotionBackend)
{ {
case MotionInputBackendType.GamepadDriver: case MotionInputBackendType.GamepadDriver:
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController); JsonSerializer.Serialize(writer, value as StandardMotionConfigController, options);
break; break;
case MotionInputBackendType.CemuHook: case MotionInputBackendType.CemuHook:
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController); JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options);
break; break;
default: default:
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}"); throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");

View file

@ -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 class MotionConfigController
{ {
public MotionInputBackendType MotionBackend { get; set; } public MotionInputBackendType MotionBackend { get; set; }

View file

@ -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
{
}
}

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{ {
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
public enum MotionInputBackendType : byte public enum MotionInputBackendType : byte
{ {
Invalid, Invalid,

View file

@ -1,12 +1,9 @@
using Ryujinx.Common.Utilities;
using System; using System;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[Flags] [Flags]
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))] // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
public enum ControllerType : int public enum ControllerType : int
{ {
None, None,

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration.Hid
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{ {
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
public enum InputBackendType public enum InputBackendType
{ {
Invalid, Invalid,

View file

@ -1,10 +1,8 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
[JsonConverter(typeof(JsonInputConfigConverter))]
public class InputConfig : INotifyPropertyChanged public class InputConfig : INotifyPropertyChanged
{ {
/// <summary> /// <summary>

View file

@ -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
{
}
}

View file

@ -1,16 +1,13 @@
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
public class JsonInputConfigConverter : JsonConverter<InputConfig> class JsonInputConfigConverter : JsonConverter<InputConfig>
{ {
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader) private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
{ {
// Temporary reader to get the backend type // Temporary reader to get the backend type
@ -57,8 +54,8 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch return backendType switch
{ {
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig), InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig), InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"), _ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
}; };
} }
@ -68,10 +65,10 @@ namespace Ryujinx.Common.Configuration.Hid
switch (value.Backend) switch (value.Backend)
{ {
case InputBackendType.WindowKeyboard: case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig); JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
break; break;
case InputBackendType.GamepadSDL2: case InputBackendType.GamepadSDL2:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig); JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
break; break;
default: default:
throw new ArgumentException($"Unknown backend type {value.Backend}"); throw new ArgumentException($"Unknown backend type {value.Backend}");

View file

@ -1,10 +1,6 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid namespace Ryujinx.Common.Configuration.Hid
{ {
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
public enum PlayerIndex : int public enum PlayerIndex : int
{ {
Player1 = 0, Player1 = 0,

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Common.Configuration
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
public enum MemoryManagerMode : byte public enum MemoryManagerMode : byte
{ {
SoftwarePageTable, SoftwarePageTable,

View file

@ -1,10 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(TitleUpdateMetadata))]
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -1,20 +1,22 @@
using System.Text; using System;
using System.Reflection;
using System.Text;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
internal class DefaultLogFormatter : ILogFormatter internal class DefaultLogFormatter : ILogFormatter
{ {
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>(); private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
public string Format(LogEventArgs args) public string Format(LogEventArgs args)
{ {
StringBuilder sb = StringBuilderPool.Allocate(); StringBuilder sb = _stringBuilderPool.Allocate();
try try
{ {
sb.Clear(); 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]}| "); sb.Append($" |{args.Level.ToString()[0]}| ");
if (args.ThreadName != null) if (args.ThreadName != null)
@ -25,17 +27,53 @@ namespace Ryujinx.Common.Logging
sb.Append(args.Message); sb.Append(args.Message);
if (args.Data is not null) if (args.Data != null)
{ {
sb.Append(' '); PropertyInfo[] props = args.Data.GetType().GetProperties();
DynamicObjectFormatter.Format(sb, args.Data);
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(); return sb.ToString();
} }
finally finally
{ {
StringBuilderPool.Release(sb); _stringBuilderPool.Release(sb);
} }
} }
} }

View file

@ -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<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
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('}');
}
}
}

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
public enum LogClass public enum LogClass
{ {
Application, Application,

View file

@ -11,7 +11,15 @@ namespace Ryujinx.Common.Logging
public readonly string Message; public readonly string Message;
public readonly object Data; 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; Level = level;
Time = time; Time = time;

View file

@ -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));
}
}
}

View file

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonSerializable(typeof(LogEventArgsJson))]
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
public enum LogLevel public enum LogLevel
{ {
Debug, Debug,

View file

@ -1,5 +1,5 @@
using Ryujinx.Common.Utilities; using System.IO;
using System.IO; using System.Text.Json;
namespace Ryujinx.Common.Logging namespace Ryujinx.Common.Logging
{ {
@ -25,8 +25,12 @@ namespace Ryujinx.Common.Logging
public void Log(object sender, LogEventArgs e) public void Log(object sender, LogEventArgs e)
{ {
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e); string text = JsonSerializer.Serialize(e);
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
using (BinaryWriter writer = new BinaryWriter(_stream))
{
writer.Write(text);
}
} }
public void Dispose() public void Dispose()

View file

@ -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<string, string>), TypeInfoPropertyName = "StringDictionary")]
public partial class CommonJsonContext : JsonSerializerContext
{
}
}

View file

@ -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;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
public class JsonHelper public class JsonHelper
{ {
private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy(); public static JsonNamingPolicy SnakeCase { get; }
private const int DefaultFileWriteBufferSize = 4096;
/// <summary>
/// Creates new serializer options with default settings.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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>(T value, JsonTypeInfo<T> typeInfo)
{
return JsonSerializer.Serialize(value, typeInfo);
}
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
{
return JsonSerializer.Deserialize(value, typeInfo);
}
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
{
using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough);
JsonSerializer.Serialize(file, value, typeInfo);
}
public static T DeserializeFromFile<T>(string filePath, JsonTypeInfo<T> typeInfo)
{
using FileStream file = File.OpenRead(filePath);
return JsonSerializer.Deserialize(file, typeInfo);
}
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
{
JsonSerializer.Serialize(stream, value, typeInfo);
}
private class SnakeCaseNamingPolicy : JsonNamingPolicy private class SnakeCaseNamingPolicy : JsonNamingPolicy
{ {
@ -67,7 +20,7 @@ namespace Ryujinx.Common.Utilities
return name; return name;
} }
StringBuilder builder = new(); StringBuilder builder = new StringBuilder();
for (int i = 0; i < name.Length; i++) for (int i = 0; i < name.Length; i++)
{ {
@ -81,7 +34,7 @@ namespace Ryujinx.Common.Utilities
} }
else else
{ {
builder.Append('_'); builder.Append("_");
builder.Append(char.ToLowerInvariant(c)); builder.Append(char.ToLowerInvariant(c));
} }
} }
@ -94,5 +47,64 @@ namespace Ryujinx.Common.Utilities
return builder.ToString(); 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<T>(Stream stream)
{
using (BinaryReader reader = new BinaryReader(stream))
{
return JsonSerializer.Deserialize<T>(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions());
}
}
public static T DeserializeFromFile<T>(string path)
{
return Deserialize<T>(File.ReadAllText(path));
}
public static T Deserialize<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, GetDefaultSerializerOptions());
}
public static void Serialize<TValue>(Stream stream, TValue obj, bool prettyPrint = false)
{
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(SerializeToUtf8Bytes(obj, prettyPrint));
}
}
public static string Serialize<TValue>(TValue obj, bool prettyPrint = false)
{
return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint));
}
public static byte[] SerializeToUtf8Bytes<T>(T obj, bool prettyPrint = false)
{
return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint));
}
} }
} }

View file

@ -1,34 +0,0 @@
#nullable enable
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Utilities
{
/// <summary>
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
/// </summary>
/// <remarks>
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
/// Get rid of this converter if dotnet supports similar functionality out of the box.
/// </remarks>
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> 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<TEnum>(enumValue);
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}

View file

@ -13,7 +13,6 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
@ -25,13 +24,14 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json;
using static Ryujinx.HLE.HOS.ModLoader; using static Ryujinx.HLE.HOS.ModLoader;
using ApplicationId = LibHac.Ncm.ApplicationId; using ApplicationId = LibHac.Ncm.ApplicationId;
using Path = System.IO.Path; using Path = System.IO.Path;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
{ {
using JsonHelper = Common.Utilities.JsonHelper;
public class ApplicationLoader public class ApplicationLoader
{ {
// Binaries from exefs are loaded into mem in this order. Do not change. // 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 string _displayVersion;
private BlitStruct<ApplicationControlProperty> _controlData; private BlitStruct<ApplicationControlProperty> _controlData;
private static readonly JsonSerializerOptions SerializerOptions = JsonHelper.GetDefaultSerializerOptions();
private static readonly DownloadableContentJsonSerializerContext ContentSerializerContext = new(SerializerOptions);
private static readonly TitleUpdateMetadataJsonSerializerContext TitleSerializerContext = new(SerializerOptions);
public BlitStruct<ApplicationControlProperty> ControlData => _controlData; public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
public string TitleName => _titleName; public string TitleName => _titleName;
public string DisplayVersion => _displayVersion; public string DisplayVersion => _displayVersion;
@ -201,7 +197,7 @@ namespace Ryujinx.HLE.HOS
if (File.Exists(titleUpdateMetadataPath)) if (File.Exists(titleUpdateMetadataPath))
{ {
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, TitleSerializerContext.TitleUpdateMetadata).Selected; updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
if (File.Exists(updatePath)) if (File.Exists(updatePath))
{ {
@ -415,7 +411,7 @@ namespace Ryujinx.HLE.HOS
if (File.Exists(titleAocMetadataPath)) if (File.Exists(titleAocMetadataPath))
{ {
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(titleAocMetadataPath, ContentSerializerContext.ListDownloadableContentContainer); List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
{ {

View file

@ -1,11 +1,11 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Account.Acc.Types;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json.Serialization;
namespace Ryujinx.HLE.HOS.Services.Account.Acc 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 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<UserProfileJson> 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; } public UserId LastOpened { get; set; }
@ -25,7 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
{ {
try try
{ {
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, SerializerContext.ProfilesJson); ProfilesJson profilesJson = JsonHelper.DeserializeFromFile<ProfilesJson>(_profilesJsonPath);
foreach (var profile in profilesJson.Profiles) 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));
} }
} }
} }

View file

@ -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
{
}
}

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.HLE.HOS.Services.Account.Acc namespace Ryujinx.HLE.HOS.Services.Account.Acc
{ {
[JsonConverter(typeof(TypedStringEnumConverter<AccountState>))]
public enum AccountState public enum AccountState
{ {
Closed, Closed,

View file

@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Account.Acc.Types
{
internal struct ProfilesJson
{
public List<UserProfileJson> Profiles { get; set; }
public string LastOpened { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -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
{
}
}

View file

@ -1,6 +1,5 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Mii.Types; using Ryujinx.HLE.HOS.Services.Mii.Types;
@ -9,6 +8,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json;
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{ {
@ -16,8 +17,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{ {
private static uint _openedApplicationAreaId; private static uint _openedApplicationAreaId;
private static readonly AmiiboJsonSerializerContext SerializerContext = AmiiboJsonSerializerContext.Default;
public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid) public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid)
{ {
if (useRandomUuid) if (useRandomUuid)
@ -174,7 +173,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
if (File.Exists(filePath)) if (File.Exists(filePath))
{ {
virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, SerializerContext.VirtualAmiiboFile); virtualAmiiboFile = JsonSerializer.Deserialize<VirtualAmiiboFile>(File.ReadAllText(filePath), new JsonSerializerOptions(JsonSerializerDefaults.General));
} }
else else
{ {
@ -198,7 +197,8 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile) private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
{ {
string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json"); string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json");
JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, SerializerContext.VirtualAmiiboFile);
File.WriteAllText(filePath, JsonSerializer.Serialize(virtualAmiiboFile));
} }
} }
} }

View file

@ -56,8 +56,6 @@ namespace Ryujinx.Headless.SDL2
private static bool _enableKeyboard; private static bool _enableKeyboard;
private static bool _enableMouse; private static bool _enableMouse;
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
static void Main(string[] args) static void Main(string[] args)
{ {
Version = ReleaseInformation.GetVersion(); Version = ReleaseInformation.GetVersion();
@ -287,7 +285,10 @@ namespace Ryujinx.Headless.SDL2
try try
{ {
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); using (Stream stream = File.OpenRead(path))
{
config = JsonHelper.Deserialize<InputConfig>(stream);
}
} }
catch (JsonException) catch (JsonException)
{ {

View file

@ -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
{
}
}

View file

@ -10,7 +10,6 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
@ -23,6 +22,7 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
using Path = System.IO.Path; using Path = System.IO.Path;
namespace Ryujinx.Ui.App.Common namespace Ryujinx.Ui.App.Common
@ -42,8 +42,6 @@ namespace Ryujinx.Ui.App.Common
private Language _desiredTitleLanguage; private Language _desiredTitleLanguage;
private CancellationTokenSource _cancellationToken; private CancellationTokenSource _cancellationToken;
private static readonly ApplicationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public ApplicationLibrary(VirtualFileSystem virtualFileSystem) public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
{ {
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
@ -492,12 +490,14 @@ namespace Ryujinx.Ui.App.Common
appMetadata = new ApplicationMetadata(); appMetadata = new ApplicationMetadata();
JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata); using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
JsonHelper.Serialize(stream, appMetadata, true);
} }
try try
{ {
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, SerializerContext.ApplicationMetadata); appMetadata = JsonHelper.DeserializeFromFile<ApplicationMetadata>(metadataFile);
} }
catch (JsonException) catch (JsonException)
{ {
@ -510,7 +510,9 @@ namespace Ryujinx.Ui.App.Common
{ {
modifyFunction(appMetadata); modifyFunction(appMetadata);
JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata); using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
JsonHelper.Serialize(stream, appMetadata, true);
} }
return appMetadata; return appMetadata;

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Ui.Common.Configuration
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.Common.Configuration
{ {
[JsonConverter(typeof(TypedStringEnumConverter<AudioBackend>))]
public enum AudioBackend public enum AudioBackend
{ {
Dummy, Dummy,

View file

@ -5,7 +5,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration.System; using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Ui.Common.Configuration.Ui; using Ryujinx.Ui.Common.Configuration.Ui;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Nodes; using System.IO;
namespace Ryujinx.Ui.Common.Configuration namespace Ryujinx.Ui.Common.Configuration
{ {
@ -321,14 +321,14 @@ namespace Ryujinx.Ui.Common.Configuration
/// </summary> /// </summary>
/// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks> /// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
/// TODO: Remove this when those older versions aren't in use anymore. /// TODO: Remove this when those older versions aren't in use anymore.
public List<JsonObject> KeyboardConfig { get; set; } public List<object> KeyboardConfig { get; set; }
/// <summary> /// <summary>
/// Legacy controller control bindings /// Legacy controller control bindings
/// </summary> /// </summary>
/// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks> /// <remarks>Kept for file format compatibility (to avoid possible failure when parsing configuration on old versions)</remarks>
/// TODO: Remove this when those older versions aren't in use anymore. /// TODO: Remove this when those older versions aren't in use anymore.
public List<JsonObject> ControllerConfig { get; set; } public List<object> ControllerConfig { get; set; }
/// <summary> /// <summary>
/// Input configurations /// Input configurations
@ -354,12 +354,11 @@ namespace Ryujinx.Ui.Common.Configuration
/// Loads a configuration file from disk /// Loads a configuration file from disk
/// </summary> /// </summary>
/// <param name="path">The path to the JSON configuration file</param> /// <param name="path">The path to the JSON configuration file</param>
/// <param name="configurationFileFormat">Parsed configuration file</param>
public static bool TryLoad(string path, out ConfigurationFileFormat configurationFileFormat) public static bool TryLoad(string path, out ConfigurationFileFormat configurationFileFormat)
{ {
try try
{ {
configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat); configurationFileFormat = JsonHelper.DeserializeFromFile<ConfigurationFileFormat>(path);
return configurationFileFormat.Version != 0; return configurationFileFormat.Version != 0;
} }
@ -377,7 +376,8 @@ namespace Ryujinx.Ui.Common.Configuration
/// <param name="path">The path to the JSON configuration file</param> /// <param name="path">The path to the JSON configuration file</param>
public void SaveConfig(string path) 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);
} }
} }
} }

View file

@ -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());
}
}

View file

@ -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
{
}
}

View file

@ -9,7 +9,6 @@ using Ryujinx.Ui.Common.Configuration.Ui;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Nodes;
namespace Ryujinx.Ui.Common.Configuration namespace Ryujinx.Ui.Common.Configuration
{ {
@ -632,8 +631,8 @@ namespace Ryujinx.Ui.Common.Configuration
EnableKeyboard = Hid.EnableKeyboard, EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse, EnableMouse = Hid.EnableMouse,
Hotkeys = Hid.Hotkeys, Hotkeys = Hid.Hotkeys,
KeyboardConfig = new List<JsonObject>(), KeyboardConfig = new List<object>(),
ControllerConfig = new List<JsonObject>(), ControllerConfig = new List<object>(),
InputConfig = Hid.InputConfig, InputConfig = Hid.InputConfig,
GraphicsBackend = Graphics.GraphicsBackend, GraphicsBackend = Graphics.GraphicsBackend,
PreferredGpu = Graphics.PreferredGpu PreferredGpu = Graphics.PreferredGpu

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Ui.Common.Configuration.System
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.Common.Configuration.System
{ {
[JsonConverter(typeof(TypedStringEnumConverter<Language>))]
public enum Language public enum Language
{ {
Japanese, Japanese,

View file

@ -1,9 +1,5 @@
using Ryujinx.Common.Utilities; namespace Ryujinx.Ui.Common.Configuration.System
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.Common.Configuration.System
{ {
[JsonConverter(typeof(TypedStringEnumConverter<Region>))]
public enum Region public enum Region
{ {
Japan, Japan,

View file

@ -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<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<string, string> Release { get; set; }
[JsonPropertyName("gamesSwitch")]
public List<AmiiboApiGamesSwitch> 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);
}
}
}

View file

@ -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<AmiiboApiUsage> AmiiboUsage { get; set; }
[JsonPropertyName("gameID")]
public List<string> GameId { get; set; }
[JsonPropertyName("gameName")]
public string GameName { get; set; }
}
}

View file

@ -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; }
}
}

View file

@ -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<AmiiboApi> Amiibo { get; set; }
[JsonPropertyName("lastUpdated")]
public DateTime LastUpdated { get; set; }
}
}

View file

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Ui.Common.Models.Amiibo
{
[JsonSerializable(typeof(AmiiboJson))]
public partial class AmiiboJsonSerializerContext : JsonSerializerContext
{
}
}

View file

@ -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; }
}
}

View file

@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace Ryujinx.Ui.Common.Models.Github
{
public class GithubReleasesJsonResponse
{
public string Name { get; set; }
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
}
}

View file

@ -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
{
}
}

View file

@ -2,14 +2,14 @@ using Gtk;
using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui; using Ryujinx.Ui;
using Ryujinx.Ui.Common.Models.Github;
using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -38,8 +38,6 @@ namespace Ryujinx.Modules
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; 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. // 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" }; private static readonly string[] WindowsDependencyDirs = new string[] { "bin", "etc", "lib", "share" };
@ -109,16 +107,22 @@ namespace Ryujinx.Modules
// Fetch latest build information // Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL); string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse); JObject jsonRoot = JObject.Parse(fetchedJson);
_buildVer = fetched.Name; 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)) string assetName = (string)asset["name"];
{ string assetState = (string)asset["state"];
_buildUrl = asset.BrowserDownloadUrl; 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) if (showVersionUpToDate)
{ {

View file

@ -31,7 +31,7 @@ namespace Ryujinx.Ui.Windows
{ {
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/"); 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<string[]>(patreonJsonString));
} }
catch catch
{ {

View file

@ -3,7 +3,6 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Models.Amiibo;
using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -12,15 +11,65 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using AmiiboApi = Ryujinx.Ui.Common.Models.Amiibo.AmiiboApi;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
namespace Ryujinx.Ui.Windows namespace Ryujinx.Ui.Windows
{ {
public partial class AmiiboWindow : Window public partial class AmiiboWindow : Window
{ {
private struct AmiiboJson
{
[JsonPropertyName("amiibo")]
public List<AmiiboApi> 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<string, string> Release { get; set; }
[JsonPropertyName("gamesSwitch")]
public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
}
private class AmiiboApiGamesSwitch
{
[JsonPropertyName("amiiboUsage")]
public List<AmiiboApiUsage> AmiiboUsage { get; set; }
[JsonPropertyName("gameID")]
public List<string> 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\": [] }"; private const string DEFAULT_JSON = "{ \"amiibo\": [] }";
public string AmiiboId { get; private set; } public string AmiiboId { get; private set; }
@ -47,8 +96,6 @@ namespace Ryujinx.Ui.Windows
private List<AmiiboApi> _amiiboList; private List<AmiiboApi> _amiiboList;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo") public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo")
{ {
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png"); 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)) 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<AmiiboJson>(amiiboJsonString).LastUpdated))
{ {
amiiboJsonString = await DownloadAmiiboJson(); amiiboJsonString = await DownloadAmiiboJson();
} }
@ -101,7 +148,7 @@ namespace Ryujinx.Ui.Windows
} }
} }
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo; _amiiboList = JsonHelper.Deserialize<AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList(); _amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
if (LastScannedAmiiboShowAll) if (LastScannedAmiiboShowAll)

View file

@ -115,8 +115,6 @@ namespace Ryujinx.Ui.Windows
private bool _mousePressed; private bool _mousePressed;
private bool _middleMousePressed; 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) { } 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")) private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
@ -1122,7 +1120,10 @@ namespace Ryujinx.Ui.Windows
try try
{ {
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig); using (Stream stream = File.OpenRead(path))
{
config = JsonHelper.Deserialize<InputConfig>(stream);
}
} }
catch (JsonException) { } catch (JsonException) { }
} }
@ -1144,7 +1145,9 @@ namespace Ryujinx.Ui.Windows
if (profileDialog.Run() == (int)ResponseType.Ok) if (profileDialog.Run() == (int)ResponseType.Ok)
{ {
string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); 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); File.WriteAllText(path, jsonString);
} }

View file

@ -7,13 +7,15 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; 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 namespace Ryujinx.Ui.Windows
{ {
@ -24,8 +26,6 @@ namespace Ryujinx.Ui.Windows
private readonly string _dlcJsonPath; private readonly string _dlcJsonPath;
private readonly List<DownloadableContentContainer> _dlcContainerList; private readonly List<DownloadableContentContainer> _dlcContainerList;
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[GUI] Label _baseTitleInfoLabel; [GUI] Label _baseTitleInfoLabel;
[GUI] TreeView _dlcTreeView; [GUI] TreeView _dlcTreeView;
@ -45,7 +45,7 @@ namespace Ryujinx.Ui.Windows
try try
{ {
_dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, SerializerContext.ListDownloadableContentContainer); _dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_dlcJsonPath);
} }
catch catch
{ {
@ -260,7 +260,10 @@ namespace Ryujinx.Ui.Windows
while (_dlcTreeView.Model.IterNext(ref parentIter)); 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(); Dispose();
} }

View file

@ -7,7 +7,6 @@ using LibHac.Ns;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Widgets; using Ryujinx.Ui.Widgets;
@ -15,8 +14,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using GUI = Gtk.Builder.ObjectAttribute; using System.Text;
using SpanHelpers = LibHac.Common.SpanHelpers;
using GUI = Gtk.Builder.ObjectAttribute;
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
namespace Ryujinx.Ui.Windows namespace Ryujinx.Ui.Windows
{ {
@ -30,7 +31,6 @@ namespace Ryujinx.Ui.Windows
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary; private readonly Dictionary<RadioButton, string> _radioButtonToPathDictionary;
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
#pragma warning disable CS0649, IDE0044 #pragma warning disable CS0649, IDE0044
[GUI] Label _baseTitleInfoLabel; [GUI] Label _baseTitleInfoLabel;
@ -53,7 +53,7 @@ namespace Ryujinx.Ui.Windows
try try
{ {
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, SerializerContext.TitleUpdateMetadata); _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_updateJsonPath);
} }
catch 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(); _parent.UpdateGameTable();