Compare commits

..

6 Commits

Author SHA1 Message Date
riperiperi
bebd8eb822 Vulkan: Cache delegate for EndRenderPass (#6132)
This prevents a small allocation each time this method is called. This is a top 3 SOH allocation during gameplay in most games, and eliminating it is pretty free.
2024-01-16 13:22:20 +01:00
gdkchan
f4b74e9ce1 Fix vertex buffer size when switching between inline and state draw parameters (#6101)
* Fix vertex buffer size when switching between inline and state draw parameters

* Format whitespace
2024-01-14 09:37:19 +01:00
Isaac Marovitz
4e19b36ad7 CI: Dependabot Groups (#6110)
* CI: Dependabot Groups

* NUnit

* Limit of 10

* Whoops

* Missing wildcard

* Remove Ryujank group
2024-01-13 15:28:57 +01:00
gdkchan
b16923a902 Revert Apple hypervisor force ordered memory change (#6068) 2024-01-13 11:58:09 +01:00
TSRBerry
7e58b21f3d Fix Amiibo regression and some minor code improvements (#6107)
* Remove redundant code and fix small issues

* Log amiibo exceptions

* Add more checks when getting Amiibo data

* Fall back to online data if local file is inaccessible

* Make dotnet format happy
2024-01-13 11:45:38 +01:00
Isaac Marovitz
4fbc978e73 Switch to Microsoft.IdentityModel.JsonWebTokens (#6108)
* Switch to `Microsoft.IdentityModel.JsonWebTokens`

* Formatting
2024-01-13 11:39:00 +01:00
14 changed files with 200 additions and 169 deletions

View File

@@ -13,7 +13,7 @@ updates:
- package-ecosystem: nuget
directory: /
open-pull-requests-limit: 5
open-pull-requests-limit: 10
schedule:
interval: daily
labels:
@@ -22,3 +22,19 @@ updates:
- marysaka
commit-message:
prefix: nuget
groups:
Avalonia:
patterns:
- "*Avalonia*"
Silk.NET:
patterns:
- "Silk.NET*"
OpenTK:
patterns:
- "OpenTK*"
SixLabors:
patterns:
- "SixLabors*"
NUnit:
patterns:
- "NUnit*"

View File

@@ -21,6 +21,7 @@
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
@@ -46,9 +47,8 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@@ -17,6 +17,7 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels
@@ -188,17 +189,25 @@ namespace Ryujinx.Ava.UI.ViewModels
_httpClient.Dispose();
}
private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
{
if (string.IsNullOrEmpty(json))
{
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
try
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson);
amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson);
return true;
}
catch
catch (JsonException exception)
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}");
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
@@ -208,27 +217,41 @@ namespace Ryujinx.Ava.UI.ViewModels
{
bool localIsValid = false;
bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
AmiiboJson amiiboJson = new();
try
{
localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson);
try
{
if (File.Exists(_amiiboJsonPath))
{
localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson);
}
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}");
}
if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
}
}
catch
catch (Exception exception)
{
if (!(localIsValid || remoteIsValid))
{
Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}");
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog();
Close();
}
else if (!remoteIsValid)
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}");
// Only the local file is valid, the local one should be used
// but the user should be warned.
ShowInfoDialog();
@@ -388,11 +411,18 @@ namespace Ryujinx.Ava.UI.ViewModels
private async Task<bool> NeedsUpdate(DateTime oldLastModified)
{
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
if (response.IsSuccessStatusCode)
try
{
return response.Content.Headers.LastModified != oldLastModified;
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
if (response.IsSuccessStatusCode)
{
return response.Content.Headers.LastModified != oldLastModified;
}
}
catch (HttpRequestException exception)
{
Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}");
}
return false;
@@ -400,21 +430,33 @@ namespace Ryujinx.Ava.UI.ViewModels
private async Task<string> DownloadAmiiboJson()
{
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
if (response.IsSuccessStatusCode)
try
{
string amiiboJsonString = await response.Content.ReadAsStringAsync();
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
using (FileStream amiiboJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough))
if (response.IsSuccessStatusCode)
{
amiiboJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
string amiiboJsonString = await response.Content.ReadAsStringAsync();
try
{
using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough);
dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'");
}
return amiiboJsonString;
}
return amiiboJsonString;
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
}
catch (HttpRequestException exception)
{
Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}");
}
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
@@ -422,9 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
Close();
return DefaultJson;
return null;
}
private void Close()

View File

@@ -1,62 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Cpu.AppleHv
{
static class HvCodePatcher
{
private const uint XMask = 0x3f808000u;
private const uint XValue = 0x8000000u;
private const uint ZrIndex = 31u;
public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
{
Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
Vector128<uint> mask = Vector128.Create(XMask);
Vector128<uint> value = Vector128.Create(XValue);
for (int index = 0; index < codeVector.Length; index++)
{
Vector128<uint> v = codeVector[index];
if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
{
int baseIndex = index * 4;
for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
{
ref uint inst = ref codeUint[instIndex];
if ((inst & XMask) != XValue)
{
continue;
}
bool isPair = (inst & (1u << 21)) != 0;
bool isLoad = (inst & (1u << 22)) != 0;
uint rt2 = (inst >> 10) & 0x1fu;
uint rs = (inst >> 16) & 0x1fu;
if (isLoad && rs != ZrIndex)
{
continue;
}
if (!isPair && rt2 != ZrIndex)
{
continue;
}
// Set the ordered flag.
inst |= 1u << 15;
}
}
}
}
}
}

View File

@@ -724,18 +724,6 @@ namespace Ryujinx.Cpu.AppleHv
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{
if (protection.HasFlag(MemoryPermission.Execute))
{
// Some applications use unordered exclusive memory access instructions
// where it is not valid to do so, leading to memory re-ordering that
// makes the code behave incorrectly on some CPUs.
// To work around this, we force all such accesses to be ordered.
using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
}
// TODO
}

View File

@@ -103,6 +103,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// <param name="argument">Method call argument</param>
public void DrawEnd(ThreedClass engine, int argument)
{
_drawState.DrawUsesEngineState = true;
DrawEnd(
engine,
_state.State.IndexBufferState.First,
@@ -205,10 +207,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
else
{
#pragma warning disable IDE0059 // Remove unnecessary value assignment
var drawState = _state.State.VertexBufferDrawState;
#pragma warning restore IDE0059
DrawImpl(engine, drawVertexCount, 1, 0, drawFirstVertex, firstInstance, indexed: false);
}
@@ -379,6 +377,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool oldDrawIndexed = _drawState.DrawIndexed;
_drawState.DrawIndexed = true;
_drawState.DrawUsesEngineState = false;
engine.ForceStateDirty(IndexBufferCountMethodOffset * 4);
DrawEnd(engine, firstIndex, indexCount, 0, 0);
@@ -424,6 +423,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool oldDrawIndexed = _drawState.DrawIndexed;
_drawState.DrawIndexed = false;
_drawState.DrawUsesEngineState = false;
engine.ForceStateDirty(VertexBufferFirstMethodOffset * 4);
DrawEnd(engine, 0, 0, firstVertex, vertexCount);
@@ -544,6 +544,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_state.State.FirstInstance = (uint)firstInstance;
_drawState.DrawIndexed = indexed;
_drawState.DrawUsesEngineState = true;
_currentSpecState.SetHasConstantBufferDrawParameters(true);
engine.UpdateState();
@@ -676,6 +677,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_drawState.DrawIndexed = indexed;
_drawState.DrawIndirect = true;
_drawState.DrawUsesEngineState = true;
_currentSpecState.SetHasConstantBufferDrawParameters(true);
engine.UpdateState();

View File

@@ -38,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
public bool DrawIndirect;
/// <summary>
/// Indicates that the draw is using the draw parameters on the 3D engine state, rather than inline parameters submitted with the draw command.
/// </summary>
public bool DrawUsesEngineState;
/// <summary>
/// Indicates if any of the currently used vertex shaders reads the instance ID.
/// </summary>
@@ -48,11 +53,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
public bool IsAnyVbInstanced;
/// <summary>
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
/// </summary>
public bool HasConstantBufferDrawParameters;
/// <summary>
/// Primitive topology for the next draw.
/// </summary>

View File

@@ -47,7 +47,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private uint _vbEnableMask;
private bool _prevDrawIndexed;
private readonly bool _prevDrawIndirect;
private bool _prevDrawIndirect;
private bool _prevDrawUsesEngineState;
private IndexType _prevIndexType;
private uint _prevFirstVertex;
private bool _prevTfEnable;
@@ -236,7 +237,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
// method when doing indexed draws, so we need to make sure
// to update the vertex buffers if we are doing a regular
// draw after a indexed one and vice-versa.
if (_drawState.DrawIndexed != _prevDrawIndexed)
// Some draws also do not update the engine state, so it is possible for it
// to not be dirty even if the vertex counts or other state changed. We need to force it to be dirty in this case.
if (_drawState.DrawIndexed != _prevDrawIndexed || _drawState.DrawUsesEngineState != _prevDrawUsesEngineState)
{
_updateTracker.ForceDirty(VertexBufferStateIndex);
@@ -251,6 +254,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
_prevDrawIndexed = _drawState.DrawIndexed;
_prevDrawUsesEngineState = _drawState.DrawUsesEngineState;
}
// Some draw parameters are used to restrict the vertex buffer size,
@@ -260,6 +264,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_drawState.DrawIndirect != _prevDrawIndirect)
{
_updateTracker.ForceDirty(VertexBufferStateIndex);
_prevDrawIndirect = _drawState.DrawIndirect;
}
// In some cases, the index type is also used to guess the

View File

@@ -520,7 +520,7 @@ namespace Ryujinx.Graphics.Vulkan
if (_gd.PipelineInternal.CurrentCommandBuffer.CommandBuffer.Handle == cbs.CommandBuffer.Handle)
{
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPass, false);
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPassDelegate, false);
}
else
{

View File

@@ -30,6 +30,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly PipelineCache PipelineCache;
public readonly AutoFlushCounter AutoFlush;
public readonly Action EndRenderPassDelegate;
protected PipelineDynamicState DynamicState;
private PipelineState _newState;
@@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.Vulkan
Device = device;
AutoFlush = new AutoFlushCounter(gd);
EndRenderPassDelegate = EndRenderPass;
var pipelineCacheCreateInfo = new PipelineCacheCreateInfo
{

View File

@@ -784,7 +784,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
{
BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPass);
BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPassDelegate);
}
public void UpdateCounters()

View File

@@ -1,10 +1,12 @@
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Account.Acc.AsyncContext;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -37,11 +39,6 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
credentials.Key.KeyId = parameters.ToString();
var header = new JwtHeader(credentials)
{
{ "jku", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" },
};
byte[] rawUserId = new byte[0x10];
RandomNumberGenerator.Fill(rawUserId);
@@ -51,23 +48,25 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
byte[] deviceAccountId = new byte[0x10];
RandomNumberGenerator.Fill(deviceId);
var payload = new JwtPayload
var descriptor = new SecurityTokenDescriptor
{
{ "sub", Convert.ToHexString(rawUserId).ToLower() },
{ "aud", "ed9e2f05d286f7b8" },
{ "di", Convert.ToHexString(deviceId).ToLower() },
{ "sn", "XAW10000000000" },
{ "bs:did", Convert.ToHexString(deviceAccountId).ToLower() },
{ "iss", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com" },
{ "typ", "id_token" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "jti", Guid.NewGuid().ToString() },
{ "exp", (DateTimeOffset.UtcNow + TimeSpan.FromHours(3)).ToUnixTimeSeconds() },
Subject = new GenericIdentity(Convert.ToHexString(rawUserId).ToLower()),
SigningCredentials = credentials,
Audience = "ed9e2f05d286f7b8",
Issuer = "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com",
TokenType = "id_token",
IssuedAt = DateTime.UtcNow,
Expires = DateTime.UtcNow + TimeSpan.FromHours(3),
Claims = new Dictionary<string, object>
{
{ "jku", "https://e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com/1.0.0/certificates" },
{ "di", Convert.ToHexString(deviceId).ToLower() },
{ "sn", "XAW10000000000" },
{ "bs:did", Convert.ToHexString(deviceAccountId).ToLower() }
}
};
JwtSecurityToken securityToken = new(header, payload);
return new JwtSecurityTokenHandler().WriteToken(securityToken);
return new JsonWebTokenHandler().CreateToken(descriptor);
}
public ResultCode CheckAvailability(ServiceCtx context)

View File

@@ -23,10 +23,10 @@
<ItemGroup>
<PackageReference Include="Concentus" />
<PackageReference Include="LibHac" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="MsgPack.Cli" />
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="NetCoreServer" />
</ItemGroup>

View File

@@ -1,3 +1,4 @@
using Gdk;
using Gtk;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -13,7 +14,9 @@ using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Window = Gtk.Window;
namespace Ryujinx.Ui.Windows
{
@@ -49,11 +52,11 @@ namespace Ryujinx.Ui.Windows
public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo")
{
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
InitializeComponent();
_httpClient = new HttpClient()
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30),
};
@@ -64,7 +67,7 @@ namespace Ryujinx.Ui.Windows
_amiiboList = new List<AmiiboApi>();
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx.Ui.Common/Resources/Logo_Amiibo.png");
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
_amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes);
_scanButton.Sensitive = false;
_randomUuidCheckBox.Sensitive = false;
@@ -72,17 +75,25 @@ namespace Ryujinx.Ui.Windows
_ = LoadContentAsync();
}
private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
private static bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
{
if (string.IsNullOrEmpty(json))
{
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
try
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson);
amiiboJson = JsonHelper.Deserialize(json, _serializerContext.AmiiboJson);
return true;
}
catch
catch (JsonException exception)
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
Logger.Error?.Print(LogClass.Application, $"Unable to deserialize amiibo data: {exception}");
amiiboJson = JsonHelper.Deserialize(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
@@ -92,27 +103,41 @@ namespace Ryujinx.Ui.Windows
{
bool localIsValid = false;
bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
AmiiboJson amiiboJson = new();
try
{
localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson);
try
{
if (File.Exists(_amiiboJsonPath))
{
localIsValid = TryGetAmiiboJson(await File.ReadAllTextAsync(_amiiboJsonPath), out amiiboJson);
}
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"Unable to read data from '{_amiiboJsonPath}': {exception}");
}
if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
}
}
catch
catch (Exception exception)
{
if (!(localIsValid || remoteIsValid))
{
Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}");
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog();
Close();
}
else if (!remoteIsValid)
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't update amiibo data: {exception}");
// Only the local file is valid, the local one should be used
// but the user should be warned.
ShowInfoDialog();
@@ -196,11 +221,18 @@ namespace Ryujinx.Ui.Windows
private async Task<bool> NeedsUpdate(DateTime oldLastModified)
{
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
if (response.IsSuccessStatusCode)
try
{
return response.Content.Headers.LastModified != oldLastModified;
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
if (response.IsSuccessStatusCode)
{
return response.Content.Headers.LastModified != oldLastModified;
}
}
catch (HttpRequestException exception)
{
Logger.Error?.Print(LogClass.Application, $"Unable to check for amiibo data updates: {exception}");
}
return false;
@@ -208,29 +240,37 @@ namespace Ryujinx.Ui.Windows
private async Task<string> DownloadAmiiboJson()
{
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
if (response.IsSuccessStatusCode)
try
{
string amiiboJsonString = await response.Content.ReadAsStringAsync();
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough))
if (response.IsSuccessStatusCode)
{
dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
string amiiboJsonString = await response.Content.ReadAsStringAsync();
try
{
using FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough);
dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
}
catch (Exception exception)
{
Logger.Warning?.Print(LogClass.Application, $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'");
}
return amiiboJsonString;
}
return amiiboJsonString;
}
else
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data. Response status code: {response.StatusCode}");
GtkDialog.CreateInfoDialog($"Amiibo API", "An error occured while fetching information from the API.");
Close();
}
catch (HttpRequestException exception)
{
Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}");
}
return DefaultJson;
GtkDialog.CreateInfoDialog("Amiibo API", "An error occured while fetching information from the API.");
return null;
}
private async Task UpdateAmiiboPreview(string imageUrl)
@@ -240,7 +280,7 @@ namespace Ryujinx.Ui.Windows
if (response.IsSuccessStatusCode)
{
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
Gdk.Pixbuf amiiboPreview = new(amiiboPreviewBytes);
Pixbuf amiiboPreview = new(amiiboPreviewBytes);
float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width,
(float)_amiiboImage.AllocatedHeight / amiiboPreview.Height);
@@ -248,7 +288,7 @@ namespace Ryujinx.Ui.Windows
int resizeHeight = (int)(amiiboPreview.Height * ratio);
int resizeWidth = (int)(amiiboPreview.Width * ratio);
_amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, Gdk.InterpType.Bilinear);
_amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, InterpType.Bilinear);
}
else
{
@@ -258,7 +298,7 @@ namespace Ryujinx.Ui.Windows
private static void ShowInfoDialog()
{
GtkDialog.CreateInfoDialog($"Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.");
GtkDialog.CreateInfoDialog("Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.");
}
//
@@ -314,7 +354,7 @@ namespace Ryujinx.Ui.Windows
{
AmiiboId = _amiiboCharsComboBox.ActiveId;
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
_amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes);
string imageUrl = _amiiboList.Find(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image;
@@ -354,7 +394,7 @@ namespace Ryujinx.Ui.Windows
private void ShowAllCheckBox_Clicked(object sender, EventArgs e)
{
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
_amiiboImage.Pixbuf = new Pixbuf(_amiiboLogoBytes);
_amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed;
_amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
@@ -365,7 +405,7 @@ namespace Ryujinx.Ui.Windows
_scanButton.Sensitive = false;
_randomUuidCheckBox.Sensitive = false;
new Task(() => ParseAmiiboData()).Start();
new Task(ParseAmiiboData).Start();
}
private void ScanButton_Pressed(object sender, EventArgs args)