Use source generated json serializers in order to improve code trimming (#4094)
* Use source generated json serializers in order to improve code trimming * Use strongly typed github releases model to fetch updates instead of raw Newtonsoft.Json parsing * Use separate model for LogEventArgs serialization * Make dynamic object formatter static. Fix string builder pooling. * Do not inherit json version of LogEventArgs from EventArgs * Fix extra space in object formatting * Write log json directly to stream instead of using buffer writer * Rebase fixes * Rebase fixes * Rebase fixes * Enforce block-scoped namespaces in the solution. Convert style for existing code * Apply suggestions from code review Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Rebase indent fix * Fix indent * Delete unnecessary json properties * Rebase fix * Remove overridden json property names as they are handled in the options * Apply suggestions from code review Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Use default json options in github api calls * Indentation and spacing fixes --------- Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
This commit is contained in:
parent
17620d18db
commit
4ce4299ca2
73 changed files with 887 additions and 609 deletions
|
@ -63,6 +63,10 @@ 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
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
namespace ARMeilleure.Decoders;
|
namespace ARMeilleure.Decoders
|
||||||
|
|
||||||
interface IOpCode32Exception
|
|
||||||
{
|
{
|
||||||
int Id { get; }
|
interface IOpCode32Exception
|
||||||
|
{
|
||||||
|
int Id { get; }
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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<Dictionary<string, string>>(languageJson);
|
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||||
|
|
||||||
foreach (var item in strings)
|
foreach (var item in strings)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,13 +4,14 @@ 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;
|
||||||
|
@ -31,6 +32,7 @@ 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");
|
||||||
|
@ -99,22 +101,16 @@ 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);
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||||
JToken assets = jsonRoot["assets"];
|
_buildVer = fetched.Name;
|
||||||
|
|
||||||
_buildVer = (string)jsonRoot["name"];
|
foreach (var asset in fetched.Assets)
|
||||||
|
|
||||||
foreach (JToken asset in assets)
|
|
||||||
{
|
{
|
||||||
string assetName = (string)asset["name"];
|
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||||
string assetState = (string)asset["state"];
|
|
||||||
string downloadURL = (string)asset["browser_download_url"];
|
|
||||||
|
|
||||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
|
||||||
{
|
{
|
||||||
_buildUrl = downloadURL;
|
_buildUrl = asset.BrowserDownloadUrl;
|
||||||
|
|
||||||
if (assetState != "uploaded")
|
if (asset.State != "uploaded")
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<string[]>(patreonJsonString)) + "\n\n";
|
Supporters = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray) + "\n\n");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,6 +17,7 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -31,8 +32,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private readonly StyleableWindow _owner;
|
private readonly StyleableWindow _owner;
|
||||||
|
|
||||||
private Bitmap _amiiboImage;
|
private Bitmap _amiiboImage;
|
||||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
private List<AmiiboApi> _amiiboList;
|
||||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
private AvaloniaList<AmiiboApi> _amiibos;
|
||||||
private ObservableCollection<string> _amiiboSeries;
|
private ObservableCollection<string> _amiiboSeries;
|
||||||
|
|
||||||
private int _amiiboSelectedIndex;
|
private int _amiiboSelectedIndex;
|
||||||
|
@ -41,6 +42,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
private bool _showAllAmiibo;
|
private bool _showAllAmiibo;
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -52,9 +55,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<Amiibo.AmiiboApi>();
|
_amiiboList = new List<AmiiboApi>();
|
||||||
_amiiboSeries = new ObservableCollection<string>();
|
_amiiboSeries = new ObservableCollection<string>();
|
||||||
_amiibos = new AvaloniaList<Amiibo.AmiiboApi>();
|
_amiibos = new AvaloniaList<AmiiboApi>();
|
||||||
|
|
||||||
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
|
||||||
|
|
||||||
|
@ -94,7 +97,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvaloniaList<Amiibo.AmiiboApi> AmiiboList
|
public AvaloniaList<AmiiboApi> AmiiboList
|
||||||
{
|
{
|
||||||
get => _amiibos;
|
get => _amiibos;
|
||||||
set
|
set
|
||||||
|
@ -187,9 +190,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
if (File.Exists(_amiiboJsonPath))
|
if (File.Exists(_amiiboJsonPath))
|
||||||
{
|
{
|
||||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||||
|
|
||||||
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
|
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
||||||
{
|
{
|
||||||
amiiboJsonString = await DownloadAmiiboJson();
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
}
|
}
|
||||||
|
@ -206,7 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
|
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
||||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
ParseAmiiboData();
|
ParseAmiiboData();
|
||||||
|
@ -223,7 +226,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
if (!ShowAllAmiibo)
|
if (!ShowAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
|
@ -255,7 +258,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
private void SelectLastScannedAmiibo()
|
private void SelectLastScannedAmiibo()
|
||||||
{
|
{
|
||||||
Amiibo.AmiiboApi scanned = _amiiboList.FirstOrDefault(amiibo => amiibo.GetId() == LastScannedAmiiboId);
|
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);
|
||||||
|
@ -270,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Amiibo.AmiiboApi> amiiboSortedList = _amiiboList
|
List<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();
|
||||||
|
|
||||||
|
@ -280,7 +283,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
if (!_showAllAmiibo)
|
if (!_showAllAmiibo)
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (game != null)
|
if (game != null)
|
||||||
{
|
{
|
||||||
|
@ -314,7 +317,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Amiibo.AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||||
|
|
||||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||||
|
|
||||||
|
@ -326,11 +329,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
bool writable = false;
|
bool writable = false;
|
||||||
|
|
||||||
foreach (Amiibo.AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||||
{
|
{
|
||||||
if (item.GameId.Contains(TitleId))
|
if (item.GameId.Contains(TitleId))
|
||||||
{
|
{
|
||||||
foreach (Amiibo.AmiiboApiUsage usageItem in item.AmiiboUsage)
|
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||||
{
|
{
|
||||||
usageString += Environment.NewLine +
|
usageString += Environment.NewLine +
|
||||||
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
$"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||||
|
|
|
@ -51,6 +51,8 @@ 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; }
|
||||||
|
|
||||||
|
@ -706,10 +708,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = File.OpenRead(path))
|
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||||
{
|
|
||||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (JsonException) { }
|
catch (JsonException) { }
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
|
@ -775,7 +774,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
config.ControllerType = Controllers[_controller].Type;
|
config.ControllerType = Controllers[_controller].Type;
|
||||||
|
|
||||||
string jsonString = JsonHelper.Serialize(config, true);
|
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
|
||||||
|
|
||||||
await File.WriteAllTextAsync(path, jsonString);
|
await File.WriteAllTextAsync(path, jsonString);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ 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;
|
||||||
|
|
||||||
|
@ -41,6 +40,8 @@ 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;
|
||||||
|
@ -100,7 +101,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -330,10 +331,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
_downloadableContentContainerList.Add(container);
|
_downloadableContentContainerList.Add(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
|
||||||
{
|
|
||||||
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,226 +25,228 @@ 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 TitleUpdateMetadata _titleUpdateWindowData;
|
public class TitleUpdateViewModel : BaseModel
|
||||||
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
|
|
||||||
{
|
{
|
||||||
get => _titleUpdates;
|
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
set
|
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;
|
||||||
|
|
||||||
|
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
|
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||||
{
|
{
|
||||||
_titleUpdates = value;
|
get => _titleUpdates;
|
||||||
OnPropertyChanged();
|
set
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AvaloniaList<object> Views
|
|
||||||
{
|
|
||||||
get => _views;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_views = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public object SelectedUpdate
|
|
||||||
{
|
|
||||||
get => _selectedUpdate;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_selectedUpdate = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
|
||||||
|
|
||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
|
||||||
{
|
{
|
||||||
Selected = "",
|
_titleUpdates = value;
|
||||||
Paths = new List<string>()
|
OnPropertyChanged();
|
||||||
};
|
|
||||||
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadUpdates()
|
|
||||||
{
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
|
||||||
{
|
|
||||||
AddUpdate(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
Views.Clear();
|
|
||||||
Views.Add(new BaseModel());
|
|
||||||
Views.AddRange(list);
|
|
||||||
|
|
||||||
if (SelectedUpdate == null)
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[0];
|
|
||||||
}
|
|
||||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
|
||||||
{
|
|
||||||
if (Views.Count > 1)
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedUpdate = Views[0];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
public AvaloniaList<object> Views
|
||||||
{
|
|
||||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
|
||||||
{
|
{
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SelectedUpdate
|
||||||
|
{
|
||||||
|
get => _selectedUpdate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedUpdate = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
try
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new();
|
Selected = "",
|
||||||
|
Paths = new List<string>()
|
||||||
|
};
|
||||||
|
|
||||||
using UniqueRef<IFile> nacpFile = new();
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
LoadUpdates();
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
}
|
||||||
|
|
||||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
private void LoadUpdates()
|
||||||
|
{
|
||||||
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
|
{
|
||||||
|
AddUpdate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
Views.Clear();
|
||||||
|
Views.Add(new BaseModel());
|
||||||
|
Views.AddRange(list);
|
||||||
|
|
||||||
|
if (SelectedUpdate == null)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[0];
|
||||||
|
}
|
||||||
|
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||||
|
{
|
||||||
|
if (Views.Count > 1)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[1];
|
||||||
}
|
}
|
||||||
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[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveUpdate(TitleUpdateModel update)
|
public void RemoveUpdate(TitleUpdateModel update)
|
||||||
{
|
|
||||||
TitleUpdates.Remove(update);
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new()
|
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
TitleUpdates.Remove(update);
|
||||||
AllowMultiple = true
|
|
||||||
};
|
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
{
|
{
|
||||||
Name = "NSP",
|
OpenFileDialog dialog = new()
|
||||||
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)
|
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)
|
||||||
{
|
{
|
||||||
AddUpdate(file);
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
AddUpdate(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
SortUpdates();
|
public void Save()
|
||||||
}
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in TitleUpdates)
|
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
_titleUpdateWindowData.Paths.Clear();
|
||||||
|
_titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
if (update == SelectedUpdate)
|
foreach (TitleUpdateModel update in TitleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Selected = update.Path;
|
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
if (update == SelectedUpdate)
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData.Selected = update.Path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<Dictionary<string, string>>(languageJson);
|
var strings = JsonHelper.Deserialize(languageJson, CommonJsonContext.Default.StringDictionary);
|
||||||
|
|
||||||
if (!strings.TryGetValue("Language", out string languageName))
|
if (!strings.TryGetValue("Language", out string languageName))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 Amiibo.AmiiboApi ScannedAmiibo { get; set; }
|
public 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)
|
||||||
{
|
{
|
||||||
Amiibo.AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||||
ScannedAmiibo = amiibo;
|
ScannedAmiibo = amiibo;
|
||||||
IsScanned = true;
|
IsScanned = true;
|
||||||
Close();
|
Close();
|
||||||
|
|
|
@ -6,11 +6,8 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||||
public enum AspectRatio
|
public enum AspectRatio
|
||||||
{
|
{
|
||||||
Fixed4x3,
|
Fixed4x3,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||||
public enum BackendThreading
|
public enum BackendThreading
|
||||||
{
|
{
|
||||||
Auto,
|
Auto,
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||||
public enum GraphicsBackend
|
public enum GraphicsBackend
|
||||||
{
|
{
|
||||||
Vulkan,
|
Vulkan,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
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,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
@ -6,6 +7,8 @@ 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
|
||||||
|
@ -52,8 +55,8 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||||
|
|
||||||
return motionBackendType switch
|
return motionBackendType switch
|
||||||
{
|
{
|
||||||
MotionInputBackendType.GamepadDriver => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(StandardMotionConfigController), options),
|
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
|
||||||
MotionInputBackendType.CemuHook => (MotionConfigController)JsonSerializer.Deserialize(ref reader, typeof(CemuHookMotionConfigController), options),
|
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
|
||||||
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -63,10 +66,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, options);
|
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
|
||||||
break;
|
break;
|
||||||
case MotionInputBackendType.CemuHook:
|
case MotionInputBackendType.CemuHook:
|
||||||
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, options);
|
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
using Ryujinx.Common.Utilities;
|
||||||
|
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,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
[Flags]
|
|
||||||
// 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
|
||||||
|
[Flags]
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||||
public enum ControllerType : int
|
public enum ControllerType : int
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration.Hid
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration.Hid
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
|
||||||
public enum InputBackendType
|
public enum InputBackendType
|
||||||
{
|
{
|
||||||
Invalid,
|
Invalid,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
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>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
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
|
||||||
{
|
{
|
||||||
class JsonInputConfigConverter : JsonConverter<InputConfig>
|
public 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
|
||||||
|
@ -54,8 +57,8 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
|
|
||||||
return backendType switch
|
return backendType switch
|
||||||
{
|
{
|
||||||
InputBackendType.WindowKeyboard => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardKeyboardInputConfig), options),
|
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
|
||||||
InputBackendType.GamepadSDL2 => (InputConfig)JsonSerializer.Deserialize(ref reader, typeof(StandardControllerInputConfig), options),
|
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
|
||||||
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,10 +68,10 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||||
switch (value.Backend)
|
switch (value.Backend)
|
||||||
{
|
{
|
||||||
case InputBackendType.WindowKeyboard:
|
case InputBackendType.WindowKeyboard:
|
||||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, options);
|
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
|
||||||
break;
|
break;
|
||||||
case InputBackendType.GamepadSDL2:
|
case InputBackendType.GamepadSDL2:
|
||||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, options);
|
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
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,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||||
public enum MemoryManagerMode : byte
|
public enum MemoryManagerMode : byte
|
||||||
{
|
{
|
||||||
SoftwarePageTable,
|
SoftwarePageTable,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Configuration
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(TitleUpdateMetadata))]
|
||||||
|
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,20 @@
|
||||||
using System;
|
using System.Text;
|
||||||
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.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
|
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
|
||||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||||
|
|
||||||
if (args.ThreadName != null)
|
if (args.ThreadName != null)
|
||||||
|
@ -27,53 +25,17 @@ namespace Ryujinx.Common.Logging
|
||||||
|
|
||||||
sb.Append(args.Message);
|
sb.Append(args.Message);
|
||||||
|
|
||||||
if (args.Data != null)
|
if (args.Data is not null)
|
||||||
{
|
{
|
||||||
PropertyInfo[] props = args.Data.GetType().GetProperties();
|
sb.Append(' ');
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
84
Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#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('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
|
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,
|
||||||
|
|
|
@ -11,15 +11,7 @@ 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)
|
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
|
||||||
{
|
|
||||||
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;
|
||||||
|
|
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
30
Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
9
Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Logging
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(LogEventArgsJson))]
|
||||||
|
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
|
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,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System.IO;
|
using Ryujinx.Common.Utilities;
|
||||||
using System.Text.Json;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Logging
|
namespace Ryujinx.Common.Logging
|
||||||
{
|
{
|
||||||
|
@ -25,12 +25,8 @@ namespace Ryujinx.Common.Logging
|
||||||
|
|
||||||
public void Log(object sender, LogEventArgs e)
|
public void Log(object sender, LogEventArgs e)
|
||||||
{
|
{
|
||||||
string text = JsonSerializer.Serialize(e);
|
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
|
||||||
|
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
|
||||||
using (BinaryWriter writer = new BinaryWriter(_stream))
|
|
||||||
{
|
|
||||||
writer.Write(text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
11
Ryujinx.Common/Utilities/CommonJsonContext.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,62 @@
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using System.IO;
|
||||||
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;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
public class JsonHelper
|
public class JsonHelper
|
||||||
{
|
{
|
||||||
public static JsonNamingPolicy SnakeCase { get; }
|
private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy();
|
||||||
|
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
|
||||||
{
|
{
|
||||||
|
@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new();
|
||||||
|
|
||||||
for (int i = 0; i < name.Length; i++)
|
for (int i = 0; i < name.Length; i++)
|
||||||
{
|
{
|
||||||
|
@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append("_");
|
builder.Append('_');
|
||||||
builder.Append(char.ToLowerInvariant(c));
|
builder.Append(char.ToLowerInvariant(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,64 +94,5 @@ 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
34
Ryujinx.Common/Utilities/TypedStringEnumConverter.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ 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;
|
||||||
|
@ -24,14 +25,13 @@ 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,6 +57,10 @@ 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;
|
||||||
|
@ -197,7 +201,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
{
|
{
|
||||||
updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
|
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, TitleSerializerContext.TitleUpdateMetadata).Selected;
|
||||||
|
|
||||||
if (File.Exists(updatePath))
|
if (File.Exists(updatePath))
|
||||||
{
|
{
|
||||||
|
@ -411,7 +415,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
if (File.Exists(titleAocMetadataPath))
|
if (File.Exists(titleAocMetadataPath))
|
||||||
{
|
{
|
||||||
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath);
|
List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile(titleAocMetadataPath, ContentSerializerContext.ListDownloadableContentContainer);
|
||||||
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,29 +13,7 @@ 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 struct ProfilesJson
|
private static readonly ProfilesJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
{
|
|
||||||
[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; }
|
||||||
|
|
||||||
|
@ -47,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile<ProfilesJson>(_profilesJsonPath);
|
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, SerializerContext.ProfilesJson);
|
||||||
|
|
||||||
foreach (var profile in profilesJson.Profiles)
|
foreach (var profile in profilesJson.Profiles)
|
||||||
{
|
{
|
||||||
|
@ -92,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(_profilesJsonPath, JsonHelper.Serialize(profilesJson, true));
|
JsonHelper.SerializeToFile(_profilesJsonPath, profilesJson, SerializerContext.ProfilesJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
|
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,
|
||||||
|
|
10
Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs
Normal file
10
Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
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;
|
||||||
|
@ -8,8 +9,6 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -17,6 +16,8 @@ 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)
|
||||||
|
@ -173,7 +174,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
virtualAmiiboFile = JsonSerializer.Deserialize<VirtualAmiiboFile>(File.ReadAllText(filePath), new JsonSerializerOptions(JsonSerializerDefaults.General));
|
virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, SerializerContext.VirtualAmiiboFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -197,8 +198,7 @@ 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,8 @@ 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();
|
||||||
|
@ -285,10 +287,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = File.OpenRead(path))
|
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||||
{
|
|
||||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
|
|
10
Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs
Normal file
10
Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.App.Common
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(ApplicationMetadata))]
|
||||||
|
internal partial class ApplicationJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ 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;
|
||||||
|
@ -22,7 +23,6 @@ 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,6 +42,8 @@ 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;
|
||||||
|
@ -490,14 +492,12 @@ namespace Ryujinx.Ui.App.Common
|
||||||
|
|
||||||
appMetadata = new ApplicationMetadata();
|
appMetadata = new ApplicationMetadata();
|
||||||
|
|
||||||
using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
|
JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata);
|
||||||
|
|
||||||
JsonHelper.Serialize(stream, appMetadata, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
appMetadata = JsonHelper.DeserializeFromFile<ApplicationMetadata>(metadataFile);
|
appMetadata = JsonHelper.DeserializeFromFile(metadataFile, SerializerContext.ApplicationMetadata);
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch (JsonException)
|
||||||
{
|
{
|
||||||
|
@ -510,9 +510,7 @@ namespace Ryujinx.Ui.App.Common
|
||||||
{
|
{
|
||||||
modifyFunction(appMetadata);
|
modifyFunction(appMetadata);
|
||||||
|
|
||||||
using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
|
JsonHelper.SerializeToFile(metadataFile, appMetadata, SerializerContext.ApplicationMetadata);
|
||||||
|
|
||||||
JsonHelper.Serialize(stream, appMetadata, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return appMetadata;
|
return appMetadata;
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Ui.Common.Configuration
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Configuration
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<AudioBackend>))]
|
||||||
public enum AudioBackend
|
public enum AudioBackend
|
||||||
{
|
{
|
||||||
Dummy,
|
Dummy,
|
||||||
|
|
|
@ -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.IO;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
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<object> KeyboardConfig { get; set; }
|
public List<JsonObject> 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<object> ControllerConfig { get; set; }
|
public List<JsonObject> ControllerConfig { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input configurations
|
/// Input configurations
|
||||||
|
@ -354,11 +354,12 @@ 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<ConfigurationFileFormat>(path);
|
configurationFileFormat = JsonHelper.DeserializeFromFile(path, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
||||||
|
|
||||||
return configurationFileFormat.Version != 0;
|
return configurationFileFormat.Version != 0;
|
||||||
}
|
}
|
||||||
|
@ -376,8 +377,7 @@ 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)
|
||||||
{
|
{
|
||||||
using FileStream fileStream = File.Create(path, 4096, FileOptions.WriteThrough);
|
JsonHelper.SerializeToFile(path, this, ConfigurationFileFormatSettings.SerializerContext.ConfigurationFileFormat);
|
||||||
JsonHelper.Serialize(fileStream, this, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Configuration
|
||||||
|
{
|
||||||
|
internal static class ConfigurationFileFormatSettings
|
||||||
|
{
|
||||||
|
public static readonly ConfigurationJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Configuration
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||||
|
[JsonSerializable(typeof(ConfigurationFileFormat))]
|
||||||
|
internal partial class ConfigurationJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ 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
|
||||||
{
|
{
|
||||||
|
@ -631,8 +632,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<object>(),
|
KeyboardConfig = new List<JsonObject>(),
|
||||||
ControllerConfig = new List<object>(),
|
ControllerConfig = new List<JsonObject>(),
|
||||||
InputConfig = Hid.InputConfig,
|
InputConfig = Hid.InputConfig,
|
||||||
GraphicsBackend = Graphics.GraphicsBackend,
|
GraphicsBackend = Graphics.GraphicsBackend,
|
||||||
PreferredGpu = Graphics.PreferredGpu
|
PreferredGpu = Graphics.PreferredGpu
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Ui.Common.Configuration.System
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Configuration.System
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<Language>))]
|
||||||
public enum Language
|
public enum Language
|
||||||
{
|
{
|
||||||
Japanese,
|
Japanese,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
namespace Ryujinx.Ui.Common.Configuration.System
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Configuration.System
|
||||||
{
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<Region>))]
|
||||||
public enum Region
|
public enum Region
|
||||||
{
|
{
|
||||||
Japan,
|
Japan,
|
||||||
|
|
57
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs
Normal file
57
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs
Normal file
15
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
12
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs
Normal file
12
Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
14
Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs
Normal file
14
Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Models.Amiibo
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(AmiiboJson))]
|
||||||
|
public partial class AmiiboJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.Ui.Common.Models.Github
|
||||||
|
{
|
||||||
|
public class GithubReleaseAssetJsonResponse
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
public string BrowserDownloadUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Models.Github
|
||||||
|
{
|
||||||
|
public class GithubReleasesJsonResponse
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Models.Github
|
||||||
|
{
|
||||||
|
[JsonSerializable(typeof(GithubReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)]
|
||||||
|
public partial class GithubReleasesJsonSerializerContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,6 +38,8 @@ 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" };
|
||||||
|
|
||||||
|
@ -107,22 +109,16 @@ namespace Ryujinx.Modules
|
||||||
|
|
||||||
// Fetch latest build information
|
// Fetch latest build information
|
||||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
var fetched = JsonHelper.Deserialize(fetchedJson, SerializerContext.GithubReleasesJsonResponse);
|
||||||
JToken assets = jsonRoot["assets"];
|
_buildVer = fetched.Name;
|
||||||
|
|
||||||
_buildVer = (string)jsonRoot["name"];
|
foreach (var asset in fetched.Assets)
|
||||||
|
|
||||||
foreach (JToken asset in assets)
|
|
||||||
{
|
{
|
||||||
string assetName = (string)asset["name"];
|
if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt))
|
||||||
string assetState = (string)asset["state"];
|
|
||||||
string downloadURL = (string)asset["browser_download_url"];
|
|
||||||
|
|
||||||
if (assetName.StartsWith("ryujinx") && assetName.EndsWith(_platformExt))
|
|
||||||
{
|
{
|
||||||
_buildUrl = downloadURL;
|
_buildUrl = asset.BrowserDownloadUrl;
|
||||||
|
|
||||||
if (assetState != "uploaded")
|
if (asset.State != "uploaded")
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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<string[]>(patreonJsonString));
|
_patreonNamesText.Buffer.Text = string.Join(", ", JsonHelper.Deserialize(patreonJsonString, CommonJsonContext.Default.StringArray));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ 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;
|
||||||
|
@ -11,65 +12,15 @@ 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.Serialization;
|
using System.Text.Json;
|
||||||
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; }
|
||||||
|
@ -96,6 +47,8 @@ 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");
|
||||||
|
@ -127,9 +80,9 @@ namespace Ryujinx.Ui.Windows
|
||||||
|
|
||||||
if (File.Exists(_amiiboJsonPath))
|
if (File.Exists(_amiiboJsonPath))
|
||||||
{
|
{
|
||||||
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
|
||||||
|
|
||||||
if (await NeedsUpdate(JsonHelper.Deserialize<AmiiboJson>(amiiboJsonString).LastUpdated))
|
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
|
||||||
{
|
{
|
||||||
amiiboJsonString = await DownloadAmiiboJson();
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
}
|
}
|
||||||
|
@ -148,7 +101,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_amiiboList = JsonHelper.Deserialize<AmiiboJson>(amiiboJsonString).Amiibo;
|
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
|
||||||
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
if (LastScannedAmiiboShowAll)
|
if (LastScannedAmiiboShowAll)
|
||||||
|
|
|
@ -115,6 +115,8 @@ 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"))
|
||||||
|
@ -1120,10 +1122,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (Stream stream = File.OpenRead(path))
|
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
|
||||||
{
|
|
||||||
config = JsonHelper.Deserialize<InputConfig>(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (JsonException) { }
|
catch (JsonException) { }
|
||||||
}
|
}
|
||||||
|
@ -1145,9 +1144,7 @@ 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;
|
string jsonString = JsonHelper.Serialize(inputConfig, SerializerContext.InputConfig);
|
||||||
|
|
||||||
jsonString = JsonHelper.Serialize(inputConfig, true);
|
|
||||||
|
|
||||||
File.WriteAllText(path, jsonString);
|
File.WriteAllText(path, jsonString);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,15 +7,13 @@ 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 System.Text;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
|
||||||
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ui.Windows
|
namespace Ryujinx.Ui.Windows
|
||||||
{
|
{
|
||||||
|
@ -26,6 +24,8 @@ 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<List<DownloadableContentContainer>>(_dlcJsonPath);
|
_dlcContainerList = JsonHelper.DeserializeFromFile(_dlcJsonPath, SerializerContext.ListDownloadableContentContainer);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -260,10 +260,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
while (_dlcTreeView.Model.IterNext(ref parentIter));
|
while (_dlcTreeView.Model.IterNext(ref parentIter));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream dlcJsonStream = File.Create(_dlcJsonPath, 4096, FileOptions.WriteThrough))
|
JsonHelper.SerializeToFile(_dlcJsonPath, _dlcContainerList, SerializerContext.ListDownloadableContentContainer);
|
||||||
{
|
|
||||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_dlcContainerList, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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;
|
||||||
|
@ -14,10 +15,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 GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
|
||||||
using JsonHelper = Ryujinx.Common.Utilities.JsonHelper;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ui.Windows
|
namespace Ryujinx.Ui.Windows
|
||||||
{
|
{
|
||||||
|
@ -31,6 +30,7 @@ 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<TitleUpdateMetadata>(_updateJsonPath);
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_updateJsonPath, SerializerContext.TitleUpdateMetadata);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -192,10 +192,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream dlcJsonStream = File.Create(_updateJsonPath, 4096, FileOptions.WriteThrough))
|
JsonHelper.SerializeToFile(_updateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
||||||
{
|
|
||||||
dlcJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
_parent.UpdateGameTable();
|
_parent.UpdateGameTable();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue