Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f4b74e9ce1 | ||
|
4e19b36ad7 | ||
|
b16923a902 | ||
|
7e58b21f3d | ||
|
4fbc978e73 |
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
@@ -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*"
|
||||
|
@@ -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>
|
@@ -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()
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user