Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
933e5144a9 | ||
|
73a42c85c4 | ||
|
39ba11054b | ||
|
c250e3392c | ||
|
e56b069081 | ||
|
204c031fef | ||
|
d9053bbe37 | ||
|
c25e8427aa | ||
|
21a081b185 | ||
|
b540ea80d1 | ||
|
d692a9b83e | ||
|
9677ddaa5d | ||
|
ce92e8cd04 | ||
|
456fc04007 | ||
|
458452279c | ||
|
817b89767a | ||
|
3fb583c98c | ||
|
d2686e0a5b | ||
|
4905101df1 | ||
|
8750b90a7f | ||
|
af01100050 | ||
|
c0821fee1f | ||
|
a5c2aead67 | ||
|
d41c95dcff | ||
|
fbf2b09706 | ||
|
1fc0f569de | ||
|
dff138229c | ||
|
472119c8da | ||
|
1865ea87e5 | ||
|
18b61aff59 | ||
|
cb22629ac1 | ||
|
6f0f99ee2b | ||
|
70f2da8fdf | ||
|
5d3ef7761b | ||
|
476b4683cf | ||
|
5fb5079730 | ||
|
3fbacd0f49 | ||
|
7aa6abc120 | ||
|
548bfd60a2 | ||
|
65778a6b78 | ||
|
f4e879a1e6 | ||
|
a1ddaa2736 | ||
|
008286b79f | ||
|
a0c77f8d11 | ||
|
ece36b274d | ||
|
f3cc2e5703 | ||
|
5a39d3c4a1 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: Something doesn't work correctly in Ryujinx. Note that game-specific issues should be instead posted on the Game Compatibility List at https://github.com/Ryujinx/Ryujinx-Games-List, unless it is a provable regression.
|
about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
|
||||||
#assignees:
|
#assignees:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@@ -21,6 +21,10 @@
|
|||||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||||
alt="">
|
alt="">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://crwd.in/ryujinx">
|
||||||
|
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||||
|
alt="">
|
||||||
|
</a>
|
||||||
<a href="https://discord.com/invite/VkQYXAZ">
|
<a href="https://discord.com/invite/VkQYXAZ">
|
||||||
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||||
alt="Discord">
|
alt="Discord">
|
||||||
@@ -36,7 +40,7 @@
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable.
|
As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
|
||||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -48,6 +52,8 @@ See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ry
|
|||||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||||
|
|
||||||
|
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||||
|
|
||||||
## Latest build
|
## Latest build
|
||||||
|
|
||||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||||
@@ -90,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
|||||||
|
|
||||||
- **GPU**
|
- **GPU**
|
||||||
|
|
||||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||||
|
|
||||||
- **Input**
|
- **Input**
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="OpenTK.OpenAL" Version="4.7.2" />
|
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Audio.Common;
|
using Ryujinx.Audio.Common;
|
||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using System;
|
using System;
|
||||||
@@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
|
|
||||||
if (device == 0)
|
if (device == 0)
|
||||||
{
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application,
|
||||||
|
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
|||||||
|
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
{
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
|
||||||
SDL_CloseAudioDevice(device);
|
SDL_CloseAudioDevice(device);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -71,6 +72,19 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
return (short)value;
|
return (short)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
|
||||||
|
{
|
||||||
|
if ((uint)index > (uint)coefficients.Length)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coefficients[index];
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
|
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
|
||||||
{
|
{
|
||||||
@@ -84,8 +98,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||||
short history0 = loopContext.History0;
|
short history0 = loopContext.History0;
|
||||||
short history1 = loopContext.History1;
|
short history1 = loopContext.History1;
|
||||||
short coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
|
||||||
short coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
|
||||||
|
|
||||||
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
|
||||||
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
|
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
|
||||||
@@ -109,8 +123,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
|
|
||||||
coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
coefficientIndex = (byte)((predScale >> 4) & 0xF);
|
||||||
|
|
||||||
coefficient0 = coefficients[coefficientIndex * 2 + 0];
|
coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
|
||||||
coefficient1 = coefficients[coefficientIndex * 2 + 1];
|
coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
|
||||||
|
|
||||||
nibbles += 2;
|
nibbles += 2;
|
||||||
|
|
||||||
|
@@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasRemainingCommands(int sessionId)
|
||||||
|
{
|
||||||
|
return _sessionCommandList[sessionId] != null;
|
||||||
|
}
|
||||||
|
|
||||||
public void Signal()
|
public void Signal()
|
||||||
{
|
{
|
||||||
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
_mailbox.SendMessage(MailboxMessage.RenderStart);
|
||||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
|
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
public uint SampleRate { get; }
|
public uint SampleRate { get; }
|
||||||
|
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.AuxiliaryBuffer;
|
public CommandType CommandType => CommandType.AuxiliaryBuffer;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint InputBufferIndex { get; }
|
public uint InputBufferIndex { get; }
|
||||||
public uint OutputBufferIndex { get; }
|
public uint OutputBufferIndex { get; }
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.BiquadFilter;
|
public CommandType CommandType => CommandType.BiquadFilter;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
public Memory<BiquadFilterState> BiquadFilterState { get; }
|
||||||
public int InputBufferIndex { get; }
|
public int InputBufferIndex { get; }
|
||||||
|
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.CaptureBuffer;
|
public CommandType CommandType => CommandType.CaptureBuffer;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint InputBufferIndex { get; }
|
public uint InputBufferIndex { get; }
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.CircularBufferSink;
|
public CommandType CommandType => CommandType.CircularBufferSink;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort[] Input { get; }
|
public ushort[] Input { get; }
|
||||||
public uint InputCount { get; }
|
public uint InputCount { get; }
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.ClearMixBuffer;
|
public CommandType CommandType => CommandType.ClearMixBuffer;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ClearMixBufferCommand(int nodeId)
|
public ClearMixBufferCommand(int nodeId)
|
||||||
{
|
{
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.CopyMixBuffer;
|
public CommandType CommandType => CommandType.CopyMixBuffer;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType { get; }
|
public CommandType CommandType { get; }
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
public uint SampleRate { get; }
|
public uint SampleRate { get; }
|
||||||
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Delay;
|
public CommandType CommandType => CommandType.Delay;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public DelayParameter Parameter => _parameter;
|
public DelayParameter Parameter => _parameter;
|
||||||
public Memory<DelayState> State { get; }
|
public Memory<DelayState> State { get; }
|
||||||
@@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
|
||||||
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
|
||||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
|
||||||
{
|
{
|
||||||
|
const ushort channelCount = 1;
|
||||||
|
|
||||||
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
|
||||||
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
|
||||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||||
@@ -70,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
float temp = input * inGain + delayLineValue * feedbackGain;
|
float temp = input * inGain + delayLineValue * feedbackGain;
|
||||||
|
|
||||||
state.UpdateLowPassFilter(ref temp, 1);
|
state.UpdateLowPassFilter(ref temp, channelCount);
|
||||||
|
|
||||||
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
Y = state.DelayLines[1].Read(),
|
Y = state.DelayLines[1].Read(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||||
|
|
||||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
|
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
W = state.DelayLines[3].Read()
|
W = state.DelayLines[3].Read()
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||||
|
|
||||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
|
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
|
||||||
|
|
||||||
@@ -171,12 +171,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
|
||||||
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
|
||||||
|
|
||||||
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain,
|
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
|
||||||
0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
|
0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
|
||||||
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f,
|
delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
|
||||||
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f,
|
0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
|
||||||
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, 0.0f,
|
delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
|
||||||
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain);
|
0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
|
||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
for (int i = 0; i < sampleCount; i++)
|
||||||
{
|
{
|
||||||
@@ -200,7 +200,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
U = state.DelayLines[5].Read()
|
U = state.DelayLines[5].Read()
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
|
Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
|
||||||
|
|
||||||
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
|
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.DepopForMixBuffers;
|
public CommandType CommandType => CommandType.DepopForMixBuffers;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint MixBufferOffset { get; }
|
public uint MixBufferOffset { get; }
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.DepopPrepare;
|
public CommandType CommandType => CommandType.DepopPrepare;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint MixBufferCount { get; }
|
public uint MixBufferCount { get; }
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.DeviceSink;
|
public CommandType CommandType => CommandType.DeviceSink;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public string DeviceName { get; }
|
public string DeviceName { get; }
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
|
public CommandType CommandType => CommandType.DownMixSurroundToStereo;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort[] InputBufferIndices { get; }
|
public ushort[] InputBufferIndices { get; }
|
||||||
public ushort[] OutputBufferIndices { get; }
|
public ushort[] OutputBufferIndices { get; }
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
private BiquadFilterParameter[] _parameters;
|
private BiquadFilterParameter[] _parameters;
|
||||||
private Memory<BiquadFilterState> _biquadFilterStates;
|
private Memory<BiquadFilterState> _biquadFilterStates;
|
||||||
@@ -47,9 +47,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Nintendo also implements a hot path for double biquad filters, but no generic path when the command definition suggests it could be done.
|
// NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
|
||||||
// As such we currently only implement a generic path for simplicity.
|
// As such we currently only implement a generic path for simplicity for double biquad.
|
||||||
// TODO: Implement double biquad filters fast path.
|
|
||||||
if (_parameters.Length == 1)
|
if (_parameters.Length == 1)
|
||||||
{
|
{
|
||||||
BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
|
BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType { get; }
|
public CommandType CommandType { get; }
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; }
|
public uint EstimatedProcessingTime { get; }
|
||||||
|
|
||||||
public void Process(CommandList context);
|
public void Process(CommandList context);
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.LimiterVersion1;
|
public CommandType CommandType => CommandType.LimiterVersion1;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public LimiterParameter Parameter => _parameter;
|
public LimiterParameter Parameter => _parameter;
|
||||||
public Memory<LimiterState> State { get; }
|
public Memory<LimiterState> State { get; }
|
||||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.LimiterVersion2;
|
public CommandType CommandType => CommandType.LimiterVersion2;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public LimiterParameter Parameter => _parameter;
|
public LimiterParameter Parameter => _parameter;
|
||||||
public Memory<LimiterState> State { get; }
|
public Memory<LimiterState> State { get; }
|
||||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Mix;
|
public CommandType CommandType => CommandType.Mix;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.MixRamp;
|
public CommandType CommandType => CommandType.MixRamp;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.MixRampGrouped;
|
public CommandType CommandType => CommandType.MixRampGrouped;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint MixBufferCount { get; }
|
public uint MixBufferCount { get; }
|
||||||
|
|
||||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
|
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
public uint SampleRate { get; }
|
public uint SampleRate { get; }
|
||||||
|
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
|
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
public uint SampleRate { get; }
|
public uint SampleRate { get; }
|
||||||
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Performance;
|
public CommandType CommandType => CommandType.Performance;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
|
public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Reverb3d;
|
public CommandType CommandType => CommandType.Reverb3d;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Reverb;
|
public CommandType CommandType => CommandType.Reverb;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ReverbParameter Parameter => _parameter;
|
public ReverbParameter Parameter => _parameter;
|
||||||
public Memory<ReverbState> State { get; }
|
public Memory<ReverbState> State { get; }
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Upsample;
|
public CommandType CommandType => CommandType.Upsample;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public uint BufferCount { get; }
|
public uint BufferCount { get; }
|
||||||
public uint InputBufferIndex { get; }
|
public uint InputBufferIndex { get; }
|
||||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.Volume;
|
public CommandType CommandType => CommandType.Volume;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
|||||||
|
|
||||||
public CommandType CommandType => CommandType.VolumeRamp;
|
public CommandType CommandType => CommandType.VolumeRamp;
|
||||||
|
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
public ushort InputBufferIndex { get; }
|
public ushort InputBufferIndex { get; }
|
||||||
public ushort OutputBufferIndex { get; }
|
public ushort OutputBufferIndex { get; }
|
||||||
|
@@ -28,6 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
|
|
||||||
|
private AudioRendererRenderingDevice _renderingDevice;
|
||||||
private AudioRendererExecutionMode _executionMode;
|
private AudioRendererExecutionMode _executionMode;
|
||||||
private IWritableEvent _systemEvent;
|
private IWritableEvent _systemEvent;
|
||||||
private ManualResetEvent _terminationEvent;
|
private ManualResetEvent _terminationEvent;
|
||||||
@@ -63,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
private uint _renderingTimeLimitPercent;
|
private uint _renderingTimeLimitPercent;
|
||||||
private bool _voiceDropEnabled;
|
private bool _voiceDropEnabled;
|
||||||
private uint _voiceDropCount;
|
private uint _voiceDropCount;
|
||||||
|
private float _voiceDropParameter;
|
||||||
private bool _isDspRunningBehind;
|
private bool _isDspRunningBehind;
|
||||||
|
|
||||||
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
|
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
|
||||||
@@ -95,6 +97,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
_totalElapsedTicksUpdating = 0;
|
_totalElapsedTicksUpdating = 0;
|
||||||
_sessionId = 0;
|
_sessionId = 0;
|
||||||
|
_voiceDropParameter = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResultCode Initialize(
|
public ResultCode Initialize(
|
||||||
@@ -130,6 +133,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
|
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
|
||||||
_appletResourceId = appletResourceId;
|
_appletResourceId = appletResourceId;
|
||||||
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
|
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
|
||||||
|
_renderingDevice = parameter.RenderingDevice;
|
||||||
_executionMode = parameter.ExecutionMode;
|
_executionMode = parameter.ExecutionMode;
|
||||||
_sessionId = sessionId;
|
_sessionId = sessionId;
|
||||||
MemoryManager = memoryManager;
|
MemoryManager = memoryManager;
|
||||||
@@ -337,6 +341,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
|
|
||||||
_processHandle = processHandle;
|
_processHandle = processHandle;
|
||||||
_elapsedFrameCount = 0;
|
_elapsedFrameCount = 0;
|
||||||
|
_voiceDropParameter = 1.0f;
|
||||||
|
|
||||||
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
|
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
|
||||||
{
|
{
|
||||||
@@ -515,7 +520,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
|
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
|
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@@ -584,7 +589,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
command.Enabled = false;
|
command.Enabled = false;
|
||||||
|
|
||||||
voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
|
voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,13 +623,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
_voiceContext.Sort();
|
_voiceContext.Sort();
|
||||||
commandGenerator.GenerateVoices();
|
commandGenerator.GenerateVoices();
|
||||||
|
|
||||||
long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
|
||||||
|
|
||||||
commandGenerator.GenerateSubMixes();
|
commandGenerator.GenerateSubMixes();
|
||||||
commandGenerator.GenerateFinalMixes();
|
commandGenerator.GenerateFinalMixes();
|
||||||
commandGenerator.GenerateSinks();
|
commandGenerator.GenerateSinks();
|
||||||
|
|
||||||
long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
|
uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
|
||||||
|
|
||||||
if (_voiceDropEnabled)
|
if (_voiceDropEnabled)
|
||||||
{
|
{
|
||||||
@@ -665,14 +670,21 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
{
|
{
|
||||||
_terminationEvent.Reset();
|
_terminationEvent.Reset();
|
||||||
|
|
||||||
GenerateCommandList(out CommandList commands);
|
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||||
|
{
|
||||||
|
GenerateCommandList(out CommandList commands);
|
||||||
|
|
||||||
_manager.Processor.Send(_sessionId,
|
_manager.Processor.Send(_sessionId,
|
||||||
commands,
|
commands,
|
||||||
GetMaxAllocatedTimeForDsp(),
|
GetMaxAllocatedTimeForDsp(),
|
||||||
_appletResourceId);
|
_appletResourceId);
|
||||||
|
|
||||||
_systemEvent.Signal();
|
_systemEvent.Signal();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_isDspRunningBehind = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -856,5 +868,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetVoiceDropParameter(float voiceDropParameter)
|
||||||
|
{
|
||||||
|
_voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetVoiceDropParameter()
|
||||||
|
{
|
||||||
|
return _voiceDropParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode ExecuteAudioRendererRendering()
|
||||||
|
{
|
||||||
|
if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
|
||||||
|
{
|
||||||
|
// NOTE: Here Nintendo aborts with this error code, we don't want that.
|
||||||
|
return ResultCode.InvalidExecutionContextOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultCode.UnsupportedOperation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -94,8 +94,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// REV11:
|
/// REV11:
|
||||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||||
|
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This was added in system update 14.0.0</remarks>
|
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
||||||
public const int Revision11 = 11 << 24;
|
public const int Revision11 = 11 << 24;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The estimated total processing time.
|
/// The estimated total processing time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ulong EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The command list that is populated by the <see cref="CommandBuffer"/>.
|
/// The command list that is populated by the <see cref="CommandBuffer"/>.
|
||||||
|
@@ -263,12 +263,12 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
|
|||||||
return UpdateResult.Success;
|
return UpdateResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
|
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0)
|
||||||
{
|
{
|
||||||
return UpdateResult.InvalidParameter;
|
return UpdateResult.InvalidParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
|
if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0)
|
||||||
{
|
{
|
||||||
return UpdateResult.InvalidParameter;
|
return UpdateResult.InvalidParameter;
|
||||||
}
|
}
|
||||||
|
@@ -17,5 +17,6 @@ namespace Ryujinx.Audio
|
|||||||
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
|
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
|
||||||
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
|
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
|
||||||
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
|
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
|
||||||
|
InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId,
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -21,6 +21,8 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
Name = $"Ryujinx {Program.Version}";
|
||||||
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -60,7 +60,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private const float VolumeDelta = 0.05f;
|
private const float VolumeDelta = 0.05f;
|
||||||
|
|
||||||
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
|
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
|
||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
@@ -349,7 +349,10 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
|
||||||
_renderingThread.Join();
|
if (_renderingThread.IsAlive)
|
||||||
|
{
|
||||||
|
_renderingThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
DisplaySleep.Restore();
|
DisplaySleep.Restore();
|
||||||
|
|
||||||
@@ -378,7 +381,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_gpuCancellationTokenSource.Cancel();
|
_gpuCancellationTokenSource.Cancel();
|
||||||
_gpuCancellationTokenSource.Dispose();
|
_gpuCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
_chrono.Stop();
|
_chrono.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +396,7 @@ namespace Ryujinx.Ava
|
|||||||
Renderer?.MakeCurrent();
|
Renderer?.MakeCurrent();
|
||||||
|
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
Renderer?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +420,6 @@ namespace Ryujinx.Ava
|
|||||||
public async Task<bool> LoadGuestApplication()
|
public async Task<bool> LoadGuestApplication()
|
||||||
{
|
{
|
||||||
InitializeSwitchInstance();
|
InitializeSwitchInstance();
|
||||||
|
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
|
|
||||||
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||||
@@ -428,17 +430,16 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
if (userError == UserError.NoFirmware)
|
if (userError == UserError.NoFirmware)
|
||||||
{
|
{
|
||||||
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
|
|
||||||
firmwareVersion.VersionString);
|
|
||||||
|
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||||
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message,
|
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"],
|
||||||
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
|
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString),
|
||||||
|
LocaleManager.Instance["InputDialogYes"],
|
||||||
|
LocaleManager.Instance["InputDialogNo"],
|
||||||
|
"");
|
||||||
|
|
||||||
if (result != UserResult.Yes)
|
if (result != UserResult.Yes)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () => await
|
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
|
||||||
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
|
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -447,8 +448,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
|
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () => await
|
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
|
||||||
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
|
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -461,11 +461,9 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_parent.RefreshFirmwareStatus();
|
_parent.RefreshFirmwareStatus();
|
||||||
|
|
||||||
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
|
|
||||||
|
|
||||||
await ContentDialogHelper.CreateInfoDialog(
|
await ContentDialogHelper.CreateInfoDialog(
|
||||||
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
|
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
|
||||||
message,
|
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString),
|
||||||
LocaleManager.Instance["InputDialogOk"],
|
LocaleManager.Instance["InputDialogOk"],
|
||||||
"",
|
"",
|
||||||
LocaleManager.Instance["RyujinxInfo"]);
|
LocaleManager.Instance["RyujinxInfo"]);
|
||||||
@@ -473,9 +471,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () => await
|
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
|
||||||
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
|
|
||||||
|
|
||||||
Device.Dispose();
|
Device.Dispose();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -514,7 +510,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
else if (File.Exists(ApplicationPath))
|
else if (File.Exists(ApplicationPath))
|
||||||
{
|
{
|
||||||
switch (System.IO.Path.GetExtension(ApplicationPath).ToLowerInvariant())
|
switch (Path.GetExtension(ApplicationPath).ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case ".xci":
|
case ".xci":
|
||||||
{
|
{
|
||||||
@@ -602,7 +598,7 @@ namespace Ryujinx.Ava
|
|||||||
if (Renderer.IsVulkan)
|
if (Renderer.IsVulkan)
|
||||||
{
|
{
|
||||||
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
||||||
|
|
||||||
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
|
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
|
||||||
"SettingsButtonSave": "Speichern",
|
"SettingsButtonSave": "Speichern",
|
||||||
"SettingsButtonClose": "Schließen",
|
"SettingsButtonClose": "Schließen",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Abbrechen",
|
||||||
"SettingsButtonApply": "Übernehmen",
|
"SettingsButtonApply": "Übernehmen",
|
||||||
"ControllerSettingsPlayer": "Spieler",
|
"ControllerSettingsPlayer": "Spieler",
|
||||||
"ControllerSettingsPlayer1": "Spieler 1",
|
"ControllerSettingsPlayer1": "Spieler 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
|
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
|
||||||
"SettingsButtonSave": "Αποθήκευση",
|
"SettingsButtonSave": "Αποθήκευση",
|
||||||
"SettingsButtonClose": "Κλείσιμο",
|
"SettingsButtonClose": "Κλείσιμο",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Ακύρωση",
|
||||||
"SettingsButtonApply": "Εφαρμογή",
|
"SettingsButtonApply": "Εφαρμογή",
|
||||||
"ControllerSettingsPlayer": "Παίχτης",
|
"ControllerSettingsPlayer": "Παίχτης",
|
||||||
"ControllerSettingsPlayer1": "Παίχτης 1",
|
"ControllerSettingsPlayer1": "Παίχτης 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
|
||||||
"SettingsButtonSave": "Save",
|
"SettingsButtonSave": "Save",
|
||||||
"SettingsButtonClose": "Close",
|
"SettingsButtonClose": "Close",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Cancel",
|
||||||
"SettingsButtonApply": "Apply",
|
"SettingsButtonApply": "Apply",
|
||||||
"ControllerSettingsPlayer": "Player",
|
"ControllerSettingsPlayer": "Player",
|
||||||
"ControllerSettingsPlayer1": "Player 1",
|
"ControllerSettingsPlayer1": "Player 1",
|
||||||
@@ -410,6 +412,8 @@
|
|||||||
"DlcManagerTableHeadingContainerPathLabel": "Container Path",
|
"DlcManagerTableHeadingContainerPathLabel": "Container Path",
|
||||||
"DlcManagerTableHeadingFullPathLabel": "Full Path",
|
"DlcManagerTableHeadingFullPathLabel": "Full Path",
|
||||||
"DlcManagerRemoveAllButton": "Remove All",
|
"DlcManagerRemoveAllButton": "Remove All",
|
||||||
|
"DlcManagerEnableAllButton": "Enable All",
|
||||||
|
"DlcManagerDisableAllButton": "Disable All",
|
||||||
"MenuBarOptionsChangeLanguage": "Change Language",
|
"MenuBarOptionsChangeLanguage": "Change Language",
|
||||||
"CommonSort": "Sort",
|
"CommonSort": "Sort",
|
||||||
"CommonShowNames": "Show Names",
|
"CommonShowNames": "Show Names",
|
||||||
@@ -562,12 +566,12 @@
|
|||||||
"Writable": "Writable",
|
"Writable": "Writable",
|
||||||
"SelectDlcDialogTitle": "Select DLC files",
|
"SelectDlcDialogTitle": "Select DLC files",
|
||||||
"SelectUpdateDialogTitle": "Select update files",
|
"SelectUpdateDialogTitle": "Select update files",
|
||||||
"UserProfileWindowTitle": "Manage User Profiles",
|
"UserProfileWindowTitle": "User Profiles Manager",
|
||||||
"CheatWindowTitle": "Manage Game Cheats",
|
"CheatWindowTitle": "Cheats Manager",
|
||||||
"DlcWindowTitle": "Manage Game DLC",
|
"DlcWindowTitle": "Downloadable Content Manager",
|
||||||
"UpdateWindowTitle": "Manage Game Updates",
|
"UpdateWindowTitle": "Title Update Manager",
|
||||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||||
"DlcWindowHeading": "DLC Available for {0} [{1}]",
|
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
|
||||||
"UserProfilesEditProfile": "Edit Selected",
|
"UserProfilesEditProfile": "Edit Selected",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
@@ -575,7 +579,7 @@
|
|||||||
"UserProfilesSetProfileImage": "Set Profile Image",
|
"UserProfilesSetProfileImage": "Set Profile Image",
|
||||||
"UserProfileEmptyNameError": "Name is required",
|
"UserProfileEmptyNameError": "Name is required",
|
||||||
"UserProfileNoImageError": "Profile image must be set",
|
"UserProfileNoImageError": "Profile image must be set",
|
||||||
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]",
|
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
|
||||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||||
"UserProfilesName": "Name:",
|
"UserProfilesName": "Name:",
|
||||||
@@ -592,7 +596,18 @@
|
|||||||
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
|
||||||
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
|
||||||
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
|
||||||
"VolumeShort": "Vol",
|
|
||||||
"SettingsEnableMacroHLE": "Enable Macro HLE",
|
"SettingsEnableMacroHLE": "Enable Macro HLE",
|
||||||
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure."
|
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
|
||||||
|
"VolumeShort": "Vol",
|
||||||
|
"UserProfilesManageSaves": "Manage Saves",
|
||||||
|
"DeleteUserSave": "Do you want to delete user save for this game?",
|
||||||
|
"IrreversibleActionNote": "This action is not reversible.",
|
||||||
|
"SaveManagerHeading": "Manage Saves for {0}",
|
||||||
|
"SaveManagerTitle": "Save Manager",
|
||||||
|
"Name": "Name",
|
||||||
|
"Size": "Size",
|
||||||
|
"Search": "Search",
|
||||||
|
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||||
|
"Recover": "Recover",
|
||||||
|
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
|
||||||
}
|
}
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
|
||||||
"SettingsButtonSave": "Guardar",
|
"SettingsButtonSave": "Guardar",
|
||||||
"SettingsButtonClose": "Cerrar",
|
"SettingsButtonClose": "Cerrar",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Cancelar",
|
||||||
"SettingsButtonApply": "Aplicar",
|
"SettingsButtonApply": "Aplicar",
|
||||||
"ControllerSettingsPlayer": "Jugador",
|
"ControllerSettingsPlayer": "Jugador",
|
||||||
"ControllerSettingsPlayer1": "Jugador 1",
|
"ControllerSettingsPlayer1": "Jugador 1",
|
||||||
|
@@ -161,6 +161,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
|
||||||
"SettingsButtonSave": "Enregistrer",
|
"SettingsButtonSave": "Enregistrer",
|
||||||
"SettingsButtonClose": "Fermer",
|
"SettingsButtonClose": "Fermer",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Annuler",
|
||||||
"SettingsButtonApply": "Appliquer",
|
"SettingsButtonApply": "Appliquer",
|
||||||
"ControllerSettingsPlayer": "Joueur",
|
"ControllerSettingsPlayer": "Joueur",
|
||||||
"ControllerSettingsPlayer1": "Joueur 1",
|
"ControllerSettingsPlayer1": "Joueur 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
|
||||||
"SettingsButtonSave": "Salva",
|
"SettingsButtonSave": "Salva",
|
||||||
"SettingsButtonClose": "Chiudi",
|
"SettingsButtonClose": "Chiudi",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Cancella",
|
||||||
"SettingsButtonApply": "Applica",
|
"SettingsButtonApply": "Applica",
|
||||||
"ControllerSettingsPlayer": "Giocatore",
|
"ControllerSettingsPlayer": "Giocatore",
|
||||||
"ControllerSettingsPlayer1": "Giocatore 1",
|
"ControllerSettingsPlayer1": "Giocatore 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
|
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
|
||||||
"SettingsButtonSave": "セーブ",
|
"SettingsButtonSave": "セーブ",
|
||||||
"SettingsButtonClose": "閉じる",
|
"SettingsButtonClose": "閉じる",
|
||||||
|
"SettingsButtonOk": "オーケー",
|
||||||
|
"SettingsButtonCancel": "キャンセル",
|
||||||
"SettingsButtonApply": "適用",
|
"SettingsButtonApply": "適用",
|
||||||
"ControllerSettingsPlayer": "プレイヤー",
|
"ControllerSettingsPlayer": "プレイヤー",
|
||||||
"ControllerSettingsPlayer1": "プレイヤー 1",
|
"ControllerSettingsPlayer1": "プレイヤー 1",
|
||||||
|
@@ -167,6 +167,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
|
||||||
"SettingsButtonSave": "구하다",
|
"SettingsButtonSave": "구하다",
|
||||||
"SettingsButtonClose": "출구",
|
"SettingsButtonClose": "출구",
|
||||||
|
"SettingsButtonOk": "좋아",
|
||||||
|
"SettingsButtonCancel": "취소",
|
||||||
"SettingsButtonApply": "적용하다",
|
"SettingsButtonApply": "적용하다",
|
||||||
"ControllerSettingsPlayer": "플레이어",
|
"ControllerSettingsPlayer": "플레이어",
|
||||||
"ControllerSettingsPlayer1": "플레이어 1",
|
"ControllerSettingsPlayer1": "플레이어 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
|
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
|
||||||
"SettingsButtonSave": "Zapisz",
|
"SettingsButtonSave": "Zapisz",
|
||||||
"SettingsButtonClose": "Zamknij",
|
"SettingsButtonClose": "Zamknij",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Anuluj",
|
||||||
"SettingsButtonApply": "Zastosuj",
|
"SettingsButtonApply": "Zastosuj",
|
||||||
"ControllerSettingsPlayer": "Gracz",
|
"ControllerSettingsPlayer": "Gracz",
|
||||||
"ControllerSettingsPlayer1": "Gracz 1",
|
"ControllerSettingsPlayer1": "Gracz 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
|
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
|
||||||
"SettingsButtonSave": "Salvar",
|
"SettingsButtonSave": "Salvar",
|
||||||
"SettingsButtonClose": "Fechar",
|
"SettingsButtonClose": "Fechar",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Cancelar",
|
||||||
"SettingsButtonApply": "Aplicar",
|
"SettingsButtonApply": "Aplicar",
|
||||||
"ControllerSettingsPlayer": "Jogador",
|
"ControllerSettingsPlayer": "Jogador",
|
||||||
"ControllerSettingsPlayer1": "Jogador 1",
|
"ControllerSettingsPlayer1": "Jogador 1",
|
||||||
|
@@ -167,6 +167,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
|
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
|
||||||
"SettingsButtonSave": "Сохранить",
|
"SettingsButtonSave": "Сохранить",
|
||||||
"SettingsButtonClose": "Закрыть",
|
"SettingsButtonClose": "Закрыть",
|
||||||
|
"SettingsButtonOk": "OK",
|
||||||
|
"SettingsButtonCancel": "Отмена",
|
||||||
"SettingsButtonApply": "Применить",
|
"SettingsButtonApply": "Применить",
|
||||||
"ControllerSettingsPlayer": "Игрок",
|
"ControllerSettingsPlayer": "Игрок",
|
||||||
"ControllerSettingsPlayer1": "Игрок 1",
|
"ControllerSettingsPlayer1": "Игрок 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
|
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
|
||||||
"SettingsButtonSave": "Kaydet",
|
"SettingsButtonSave": "Kaydet",
|
||||||
"SettingsButtonClose": "Kapat",
|
"SettingsButtonClose": "Kapat",
|
||||||
|
"SettingsButtonOk": "Tamam",
|
||||||
|
"SettingsButtonCancel": "İptal",
|
||||||
"SettingsButtonApply": "Uygula",
|
"SettingsButtonApply": "Uygula",
|
||||||
"ControllerSettingsPlayer": "Oyuncu",
|
"ControllerSettingsPlayer": "Oyuncu",
|
||||||
"ControllerSettingsPlayer1": "Oyuncu 1",
|
"ControllerSettingsPlayer1": "Oyuncu 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
|
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
|
||||||
"SettingsButtonSave": "保存",
|
"SettingsButtonSave": "保存",
|
||||||
"SettingsButtonClose": "关闭",
|
"SettingsButtonClose": "关闭",
|
||||||
|
"SettingsButtonOk": "批准",
|
||||||
|
"SettingsButtonCancel": "取消",
|
||||||
"SettingsButtonApply": "应用",
|
"SettingsButtonApply": "应用",
|
||||||
"ControllerSettingsPlayer": "玩家",
|
"ControllerSettingsPlayer": "玩家",
|
||||||
"ControllerSettingsPlayer1": "玩家 1",
|
"ControllerSettingsPlayer1": "玩家 1",
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
|
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
|
||||||
"SettingsButtonSave": "儲存",
|
"SettingsButtonSave": "儲存",
|
||||||
"SettingsButtonClose": "關閉",
|
"SettingsButtonClose": "關閉",
|
||||||
|
"SettingsButtonOk": "嘛好",
|
||||||
|
"SettingsButtonCancel": "取消",
|
||||||
"SettingsButtonApply": "套用",
|
"SettingsButtonApply": "套用",
|
||||||
"ControllerSettingsPlayer": "玩家",
|
"ControllerSettingsPlayer": "玩家",
|
||||||
"ControllerSettingsPlayer1": "玩家 1",
|
"ControllerSettingsPlayer1": "玩家 1",
|
||||||
|
@@ -41,6 +41,9 @@
|
|||||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||||
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
|
||||||
|
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
|
||||||
|
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
|
||||||
|
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||||
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
|
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
|
||||||
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<Styles
|
<Styles
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Height="2000" Padding="20">
|
<Border Height="2000" Padding="20">
|
||||||
@@ -269,13 +268,15 @@
|
|||||||
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
|
||||||
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
|
||||||
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
|
||||||
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
|
<x:Double x:Key="ScrollBarThickness">15</x:Double>
|
||||||
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
|
<x:Double x:Key="FontSizeSmall">8</x:Double>
|
||||||
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
|
<x:Double x:Key="FontSizeNormal">10</x:Double>
|
||||||
<sys:Double x:Key="FontSize">12</sys:Double>
|
<x:Double x:Key="FontSize">12</x:Double>
|
||||||
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
|
<x:Double x:Key="FontSizeLarge">15</x:Double>
|
||||||
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
|
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
|
||||||
<x:Double x:Key="MenuItemHeight">26</x:Double>
|
<x:Double x:Key="MenuItemHeight">26</x:Double>
|
||||||
<x:Double x:Key="TabItemMinHeight">28</x:Double>
|
<x:Double x:Key="TabItemMinHeight">28</x:Double>
|
||||||
|
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
|
||||||
|
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
</Styles>
|
</Styles>
|
@@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpenSaveDir(saveDataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OpenSaveDir(ulong saveDataId)
|
||||||
|
{
|
||||||
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
|
||||||
|
|
||||||
if (!Directory.Exists(saveRootPath))
|
if (!Directory.Exists(saveRootPath))
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Rendering;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
@@ -11,6 +9,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Common.System;
|
using Ryujinx.Common.System;
|
||||||
using Ryujinx.Common.SystemInfo;
|
using Ryujinx.Common.SystemInfo;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
|
using Ryujinx.SDL2.Common;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
@@ -24,18 +23,14 @@ namespace Ryujinx.Ava
|
|||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
public static double WindowScaleFactor { get; set; }
|
public static double WindowScaleFactor { get; set; }
|
||||||
public static double ActualScaleFactor { get; set; }
|
public static string Version { get; private set; }
|
||||||
public static string Version { get; private set; }
|
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
|
|
||||||
public static RenderTimer RenderTimer { get; private set; }
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||||
|
|
||||||
private const uint MB_ICONWARNING = 0x30;
|
private const uint MB_ICONWARNING = 0x30;
|
||||||
private const int BaseDpi = 96;
|
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
@@ -43,18 +38,14 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
||||||
{
|
{
|
||||||
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
|
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreviewerDetached = true;
|
PreviewerDetached = true;
|
||||||
|
|
||||||
Initialize(args);
|
Initialize(args);
|
||||||
|
|
||||||
RenderTimer = new RenderTimer();
|
|
||||||
|
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
RenderTimer.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
@@ -64,24 +55,18 @@ namespace Ryujinx.Ava
|
|||||||
.With(new X11PlatformOptions
|
.With(new X11PlatformOptions
|
||||||
{
|
{
|
||||||
EnableMultiTouch = true,
|
EnableMultiTouch = true,
|
||||||
EnableIme = true,
|
EnableIme = true,
|
||||||
UseEGL = false,
|
UseEGL = false,
|
||||||
UseGpu = false
|
UseGpu = true
|
||||||
})
|
})
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
EnableMultitouch = true,
|
EnableMultitouch = true,
|
||||||
UseWgl = false,
|
UseWgl = false,
|
||||||
AllowEglInitialization = false,
|
AllowEglInitialization = false,
|
||||||
CompositionBackdropCornerRadius = 8f,
|
CompositionBackdropCornerRadius = 8.0f,
|
||||||
})
|
})
|
||||||
.UseSkia()
|
.UseSkia()
|
||||||
.AfterSetup(_ =>
|
|
||||||
{
|
|
||||||
AvaloniaLocator.CurrentMutable
|
|
||||||
.Bind<IRenderTimer>().ToConstant(RenderTimer)
|
|
||||||
.Bind<IRenderLoop>().ToConstant(new RenderLoop(RenderTimer, Dispatcher.UIThread));
|
|
||||||
})
|
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,23 +96,23 @@ namespace Ryujinx.Ava
|
|||||||
// Initialize Discord integration.
|
// Initialize Discord integration.
|
||||||
DiscordIntegrationModule.Initialize();
|
DiscordIntegrationModule.Initialize();
|
||||||
|
|
||||||
|
// Initialize SDL2 driver
|
||||||
|
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
|
||||||
|
|
||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
|
|
||||||
ForceDpiAware.Windows();
|
ForceDpiAware.Windows();
|
||||||
|
|
||||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||||
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
|
||||||
|
|
||||||
// Logging system information.
|
// Logging system information.
|
||||||
PrintSystemInfo();
|
PrintSystemInfo();
|
||||||
|
|
||||||
// Enable OGL multithreading on the driver, when available.
|
// Enable OGL multithreading on the driver, when available.
|
||||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
|
||||||
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
|
|
||||||
|
|
||||||
// Check if keys exists.
|
// Check if keys exists.
|
||||||
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
|
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
|
||||||
if (!hasSystemProdKeys)
|
|
||||||
{
|
{
|
||||||
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
|
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
|
||||||
{
|
{
|
||||||
@@ -143,7 +128,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public static void ReloadConfig()
|
public static void ReloadConfig()
|
||||||
{
|
{
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
||||||
|
|
||||||
// Now load the configuration as the other subsystems are now registered
|
// Now load the configuration as the other subsystems are now registered
|
||||||
@@ -197,8 +182,7 @@ namespace Ryujinx.Ava
|
|||||||
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
|
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
|
||||||
SystemInfo.Gather().Print();
|
SystemInfo.Gather().Print();
|
||||||
|
|
||||||
var enabledLogs = Logger.GetEnabledLevels();
|
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
|
||||||
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
|
|
||||||
|
|
||||||
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
|
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
|
||||||
{
|
{
|
||||||
@@ -240,4 +224,4 @@ namespace Ryujinx.Ava
|
|||||||
Logger.Shutdown();
|
Logger.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,25 +19,25 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.15" />
|
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Svg" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Svg" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||||
<PackageReference Include="DynamicData" Version="7.9.4" />
|
<PackageReference Include="DynamicData" Version="7.12.8" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
|
||||||
|
|
||||||
<PackageReference Include="OpenTK.Core" Version="4.7.2" />
|
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||||
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
<PackageReference Include="SharpZipLib" Version="1.4.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -57,14 +57,14 @@ namespace Ryujinx.Ava.Ui.Applet
|
|||||||
|
|
||||||
bool opened = false;
|
bool opened = false;
|
||||||
|
|
||||||
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
"",
|
"",
|
||||||
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
|
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
|
||||||
"",
|
"",
|
||||||
LocaleManager.Instance["SettingsButtonClose"],
|
LocaleManager.Instance["SettingsButtonClose"],
|
||||||
(int)Symbol.Important,
|
(int)Symbol.Important,
|
||||||
deferEvent,
|
deferEvent,
|
||||||
async (window) =>
|
async (window) =>
|
||||||
{
|
{
|
||||||
@@ -168,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
|||||||
|
|
||||||
object response = await msgDialog.Run();
|
object response = await msgDialog.Run();
|
||||||
|
|
||||||
if (response != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
|
if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
|
||||||
{
|
{
|
||||||
showDetails = true;
|
showDetails = true;
|
||||||
}
|
}
|
||||||
|
@@ -127,9 +127,16 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
contentDialog.PrimaryButtonClick += deferCloseAction;
|
contentDialog.PrimaryButtonClick += deferCloseAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
if (useOverlay)
|
||||||
|
{
|
||||||
|
await contentDialog.ShowAsync(overlay, ContentDialogPlacement.Popup);
|
||||||
|
|
||||||
overlay?.Close();
|
overlay!.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useOverlay)
|
if (useOverlay)
|
||||||
@@ -391,4 +398,4 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -6,8 +6,8 @@ using SPB.Graphics;
|
|||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
using SPB.Platform.X11;
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -15,12 +15,12 @@ using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
public unsafe class EmbeddedWindow : NativeControlHost
|
public class EmbeddedWindow : NativeControlHost
|
||||||
{
|
{
|
||||||
private WindowProc _wndProcDelegate;
|
private WindowProc _wndProcDelegate;
|
||||||
private string _className;
|
private string _className;
|
||||||
|
|
||||||
protected GLXWindow X11Window { get; private set; }
|
protected GLXWindow X11Window { get; set; }
|
||||||
protected IntPtr WindowHandle { get; set; }
|
protected IntPtr WindowHandle { get; set; }
|
||||||
protected IntPtr X11Display { get; set; }
|
protected IntPtr X11Display { get; set; }
|
||||||
|
|
||||||
@@ -94,19 +94,17 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
[SupportedOSPlatform("linux")]
|
||||||
IPlatformHandle CreateLinux(IPlatformHandle parent)
|
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||||
{
|
{
|
||||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||||
|
|
||||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||||
|
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "X11");
|
return new PlatformHandle(WindowHandle, "X11");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
unsafe IPlatformHandle CreateWin32(IPlatformHandle parent)
|
IPlatformHandle CreateWin32(IPlatformHandle parent)
|
||||||
{
|
{
|
||||||
_className = "NativeWindow-" + Guid.NewGuid();
|
_className = "NativeWindow-" + Guid.NewGuid();
|
||||||
_wndProcDelegate = WndProc;
|
_wndProcDelegate = WndProc;
|
||||||
@@ -142,7 +140,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||||
{
|
{
|
||||||
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
||||||
var root = VisualRoot as Window;
|
var root = VisualRoot as Window;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using LibHac;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Ui.ViewModels;
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
@@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
{
|
{
|
||||||
public AccountManager AccountManager { get; }
|
public AccountManager AccountManager { get; }
|
||||||
public ContentManager ContentManager { get; }
|
public ContentManager ContentManager { get; }
|
||||||
|
public VirtualFileSystem VirtualFileSystem { get; }
|
||||||
|
public HorizonClient HorizonClient { get; }
|
||||||
public UserProfileViewModel ViewModel { get; set; }
|
public UserProfileViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
public NavigationDialogHost()
|
public NavigationDialogHost()
|
||||||
@@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
|
||||||
VirtualFileSystem virtualFileSystem)
|
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
|
||||||
{
|
{
|
||||||
AccountManager = accountManager;
|
AccountManager = accountManager;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
VirtualFileSystem = virtualFileSystem;
|
||||||
|
HorizonClient = horizonClient;
|
||||||
ViewModel = new UserProfileViewModel(this);
|
ViewModel = new UserProfileViewModel(this);
|
||||||
|
|
||||||
|
|
||||||
@@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
ContentFrame.Navigate(sourcePageType, parameter);
|
ContentFrame.Navigate(sourcePageType, parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
|
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
|
||||||
|
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
|
||||||
{
|
{
|
||||||
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
|
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
|
||||||
ContentDialog contentDialog = new ContentDialog
|
ContentDialog contentDialog = new ContentDialog
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
Title = LocaleManager.Instance["UserProfileWindowTitle"],
|
||||||
|
@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
{
|
{
|
||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = OpenGLContextFlags.Compat;
|
var flags = OpenGLContextFlags.Compat;
|
||||||
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
|
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
|
||||||
{
|
{
|
||||||
@@ -69,12 +69,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
|
|
||||||
public void MakeCurrent()
|
public void MakeCurrent()
|
||||||
{
|
{
|
||||||
Context.MakeCurrent(_window);
|
Context?.MakeCurrent(_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MakeCurrent(NativeWindowBase window)
|
public void MakeCurrent(NativeWindowBase window)
|
||||||
{
|
{
|
||||||
Context.MakeCurrent(window);
|
Context?.MakeCurrent(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SwapBuffers()
|
public void SwapBuffers()
|
||||||
|
@@ -1,100 +0,0 @@
|
|||||||
using Avalonia.Rendering;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Timers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
|
||||||
{
|
|
||||||
internal class RenderTimer : IRenderTimer, IDisposable
|
|
||||||
{
|
|
||||||
public event Action<TimeSpan> Tick
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
_tick += value;
|
|
||||||
|
|
||||||
if (_subscriberCount++ == 0)
|
|
||||||
{
|
|
||||||
Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
if (--_subscriberCount == 0)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
_tick -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Thread _tickThread;
|
|
||||||
private readonly System.Timers.Timer _timer;
|
|
||||||
|
|
||||||
private Action<TimeSpan> _tick;
|
|
||||||
private int _subscriberCount;
|
|
||||||
|
|
||||||
private bool _isRunning;
|
|
||||||
|
|
||||||
private AutoResetEvent _resetEvent;
|
|
||||||
|
|
||||||
public RenderTimer()
|
|
||||||
{
|
|
||||||
_timer = new System.Timers.Timer(15);
|
|
||||||
_resetEvent = new AutoResetEvent(true);
|
|
||||||
_timer.Elapsed += Timer_Elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
|
||||||
{
|
|
||||||
TickNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_timer.Start();
|
|
||||||
if (_tickThread == null)
|
|
||||||
{
|
|
||||||
_tickThread = new Thread(RunTick);
|
|
||||||
_tickThread.Name = "RenderTimerTickThread";
|
|
||||||
_tickThread.IsBackground = true;
|
|
||||||
_isRunning = true;
|
|
||||||
_tickThread.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunTick()
|
|
||||||
{
|
|
||||||
while (_isRunning)
|
|
||||||
{
|
|
||||||
_resetEvent.WaitOne();
|
|
||||||
_tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TickNow()
|
|
||||||
{
|
|
||||||
lock (_timer)
|
|
||||||
{
|
|
||||||
_resetEvent.Set();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
_timer.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_timer.Elapsed -= Timer_Elapsed;
|
|
||||||
_timer.Stop();
|
|
||||||
_isRunning = false;
|
|
||||||
_resetEvent.Set();
|
|
||||||
_tickThread.Join();
|
|
||||||
_resetEvent.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,11 +4,4 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||||
<ContentControl
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
HorizontalContentAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
VerticalContentAlignment="Stretch"
|
|
||||||
Name="View"
|
|
||||||
/>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
{
|
{
|
||||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||||
View.Content = _currentWindow;
|
Content = _currentWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateVulkan()
|
public void CreateVulkan()
|
||||||
|
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
102
Ryujinx.Ava/Ui/Controls/SaveManager.axaml
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||||
|
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
Height="400"
|
||||||
|
Width="550"
|
||||||
|
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||||
|
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
|
||||||
|
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||||
|
Content="{locale:Locale Name}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||||
|
Content="{locale:Locale Size}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||||
|
Content="{locale:Locale OrderAscending}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
|
||||||
|
Content="{locale:Locale Descending}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
|
||||||
|
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
|
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="models:SaveModel">
|
||||||
|
<Grid HorizontalAlignment="Stretch" Margin="0,5">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||||
|
<Border Height="42" Margin="2" Width="42" Padding="10"
|
||||||
|
IsVisible="{Binding !InGameList}">
|
||||||
|
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
<Image IsVisible="{Binding InGameList}"
|
||||||
|
Margin="2"
|
||||||
|
Width="42"
|
||||||
|
Height="42"
|
||||||
|
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||||
|
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
|
||||||
|
Text="{Binding Title}" VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
|
||||||
|
VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
|
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||||
|
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
|
||||||
|
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
|
||||||
|
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
|
||||||
|
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
160
Ryujinx.Ava/Ui/Controls/SaveManager.axaml.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
|
using Ryujinx.Ava.Common;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Ui.Models;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
public partial class SaveManager : UserControl
|
||||||
|
{
|
||||||
|
private readonly UserProfile _userProfile;
|
||||||
|
private readonly HorizonClient _horizonClient;
|
||||||
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
|
private int _sortIndex;
|
||||||
|
private int _orderIndex;
|
||||||
|
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
|
||||||
|
private string _search;
|
||||||
|
|
||||||
|
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
|
||||||
|
|
||||||
|
public ObservableCollection<SaveModel> View
|
||||||
|
{
|
||||||
|
get => _view;
|
||||||
|
set => _view = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SortIndex
|
||||||
|
{
|
||||||
|
get => _sortIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sortIndex = value;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int OrderIndex
|
||||||
|
{
|
||||||
|
get => _orderIndex;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_orderIndex = value;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Search
|
||||||
|
{
|
||||||
|
get => _search;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_search = value;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveManager()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||||
|
{
|
||||||
|
_userProfile = userProfile;
|
||||||
|
_horizonClient = horizonClient;
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
DataContext = this;
|
||||||
|
|
||||||
|
Task.Run(LoadSaves);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadSaves()
|
||||||
|
{
|
||||||
|
Saves.Clear();
|
||||||
|
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||||
|
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
|
||||||
|
|
||||||
|
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||||
|
|
||||||
|
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||||
|
|
||||||
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||||
|
|
||||||
|
if (readCount == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < readCount; i++)
|
||||||
|
{
|
||||||
|
var save = saveDataInfo[i];
|
||||||
|
if (save.ProgramId.Value != 0)
|
||||||
|
{
|
||||||
|
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
|
||||||
|
Saves.Add(saveModel);
|
||||||
|
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
|
||||||
|
}
|
||||||
|
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Sort()
|
||||||
|
{
|
||||||
|
Saves.AsObservableChangeSet()
|
||||||
|
.Filter(Filter)
|
||||||
|
.Sort(GetComparer())
|
||||||
|
.Bind(out var view).AsObservableList();
|
||||||
|
|
||||||
|
_view.Clear();
|
||||||
|
_view.AddRange(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IComparer<SaveModel> GetComparer()
|
||||||
|
{
|
||||||
|
switch (SortIndex)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return OrderIndex == 0
|
||||||
|
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
|
||||||
|
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
|
||||||
|
case 1:
|
||||||
|
return OrderIndex == 0
|
||||||
|
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
|
||||||
|
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Filter(object arg)
|
||||||
|
{
|
||||||
|
if (arg is SaveModel save)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@
|
|||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
|
MinWidth="500"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
MaxLength="{Binding MaxProfileNameLength}"
|
MaxLength="{Binding MaxProfileNameLength}"
|
||||||
Text="{Binding Name}" />
|
Text="{Binding Name}" />
|
||||||
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
|
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
|
||||||
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
@@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
case NavigationMode.New:
|
case NavigationMode.New:
|
||||||
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
|
||||||
_isNewUser = args.isNewUser;
|
_isNewUser = args.isNewUser;
|
||||||
if (!_isNewUser)
|
_profile = args.profile;
|
||||||
{
|
TempProfile = new TempProfile(_profile);
|
||||||
_profile = args.profile;
|
|
||||||
TempProfile = new TempProfile(_profile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TempProfile = new TempProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
_parent = args.parent;
|
_parent = args.parent;
|
||||||
break;
|
break;
|
||||||
@@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
DataContext = TempProfile;
|
DataContext = TempProfile;
|
||||||
|
|
||||||
AddPictureButton.IsVisible = _isNewUser;
|
AddPictureButton.IsVisible = _isNewUser;
|
||||||
IdLabel.IsVisible = !_isNewUser;
|
IdLabel.IsVisible = _profile != null;
|
||||||
|
IdText.IsVisible = _profile != null;
|
||||||
ChangePictureButton.IsVisible = !_isNewUser;
|
ChangePictureButton.IsVisible = !_isNewUser;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_profile != null)
|
if (_profile != null && !_isNewUser)
|
||||||
{
|
{
|
||||||
_profile.Name = TempProfile.Name;
|
_profile.Name = TempProfile.Name;
|
||||||
_profile.Image = TempProfile.Image;
|
_profile.Image = TempProfile.Image;
|
||||||
@@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
|||||||
}
|
}
|
||||||
else if (_isNewUser)
|
else if (_isNewUser)
|
||||||
{
|
{
|
||||||
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
|
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
70
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
MinWidth="500"
|
||||||
|
MinHeight="400"
|
||||||
|
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||||
|
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer">
|
||||||
|
<Design.DataContext>
|
||||||
|
<viewModels:UserProfileViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<Grid HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Margin="5"
|
||||||
|
Height="30"
|
||||||
|
Width="50"
|
||||||
|
MinWidth="50"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Command="{Binding GoBack}">
|
||||||
|
<ui:SymbolIcon Symbol="Back"/>
|
||||||
|
</Button>
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
|
||||||
|
<ListBox
|
||||||
|
Margin="5"
|
||||||
|
Grid.Row="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Items="{Binding LostProfiles}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border
|
||||||
|
Margin="2"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
ClipToBounds="True"
|
||||||
|
CornerRadius="5">
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding UserId}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Command="{Binding Recover}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Content="{Locale:Locale Recover}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
44
Ryujinx.Ava/Ui/Controls/UserRecoverer.axaml.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Navigation;
|
||||||
|
using Ryujinx.Ava.Ui.Models;
|
||||||
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
public partial class UserRecoverer : UserControl
|
||||||
|
{
|
||||||
|
private UserProfileViewModel _viewModel;
|
||||||
|
private NavigationDialogHost _parent;
|
||||||
|
|
||||||
|
public UserRecoverer()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
AddHandler(Frame.NavigatedToEvent, (s, e) =>
|
||||||
|
{
|
||||||
|
NavigatedTo(e);
|
||||||
|
}, RoutingStrategies.Direct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigatedTo(NavigationEventArgs arg)
|
||||||
|
{
|
||||||
|
if (Program.PreviewerDetached)
|
||||||
|
{
|
||||||
|
switch (arg.NavigationMode)
|
||||||
|
{
|
||||||
|
case NavigationMode.New:
|
||||||
|
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
|
||||||
|
|
||||||
|
_viewModel = args.viewModel;
|
||||||
|
_parent = args.parent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataContext = _viewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@
|
|||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
|
MinWidth="500"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<ListBox
|
<ListBox
|
||||||
Margin="5"
|
Margin="5"
|
||||||
|
MaxHeight="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
DoubleTapped="ProfilesList_DoubleTapped"
|
DoubleTapped="ProfilesList_DoubleTapped"
|
||||||
@@ -88,21 +90,56 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
<StackPanel
|
<Grid
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="10,0"
|
HorizontalAlignment="Center">
|
||||||
HorizontalAlignment="Center"
|
<Grid.RowDefinitions>
|
||||||
Orientation="Horizontal"
|
<RowDefinition Height="Auto"/>
|
||||||
Spacing="10">
|
<RowDefinition Height="Auto"/>
|
||||||
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="2"
|
||||||
|
Command="{Binding AddUser}"
|
||||||
|
Content="{Locale:Locale UserProfilesAddNewProfile}" />
|
||||||
<Button
|
<Button
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Grid.Row="0"
|
||||||
|
Margin="2"
|
||||||
|
Grid.Column="1"
|
||||||
Command="{Binding EditUser}"
|
Command="{Binding EditUser}"
|
||||||
Content="{Locale:Locale UserProfilesEditProfile}"
|
Content="{Locale:Locale UserProfilesEditProfile}"
|
||||||
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
IsEnabled="{Binding IsSelectedProfiledEditable}" />
|
||||||
|
<Button
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="2"
|
||||||
|
Content="{Locale:Locale UserProfilesManageSaves}"
|
||||||
|
Command="{Binding ManageSaves}" />
|
||||||
<Button
|
<Button
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="2"
|
||||||
Command="{Binding DeleteUser}"
|
Command="{Binding DeleteUser}"
|
||||||
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
|
||||||
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
IsEnabled="{Binding IsSelectedProfileDeletable}" />
|
||||||
</StackPanel>
|
<Button
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="2"
|
||||||
|
Command="{Binding RecoverLostAccounts}"
|
||||||
|
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
@@ -1,10 +1,13 @@
|
|||||||
|
using Avalonia.Platform;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
|
using SPB.Platform.GLX;
|
||||||
using SPB.Platform.Win32;
|
using SPB.Platform.Win32;
|
||||||
using SPB.Platform.X11;
|
using SPB.Platform.X11;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui
|
namespace Ryujinx.Ava.Ui
|
||||||
{
|
{
|
||||||
@@ -12,6 +15,18 @@ namespace Ryujinx.Ava.Ui
|
|||||||
{
|
{
|
||||||
private NativeWindowBase _window;
|
private NativeWindowBase _window;
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||||
|
{
|
||||||
|
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
|
||||||
|
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||||
|
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||||
|
|
||||||
|
X11Window.Hide();
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "X11");
|
||||||
|
}
|
||||||
|
|
||||||
public SurfaceKHR CreateSurface(Instance instance)
|
public SurfaceKHR CreateSurface(Instance instance)
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
@@ -20,7 +35,7 @@ namespace Ryujinx.Ava.Ui
|
|||||||
}
|
}
|
||||||
else if (OperatingSystem.IsLinux())
|
else if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
_window = X11Window;
|
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -1,8 +1,22 @@
|
|||||||
namespace Ryujinx.Ava.Ui.Models
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Models
|
||||||
{
|
{
|
||||||
public class DownloadableContentModel
|
public class DownloadableContentModel : BaseModel
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
private bool _enabled;
|
||||||
|
|
||||||
|
public bool Enabled
|
||||||
|
{
|
||||||
|
get => _enabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_enabled = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string TitleId { get; }
|
public string TitleId { get; }
|
||||||
public string ContainerPath { get; }
|
public string ContainerPath { get; }
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
|
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
122
Ryujinx.Ava/Ui/Models/SaveModel.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using LibHac;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using Ryujinx.Ava.Common;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.App.Common;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Models
|
||||||
|
{
|
||||||
|
public class SaveModel : BaseModel
|
||||||
|
{
|
||||||
|
private readonly HorizonClient _horizonClient;
|
||||||
|
private long _size;
|
||||||
|
|
||||||
|
public Action DeleteAction { get; set; }
|
||||||
|
public ulong SaveId { get; }
|
||||||
|
public ProgramId TitleId { get; }
|
||||||
|
public string TitleIdString => $"{TitleId.Value:X16}";
|
||||||
|
public UserId UserId { get; }
|
||||||
|
public bool InGameList { get; }
|
||||||
|
public string Title { get; }
|
||||||
|
public byte[] Icon { get; }
|
||||||
|
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get => _size; set
|
||||||
|
{
|
||||||
|
_size = value;
|
||||||
|
SizeAvailable = true;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(SizeString));
|
||||||
|
OnPropertyChanged(nameof(SizeAvailable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SizeAvailable { get; set; }
|
||||||
|
|
||||||
|
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
|
||||||
|
|
||||||
|
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
|
||||||
|
{
|
||||||
|
_horizonClient = horizonClient;
|
||||||
|
SaveId = info.SaveDataId;
|
||||||
|
TitleId = info.ProgramId;
|
||||||
|
UserId = info.UserId;
|
||||||
|
|
||||||
|
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
|
||||||
|
|
||||||
|
InGameList = appData != null;
|
||||||
|
|
||||||
|
if (InGameList)
|
||||||
|
{
|
||||||
|
Icon = appData.Icon;
|
||||||
|
Title = appData.TitleName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
|
||||||
|
Title = appMetadata.Title ?? TitleIdString;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
|
||||||
|
|
||||||
|
long total_size = GetDirectorySize(saveRoot);
|
||||||
|
long GetDirectorySize(string path)
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
var directories = Directory.GetDirectories(path);
|
||||||
|
foreach (var directory in directories)
|
||||||
|
{
|
||||||
|
size += GetDirectorySize(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = Directory.GetFiles(path);
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
size += new FileInfo(file).Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size = total_size;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenLocation()
|
||||||
|
{
|
||||||
|
ApplicationHelper.OpenSaveDir(SaveId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Delete()
|
||||||
|
{
|
||||||
|
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
|
||||||
|
LocaleManager.Instance["IrreversibleActionNote"],
|
||||||
|
LocaleManager.Instance["InputDialogYes"],
|
||||||
|
LocaleManager.Instance["InputDialogNo"], "");
|
||||||
|
|
||||||
|
if (result == UserResult.Yes)
|
||||||
|
{
|
||||||
|
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
|
||||||
|
|
||||||
|
DeleteAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -45,9 +45,12 @@ namespace Ryujinx.Ava.Ui.Models
|
|||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
|
|
||||||
Image = profile.Image;
|
if (_profile != null)
|
||||||
Name = profile.Name;
|
{
|
||||||
UserId = profile.UserId;
|
Image = profile.Image;
|
||||||
|
Name = profile.Name;
|
||||||
|
UserId = profile.UserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TempProfile(){}
|
public TempProfile(){}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.ViewModels;
|
using Ryujinx.Ava.Ui.ViewModels;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
||||||
@@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
|
|||||||
public class UserProfile : BaseModel
|
public class UserProfile : BaseModel
|
||||||
{
|
{
|
||||||
private readonly Profile _profile;
|
private readonly Profile _profile;
|
||||||
|
private readonly NavigationDialogHost _owner;
|
||||||
private byte[] _image;
|
private byte[] _image;
|
||||||
private string _name;
|
private string _name;
|
||||||
private UserId _userId;
|
private UserId _userId;
|
||||||
@@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserProfile(Profile profile)
|
public UserProfile(Profile profile, NavigationDialogHost owner)
|
||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
|
_owner = owner;
|
||||||
|
|
||||||
Image = profile.Image;
|
Image = profile.Image;
|
||||||
Name = profile.Name;
|
Name = profile.Name;
|
||||||
@@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
|
|||||||
OnPropertyChanged(nameof(IsOpened));
|
OnPropertyChanged(nameof(IsOpened));
|
||||||
OnPropertyChanged(nameof(Name));
|
OnPropertyChanged(nameof(Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Recover(UserProfile userProfile)
|
||||||
|
{
|
||||||
|
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration;
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE;
|
using Ryujinx.HLE;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
@@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
private string _loadHeading;
|
private string _loadHeading;
|
||||||
private string _cacheLoadStatus;
|
private string _cacheLoadStatus;
|
||||||
private string _searchText;
|
private string _searchText;
|
||||||
|
private Timer _searchTimer;
|
||||||
private string _dockedStatusText;
|
private string _dockedStatusText;
|
||||||
private string _fifoStatusText;
|
private string _fifoStatusText;
|
||||||
private string _gameStatusText;
|
private string _gameStatusText;
|
||||||
@@ -74,6 +76,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
private bool _showAll;
|
private bool _showAll;
|
||||||
private string _lastScannedAmiiboId;
|
private string _lastScannedAmiiboId;
|
||||||
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
private ReadOnlyObservableCollection<ApplicationData> _appsObservableList;
|
||||||
|
public ApplicationLibrary ApplicationLibrary => _owner.ApplicationLibrary;
|
||||||
|
|
||||||
public string TitleName { get; internal set; }
|
public string TitleName { get; internal set; }
|
||||||
|
|
||||||
@@ -101,8 +104,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_owner.ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||||
_owner.ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||||
|
|
||||||
Ptc.PtcStateChanged -= ProgressHandler;
|
Ptc.PtcStateChanged -= ProgressHandler;
|
||||||
Ptc.PtcStateChanged += ProgressHandler;
|
Ptc.PtcStateChanged += ProgressHandler;
|
||||||
@@ -115,10 +118,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
_searchText = value;
|
_searchText = value;
|
||||||
|
|
||||||
RefreshView();
|
_searchTimer?.Dispose();
|
||||||
|
|
||||||
|
_searchTimer = new Timer(TimerCallback, null, 1000, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TimerCallback(object obj)
|
||||||
|
{
|
||||||
|
RefreshView();
|
||||||
|
|
||||||
|
_searchTimer.Dispose();
|
||||||
|
_searchTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
|
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
|
||||||
{
|
{
|
||||||
get => _appsObservableList;
|
get => _appsObservableList;
|
||||||
@@ -200,22 +213,19 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
private string _showUikey = "F4";
|
private string _showUikey = "F4";
|
||||||
private string _pauseKey = "F5";
|
private string _pauseKey = "F5";
|
||||||
private string _screenshotkey = "F8";
|
private string _screenshotkey = "F8";
|
||||||
private float _volume;
|
private float _volume;
|
||||||
private string _backendText;
|
private string _backendText;
|
||||||
|
|
||||||
public ApplicationData SelectedApplication
|
public ApplicationData SelectedApplication
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
switch (Glyph)
|
return Glyph switch
|
||||||
{
|
{
|
||||||
case Glyph.List:
|
Glyph.List => _owner.GameList.SelectedApplication,
|
||||||
return _owner.GameList.SelectedApplication;
|
Glyph.Grid => _owner.GameGrid.SelectedApplication,
|
||||||
case Glyph.Grid:
|
_ => null,
|
||||||
return _owner.GameGrid.SelectedApplication;
|
};
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +418,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
_owner.AppHost.Device.SetVolume(_volume);
|
_owner.AppHost.Device.SetVolume(_volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPropertyChanged(nameof(VolumeStatusText));
|
OnPropertyChanged(nameof(VolumeStatusText));
|
||||||
OnPropertyChanged(nameof(VolumeMuted));
|
OnPropertyChanged(nameof(VolumeMuted));
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
@@ -477,38 +488,36 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
internal void Sort(bool isAscending)
|
internal void Sort(bool isAscending)
|
||||||
{
|
{
|
||||||
IsAscending = isAscending;
|
IsAscending = isAscending;
|
||||||
|
|
||||||
RefreshView();
|
RefreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Sort(ApplicationSort sort)
|
internal void Sort(ApplicationSort sort)
|
||||||
{
|
{
|
||||||
SortMode = sort;
|
SortMode = sort;
|
||||||
|
|
||||||
RefreshView();
|
RefreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IComparer<ApplicationData> GetComparer()
|
private IComparer<ApplicationData> GetComparer()
|
||||||
{
|
{
|
||||||
switch (SortMode)
|
return SortMode switch
|
||||||
{
|
{
|
||||||
case ApplicationSort.LastPlayed:
|
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
|
||||||
return new Models.Generic.LastPlayedSortComparer(IsAscending);
|
ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
|
||||||
case ApplicationSort.FileSize:
|
ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
|
||||||
return new Models.Generic.FileSizeSortComparer(IsAscending);
|
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
||||||
case ApplicationSort.TotalTimePlayed:
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
||||||
return new Models.Generic.TimePlayedSortComparer(IsAscending);
|
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
||||||
case ApplicationSort.Title:
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
||||||
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName);
|
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||||
case ApplicationSort.Favorite:
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||||
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite);
|
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
||||||
case ApplicationSort.Developer:
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
||||||
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer);
|
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||||
case ApplicationSort.FileType:
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||||
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension);
|
_ => null,
|
||||||
case ApplicationSort.Path:
|
};
|
||||||
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshView()
|
private void RefreshView()
|
||||||
@@ -611,40 +620,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
|
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
|
||||||
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
|
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
|
||||||
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
|
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
|
||||||
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
|
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
|
||||||
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
|
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
|
||||||
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
|
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
|
||||||
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
|
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
|
||||||
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
|
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
|
||||||
|
|
||||||
public string SortName
|
public string SortName
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
switch (SortMode)
|
return SortMode switch
|
||||||
{
|
{
|
||||||
case ApplicationSort.Title:
|
ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"],
|
||||||
return LocaleManager.Instance["GameListHeaderApplication"];
|
ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"],
|
||||||
case ApplicationSort.Developer:
|
ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"],
|
||||||
return LocaleManager.Instance["GameListHeaderDeveloper"];
|
ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"],
|
||||||
case ApplicationSort.LastPlayed:
|
ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"],
|
||||||
return LocaleManager.Instance["GameListHeaderLastPlayed"];
|
ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"],
|
||||||
case ApplicationSort.TotalTimePlayed:
|
ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"],
|
||||||
return LocaleManager.Instance["GameListHeaderTimePlayed"];
|
ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"],
|
||||||
case ApplicationSort.FileType:
|
_ => string.Empty,
|
||||||
return LocaleManager.Instance["GameListHeaderFileExtension"];
|
};
|
||||||
case ApplicationSort.FileSize:
|
|
||||||
return LocaleManager.Instance["GameListHeaderFileSize"];
|
|
||||||
case ApplicationSort.Path:
|
|
||||||
return LocaleManager.Instance["GameListHeaderPath"];
|
|
||||||
case ApplicationSort.Favorite:
|
|
||||||
return LocaleManager.Instance["CommonFavorite"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,6 +668,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
get => KeyGesture.Parse(_showUikey); set
|
get => KeyGesture.Parse(_showUikey); set
|
||||||
{
|
{
|
||||||
_showUikey = value.ToString();
|
_showUikey = value.ToString();
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,6 +678,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
get => KeyGesture.Parse(_screenshotkey); set
|
get => KeyGesture.Parse(_screenshotkey); set
|
||||||
{
|
{
|
||||||
_screenshotkey = value.ToString();
|
_screenshotkey = value.ToString();
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,14 +688,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
get => KeyGesture.Parse(_pauseKey); set
|
get => KeyGesture.Parse(_pauseKey); set
|
||||||
{
|
{
|
||||||
_pauseKey = value.ToString();
|
_pauseKey = value.ToString();
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
|
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
|
||||||
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
|
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
|
||||||
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
|
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
|
||||||
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
|
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
|
||||||
|
|
||||||
public int GridSizeScale
|
public int GridSizeScale
|
||||||
{
|
{
|
||||||
@@ -728,14 +731,14 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
|
||||||
{
|
{
|
||||||
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
|
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
|
||||||
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
|
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
|
||||||
|
|
||||||
await window.ShowDialog(_owner);
|
await window.ShowDialog(_owner);
|
||||||
|
|
||||||
if (window.IsScanned)
|
if (window.IsScanned)
|
||||||
{
|
{
|
||||||
_showAll = window.ViewModel.ShowAllAmiibo;
|
_showAll = window.ViewModel.ShowAllAmiibo;
|
||||||
_lastScannedAmiiboId = window.ScannedAmiibo.GetId();
|
_lastScannedAmiiboId = window.ScannedAmiibo.GetId();
|
||||||
|
|
||||||
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
|
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
|
||||||
@@ -766,8 +769,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
|
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
|
||||||
{
|
{
|
||||||
StatusBarProgressValue = e.NumAppsLoaded;
|
StatusBarProgressValue = e.NumAppsLoaded;
|
||||||
StatusBarProgressMaximum = e.NumAppsFound;
|
StatusBarProgressMaximum = e.NumAppsFound;
|
||||||
|
|
||||||
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
|
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
@@ -792,9 +796,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
Applications.Clear();
|
Applications.Clear();
|
||||||
|
|
||||||
_owner.LoadProgressBar.IsVisible = true;
|
_owner.LoadProgressBar.IsVisible = true;
|
||||||
StatusBarProgressMaximum = 0;
|
StatusBarProgressMaximum = 0;
|
||||||
StatusBarProgressValue = 0;
|
StatusBarProgressValue = 0;
|
||||||
|
|
||||||
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
|
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -812,7 +818,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
Thread thread = new(() =>
|
Thread thread = new(() =>
|
||||||
{
|
{
|
||||||
_owner.ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
|
||||||
|
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
})
|
})
|
||||||
@@ -842,12 +848,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
|
||||||
|
|
||||||
string[] files = await dialog.ShowAsync(_owner);
|
string[] files = await dialog.ShowAsync(_owner);
|
||||||
|
|
||||||
@@ -878,10 +884,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
|
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
|
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
|
||||||
{
|
{
|
||||||
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
|
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
|
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
|
||||||
{
|
{
|
||||||
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
|
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
|
||||||
@@ -941,9 +949,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
_lastFullscreenToggle = Environment.TickCount64;
|
_lastFullscreenToggle = Environment.TickCount64;
|
||||||
|
|
||||||
WindowState state = _owner.WindowState;
|
if (_owner.WindowState == WindowState.FullScreen)
|
||||||
|
|
||||||
if (state == WindowState.FullScreen)
|
|
||||||
{
|
{
|
||||||
_owner.WindowState = WindowState.Normal;
|
_owner.WindowState = WindowState.Normal;
|
||||||
|
|
||||||
@@ -971,8 +977,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
if (IsGameRunning)
|
if (IsGameRunning)
|
||||||
{
|
{
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Value =
|
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
|
||||||
!ConfigurationState.Instance.System.EnableDockedMode.Value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,6 +990,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
else if (IsGameRunning)
|
else if (IsGameRunning)
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
_owner.AppHost?.ShowExitPrompt();
|
_owner.AppHost?.ShowExitPrompt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -994,19 +1000,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
|
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
|
||||||
|
|
||||||
await _owner.SettingsWindow.ShowDialog(_owner);
|
await _owner.SettingsWindow.ShowDialog(_owner);
|
||||||
|
|
||||||
LoadConfigurableHotKeys();
|
LoadConfigurableHotKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void ManageProfiles()
|
public async void ManageProfiles()
|
||||||
{
|
{
|
||||||
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
|
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem, _owner.LibHacHorizonManager.RyujinxClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenAboutWindow()
|
public async void OpenAboutWindow()
|
||||||
{
|
{
|
||||||
AboutWindow window = new();
|
await new AboutWindow().ShowDialog(_owner);
|
||||||
|
|
||||||
await window.ShowDialog(_owner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeLanguage(object obj)
|
public void ChangeLanguage(object obj)
|
||||||
@@ -1020,7 +1025,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProgressMaximum = total;
|
ProgressMaximum = total;
|
||||||
ProgressValue = current;
|
ProgressValue = current;
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
@@ -1030,13 +1035,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
case PtcLoadingState.Start:
|
case PtcLoadingState.Start:
|
||||||
case PtcLoadingState.Loading:
|
case PtcLoadingState.Loading:
|
||||||
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
|
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
|
||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case PtcLoadingState.Loaded:
|
case PtcLoadingState.Loaded:
|
||||||
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
|
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1046,13 +1051,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
case ShaderCacheLoadingState.Start:
|
case ShaderCacheLoadingState.Start:
|
||||||
case ShaderCacheLoadingState.Loading:
|
case ShaderCacheLoadingState.Loading:
|
||||||
LoadHeading = LocaleManager.Instance["CompilingShaders"];
|
LoadHeading = LocaleManager.Instance["CompilingShaders"];
|
||||||
IsLoadingIndeterminate = false;
|
IsLoadingIndeterminate = false;
|
||||||
break;
|
break;
|
||||||
case ShaderCacheLoadingState.Loaded:
|
case ShaderCacheLoadingState.Loaded:
|
||||||
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
|
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
|
||||||
IsLoadingIndeterminate = true;
|
IsLoadingIndeterminate = true;
|
||||||
CacheLoadStatus = "";
|
CacheLoadStatus = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1065,14 +1070,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenUserSaveDirectory()
|
public void OpenUserSaveDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||||
out ulong titleIdNumber))
|
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
@@ -1082,8 +1085,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
|
UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
|
||||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
|
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
|
||||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1091,13 +1094,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void ToggleFavorite()
|
public void ToggleFavorite()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
selection.Favorite = !selection.Favorite;
|
selection.Favorite = !selection.Favorite;
|
||||||
|
|
||||||
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.Favorite = selection.Favorite;
|
appMetadata.Favorite = selection.Favorite;
|
||||||
});
|
});
|
||||||
@@ -1108,11 +1110,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenModsDirectory()
|
public void OpenModsDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
|
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
@@ -1121,12 +1122,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenSdModsDirectory()
|
public void OpenSdModsDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
|
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@@ -1134,13 +1135,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenPtcDirectory()
|
public void OpenPtcDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
|
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
|
||||||
|
string mainPath = Path.Combine(ptcDir, "0");
|
||||||
string mainPath = Path.Combine(ptcDir, "0");
|
|
||||||
string backupPath = Path.Combine(ptcDir, "1");
|
string backupPath = Path.Combine(ptcDir, "1");
|
||||||
|
|
||||||
if (!Directory.Exists(ptcDir))
|
if (!Directory.Exists(ptcDir))
|
||||||
@@ -1156,16 +1155,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public async void PurgePtcCache()
|
public async void PurgePtcCache()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
|
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
|
||||||
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
|
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
|
||||||
|
|
||||||
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
|
||||||
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
|
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName),
|
||||||
|
LocaleManager.Instance["InputDialogYes"],
|
||||||
|
LocaleManager.Instance["InputDialogNo"],
|
||||||
|
LocaleManager.Instance["RyujinxConfirm"]);
|
||||||
|
|
||||||
List<FileInfo> cacheFiles = new();
|
List<FileInfo> cacheFiles = new();
|
||||||
|
|
||||||
@@ -1198,8 +1199,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenShaderCacheDirectory()
|
public void OpenShaderCacheDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
|
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
|
||||||
@@ -1220,18 +1220,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public async void PurgeShaderCache()
|
public async void PurgeShaderCache()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
|
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
|
||||||
|
|
||||||
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
||||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
|
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
|
||||||
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
|
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName),
|
||||||
|
LocaleManager.Instance["InputDialogYes"],
|
||||||
|
LocaleManager.Instance["InputDialogNo"],
|
||||||
|
LocaleManager.Instance["RyujinxConfirm"]);
|
||||||
|
|
||||||
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
|
List<DirectoryInfo> oldCacheDirectories = new();
|
||||||
List<FileInfo> newCacheFiles = new List<FileInfo>();
|
List<FileInfo> newCacheFiles = new();
|
||||||
|
|
||||||
if (shaderCacheDir.Exists)
|
if (shaderCacheDir.Exists)
|
||||||
{
|
{
|
||||||
@@ -1279,38 +1281,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public async void OpenTitleUpdateManager()
|
public async void OpenTitleUpdateManager()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
TitleUpdateWindow titleUpdateManager =
|
await new TitleUpdateWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
|
||||||
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
|
|
||||||
|
|
||||||
await titleUpdateManager.ShowDialog(_owner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenDownloadableContentManager()
|
public async void OpenDownloadableContentManager()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName);
|
await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
|
||||||
|
|
||||||
await downloadableContentManager.ShowDialog(_owner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenCheatManager()
|
public async void OpenCheatManager()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
|
await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
|
||||||
|
|
||||||
await cheatManager.ShowDialog(_owner);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1321,13 +1313,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var application = _owner.AppHost.Device.Application;
|
ApplicationLoader application = _owner.AppHost.Device.Application;
|
||||||
|
|
||||||
if (application != null)
|
if (application != null)
|
||||||
{
|
{
|
||||||
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName);
|
await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner);
|
||||||
|
|
||||||
await cheatManager.ShowDialog(_owner);
|
|
||||||
|
|
||||||
_owner.AppHost.Device.EnableCheats();
|
_owner.AppHost.Device.EnableCheats();
|
||||||
}
|
}
|
||||||
@@ -1335,14 +1324,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenDeviceSaveDirectory()
|
public void OpenDeviceSaveDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||||
out ulong titleIdNumber))
|
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
@@ -1360,14 +1347,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public void OpenBcatSaveDirectory()
|
public void OpenBcatSaveDirectory()
|
||||||
{
|
{
|
||||||
var selection = SelectedApplication;
|
ApplicationData selection = SelectedApplication;
|
||||||
|
|
||||||
if (selection != null)
|
if (selection != null)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
|
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||||
out ulong titleIdNumber))
|
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
@@ -1420,12 +1405,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
_owner.Close();
|
_owner.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleFirmwareInstallation(string path)
|
private async Task HandleFirmwareInstallation(string filename)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string filename = path;
|
|
||||||
|
|
||||||
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
|
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
|
||||||
|
|
||||||
if (firmwareVersion == null)
|
if (firmwareVersion == null)
|
||||||
@@ -1437,7 +1420,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
|
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
|
||||||
|
|
||||||
|
|
||||||
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
|
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
|
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
|
||||||
@@ -1480,11 +1462,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
|
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
|
||||||
|
|
||||||
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
|
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, message);
|
Logger.Info?.Print(LogClass.Application, message);
|
||||||
|
|
||||||
// Purge Applet Cache.
|
// Purge Applet Cache.
|
||||||
|
|
||||||
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
|
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
|
||||||
|
|
||||||
if (miiEditorCacheFolder.Exists)
|
if (miiEditorCacheFolder.Exists)
|
||||||
{
|
{
|
||||||
@@ -1514,8 +1497,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
catch (LibHac.Common.Keys.MissingKeyException ex)
|
catch (LibHac.Common.Keys.MissingKeyException ex)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, ex.ToString());
|
Logger.Error?.Print(LogClass.Application, ex.ToString());
|
||||||
Dispatcher.UIThread.Post(async () => await
|
|
||||||
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
|
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1527,8 +1510,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
{
|
{
|
||||||
OpenFileDialog dialog = new() { AllowMultiple = false };
|
OpenFileDialog dialog = new() { AllowMultiple = false };
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
|
||||||
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
|
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
|
||||||
|
|
||||||
string[] file = await dialog.ShowAsync(_owner);
|
string[] file = await dialog.ShowAsync(_owner);
|
||||||
|
|
||||||
|
@@ -118,7 +118,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsMacOS
|
||||||
|
{
|
||||||
|
get => OperatingSystem.IsMacOS();
|
||||||
|
}
|
||||||
|
|
||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
@@ -474,11 +479,40 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
|
|
||||||
_previousVolumeLevel = Volume;
|
_previousVolumeLevel = Volume;
|
||||||
|
|
||||||
|
if (_owner is SettingsWindow owner)
|
||||||
|
{
|
||||||
|
owner.ControllerSettings?.SaveCurrentProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_owner.Owner is MainWindow window && _directoryChanged)
|
||||||
|
{
|
||||||
|
window.ViewModel.LoadApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
_directoryChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RevertIfNotSaved()
|
public void RevertIfNotSaved()
|
||||||
{
|
{
|
||||||
Program.ReloadConfig();
|
Program.ReloadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplyButton()
|
||||||
|
{
|
||||||
|
SaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OkButton()
|
||||||
|
{
|
||||||
|
SaveSettings();
|
||||||
|
_owner.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelButton()
|
||||||
|
{
|
||||||
|
RevertIfNotSaved();
|
||||||
|
_owner.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Shim;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
|
||||||
@@ -19,6 +25,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
public UserProfileViewModel()
|
public UserProfileViewModel()
|
||||||
{
|
{
|
||||||
Profiles = new ObservableCollection<UserProfile>();
|
Profiles = new ObservableCollection<UserProfile>();
|
||||||
|
LostProfiles = new ObservableCollection<UserProfile>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
public UserProfileViewModel(NavigationDialogHost owner) : this()
|
||||||
@@ -30,6 +37,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
public ObservableCollection<UserProfile> Profiles { get; set; }
|
public ObservableCollection<UserProfile> Profiles { get; set; }
|
||||||
|
|
||||||
|
public ObservableCollection<UserProfile> LostProfiles { get; set; }
|
||||||
|
|
||||||
public UserProfile SelectedProfile
|
public UserProfile SelectedProfile
|
||||||
{
|
{
|
||||||
get => _selectedProfile;
|
get => _selectedProfile;
|
||||||
@@ -65,12 +74,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
public void LoadProfiles()
|
public void LoadProfiles()
|
||||||
{
|
{
|
||||||
Profiles.Clear();
|
Profiles.Clear();
|
||||||
|
LostProfiles.Clear();
|
||||||
|
|
||||||
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
var profiles = _owner.AccountManager.GetAllUsers().OrderByDescending(x => x.AccountState == AccountState.Open);
|
||||||
|
|
||||||
foreach (var profile in profiles)
|
foreach (var profile in profiles)
|
||||||
{
|
{
|
||||||
Profiles.Add(new UserProfile(profile));
|
Profiles.Add(new UserProfile(profile, _owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
SelectedProfile = Profiles.FirstOrDefault(x => x.UserId == _owner.AccountManager.LastOpenedUser.UserId);
|
||||||
@@ -84,6 +94,42 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
_owner.AccountManager.OpenUser(_selectedProfile.UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
|
||||||
|
default, saveDataId: default, index: default);
|
||||||
|
|
||||||
|
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||||
|
|
||||||
|
_owner.HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||||
|
|
||||||
|
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||||
|
|
||||||
|
HashSet<HLE.HOS.Services.Account.Acc.UserId> lostAccounts = new HashSet<HLE.HOS.Services.Account.Acc.UserId>();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
|
||||||
|
|
||||||
|
if (readCount == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < readCount; i++)
|
||||||
|
{
|
||||||
|
var save = saveDataInfo[i];
|
||||||
|
var id = new HLE.HOS.Services.Account.Acc.UserId((long)save.UserId.Id.Low, (long)save.UserId.Id.High);
|
||||||
|
if (Profiles.FirstOrDefault( x=> x.UserId == id) == null)
|
||||||
|
{
|
||||||
|
lostAccounts.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var account in lostAccounts)
|
||||||
|
{
|
||||||
|
LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), _owner));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddUser()
|
public void AddUser()
|
||||||
@@ -93,6 +139,25 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async void ManageSaves()
|
||||||
|
{
|
||||||
|
UserProfile userProfile = _highlightedProfile ?? SelectedProfile;
|
||||||
|
|
||||||
|
SaveManager manager = new SaveManager(userProfile, _owner.HorizonClient, _owner.VirtualFileSystem);
|
||||||
|
|
||||||
|
ContentDialog contentDialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = string.Format(LocaleManager.Instance["SaveManagerHeading"], userProfile.Name),
|
||||||
|
PrimaryButtonText = "",
|
||||||
|
SecondaryButtonText = "",
|
||||||
|
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
|
||||||
|
Content = manager,
|
||||||
|
Padding = new Thickness(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
await contentDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public void EditUser()
|
public void EditUser()
|
||||||
{
|
{
|
||||||
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
|
||||||
@@ -134,5 +199,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
|||||||
|
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GoBack()
|
||||||
|
{
|
||||||
|
_owner.GoBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecoverLostAccounts()
|
||||||
|
{
|
||||||
|
_owner.Navigate(typeof(UserRecoverer), (this._owner, this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,89 +3,128 @@
|
|||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||||
|
Width="800"
|
||||||
|
Height="500"
|
||||||
|
MinWidth="800"
|
||||||
|
MinHeight="500"
|
||||||
|
MaxWidth="800"
|
||||||
|
MaxHeight="500"
|
||||||
SizeToContent="Height"
|
SizeToContent="Height"
|
||||||
Width="600" MinHeight="500" Height="500"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
MinWidth="600"
|
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid Name="DownloadableContentGrid" Margin="15">
|
<Grid Name="DownloadableContentGrid" Margin="15">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
Name="Heading"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
MaxWidth="500"
|
||||||
Margin="20,15,20,20"
|
Margin="20,15,20,20"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
MaxWidth="500"
|
|
||||||
LineHeight="18"
|
LineHeight="18"
|
||||||
TextWrapping="Wrap"
|
TextAlignment="Center"
|
||||||
Text="{Binding Heading}"
|
TextWrapping="Wrap" />
|
||||||
TextAlignment="Center" />
|
<DockPanel
|
||||||
<Border
|
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
Margin="0"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<Button
|
||||||
|
Name="EnableAllButton"
|
||||||
|
MinWidth="90"
|
||||||
|
Margin="5"
|
||||||
|
Command="{Binding EnableAll}">
|
||||||
|
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
Name="DisableAllButton"
|
||||||
|
MinWidth="90"
|
||||||
|
Margin="5"
|
||||||
|
Command="{Binding DisableAll}">
|
||||||
|
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||||
|
</Button>
|
||||||
|
</DockPanel>
|
||||||
|
<Border
|
||||||
|
Grid.Row="3"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
BorderBrush="Gray"
|
BorderBrush="Gray"
|
||||||
BorderThickness="1">
|
BorderThickness="1">
|
||||||
<DataGrid
|
<ScrollViewer
|
||||||
MinHeight="200"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
Items="{Binding DownloadableContents}"
|
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<DataGrid.Columns>
|
<DataGrid
|
||||||
<DataGridTemplateColumn Width="90">
|
Name="DlcDataGrid"
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
MinHeight="200"
|
||||||
<DataTemplate>
|
HorizontalAlignment="Stretch"
|
||||||
<CheckBox
|
VerticalAlignment="Stretch"
|
||||||
Width="50"
|
CanUserReorderColumns="False"
|
||||||
MinWidth="40"
|
CanUserResizeColumns="True"
|
||||||
HorizontalAlignment="Right"
|
CanUserSortColumns="True"
|
||||||
IsChecked="{Binding Enabled}" />
|
HorizontalScrollBarVisibility="Auto"
|
||||||
</DataTemplate>
|
Items="{Binding _downloadableContents}"
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
SelectionMode="Extended"
|
||||||
<DataGridTemplateColumn.Header>
|
VerticalScrollBarVisibility="Auto">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
<DataGrid.Styles>
|
||||||
</DataGridTemplateColumn.Header>
|
<Styles>
|
||||||
</DataGridTemplateColumn>
|
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
|
||||||
<DataGridTextColumn
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
Width="190"
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
Binding="{Binding TitleId}"
|
</Style>
|
||||||
CanUserResize="True">
|
</Styles>
|
||||||
<DataGridTextColumn.Header>
|
<Styles>
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
<Style Selector="DataGridCell:nth-child(1)">
|
||||||
</DataGridTextColumn.Header>
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
</DataGridTextColumn>
|
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
||||||
<DataGridTextColumn
|
</Style>
|
||||||
Width="*"
|
</Styles>
|
||||||
Binding="{Binding ContainerPath}"
|
</DataGrid.Styles>
|
||||||
CanUserResize="True">
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn.Header>
|
<DataGridTemplateColumn Width="90">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTextColumn.Header>
|
<DataTemplate>
|
||||||
</DataGridTextColumn>
|
<CheckBox
|
||||||
<DataGridTextColumn
|
Width="50"
|
||||||
Width="*"
|
MinWidth="40"
|
||||||
Binding="{Binding FullPath}"
|
HorizontalAlignment="Center"
|
||||||
CanUserResize="True">
|
IsChecked="{Binding Enabled}" />
|
||||||
<DataGridTextColumn.Header>
|
</DataTemplate>
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTextColumn.Header>
|
<DataGridTemplateColumn.Header>
|
||||||
</DataGridTextColumn>
|
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
||||||
</DataGrid.Columns>
|
</DataGridTemplateColumn.Header>
|
||||||
</DataGrid>
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
|
||||||
|
<DataGridTextColumn.Header>
|
||||||
|
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
||||||
|
</DataGridTextColumn.Header>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
|
||||||
|
<DataGridTextColumn.Header>
|
||||||
|
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
||||||
|
</DataGridTextColumn.Header>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
<DataGridTextColumn Binding="{Binding ContainerPath}">
|
||||||
|
<DataGridTextColumn.Header>
|
||||||
|
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
||||||
|
</DataGridTextColumn.Header>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel
|
<DockPanel
|
||||||
Grid.Row="3"
|
Grid.Row="4"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||||
|
@@ -18,6 +18,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
@@ -27,14 +28,13 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
public partial class DownloadableContentManagerWindow : StyleableWindow
|
public partial class DownloadableContentManagerWindow : StyleableWindow
|
||||||
{
|
{
|
||||||
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
||||||
private readonly string _downloadableContentJsonPath;
|
private readonly string _downloadableContentJsonPath;
|
||||||
|
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
private VirtualFileSystem _virtualFileSystem { get; }
|
||||||
public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>();
|
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
|
||||||
public ulong TitleId { get; }
|
|
||||||
public string TitleName { get; }
|
|
||||||
|
|
||||||
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16"));
|
private ulong _titleId { get; }
|
||||||
|
private string _titleName { get; }
|
||||||
|
|
||||||
public DownloadableContentManagerWindow()
|
public DownloadableContentManagerWindow()
|
||||||
{
|
{
|
||||||
@@ -42,14 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
|
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
TitleId = titleId;
|
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
|
||||||
TitleName = titleName;
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||||
|
|
||||||
@@ -66,9 +68,24 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
|
RemoveButton.IsEnabled = false;
|
||||||
|
|
||||||
|
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
|
||||||
|
|
||||||
|
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||||
|
|
||||||
LoadDownloadableContents();
|
LoadDownloadableContents();
|
||||||
|
PrintHeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintHeading()
|
||||||
|
{
|
||||||
|
Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDownloadableContents()
|
private void LoadDownloadableContents()
|
||||||
@@ -79,23 +96,23 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||||
|
|
||||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
|
||||||
VirtualFileSystem.ImportTickets(pfs);
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
||||||
if (nca != null)
|
if (nca != null)
|
||||||
{
|
{
|
||||||
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
||||||
downloadableContentContainer.ContainerPath,
|
downloadableContentContainer.ContainerPath,
|
||||||
downloadableContentNca.FullPath,
|
downloadableContentNca.FullPath,
|
||||||
downloadableContentNca.Enabled));
|
downloadableContentNca.Enabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,11 +122,11 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new Nca(VirtualFileSystem.KeySet, ncaStorage);
|
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -124,61 +141,73 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
private async Task AddDownloadableContent(string path)
|
private async Task AddDownloadableContent(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream containerFile = File.OpenRead(path))
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
bool containsDownloadableContent = false;
|
||||||
|
|
||||||
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
bool containsDownloadableContent = false;
|
|
||||||
|
|
||||||
VirtualFileSystem.ImportTickets(pfs);
|
partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
if (nca == null)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
continue;
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
|
|
||||||
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
|
|
||||||
|
|
||||||
containsDownloadableContent = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!containsDownloadableContent)
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
|
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
|
||||||
|
|
||||||
|
containsDownloadableContent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!containsDownloadableContent)
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
|
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
|
||||||
{
|
{
|
||||||
if (removeSelectedOnly)
|
if (removeSelectedOnly)
|
||||||
{
|
{
|
||||||
DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList());
|
AvaloniaList<DownloadableContentModel> removedItems = new();
|
||||||
|
|
||||||
|
foreach (var item in DlcDataGrid.SelectedItems)
|
||||||
|
{
|
||||||
|
removedItems.Add(item as DownloadableContentModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
DlcDataGrid.SelectedItems.Clear();
|
||||||
|
|
||||||
|
foreach (var item in removedItems)
|
||||||
|
{
|
||||||
|
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DownloadableContents.Clear();
|
_downloadableContents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrintHeading();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveSelected()
|
public void RemoveSelected()
|
||||||
@@ -191,6 +220,22 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
RemoveDownloadableContents();
|
RemoveDownloadableContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnableAll()
|
||||||
|
{
|
||||||
|
foreach(var item in _downloadableContents)
|
||||||
|
{
|
||||||
|
item.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableAll()
|
||||||
|
{
|
||||||
|
foreach (var item in _downloadableContents)
|
||||||
|
{
|
||||||
|
item.Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async void Add()
|
public async void Add()
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new OpenFileDialog()
|
OpenFileDialog dialog = new OpenFileDialog()
|
||||||
@@ -214,6 +259,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
await AddDownloadableContent(file);
|
await AddDownloadableContent(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrintHeading();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
@@ -222,7 +269,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
DownloadableContentContainer container = default;
|
DownloadableContentContainer container = default;
|
||||||
|
|
||||||
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
|
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
|
||||||
{
|
{
|
||||||
if (container.ContainerPath != downloadableContent.ContainerPath)
|
if (container.ContainerPath != downloadableContent.ContainerPath)
|
||||||
{
|
{
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
Title="Ryujinx"
|
Title="Ryujinx"
|
||||||
Width="1280"
|
Width="1280"
|
||||||
Height="785"
|
Height="785"
|
||||||
MinWidth="1024"
|
MinWidth="1092"
|
||||||
MinHeight="680"
|
MinHeight="680"
|
||||||
d:DesignHeight="720"
|
d:DesignHeight="720"
|
||||||
d:DesignWidth="1280"
|
d:DesignWidth="1280"
|
||||||
@@ -57,6 +57,8 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
Name="MenuBar"
|
||||||
|
MinHeight="35"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
@@ -549,6 +551,7 @@
|
|||||||
<Grid
|
<Grid
|
||||||
Name="StatusBar"
|
Name="StatusBar"
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
|
MinHeight="30"
|
||||||
Height="30"
|
Height="30"
|
||||||
Margin="0,0"
|
Margin="0,0"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
public partial class MainWindow : StyleableWindow
|
public partial class MainWindow : StyleableWindow
|
||||||
{
|
{
|
||||||
|
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
||||||
private bool _canUpdate;
|
private bool _canUpdate;
|
||||||
private bool _isClosing;
|
private bool _isClosing;
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
ViewModel = new MainWindowViewModel(this);
|
ViewModel = new MainWindowViewModel(this);
|
||||||
|
|
||||||
|
MainWindowViewModel = ViewModel;
|
||||||
|
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -90,8 +93,10 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
Title = $"Ryujinx {Program.Version}";
|
Title = $"Ryujinx {Program.Version}";
|
||||||
|
|
||||||
Height = Height / Program.WindowScaleFactor;
|
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||||
Width = Width / Program.WindowScaleFactor;
|
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight;
|
||||||
|
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
|
||||||
|
Width /= Program.WindowScaleFactor;
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
@@ -251,24 +256,29 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||||
|
|
||||||
if (!AppHost.LoadGuestApplication().Result)
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
AppHost.DisposeContext();
|
if (!await AppHost.LoadGuestApplication())
|
||||||
|
{
|
||||||
|
AppHost.DisposeContext();
|
||||||
|
AppHost = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
|
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
|
||||||
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
|
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
|
||||||
|
|
||||||
SwitchToGameControl(startFullscreen);
|
SwitchToGameControl(startFullscreen);
|
||||||
|
|
||||||
_currentEmulatedGamePath = path;
|
_currentEmulatedGamePath = path;
|
||||||
Thread gameThread = new Thread(InitializeGame)
|
|
||||||
{
|
Thread gameThread = new(InitializeGame)
|
||||||
Name = "GUI.WindowThread"
|
{
|
||||||
};
|
Name = "GUI.WindowThread"
|
||||||
gameThread.Start();
|
};
|
||||||
|
gameThread.Start();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
@@ -518,23 +528,20 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
public static void UpdateGraphicsConfig()
|
public static void UpdateGraphicsConfig()
|
||||||
{
|
{
|
||||||
int resScale = ConfigurationState.Instance.Graphics.ResScale;
|
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
|
||||||
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
|
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
||||||
|
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
||||||
GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale;
|
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
||||||
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
|
|
||||||
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
|
|
||||||
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
|
|
||||||
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
|
||||||
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadHotKeys()
|
public void LoadHotKeys()
|
||||||
{
|
{
|
||||||
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
||||||
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
||||||
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
||||||
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveConfig()
|
public static void SaveConfig()
|
||||||
@@ -546,10 +553,12 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||||
{
|
{
|
||||||
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
|
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
|
||||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
{
|
||||||
|
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
|
||||||
|
|
||||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -75,7 +75,7 @@
|
|||||||
Spacing="10">
|
Spacing="10">
|
||||||
<ListBox
|
<ListBox
|
||||||
Name="GameList"
|
Name="GameList"
|
||||||
MinHeight="150"
|
MinHeight="250"
|
||||||
Items="{Binding GameDirectories}" />
|
Items="{Binding GameDirectories}" />
|
||||||
<Grid HorizontalAlignment="Stretch">
|
<Grid HorizontalAlignment="Stretch">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -955,16 +955,25 @@
|
|||||||
Icon="Document" />
|
Icon="Document" />
|
||||||
</ui:NavigationView.MenuItems>
|
</ui:NavigationView.MenuItems>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
<StackPanel
|
<ReversibleStackPanel
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="10"
|
Margin="10"
|
||||||
Spacing="10"
|
Spacing="10"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
HorizontalAlignment="Right">
|
HorizontalAlignment="Right"
|
||||||
<Button Content="{locale:Locale SettingsButtonSave}" Click="SaveButton_Clicked" />
|
ReverseOrder="{ReflectionBinding IsMacOS}">
|
||||||
<Button Content="{locale:Locale SettingsButtonClose}" Click="CloseButton_Clicked" />
|
<Button
|
||||||
<Button Content="{locale:Locale SettingsButtonApply}"
|
HotKey="Enter"
|
||||||
Click="ApplyButton_Clicked" />
|
Classes="accent"
|
||||||
</StackPanel>
|
Content="{locale:Locale SettingsButtonOk}"
|
||||||
|
Command="{ReflectionBinding OkButton}" />
|
||||||
|
<Button
|
||||||
|
HotKey="Escape"
|
||||||
|
Content="{locale:Locale SettingsButtonCancel}"
|
||||||
|
Command="{ReflectionBinding CancelButton}" />
|
||||||
|
<Button
|
||||||
|
Content="{locale:Locale SettingsButtonApply}"
|
||||||
|
Command="{ReflectionBinding ApplyButton}" />
|
||||||
|
</ReversibleStackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableWindow>
|
</window:StyleableWindow>
|
@@ -16,7 +16,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.Threading.Tasks;
|
|
||||||
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Windows
|
namespace Ryujinx.Ava.Ui.Windows
|
||||||
@@ -31,7 +30,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
|
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
|
||||||
|
|
||||||
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
|
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -48,7 +47,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
public SettingsWindow()
|
public SettingsWindow()
|
||||||
{
|
{
|
||||||
ViewModel = new SettingsViewModel();
|
ViewModel = new SettingsViewModel();
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -79,7 +78,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
PointerPressed += MouseClick;
|
PointerPressed += MouseClick;
|
||||||
|
|
||||||
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
|
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
|
||||||
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
|
||||||
|
|
||||||
_currentAssigner.GetInputAndAssign(assigner);
|
_currentAssigner.GetInputAndAssign(assigner);
|
||||||
@@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
_currentAssigner.Cancel();
|
_currentAssigner.Cancel();
|
||||||
_currentAssigner = null;
|
_currentAssigner = null;
|
||||||
|
|
||||||
button.IsChecked = false;
|
button.IsChecked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
if (e.SelectedItem is NavigationViewItem navitem)
|
if (e.SelectedItem is NavigationViewItem navitem)
|
||||||
{
|
{
|
||||||
switch (navitem.Tag.ToString())
|
NavPanel.Content = navitem.Tag.ToString() switch
|
||||||
{
|
{
|
||||||
case "UiPage":
|
"UiPage" => UiPage,
|
||||||
NavPanel.Content = UiPage;
|
"InputPage" => InputPage,
|
||||||
break;
|
"HotkeysPage" => HotkeysPage,
|
||||||
case "InputPage":
|
"SystemPage" => SystemPage,
|
||||||
NavPanel.Content = InputPage;
|
"CpuPage" => CpuPage,
|
||||||
break;
|
"GraphicsPage" => GraphicsPage,
|
||||||
case "HotkeysPage":
|
"AudioPage" => AudioPage,
|
||||||
NavPanel.Content = HotkeysPage;
|
"NetworkPage" => NetworkPage,
|
||||||
break;
|
"LoggingPage" => LoggingPage,
|
||||||
case "SystemPage":
|
_ => throw new NotImplementedException()
|
||||||
NavPanel.Content = SystemPage;
|
};
|
||||||
break;
|
|
||||||
case "CpuPage":
|
|
||||||
NavPanel.Content = CpuPage;
|
|
||||||
break;
|
|
||||||
case "GraphicsPage":
|
|
||||||
NavPanel.Content = GraphicsPage;
|
|
||||||
break;
|
|
||||||
case "AudioPage":
|
|
||||||
NavPanel.Content = AudioPage;
|
|
||||||
break;
|
|
||||||
case "NetworkPage":
|
|
||||||
NavPanel.Content = NetworkPage;
|
|
||||||
break;
|
|
||||||
case "LoggingPage":
|
|
||||||
NavPanel.Content = LoggingPage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
|
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
List<string> selected = new(GameList.SelectedItems.Cast<string>());
|
int oldIndex = GameList.SelectedIndex;
|
||||||
|
|
||||||
foreach (string path in selected)
|
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
|
||||||
{
|
{
|
||||||
ViewModel.GameDirectories.Remove(path);
|
ViewModel.GameDirectories.Remove(path);
|
||||||
ViewModel.DirectoryChanged = true;
|
ViewModel.DirectoryChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameList.ItemCount > 0)
|
||||||
|
{
|
||||||
|
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
@@ -211,43 +199,13 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
SaveSettings();
|
|
||||||
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseButton_Clicked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
ViewModel.RevertIfNotSaved();
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyButton_Clicked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
SaveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveSettings()
|
|
||||||
{
|
|
||||||
ViewModel.SaveSettings();
|
|
||||||
|
|
||||||
ControllerSettings?.SaveCurrentProfile();
|
|
||||||
|
|
||||||
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
|
|
||||||
{
|
|
||||||
window.ViewModel.LoadApplications();
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewModel.DirectoryChanged = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClosed(EventArgs e)
|
protected override void OnClosed(EventArgs e)
|
||||||
{
|
{
|
||||||
ControllerSettings.Dispose();
|
ControllerSettings.Dispose();
|
||||||
|
|
||||||
_currentAssigner?.Cancel();
|
_currentAssigner?.Cancel();
|
||||||
_currentAssigner = null;
|
_currentAssigner = null;
|
||||||
|
|
||||||
base.OnClosed(e);
|
base.OnClosed(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,17 @@
|
|||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||||
SizeToContent="Height"
|
Width="600"
|
||||||
Width="600" MinHeight="500" Height="500"
|
Height="400"
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
MinWidth="600"
|
MinWidth="600"
|
||||||
|
MinHeight="400"
|
||||||
|
MaxWidth="600"
|
||||||
|
MaxHeight="400"
|
||||||
|
SizeToContent="Height"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid Margin="15">
|
<Grid Margin="15">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -19,15 +23,15 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
Name="Heading"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
MaxWidth="500"
|
||||||
Margin="20,15,20,20"
|
Margin="20,15,20,20"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
MaxWidth="500"
|
|
||||||
LineHeight="18"
|
LineHeight="18"
|
||||||
TextWrapping="Wrap"
|
TextAlignment="Center"
|
||||||
Text="{Binding Heading}"
|
TextWrapping="Wrap" />
|
||||||
TextAlignment="Center" />
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
@@ -36,8 +40,6 @@
|
|||||||
BorderBrush="Gray"
|
BorderBrush="Gray"
|
||||||
BorderThickness="1">
|
BorderThickness="1">
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Width="550"
|
|
||||||
MinHeight="200"
|
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
@@ -45,11 +47,19 @@
|
|||||||
Margin="10"
|
Margin="10"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Items="{Binding TitleUpdates}">
|
Items="{Binding _titleUpdates}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
<RadioButton
|
||||||
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" />
|
Padding="8,0"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
GroupName="Update"
|
||||||
|
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
||||||
|
<Label
|
||||||
|
Margin="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Content="{Binding Label}"
|
||||||
|
FontSize="12" />
|
||||||
</RadioButton>
|
</RadioButton>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
@@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
private readonly string _titleUpdateJsonPath;
|
private readonly string _titleUpdateJsonPath;
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
|
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
private VirtualFileSystem _virtualFileSystem { get; }
|
||||||
|
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
|
||||||
|
|
||||||
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>();
|
private ulong _titleId { get; }
|
||||||
public string TitleId { get; }
|
private string _titleName { get; }
|
||||||
public string TitleName { get; }
|
|
||||||
|
|
||||||
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
|
|
||||||
|
|
||||||
public TitleUpdateWindow()
|
public TitleUpdateWindow()
|
||||||
{
|
{
|
||||||
@@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
|
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
TitleId = titleId;
|
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
|
||||||
TitleName = titleName;
|
|
||||||
|
|
||||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
|
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||||
{
|
{
|
||||||
Selected = "",
|
Selected = "",
|
||||||
Paths = new List<string>()
|
Paths = new List<string>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"];
|
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
|
||||||
|
|
||||||
LoadUpdates();
|
LoadUpdates();
|
||||||
|
PrintHeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintHeading()
|
||||||
|
{
|
||||||
|
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadUpdates()
|
private void LoadUpdates()
|
||||||
{
|
{
|
||||||
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
@@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
if (_titleUpdateWindowData.Selected == "")
|
if (_titleUpdateWindowData.Selected == "")
|
||||||
{
|
{
|
||||||
TitleUpdates[0].IsEnabled = true;
|
_titleUpdates[0].IsEnabled = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
||||||
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList();
|
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in enabled)
|
foreach (TitleUpdateModel update in enabled)
|
||||||
{
|
{
|
||||||
@@ -111,43 +117,47 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
private void AddUpdate(string path)
|
private void AddUpdate(string path)
|
||||||
{
|
{
|
||||||
if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path))
|
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
|
||||||
{
|
{
|
||||||
using (FileStream file = new(path, FileMode.Open, FileAccess.Read))
|
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||||
|
|
||||||
try
|
if (controlNca != null && patchNca != null)
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0);
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
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));
|
||||||
|
|
||||||
|
foreach (var update in _titleUpdates)
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new ApplicationControlProperty();
|
update.IsEnabled = false;
|
||||||
|
|
||||||
using var nacpFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
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["DialogUpdateAddUpdateErrorMessage"]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_titleUpdates.Last().IsEnabled = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
Dispatcher.UIThread.Post(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,16 +165,17 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
{
|
{
|
||||||
if (removeSelectedOnly)
|
if (removeSelectedOnly)
|
||||||
{
|
{
|
||||||
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
||||||
|
|
||||||
SortUpdates();
|
SortUpdates();
|
||||||
|
PrintHeading();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveSelected()
|
public void RemoveSelected()
|
||||||
@@ -179,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
public async void Add()
|
public async void Add()
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new OpenFileDialog()
|
OpenFileDialog dialog = new()
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
|
Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
|
||||||
AllowMultiple = true
|
AllowMultiple = true
|
||||||
@@ -202,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
}
|
}
|
||||||
|
|
||||||
SortUpdates();
|
SortUpdates();
|
||||||
|
PrintHeading();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortUpdates()
|
private void SortUpdates()
|
||||||
{
|
{
|
||||||
var list = TitleUpdates.ToList();
|
var list = _titleUpdates.ToList();
|
||||||
|
|
||||||
list.Sort((first, second) =>
|
list.Sort((first, second) =>
|
||||||
{
|
{
|
||||||
@@ -222,8 +234,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
TitleUpdates.Clear();
|
_titleUpdates.Clear();
|
||||||
TitleUpdates.AddRange(list);
|
_titleUpdates.AddRange(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
@@ -232,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
|||||||
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
_titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in TitleUpdates)
|
foreach (TitleUpdateModel update in _titleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
@@ -7,49 +8,15 @@ namespace Ryujinx.Common
|
|||||||
public static class BinaryReaderExtensions
|
public static class BinaryReaderExtensions
|
||||||
{
|
{
|
||||||
public unsafe static T ReadStruct<T>(this BinaryReader reader)
|
public unsafe static T ReadStruct<T>(this BinaryReader reader)
|
||||||
where T : struct
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
int size = Marshal.SizeOf<T>();
|
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||||
|
|
||||||
byte[] data = reader.ReadBytes(size);
|
|
||||||
|
|
||||||
fixed (byte* ptr = data)
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStructure<T>((IntPtr)ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe static T[] ReadStructArray<T>(this BinaryReader reader, int count)
|
|
||||||
where T : struct
|
|
||||||
{
|
|
||||||
int size = Marshal.SizeOf<T>();
|
|
||||||
|
|
||||||
T[] result = new T[count];
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
byte[] data = reader.ReadBytes(size);
|
|
||||||
|
|
||||||
fixed (byte* ptr = data)
|
|
||||||
{
|
|
||||||
result[i] = Marshal.PtrToStructure<T>((IntPtr)ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||||
where T : struct
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
long size = Marshal.SizeOf<T>();
|
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||||
|
|
||||||
byte[] data = new byte[size];
|
|
||||||
|
|
||||||
fixed (byte* ptr = data)
|
|
||||||
{
|
|
||||||
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write(data);
|
writer.Write(data);
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Management" Version="6.0.0" />
|
<PackageReference Include="System.Management" Version="7.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -28,9 +28,39 @@ namespace Ryujinx.Common.SystemInfo
|
|||||||
|
|
||||||
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
||||||
RamTotal = totalRAM;
|
RamTotal = totalRAM;
|
||||||
|
RamAvailable = GetVMInfoAvailableMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("libSystem.dylib", CharSet = CharSet.Ansi, SetLastError = true)]
|
static ulong GetVMInfoAvailableMemory()
|
||||||
|
{
|
||||||
|
var port = mach_host_self();
|
||||||
|
|
||||||
|
uint pageSize = 0;
|
||||||
|
var result = host_page_size(port, ref pageSize);
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int flavor = 4; // HOST_VM_INFO64
|
||||||
|
uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT
|
||||||
|
VMStatistics64 stats = new();
|
||||||
|
result = host_statistics64(port, flavor, ref stats, ref count);
|
||||||
|
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string SystemLibraryName = "libSystem.dylib";
|
||||||
|
|
||||||
|
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
private static extern int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||||
|
|
||||||
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
|
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
|
||||||
@@ -85,5 +115,43 @@ namespace Ryujinx.Common.SystemInfo
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
|
private static extern uint mach_host_self();
|
||||||
|
|
||||||
|
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
|
private static extern int host_page_size(uint host, ref uint out_page_size);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||||
|
struct VMStatistics64
|
||||||
|
{
|
||||||
|
public uint FreeCount;
|
||||||
|
public uint ActiveCount;
|
||||||
|
public uint InactiveCount;
|
||||||
|
public uint WireCount;
|
||||||
|
public ulong ZeroFillCount;
|
||||||
|
public ulong Reactivations;
|
||||||
|
public ulong Pageins;
|
||||||
|
public ulong Pageouts;
|
||||||
|
public ulong Faults;
|
||||||
|
public ulong CowFaults;
|
||||||
|
public ulong Lookups;
|
||||||
|
public ulong Hits;
|
||||||
|
public ulong Purges;
|
||||||
|
public uint PurgeableCount;
|
||||||
|
public uint SpeculativeCount;
|
||||||
|
public ulong Decompressions;
|
||||||
|
public ulong Compressions;
|
||||||
|
public ulong Swapins;
|
||||||
|
public ulong Swapouts;
|
||||||
|
public uint CompressorPageCount;
|
||||||
|
public uint ThrottledCount;
|
||||||
|
public uint ExternalPageCount;
|
||||||
|
public uint InternalPageCount;
|
||||||
|
public ulong TotalUncompressedPagesInCompressor;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(SystemLibraryName, CharSet = CharSet.Ansi, SetLastError = true)]
|
||||||
|
private static extern int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
WriteImpl(va, data);
|
WriteImpl(va, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||||
|
|
||||||
|
if (IsContiguousAndMapped(va, data.Length))
|
||||||
|
{
|
||||||
|
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
|
||||||
|
|
||||||
|
bool changed = !data.SequenceEqual(target);
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
data.CopyTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteImpl(va, data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to CPU mapped memory.
|
/// Writes data to CPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||||
|
|
||||||
|
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
|
||||||
|
bool changed = !data.SequenceEqual(target);
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
data.CopyTo(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -23,34 +24,18 @@ namespace Ryujinx.Cpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : struct
|
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : unmanaged
|
||||||
{
|
{
|
||||||
long size = Marshal.SizeOf<T>();
|
return MemoryMarshal.Cast<byte, T>(memory.GetSpan(position, Unsafe.SizeOf<T>()))[0];
|
||||||
|
|
||||||
byte[] data = new byte[size];
|
|
||||||
|
|
||||||
memory.Read(position, data);
|
|
||||||
|
|
||||||
fixed (byte* ptr = data)
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStructure<T>((IntPtr)ptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : struct
|
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : unmanaged
|
||||||
{
|
{
|
||||||
long size = Marshal.SizeOf<T>();
|
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||||
|
|
||||||
byte[] data = new byte[size];
|
|
||||||
|
|
||||||
fixed (byte* ptr = data)
|
|
||||||
{
|
|
||||||
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
memory.Write(position, data);
|
memory.Write(position, data);
|
||||||
|
|
||||||
return (ulong)size;
|
return (ulong)data.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
||||||
|
14
Ryujinx.Graphics.GAL/BufferAssignment.cs
Normal file
14
Ryujinx.Graphics.GAL/BufferAssignment.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct BufferAssignment
|
||||||
|
{
|
||||||
|
public readonly int Binding;
|
||||||
|
public readonly BufferRange Range;
|
||||||
|
|
||||||
|
public BufferAssignment(int binding, BufferRange range)
|
||||||
|
{
|
||||||
|
Binding = binding;
|
||||||
|
Range = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -86,12 +86,12 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
void SetStencilTest(StencilTestDescriptor stencilTest);
|
void SetStencilTest(StencilTestDescriptor stencilTest);
|
||||||
|
|
||||||
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||||
|
|
||||||
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
||||||
|
|
||||||
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
||||||
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
|
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||||
|
|
||||||
void SetUserClipDistance(int index, bool enableClip);
|
void SetUserClipDistance(int index, bool enableClip);
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||||
|
|
||||||
Capabilities GetCapabilities();
|
Capabilities GetCapabilities();
|
||||||
|
ulong GetCurrentSync();
|
||||||
HardwareInfo GetHardwareInfo();
|
HardwareInfo GetHardwareInfo();
|
||||||
|
|
||||||
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
IProgram LoadProgramBinary(byte[] programBinary, bool hasFragmentShader, ShaderInfo info);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user