Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
00a86f8fc2 ci: bump actions/github-script from 6 to 7
Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 16:10:59 +00:00
15 changed files with 248 additions and 366 deletions

View File

@@ -79,21 +79,21 @@ jobs:
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Upload Ryujinx.Ava artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_ava
@@ -144,14 +144,14 @@ jobs:
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"

View File

@@ -63,7 +63,7 @@ jobs:
- name: Upload report
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: dotnet-format
path: ./*-report.json

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
steps:
- uses: actions/github-script@v6
- uses: actions/github-script@v7
with:
script: |
const {owner, repo} = context.repo;

View File

@@ -34,7 +34,7 @@ jobs:
shell: bash
- name: Create tag
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
github.rest.git.createRef({

View File

@@ -0,0 +1,40 @@
using Ryujinx.Ava.UI.ViewModels;
using System;
namespace Ryujinx.Ava.UI.Models
{
public class CheatModel : BaseModel
{
private bool _isEnabled;
public event EventHandler<bool> EnableToggled;
public CheatModel(string name, string buildId, bool isEnabled)
{
Name = name;
BuildId = buildId;
IsEnabled = isEnabled;
}
public bool IsEnabled
{
get => _isEnabled;
set
{
_isEnabled = value;
EnableToggled?.Invoke(this, _isEnabled);
OnPropertyChanged();
}
}
public string BuildId { get; }
public string BuildIdKey => $"{BuildId}-{Name}";
public string Name { get; }
public string CleanName => Name[1..^7];
}
}

View File

@@ -1,57 +0,0 @@
using Ryujinx.Ava.UI.ViewModels;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
namespace Ryujinx.Ava.UI.Models
{
public class CheatNode : BaseModel
{
private bool _isEnabled = false;
public ObservableCollection<CheatNode> SubNodes { get; } = new();
public string CleanName => Name[1..^7];
public string BuildIdKey => $"{BuildId}-{Name}";
public bool IsRootNode { get; }
public string Name { get; }
public string BuildId { get; }
public string Path { get; }
public bool IsEnabled
{
get
{
if (SubNodes.Count > 0)
{
return SubNodes.ToList().TrueForAll(x => x.IsEnabled);
}
return _isEnabled;
}
set
{
foreach (var cheat in SubNodes)
{
cheat.IsEnabled = value;
cheat.OnPropertyChanged();
}
_isEnabled = value;
}
}
public CheatNode(string name, string buildId, string path, bool isRootNode, bool isEnabled = false)
{
Name = name;
BuildId = buildId;
Path = path;
IsEnabled = isEnabled;
IsRootNode = isRootNode;
SubNodes.CollectionChanged += CheatsList_CollectionChanged;
}
private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(nameof(IsEnabled));
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace Ryujinx.Ava.UI.Models
{
public class CheatsList : ObservableCollection<CheatModel>
{
public CheatsList(string buildId, string path)
{
BuildId = buildId;
Path = path;
CollectionChanged += CheatsList_CollectionChanged;
}
public string BuildId { get; }
public string Path { get; }
public bool IsEnabled
{
get
{
return this.ToList().TrueForAll(x => x.IsEnabled);
}
set
{
foreach (var cheat in this)
{
cheat.IsEnabled = value;
}
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
}
}
private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
(e.NewItems[0] as CheatModel).EnableToggled += Item_EnableToggled;
}
}
private void Item_EnableToggled(object sender, bool e)
{
OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
}
}
}

View File

@@ -188,61 +188,35 @@ namespace Ryujinx.Ava.UI.ViewModels
_httpClient.Dispose();
}
private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
{
try
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson);
return true;
}
catch
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
}
private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson()
{
bool localIsValid = false;
bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
try
{
localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson);
if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
}
}
catch
{
if (!(localIsValid || remoteIsValid))
{
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog();
Close();
}
else if (!remoteIsValid)
{
// Only the local file is valid, the local one should be used
// but the user should be warned.
ShowInfoDialog();
}
}
return amiiboJson;
}
private async Task LoadContentAsync()
{
AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson();
string amiiboJsonString = DefaultJson;
_amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
if (File.Exists(_amiiboJsonPath))
{
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).LastUpdated))
{
amiiboJsonString = await DownloadAmiiboJson();
}
}
else
{
try
{
amiiboJsonString = await DownloadAmiiboJson();
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data: {ex}");
ShowInfoDialog();
}
}
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData();
}
@@ -388,14 +362,26 @@ 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/"));
return false;
if (response.IsSuccessStatusCode)
{
return response.Content.Headers.LastModified != new DateTimeOffset(oldLastModified.Ticks - (oldLastModified.Ticks % TimeSpan.TicksPerSecond), TimeSpan.Zero);
}
return false;
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to check for amiibo updates: {ex}");
ShowInfoDialog();
return false;
}
}
private async Task<string> DownloadAmiiboJson()

View File

@@ -86,16 +86,28 @@
</Style>
</Styles>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding SubNodes}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" />
<TextBlock Width="150" Text="{Binding CleanName}" IsVisible="{Binding !IsRootNode}" />
<TextBlock Width="150" Text="{Binding BuildId}" IsVisible="{Binding IsRootNode}" />
<TextBlock Text="{Binding Path}" IsVisible="{Binding IsRootNode}" />
<TextBlock Width="150" Text="{Binding BuildId}" />
<TextBlock Text="{Binding Path}" />
</StackPanel>
</TreeDataTemplate>
</TreeView.ItemTemplate>
<DataTemplate x:DataType="model:CheatModel">
<StackPanel
Margin="0"
HorizontalAlignment="Left"
Orientation="Horizontal">
<CheckBox
MinWidth="20"
Margin="5,0"
Padding="0"
IsChecked="{Binding IsEnabled}" />
<TextBlock VerticalAlignment="Center" Text="{Binding CleanName}" />
</StackPanel>
</DataTemplate>
</TreeView.DataTemplates>
</TreeView>
</Border>
<DockPanel

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
private readonly string _enabledCheatsPath;
public bool NoCheatsFound { get; }
public AvaloniaList<CheatNode> LoadedCheats { get; }
public AvaloniaList<CheatsList> LoadedCheats { get; }
public string Heading { get; }
public string BuildId { get; }
@@ -33,7 +33,7 @@ namespace Ryujinx.Ava.UI.Windows
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
{
LoadedCheats = new AvaloniaList<CheatNode>();
LoadedCheats = new AvaloniaList<CheatsList>();
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
@@ -62,7 +62,7 @@ namespace Ryujinx.Ava.UI.Windows
string currentCheatFile = string.Empty;
string buildId = string.Empty;
CheatNode currentGroup = null;
CheatsList currentGroup = null;
foreach (var cheat in mods.Cheats)
{
@@ -72,13 +72,13 @@ namespace Ryujinx.Ava.UI.Windows
string parentPath = currentCheatFile.Replace(titleModsPath, "");
buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper();
currentGroup = new CheatNode("", buildId, parentPath, true);
currentGroup = new CheatsList(buildId, parentPath);
LoadedCheats.Add(currentGroup);
}
var model = new CheatNode(cheat.Name, buildId, "", false, enabled.Contains($"{buildId}-{cheat.Name}"));
currentGroup?.SubNodes.Add(model);
var model = new CheatModel(cheat.Name, buildId, enabled.Contains($"{buildId}-{cheat.Name}"));
currentGroup?.Add(model);
cheatAdded++;
}
@@ -104,7 +104,7 @@ namespace Ryujinx.Ava.UI.Windows
foreach (var cheats in LoadedCheats)
{
foreach (var cheat in cheats.SubNodes)
foreach (var cheat in cheats)
{
if (cheat.IsEnabled)
{

View File

@@ -675,7 +675,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
KMemoryPermission.None,
MemoryAttribute.Mask,
MemoryAttribute.None,
MemoryAttribute.IpcAndDeviceMapped | MemoryAttribute.PermissionLocked,
MemoryAttribute.IpcAndDeviceMapped,
out MemoryState state,
out _,
out _);
@@ -687,7 +687,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
state,
KMemoryPermission.None,
KMemoryPermission.None,
MemoryAttribute.Mask & ~MemoryAttribute.PermissionLocked,
MemoryAttribute.Mask,
MemoryAttribute.None);
if (success)
@@ -913,27 +913,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
return Result.Success;
}
public Result SetMemoryAttribute(ulong address, ulong size, MemoryAttribute attributeMask, MemoryAttribute attributeValue)
public Result SetMemoryAttribute(
ulong address,
ulong size,
MemoryAttribute attributeMask,
MemoryAttribute attributeValue)
{
lock (_blockManager)
{
MemoryState stateCheckMask = 0;
if (attributeMask.HasFlag(MemoryAttribute.Uncached))
{
stateCheckMask = MemoryState.AttributeChangeAllowed;
}
if (attributeMask.HasFlag(MemoryAttribute.PermissionLocked))
{
stateCheckMask |= MemoryState.PermissionLockAllowed;
}
if (CheckRange(
address,
size,
stateCheckMask,
stateCheckMask,
MemoryState.AttributeChangeAllowed,
MemoryState.AttributeChangeAllowed,
KMemoryPermission.None,
KMemoryPermission.None,
MemoryAttribute.BorrowedAndIpcMapped,

View File

@@ -12,10 +12,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
IpcMapped = 1 << 1,
DeviceMapped = 1 << 2,
Uncached = 1 << 3,
PermissionLocked = 1 << 4,
IpcAndDeviceMapped = IpcMapped | DeviceMapped,
BorrowedAndIpcMapped = Borrowed | IpcMapped,
DeviceMappedAndUncached = DeviceMapped | Uncached,
}
}

View File

@@ -5,155 +5,35 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
[Flags]
enum MemoryState : uint
{
Unmapped = 0x0,
Io = Mapped | 0x1,
Normal = Mapped | QueryPhysicalAddressAllowed | 0x2,
CodeStatic = ForceReadWritableByDebugSyscalls |
IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
ProcessPermissionChangeAllowed |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
MapProcessAllowed |
LinearMapped |
0x3,
CodeMutable = PermissionChangeAllowed |
IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
MapAllowed |
TransferMemoryAllowed |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IpcBufferAllowed |
IsPoolAllocated |
MapProcessAllowed |
AttributeChangeAllowed |
CodeMemoryAllowed |
LinearMapped |
PermissionLockAllowed |
0x4,
Heap = PermissionChangeAllowed |
IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
MapAllowed |
TransferMemoryAllowed |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IpcBufferAllowed |
IsPoolAllocated |
AttributeChangeAllowed |
CodeMemoryAllowed |
LinearMapped |
0x5,
SharedMemory = Mapped | IsPoolAllocated | LinearMapped | 0x6,
ModCodeStatic = ForceReadWritableByDebugSyscalls |
IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
ProcessPermissionChangeAllowed |
UnmapProcessCodeMemoryAllowed |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
MapProcessAllowed |
LinearMapped |
0x8,
ModCodeMutable = PermissionChangeAllowed |
IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
MapAllowed |
UnmapProcessCodeMemoryAllowed |
TransferMemoryAllowed |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IpcBufferAllowed |
IsPoolAllocated |
MapProcessAllowed |
AttributeChangeAllowed |
CodeMemoryAllowed |
LinearMapped |
PermissionLockAllowed |
0x9,
IpcBuffer0 = IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
LinearMapped |
0xA,
Stack = IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
LinearMapped |
0xB,
ThreadLocal = Mapped | IsPoolAllocated | LinearMapped | 0xC,
TransferMemoryIsolated = IpcSendAllowedType0 |
IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
AttributeChangeAllowed |
LinearMapped |
0xD,
TransferMemory = IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
LinearMapped |
0xE,
ProcessMemory = IpcSendAllowedType3 | IpcSendAllowedType1 | Mapped | IsPoolAllocated | LinearMapped | 0xF,
Reserved = 0x10,
IpcBuffer1 = IpcSendAllowedType3 |
IpcSendAllowedType1 |
Mapped |
QueryPhysicalAddressAllowed |
MapDeviceAllowed |
MapDeviceAlignedAllowed |
IsPoolAllocated |
LinearMapped |
0x11,
IpcBuffer3 = IpcSendAllowedType3 | Mapped | QueryPhysicalAddressAllowed | MapDeviceAllowed | IsPoolAllocated | LinearMapped | 0x12,
KernelStack = Mapped | 0x13,
CodeReadOnly = ForceReadWritableByDebugSyscalls | Mapped | IsPoolAllocated | LinearMapped | 0x14,
CodeWritable = Mapped | IsPoolAllocated | LinearMapped | 0x15,
UserMask = 0xFF,
Mask = 0xFFFFFFFF,
Unmapped = 0x00000000,
Io = 0x00002001,
Normal = 0x00042002,
CodeStatic = 0x00DC7E03,
CodeMutable = 0x03FEBD04,
Heap = 0x037EBD05,
SharedMemory = 0x00402006,
ModCodeStatic = 0x00DD7E08,
ModCodeMutable = 0x03FFBD09,
IpcBuffer0 = 0x005C3C0A,
Stack = 0x005C3C0B,
ThreadLocal = 0x0040200C,
TransferMemoryIsolated = 0x015C3C0D,
TransferMemory = 0x005C380E,
ProcessMemory = 0x0040380F,
Reserved = 0x00000010,
IpcBuffer1 = 0x005C3811,
IpcBuffer3 = 0x004C2812,
KernelStack = 0x00002013,
CodeReadOnly = 0x00402214,
CodeWritable = 0x00402015,
UserMask = 0xff,
Mask = 0xffffffff,
PermissionChangeAllowed = 1 << 8,
ForceReadWritableByDebugSyscalls = 1 << 9,
IpcSendAllowedType0 = 1 << 10,
IpcSendAllowedType3 = 1 << 11,
IpcSendAllowedType1 = 1 << 12,
Mapped = 1 << 13,
ProcessPermissionChangeAllowed = 1 << 14,
MapAllowed = 1 << 15,
UnmapProcessCodeMemoryAllowed = 1 << 16,
@@ -166,7 +46,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MapProcessAllowed = 1 << 23,
AttributeChangeAllowed = 1 << 24,
CodeMemoryAllowed = 1 << 25,
LinearMapped = 1 << 26,
PermissionLockAllowed = 1 << 27,
}
}

View File

@@ -949,16 +949,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
MemoryAttribute attributes = attributeMask | attributeValue;
const MemoryAttribute SupportedAttributes = MemoryAttribute.Uncached | MemoryAttribute.PermissionLocked;
if (attributes != attributeMask ||
(attributes | SupportedAttributes) != SupportedAttributes)
{
return KernelResult.InvalidCombination;
}
// The permission locked attribute can't be unset.
if ((attributeMask & MemoryAttribute.PermissionLocked) != (attributeValue & MemoryAttribute.PermissionLocked))
(attributes | MemoryAttribute.Uncached) != MemoryAttribute.Uncached)
{
return KernelResult.InvalidCombination;
}

View File

@@ -72,61 +72,37 @@ namespace Ryujinx.Ui.Windows
_ = LoadContentAsync();
}
private bool TryGetAmiiboJson(string json, out AmiiboJson amiiboJson)
{
try
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(json, _serializerContext.AmiiboJson);
return true;
}
catch
{
amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
return false;
}
}
private async Task<AmiiboJson> GetMostRecentAmiiboListOrDefaultJson()
{
bool localIsValid = false;
bool remoteIsValid = false;
AmiiboJson amiiboJson = JsonHelper.Deserialize<AmiiboJson>(DefaultJson, _serializerContext.AmiiboJson);
try
{
localIsValid = TryGetAmiiboJson(File.ReadAllText(_amiiboJsonPath), out amiiboJson);
if (!localIsValid || await NeedsUpdate(amiiboJson.LastUpdated))
{
remoteIsValid = TryGetAmiiboJson(await DownloadAmiiboJson(), out amiiboJson);
}
}
catch
{
if (!(localIsValid || remoteIsValid))
{
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog();
Close();
}
else if (!remoteIsValid)
{
// Only the local file is valid, the local one should be used
// but the user should be warned.
ShowInfoDialog();
}
}
return amiiboJson;
}
private async Task LoadContentAsync()
{
AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson();
string amiiboJsonString = DefaultJson;
_amiiboList = amiiboJson.Amiibo.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
if (File.Exists(_amiiboJsonPath))
{
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).LastUpdated))
{
amiiboJsonString = await DownloadAmiiboJson();
}
}
else
{
try
{
amiiboJsonString = await DownloadAmiiboJson();
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to download amiibo data: {ex}");
ShowInfoDialog();
Close();
}
}
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
if (LastScannedAmiiboShowAll)
{
@@ -196,14 +172,25 @@ 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/"));
return false;
if (response.IsSuccessStatusCode)
{
return response.Content.Headers.LastModified != new DateTimeOffset(oldLastModified.Ticks - (oldLastModified.Ticks % TimeSpan.TicksPerSecond), TimeSpan.Zero);
}
return false;
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to check for amiibo updates: {ex}");
ShowInfoDialog();
return false;
}
}
private async Task<string> DownloadAmiiboJson()