Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
ad6ff6ce99 | |||
dc30d94852 | |||
4f293f8cbe | |||
32a1cd83fd | |||
e3d0ccf8d5 | |||
c14844d12c | |||
7fea26e97e | |||
7b7f62c776 | |||
423dbc8888 | |||
6adf15e479 | |||
2747f12591 | |||
a47824f961 | |||
8474d52778 | |||
dd7a924596 | |||
a76eaf9a9a | |||
009e6bcd1b | |||
eb2cc159fa | |||
bb89e36fd8 | |||
de3134adbe | |||
36d53819a4 | |||
ae4324032a | |||
f449895e6d | |||
410be95ab6 | |||
cff9046fc7 | |||
86fd0643c2 | |||
43a83a401e | |||
f0e27a23a5 | |||
e68650237d | |||
1faff14e73 | |||
784cf9d594 | |||
64263c5218 | |||
065c4e520d | |||
139a930407 | |||
719dc97bbd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -170,3 +170,6 @@ launchSettings.json
|
||||
|
||||
# NetCore Publishing Profiles
|
||||
PublishProfiles/
|
||||
|
||||
# Glade backup files
|
||||
*.glade~
|
||||
|
@ -16,4 +16,10 @@
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Ryujinx.Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Arm64
|
||||
@ -32,9 +31,12 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR)
|
||||
{
|
||||
ulong value = operand.Value;
|
||||
return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR);
|
||||
}
|
||||
|
||||
if (operand.Type == OperandType.I32)
|
||||
public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR)
|
||||
{
|
||||
if (type == OperandType.I32)
|
||||
{
|
||||
value |= value << 32;
|
||||
}
|
||||
@ -50,7 +52,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
// Any value AND all ones will be equal itself, so it's effectively a no-op.
|
||||
// Any value OR all ones will be equal all ones, so one can just use MOV.
|
||||
// Any value XOR all ones will be equal its inverse, so one can just use MVN.
|
||||
if (value == ulong.MaxValue)
|
||||
if (value == 0 || value == ulong.MaxValue)
|
||||
{
|
||||
immN = 0;
|
||||
immS = 0;
|
||||
@ -59,79 +61,18 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
return false;
|
||||
}
|
||||
|
||||
int bitLength = CountSequence(value);
|
||||
// Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not
|
||||
// been cut-in-half across the word boundary.
|
||||
int rotation = BitOperations.TrailingZeroCount(value & (value + 1));
|
||||
ulong rotatedValue = ulong.RotateRight(value, rotation);
|
||||
|
||||
if ((value >> bitLength) != 0)
|
||||
{
|
||||
bitLength += CountSequence(value >> bitLength);
|
||||
}
|
||||
// Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones
|
||||
// in element.
|
||||
int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1));
|
||||
int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue);
|
||||
|
||||
int bitLengthLog2 = BitOperations.Log2((uint)bitLength);
|
||||
int bitLengthPow2 = 1 << bitLengthLog2;
|
||||
|
||||
if (bitLengthPow2 < bitLength)
|
||||
{
|
||||
bitLengthLog2++;
|
||||
bitLengthPow2 <<= 1;
|
||||
}
|
||||
|
||||
int selectedESize = 64;
|
||||
int repetitions = 1;
|
||||
int onesCount = BitOperations.PopCount(value);
|
||||
|
||||
if (bitLengthPow2 < 64 && (value >> bitLengthPow2) != 0)
|
||||
{
|
||||
for (int eSizeLog2 = bitLengthLog2; eSizeLog2 < 6; eSizeLog2++)
|
||||
{
|
||||
bool match = true;
|
||||
int eSize = 1 << eSizeLog2;
|
||||
ulong mask = (1UL << eSize) - 1;
|
||||
ulong eValue = value & mask;
|
||||
|
||||
for (int e = 1; e < 64 / eSize; e++)
|
||||
{
|
||||
if (((value >> (e * eSize)) & mask) != eValue)
|
||||
{
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
selectedESize = eSize;
|
||||
repetitions = 64 / eSize;
|
||||
onesCount = BitOperations.PopCount(eValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find rotation. We have two cases, one where the highest bit is 0
|
||||
// and one where it is 1.
|
||||
// If it's 1, we just need to count the number of 1 bits on the MSB to find the right rotation.
|
||||
// If it's 0, we just need to count the number of 0 bits on the LSB to find the left rotation,
|
||||
// then we can convert it to the right rotation shift by subtracting the value from the element size.
|
||||
int rotation;
|
||||
long vHigh = (long)(value << (64 - selectedESize));
|
||||
if (vHigh < 0)
|
||||
{
|
||||
rotation = BitOperations.LeadingZeroCount(~(ulong)vHigh);
|
||||
}
|
||||
else
|
||||
{
|
||||
rotation = (selectedESize - BitOperations.TrailingZeroCount(value)) & (selectedESize - 1);
|
||||
}
|
||||
|
||||
// Reconstruct value and see if it matches. If not, we can't encode.
|
||||
ulong reconstructed = onesCount == 64 ? ulong.MaxValue : RotateRight((1UL << onesCount) - 1, rotation, selectedESize);
|
||||
|
||||
for (int bit = 32; bit >= selectedESize; bit >>= 1)
|
||||
{
|
||||
reconstructed |= reconstructed << bit;
|
||||
}
|
||||
|
||||
if (reconstructed != value || onesCount == 0)
|
||||
// Check the value is repeating; also ensures element size is a power of two.
|
||||
if (ulong.RotateRight(value, elementSize) != value)
|
||||
{
|
||||
immN = 0;
|
||||
immS = 0;
|
||||
@ -140,34 +81,11 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
return false;
|
||||
}
|
||||
|
||||
immR = rotation;
|
||||
|
||||
// immN indicates that there are no repetitions.
|
||||
// The MSB of immS indicates the amount of repetitions, and the LSB the number of bits set.
|
||||
if (repetitions == 1)
|
||||
{
|
||||
immN = 1;
|
||||
immS = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
immN = 0;
|
||||
immS = (0xf80 >> BitOperations.Log2((uint)repetitions)) & 0x3f;
|
||||
}
|
||||
|
||||
immS |= onesCount - 1;
|
||||
immN = (elementSize >> 6) & 1;
|
||||
immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f;
|
||||
immR = (elementSize - rotation) & (elementSize - 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int CountSequence(ulong value)
|
||||
{
|
||||
return BitOperations.TrailingZeroCount(value) + BitOperations.TrailingZeroCount(~value);
|
||||
}
|
||||
|
||||
private static ulong RotateRight(ulong bits, int shift, int size)
|
||||
{
|
||||
return (bits >> shift) | ((bits << (size - shift)) & (size == 64 ? ulong.MaxValue : (1UL << size) - 1));
|
||||
}
|
||||
}
|
||||
}
|
@ -1303,7 +1303,15 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value)
|
||||
{
|
||||
if (value != 0)
|
||||
if (value == 0)
|
||||
{
|
||||
context.Assembler.Mov(dest, Register(ZrRegister, dest.Type));
|
||||
}
|
||||
else if (CodeGenCommon.TryEncodeBitMask(dest.Type, value, out _, out _, out _))
|
||||
{
|
||||
context.Assembler.Orr(dest, Register(ZrRegister, dest.Type), Const(dest.Type, (long)value));
|
||||
}
|
||||
else
|
||||
{
|
||||
int hw = 0;
|
||||
bool first = true;
|
||||
@ -1328,10 +1336,6 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
value >>= 16;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Assembler.Mov(dest, Register(ZrRegister, dest.Type));
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateAtomicCas(
|
||||
|
@ -1339,7 +1339,7 @@ namespace ARMeilleure.Decoders
|
||||
|
||||
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp)
|
||||
{
|
||||
string reversedEncoding = encoding.Substring(16) + encoding.Substring(0, 16);
|
||||
string reversedEncoding = $"{encoding.AsSpan(16)}{encoding.AsSpan(0, 16)}";
|
||||
MakeOp reversedMakeOp =
|
||||
(inst, address, opCode)
|
||||
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
|
||||
@ -1353,7 +1353,7 @@ namespace ARMeilleure.Decoders
|
||||
string thumbEncoding = encoding;
|
||||
if (thumbEncoding.StartsWith("<<<<"))
|
||||
{
|
||||
thumbEncoding = "1110" + thumbEncoding.Substring(4);
|
||||
thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}";
|
||||
}
|
||||
SetT32(thumbEncoding, name, emitter, makeOpT32);
|
||||
}
|
||||
@ -1365,19 +1365,19 @@ namespace ARMeilleure.Decoders
|
||||
string thumbEncoding = encoding;
|
||||
if (thumbEncoding.StartsWith("11110100"))
|
||||
{
|
||||
thumbEncoding = "11111001" + encoding.Substring(8);
|
||||
thumbEncoding = $"11111001{encoding.AsSpan(8)}";
|
||||
}
|
||||
else if (thumbEncoding.StartsWith("1111001x"))
|
||||
{
|
||||
thumbEncoding = "111x1111" + encoding.Substring(8);
|
||||
thumbEncoding = $"111x1111{encoding.AsSpan(8)}";
|
||||
}
|
||||
else if (thumbEncoding.StartsWith("11110010"))
|
||||
{
|
||||
thumbEncoding = "11101111" + encoding.Substring(8);
|
||||
thumbEncoding = $"11101111{encoding.AsSpan(8)}";
|
||||
}
|
||||
else if (thumbEncoding.StartsWith("11110011"))
|
||||
{
|
||||
thumbEncoding = "11111111" + encoding.Substring(8);
|
||||
thumbEncoding = $"11111111{encoding.AsSpan(8)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -4,5 +4,7 @@
|
||||
{
|
||||
IJitMemoryBlock Allocate(ulong size);
|
||||
IJitMemoryBlock Reserve(ulong size);
|
||||
|
||||
ulong GetPageSize();
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +71,8 @@ namespace ARMeilleure.Signal
|
||||
|
||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||
|
||||
private static ulong _pageSize = GetPageSize();
|
||||
private static ulong _pageMask = _pageSize - 1;
|
||||
private static ulong _pageSize;
|
||||
private static ulong _pageMask;
|
||||
|
||||
private static IntPtr _handlerConfig;
|
||||
private static IntPtr _signalHandlerPtr;
|
||||
@ -81,19 +81,6 @@ namespace ARMeilleure.Signal
|
||||
private static readonly object _lock = new object();
|
||||
private static bool _initialized;
|
||||
|
||||
private static ulong GetPageSize()
|
||||
{
|
||||
// TODO: This needs to be based on the current memory manager configuration.
|
||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
return 1UL << 14;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1UL << 12;
|
||||
}
|
||||
}
|
||||
|
||||
static NativeSignalHandler()
|
||||
{
|
||||
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
||||
@ -102,12 +89,12 @@ namespace ARMeilleure.Signal
|
||||
config = new SignalHandlerConfig();
|
||||
}
|
||||
|
||||
public static void InitializeJitCache(IJitMemoryAllocator allocator)
|
||||
public static void Initialize(IJitMemoryAllocator allocator)
|
||||
{
|
||||
JitCache.Initialize(allocator);
|
||||
}
|
||||
|
||||
public static void InitializeSignalHandler(Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
@ -115,16 +102,13 @@ namespace ARMeilleure.Signal
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
_pageSize = pageSize;
|
||||
_pageMask = pageSize - 1;
|
||||
|
||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
// Unix siginfo struct locations.
|
||||
// NOTE: These are incredibly likely to be different between kernel version and architectures.
|
||||
|
||||
config.StructAddressOffset = OperatingSystem.IsMacOS() ? 24 : 16; // si_addr
|
||||
config.StructWriteOffset = 8; // si_code
|
||||
|
||||
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
|
||||
|
||||
if (customSignalHandlerFactory != null)
|
||||
@ -261,18 +245,88 @@ namespace ARMeilleure.Signal
|
||||
return context.Copy(inRegionLocal);
|
||||
}
|
||||
|
||||
private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr)
|
||||
{
|
||||
ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr
|
||||
return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset)));
|
||||
}
|
||||
|
||||
private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr)
|
||||
{
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
const ulong mcontextOffset = 48; // uc_mcontext
|
||||
Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(mcontextOffset)));
|
||||
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
const ulong esrOffset = 8; // __es.__esr
|
||||
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(esrOffset)));
|
||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||
}
|
||||
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
{
|
||||
const ulong errOffset = 4; // __es.__err
|
||||
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(errOffset)));
|
||||
return context.BitwiseAnd(err, Const(2ul));
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
Operand auxPtr = context.AllocateLocal(OperandType.I64);
|
||||
|
||||
Operand loopLabel = Label();
|
||||
Operand successLabel = Label();
|
||||
|
||||
const ulong auxOffset = 464; // uc_mcontext.__reserved
|
||||
const uint esrMagic = 0x45535201;
|
||||
|
||||
context.Copy(auxPtr, context.Add(ucontextPtr, Const(auxOffset)));
|
||||
|
||||
context.MarkLabel(loopLabel);
|
||||
|
||||
// _aarch64_ctx::magic
|
||||
Operand magic = context.Load(OperandType.I32, auxPtr);
|
||||
// _aarch64_ctx::size
|
||||
Operand size = context.Load(OperandType.I32, context.Add(auxPtr, Const(4ul)));
|
||||
|
||||
context.BranchIf(successLabel, magic, Const(esrMagic), Comparison.Equal);
|
||||
|
||||
context.Copy(auxPtr, context.Add(auxPtr, context.ZeroExtend32(OperandType.I64, size)));
|
||||
|
||||
context.Branch(loopLabel);
|
||||
|
||||
context.MarkLabel(successLabel);
|
||||
|
||||
// esr_context::esr
|
||||
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
|
||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||
}
|
||||
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||
{
|
||||
const int errOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
||||
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(errOffset)));
|
||||
return context.BitwiseAnd(err, Const(2ul));
|
||||
}
|
||||
}
|
||||
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
// (int sig, SigInfo* sigInfo, void* ucontext)
|
||||
Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1);
|
||||
Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2);
|
||||
|
||||
Operand structAddressOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructAddressOffset));
|
||||
Operand structWriteOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructWriteOffset));
|
||||
|
||||
Operand faultAddress = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset)));
|
||||
Operand writeFlag = context.Load(OperandType.I64, context.Add(sigInfoPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset)));
|
||||
Operand faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr);
|
||||
Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr);
|
||||
|
||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 4272; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4328; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
@ -183,8 +183,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private void PreLoad()
|
||||
{
|
||||
string fileNameActual = string.Concat(CachePathActual, ".cache");
|
||||
string fileNameBackup = string.Concat(CachePathBackup, ".cache");
|
||||
string fileNameActual = $"{CachePathActual}.cache";
|
||||
string fileNameBackup = $"{CachePathBackup}.cache";
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
||||
@ -400,8 +400,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
try
|
||||
{
|
||||
string fileNameActual = string.Concat(CachePathActual, ".cache");
|
||||
string fileNameBackup = string.Concat(CachePathBackup, ".cache");
|
||||
string fileNameActual = $"{CachePathActual}.cache";
|
||||
string fileNameBackup = $"{CachePathBackup}.cache";
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
|
||||
|
@ -125,8 +125,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
_lastHash = default;
|
||||
|
||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
||||
string fileNameActual = $"{_ptc.CachePathActual}.info";
|
||||
string fileNameBackup = $"{_ptc.CachePathBackup}.info";
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
|
||||
@ -246,8 +246,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
_waitEvent.Reset();
|
||||
|
||||
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
|
||||
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
|
||||
string fileNameActual = $"{_ptc.CachePathActual}.info";
|
||||
string fileNameBackup = $"{_ptc.CachePathBackup}.info";
|
||||
|
||||
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
if (memory.Type.IsHostMapped())
|
||||
{
|
||||
NativeSignalHandler.InitializeSignalHandler();
|
||||
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.17.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
@ -44,7 +44,7 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.26.0" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
|
@ -75,9 +75,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
return SampleFormat.PcmFloat;
|
||||
}
|
||||
|
||||
// TODO: Implement PCM24 conversion.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24))
|
||||
{
|
||||
return SampleFormat.PcmInt24;
|
||||
}
|
||||
|
||||
// If nothing is truly supported, attempt PCM8 at the cost of loosing quality.
|
||||
// If nothing is truly supported, attempt PCM8 at the cost of losing quality.
|
||||
if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
|
||||
{
|
||||
return SampleFormat.PcmInt8;
|
||||
|
@ -58,10 +58,13 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
switch (realSampleFormat)
|
||||
{
|
||||
case SampleFormat.PcmInt8:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
||||
PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast<byte, sbyte>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmInt24:
|
||||
PcmHelper.ConvertSampleToPcm24(convertedSamples, samples);
|
||||
break;
|
||||
case SampleFormat.PcmInt32:
|
||||
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
||||
PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
|
||||
break;
|
||||
case SampleFormat.PcmFloat:
|
||||
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);
|
||||
|
@ -37,19 +37,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
{
|
||||
TInput conversionRate = TInput.CreateSaturating(TOutput.MaxValue / TOutput.CreateSaturating(TInput.MaxValue));
|
||||
|
||||
return TOutput.CreateSaturating(value * conversionRate);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Convert<TInput, TOutput>(Span<TOutput> output, ReadOnlySpan<TInput> input) where TInput : INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput>
|
||||
public static void ConvertSampleToPcm8(Span<sbyte> output, ReadOnlySpan<short> input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ConvertSample<TInput, TOutput>(input[i]);
|
||||
// Output most significant byte
|
||||
output[i] = (sbyte)(input[i] >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ConvertSampleToPcm24(Span<byte> output, ReadOnlySpan<short> input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i * 3 + 2] = (byte)(input[i] >> 8);
|
||||
output[i * 3 + 1] = (byte)(input[i] & 0xff);
|
||||
output[i * 3 + 0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ConvertSampleToPcm32(Span<int> output, ReadOnlySpan<short> input)
|
||||
{
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
output[i] = ((int)input[i]) << 16;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@ using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Renderer;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
@ -44,7 +44,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
@ -66,8 +65,8 @@ namespace Ryujinx.Ava
|
||||
private const float VolumeDelta = 0.05f;
|
||||
|
||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||
private readonly IntPtr InvisibleCursorWin;
|
||||
private readonly IntPtr DefaultCursorWin;
|
||||
private readonly IntPtr InvisibleCursorWin;
|
||||
private readonly IntPtr DefaultCursorWin;
|
||||
|
||||
private readonly long _ticksPerFrame;
|
||||
private readonly Stopwatch _chrono;
|
||||
@ -80,6 +79,7 @@ namespace Ryujinx.Ava
|
||||
private readonly MainWindowViewModel _viewModel;
|
||||
private readonly IKeyboard _keyboardInterface;
|
||||
private readonly TopLevel _topLevel;
|
||||
public RendererHost _rendererHost;
|
||||
|
||||
private readonly GraphicsDebugLevel _glLogLevel;
|
||||
private float _newVolume;
|
||||
@ -105,7 +105,6 @@ namespace Ryujinx.Ava
|
||||
public event EventHandler AppExit;
|
||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||
|
||||
public RendererHost Renderer { get; }
|
||||
public VirtualFileSystem VirtualFileSystem { get; }
|
||||
public ContentManager ContentManager { get; }
|
||||
public NpadManager NpadManager { get; }
|
||||
@ -117,7 +116,6 @@ namespace Ryujinx.Ava
|
||||
public string ApplicationPath { get; private set; }
|
||||
public bool ScreenshotRequested { get; set; }
|
||||
|
||||
|
||||
public AppHost(
|
||||
RendererHost renderer,
|
||||
InputManager inputManager,
|
||||
@ -144,11 +142,12 @@ namespace Ryujinx.Ava
|
||||
|
||||
NpadManager = _inputManager.CreateNpadManager();
|
||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||
Renderer = renderer;
|
||||
ApplicationPath = applicationPath;
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
ContentManager = contentManager;
|
||||
|
||||
_rendererHost = renderer;
|
||||
|
||||
_chrono = new Stopwatch();
|
||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||
|
||||
@ -183,10 +182,10 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||
|
||||
if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null)
|
||||
if (_rendererHost.EmbeddedWindow.TransformedBounds != null)
|
||||
{
|
||||
var point = e.GetCurrentPoint(window).Position;
|
||||
var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip;
|
||||
var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
|
||||
|
||||
_isCursorInRenderer = point.X >= bounds.X &&
|
||||
point.X <= bounds.Width + bounds.X &&
|
||||
@ -232,7 +231,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e)
|
||||
private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e)
|
||||
{
|
||||
if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0)
|
||||
{
|
||||
@ -241,7 +240,7 @@ namespace Ryujinx.Ava
|
||||
lock (_lockObject)
|
||||
{
|
||||
DateTime currentTime = DateTime.Now;
|
||||
string filename = $"ryujinx_capture_{currentTime}-{currentTime:D2}-{currentTime:D2}_{currentTime:D2}-{currentTime:D2}-{currentTime:D2}.png";
|
||||
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||
|
||||
string directory = AppDataManager.Mode switch
|
||||
{
|
||||
@ -318,7 +317,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
_viewModel.SetUIProgressHandlers(Device);
|
||||
|
||||
Renderer.SizeChanged += Window_SizeChanged;
|
||||
_rendererHost.SizeChanged += Window_SizeChanged;
|
||||
|
||||
_isActive = true;
|
||||
|
||||
@ -430,11 +429,11 @@ namespace Ryujinx.Ava
|
||||
_windowsMultimediaTimerResolution = null;
|
||||
}
|
||||
|
||||
Renderer?.MakeCurrent();
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent();
|
||||
|
||||
Device.DisposeGpu();
|
||||
|
||||
Renderer?.MakeCurrent(null);
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||
}
|
||||
|
||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||
@ -463,8 +462,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage],
|
||||
firmwareVersion.VersionString),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString),
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
"");
|
||||
@ -494,10 +492,8 @@ namespace Ryujinx.Ava
|
||||
_viewModel.RefreshFirmwareStatus();
|
||||
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage],
|
||||
firmwareVersion.VersionString),
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage],
|
||||
firmwareVersion.VersionString),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString),
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
@ -635,11 +631,12 @@ namespace Ryujinx.Ava
|
||||
// Initialize Renderer.
|
||||
IRenderer renderer;
|
||||
|
||||
if (Renderer.IsVulkan)
|
||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||
{
|
||||
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
||||
|
||||
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
||||
renderer = new VulkanRenderer(
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
|
||||
VulkanHelper.GetRequiredInstanceExtensions,
|
||||
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -787,14 +784,12 @@ namespace Ryujinx.Ava
|
||||
|
||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||
|
||||
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext()));
|
||||
|
||||
Renderer.MakeCurrent();
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer);
|
||||
|
||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
Width = (int)Renderer.Bounds.Width;
|
||||
Height = (int)Renderer.Bounds.Height;
|
||||
Width = (int)_rendererHost.Bounds.Width;
|
||||
Height = (int)_rendererHost.Bounds.Height;
|
||||
|
||||
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
||||
|
||||
@ -827,7 +822,7 @@ namespace Ryujinx.Ava
|
||||
_viewModel.SwitchToRenderer(false);
|
||||
}
|
||||
|
||||
Device.PresentFrame(() => Renderer?.SwapBuffers());
|
||||
Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||
}
|
||||
|
||||
if (_ticks >= _ticksPerFrame)
|
||||
@ -837,7 +832,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
});
|
||||
|
||||
Renderer?.MakeCurrent(null);
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||
}
|
||||
|
||||
public void UpdateStatus()
|
||||
@ -853,7 +848,7 @@ namespace Ryujinx.Ava
|
||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||
Device.EnableDeviceVsync,
|
||||
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
|
||||
Renderer.IsVulkan ? "Vulkan" : "OpenGL",
|
||||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL",
|
||||
dockedMode,
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;",
|
||||
|
@ -26,6 +26,9 @@
|
||||
"MenuBarToolsInstallFirmware": "Install Firmware",
|
||||
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
|
||||
"MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory",
|
||||
"MenuBarToolsManageFileTypes": "Manage file types",
|
||||
"MenuBarToolsInstallFileTypes": "Install file types",
|
||||
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
|
||||
"MenuBarHelp": "Help",
|
||||
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
||||
"MenuBarHelpAbout": "About",
|
||||
@ -339,6 +342,10 @@
|
||||
"DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.",
|
||||
"DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed",
|
||||
"DialogFirmwareInstalledMessage": "Firmware {0} was installed",
|
||||
"DialogInstallFileTypesSuccessMessage": "Successfully installed file types!",
|
||||
"DialogInstallFileTypesErrorMessage": "Failed to install file types.",
|
||||
"DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!",
|
||||
"DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.",
|
||||
"DialogOpenSettingsWindowLabel": "Open Settings Window",
|
||||
"DialogControllerAppletTitle": "Controller Applet",
|
||||
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
|
||||
@ -375,7 +382,7 @@
|
||||
"DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Errored File: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Errored File: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?",
|
||||
@ -524,7 +531,7 @@
|
||||
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
|
||||
"OpenSetupGuideMessage": "Open the Setup Guide",
|
||||
"NoUpdate": "No Update",
|
||||
"TitleUpdateVersionLabel": "Version {0} - {1}",
|
||||
"TitleUpdateVersionLabel": "Version {0}",
|
||||
"RyujinxInfo": "Ryujinx - Info",
|
||||
"RyujinxConfirm": "Ryujinx - Confirmation",
|
||||
"FileDialogAllTypes": "All types",
|
||||
@ -585,7 +592,7 @@
|
||||
"UserProfilesSetProfileImage": "Set Profile Image",
|
||||
"UserProfileEmptyNameError": "Name is required",
|
||||
"UserProfileNoImageError": "Profile image must be set",
|
||||
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
|
||||
"GameUpdateWindowHeading": "Manage Updates for {0} ({1})",
|
||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||
"UserProfilesName": "Name:",
|
||||
@ -619,4 +626,4 @@
|
||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||
"UserEditorTitle" : "Edit User",
|
||||
"UserEditorTitleCreate" : "Create User"
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Archivo con error: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Fichier erroné : {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. File errato: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. File errato: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}입니다. 오류 발생 파일: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}입니다. 오류 발생 파일: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로깅이 활성화되어 있습니다.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로깅을 비활성화하는 것이 좋습니다. 지금 추적 로깅을 비활성화하겠습니까?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Błędny Plik: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. 错误的文件: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. 错误的文件: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?",
|
||||
|
@ -370,7 +370,7 @@
|
||||
"DialogUserProfileDeletionConfirmMessage": "是否刪除選擇的帳號",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "目前的輸入預設已更新",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "要儲存嗎?",
|
||||
"DialogDlcLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}",
|
||||
"DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}",
|
||||
"DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "您啟用了跟蹤日誌,僅供開發人員使用。",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用跟蹤日誌記錄。您是否要立即停用?",
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Threading;
|
||||
using LibHac;
|
||||
using LibHac.Account;
|
||||
@ -12,7 +13,6 @@ using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Logging;
|
||||
@ -44,14 +44,11 @@ namespace Ryujinx.Ava.Common
|
||||
_accountManager = accountManager;
|
||||
}
|
||||
|
||||
private static bool TryFindSaveData(string titleName, ulong titleId,
|
||||
BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
|
||||
private static bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
|
||||
{
|
||||
saveDataId = default;
|
||||
|
||||
Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo,
|
||||
SaveDataSpaceId.User, in filter);
|
||||
|
||||
Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter);
|
||||
if (ResultFs.TargetNotFound.Includes(result))
|
||||
{
|
||||
ref ApplicationControlProperty control = ref controlHolder.Value;
|
||||
@ -68,20 +65,17 @@ namespace Ryujinx.Ava.Common
|
||||
control.UserAccountSaveDataSize = 0x4000;
|
||||
control.UserAccountSaveDataJournalSize = 0x4000;
|
||||
|
||||
Logger.Warning?.Print(LogClass.Application,
|
||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||
}
|
||||
|
||||
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||
Uid user = new((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||
|
||||
result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
|
||||
|
||||
if (result.IsFailure())
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageCreateSaveErrorMessage], result.ToStringWithName()));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName()));
|
||||
});
|
||||
|
||||
return false;
|
||||
@ -98,16 +92,15 @@ namespace Ryujinx.Ava.Common
|
||||
return true;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageFindSaveErrorMessage], result.ToStringWithName()));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageFindSaveErrorMessage, result.ToStringWithName()));
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId,
|
||||
BlitStruct<ApplicationControlProperty> controlData, string titleName)
|
||||
public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, BlitStruct<ApplicationControlProperty> controlData, string titleName)
|
||||
{
|
||||
if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId))
|
||||
{
|
||||
@ -148,14 +141,15 @@ namespace Ryujinx.Ava.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath,
|
||||
int programIndex = 0)
|
||||
public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
|
||||
{
|
||||
OpenFolderDialog folderDialog = new() { Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle] };
|
||||
OpenFolderDialog folderDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
|
||||
};
|
||||
|
||||
string destination = await folderDialog.ShowAsync(_owner);
|
||||
|
||||
var cancellationToken = new CancellationTokenSource();
|
||||
string destination = await folderDialog.ShowAsync(_owner);
|
||||
var cancellationToken = new CancellationTokenSource();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(destination))
|
||||
{
|
||||
@ -164,7 +158,7 @@ namespace Ryujinx.Ava.Common
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMessage], ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogCancel],
|
||||
@ -175,133 +169,122 @@ namespace Ryujinx.Ava.Common
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
});
|
||||
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Thread.Sleep(1000);
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
||||
using (FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read))
|
||||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||
{
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||
|
||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||
if (extension == ".xci")
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
}
|
||||
|
||||
if (extension == ".xci")
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
{
|
||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
int dataIndex =
|
||||
Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (extension == ".nca")
|
||||
{
|
||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
}
|
||||
}
|
||||
else if (extension == ".nca")
|
||||
{
|
||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
"Extraction failure. The main NCA was not present in the selected file");
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
|
||||
|
||||
try
|
||||
{
|
||||
IFileSystem ncaFileSystem = patchNca != null
|
||||
? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
|
||||
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
|
||||
FileSystemClient fsClient = _horizonClient.Fs;
|
||||
|
||||
string source = DateTime.Now.ToFileTime().ToString()[10..];
|
||||
string output = DateTime.Now.ToFileTime().ToString()[10..];
|
||||
|
||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
||||
|
||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||
|
||||
if (!canceled)
|
||||
{
|
||||
if (resultCode.Value.IsFailure())
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}");
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem,
|
||||
mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
|
||||
|
||||
try
|
||||
{
|
||||
IFileSystem ncaFileSystem = patchNca != null
|
||||
? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
|
||||
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
|
||||
FileSystemClient fsClient = _horizonClient.Fs;
|
||||
|
||||
string source = DateTime.Now.ToFileTime().ToString()[10..];
|
||||
string output = DateTime.Now.ToFileTime().ToString()[10..];
|
||||
|
||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
||||
|
||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||
|
||||
if (!canceled)
|
||||
{
|
||||
if (resultCode.Value.IsFailure())
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"LibHac returned error code: {resultCode.Value.ErrorCode}");
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||
});
|
||||
}
|
||||
else if (resultCode.Value.IsSuccess())
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage],
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle]);
|
||||
});
|
||||
}
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
|
||||
});
|
||||
}
|
||||
|
||||
fsClient.Unmount(source.ToU8Span());
|
||||
fsClient.Unmount(output.ToU8Span());
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
else if (resultCode.Value.IsSuccess())
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||
});
|
||||
NotificationHelper.Show(
|
||||
LocaleManager.Instance[LocaleKeys.DialogNcaExtractionTitle],
|
||||
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
||||
NotificationType.Information);
|
||||
}
|
||||
}
|
||||
|
||||
fsClient.Unmount(source.ToU8Span());
|
||||
fsClient.Unmount(output.ToU8Span());
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"{ex.Message}");
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(ex.Message);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
|
||||
ReflectionBindingExtension binding = new($"[{keyToUse}]")
|
||||
{
|
||||
Mode = BindingMode.OneWay,
|
||||
Mode = BindingMode.OneWay,
|
||||
Source = LocaleManager.Instance
|
||||
};
|
||||
|
||||
|
@ -13,56 +13,87 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
{
|
||||
private const string DefaultLanguageCode = "en_US";
|
||||
|
||||
private Dictionary<LocaleKeys, string> _localeStrings;
|
||||
private ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||
private Dictionary<LocaleKeys, string> _localeStrings;
|
||||
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||
|
||||
public static LocaleManager Instance { get; } = new LocaleManager();
|
||||
public Dictionary<LocaleKeys, string> LocaleStrings { get => _localeStrings; set => _localeStrings = value; }
|
||||
|
||||
|
||||
public LocaleManager()
|
||||
{
|
||||
_localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
||||
_localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
|
||||
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
||||
|
||||
Load();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
// Load the system Language Code.
|
||||
string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
||||
|
||||
// If the view is loaded with the UI Previewer detached, then override it with the saved one or default.
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value))
|
||||
{
|
||||
localeLanguageCode = ConfigurationState.Instance.Ui.LanguageCode.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
localeLanguageCode = DefaultLanguageCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Load english first, if the target language translation is incomplete, we default to english.
|
||||
// Load en_US as default, if the target language translation is incomplete.
|
||||
LoadDefaultLanguage();
|
||||
|
||||
if (localeLanguageCode != DefaultLanguageCode)
|
||||
{
|
||||
LoadLanguage(localeLanguageCode);
|
||||
}
|
||||
LoadLanguage(localeLanguageCode);
|
||||
}
|
||||
|
||||
public string this[LocaleKeys key]
|
||||
{
|
||||
get
|
||||
{
|
||||
// Check if the locale contains the key.
|
||||
if (_localeStrings.TryGetValue(key, out string value))
|
||||
{
|
||||
// Check if the localized string needs to be formatted.
|
||||
if (_dynamicValues.TryGetValue(key, out var dynamicValue))
|
||||
{
|
||||
return string.Format(value, dynamicValue);
|
||||
try
|
||||
{
|
||||
return string.Format(value, dynamicValue);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If formatting failed use the default text instead.
|
||||
if (_localeDefaultStrings.TryGetValue(key, out value))
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.Format(value, dynamicValue);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If formatting the default text failed return the key.
|
||||
return key.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// If the locale doesn't contain the key return the default one.
|
||||
if (_localeDefaultStrings.TryGetValue(key, out string defaultValue))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// If the locale text doesn't exist return the key.
|
||||
return key.ToString();
|
||||
}
|
||||
set
|
||||
@ -73,42 +104,43 @@ namespace Ryujinx.Ava.Common.Locale
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDynamicValue(LocaleKeys key, params object[] values)
|
||||
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
|
||||
{
|
||||
_dynamicValues[key] = values;
|
||||
|
||||
OnPropertyChanged("Item");
|
||||
|
||||
return this[key];
|
||||
}
|
||||
|
||||
public void LoadDefaultLanguage()
|
||||
private void LoadDefaultLanguage()
|
||||
{
|
||||
LoadLanguage(DefaultLanguageCode);
|
||||
_localeDefaultStrings = LoadJsonLanguage();
|
||||
}
|
||||
|
||||
public void LoadLanguage(string languageCode)
|
||||
{
|
||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||
|
||||
if (languageJson == null)
|
||||
foreach (var item in LoadJsonLanguage(languageCode))
|
||||
{
|
||||
return;
|
||||
this[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
private Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode = DefaultLanguageCode)
|
||||
{
|
||||
var localeStrings = new Dictionary<LocaleKeys, string>();
|
||||
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json");
|
||||
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
|
||||
|
||||
foreach (var item in strings)
|
||||
{
|
||||
if (Enum.TryParse<LocaleKeys>(item.Key, out var key))
|
||||
{
|
||||
this[key] = item.Value;
|
||||
localeStrings[key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ConfigurationState.Instance.Ui.LanguageCode.Value = languageCode;
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
return localeStrings;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
@ -30,6 +31,7 @@ namespace Ryujinx.Ava.Input
|
||||
_control.KeyDown += OnKeyPress;
|
||||
_control.KeyUp += OnKeyRelease;
|
||||
_control.TextInput += Control_TextInput;
|
||||
_control.AddHandler(InputElement.TextInputEvent, Control_LastChanceTextInput, RoutingStrategies.Bubble);
|
||||
}
|
||||
|
||||
private void Control_TextInput(object sender, TextInputEventArgs e)
|
||||
@ -37,6 +39,12 @@ namespace Ryujinx.Ava.Input
|
||||
TextInput?.Invoke(this, e.Text);
|
||||
}
|
||||
|
||||
private void Control_LastChanceTextInput(object sender, TextInputEventArgs e)
|
||||
{
|
||||
// Swallow event
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
public event Action<string> OnGamepadConnected
|
||||
{
|
||||
add { }
|
||||
|
@ -7,9 +7,7 @@ using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
@ -32,31 +30,29 @@ namespace Ryujinx.Modules
|
||||
internal static class Updater
|
||||
{
|
||||
private const string GitHubApiURL = "https://api.github.com";
|
||||
internal static bool Running;
|
||||
|
||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
|
||||
private static readonly int ConnectionCount = 4;
|
||||
private static readonly int ConnectionCount = 4;
|
||||
|
||||
private static string _buildVer;
|
||||
private static string _platformExt;
|
||||
private static string _buildUrl;
|
||||
private static long _buildSize;
|
||||
private static long _buildSize;
|
||||
private static bool _updateSuccessful;
|
||||
private static bool _running;
|
||||
|
||||
private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
|
||||
|
||||
public static bool UpdateSuccessful { get; private set; }
|
||||
|
||||
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
|
||||
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
|
||||
{
|
||||
if (Running)
|
||||
if (_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Running = true;
|
||||
mainWindow.ViewModel.CanUpdate = false;
|
||||
_running = true;
|
||||
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
@ -82,77 +78,87 @@ namespace Ryujinx.Modules
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
});
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get latest version number from GitHub API
|
||||
try
|
||||
{
|
||||
using (HttpClient jsonClient = ConstructHttpClient())
|
||||
using HttpClient jsonClient = ConstructHttpClient();
|
||||
|
||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||
JToken assets = jsonRoot["assets"];
|
||||
|
||||
_buildVer = (string)jsonRoot["name"];
|
||||
|
||||
foreach (JToken asset in assets)
|
||||
{
|
||||
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
string assetName = (string)asset["name"];
|
||||
string assetState = (string)asset["state"];
|
||||
string downloadURL = (string)asset["browser_download_url"];
|
||||
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
|
||||
JObject jsonRoot = JObject.Parse(fetchedJson);
|
||||
JToken assets = jsonRoot["assets"];
|
||||
|
||||
_buildVer = (string)jsonRoot["name"];
|
||||
|
||||
foreach (JToken asset in assets)
|
||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||
{
|
||||
string assetName = (string)asset["name"];
|
||||
string assetState = (string)asset["state"];
|
||||
string downloadURL = (string)asset["browser_download_url"];
|
||||
_buildUrl = downloadURL;
|
||||
|
||||
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
|
||||
if (assetState != "uploaded")
|
||||
{
|
||||
_buildUrl = downloadURL;
|
||||
|
||||
if (assetState != "uploaded")
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
_running = false;
|
||||
|
||||
// If build not done, assume no new update are availaible.
|
||||
if (_buildUrl == null)
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are availaible.
|
||||
if (_buildUrl == null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
|
||||
});
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
||||
});
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -163,11 +169,16 @@ namespace Ryujinx.Modules
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
});
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -181,8 +192,7 @@ namespace Ryujinx.Modules
|
||||
});
|
||||
}
|
||||
|
||||
Running = false;
|
||||
mainWindow.ViewModel.CanUpdate = true;
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
@ -210,7 +220,8 @@ namespace Ryujinx.Modules
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
|
||||
$"{Program.Version} -> {newVersion}");
|
||||
|
||||
@ -218,12 +229,16 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
UpdateRyujinx(mainWindow, _buildUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpClient ConstructHttpClient()
|
||||
{
|
||||
HttpClient result = new HttpClient();
|
||||
HttpClient result = new();
|
||||
|
||||
// Required by GitHub to interract with APIs.
|
||||
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
|
||||
@ -233,7 +248,7 @@ namespace Ryujinx.Modules
|
||||
|
||||
public static async void UpdateRyujinx(Window parent, string downloadUrl)
|
||||
{
|
||||
UpdateSuccessful = false;
|
||||
_updateSuccessful = false;
|
||||
|
||||
// Empty update dir, although it shouldn't ever have anything inside it
|
||||
if (Directory.Exists(UpdateDir))
|
||||
@ -245,17 +260,16 @@ namespace Ryujinx.Modules
|
||||
|
||||
string updateFile = Path.Combine(UpdateDir, "update.bin");
|
||||
|
||||
var taskDialog = new TaskDialog()
|
||||
TaskDialog taskDialog = new()
|
||||
{
|
||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
Buttons = { },
|
||||
ShowProgressBar = true
|
||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
Buttons = { },
|
||||
ShowProgressBar = true,
|
||||
XamlRoot = parent
|
||||
};
|
||||
|
||||
taskDialog.XamlRoot = parent;
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
{
|
||||
if (_buildSize >= 0)
|
||||
@ -270,7 +284,7 @@ namespace Ryujinx.Modules
|
||||
|
||||
await taskDialog.ShowAsync(true);
|
||||
|
||||
if (UpdateSuccessful)
|
||||
if (_updateSuccessful)
|
||||
{
|
||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
@ -279,7 +293,7 @@ namespace Ryujinx.Modules
|
||||
if (shouldRestart)
|
||||
{
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
|
||||
if (!Path.Exists(ryuExe))
|
||||
{
|
||||
@ -298,15 +312,15 @@ namespace Ryujinx.Modules
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long chunkSize = _buildSize / ConnectionCount;
|
||||
long remainderChunk = _buildSize % ConnectionCount;
|
||||
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
int completedRequests = 0;
|
||||
int totalProgressPercentage = 0;
|
||||
int[] progressPercentage = new int[ConnectionCount];
|
||||
|
||||
List<byte[]> list = new List<byte[]>(ConnectionCount);
|
||||
List<WebClient> webClients = new List<WebClient>(ConnectionCount);
|
||||
List<byte[]> list = new(ConnectionCount);
|
||||
List<WebClient> webClients = new(ConnectionCount);
|
||||
|
||||
for (int i = 0; i < ConnectionCount; i++)
|
||||
{
|
||||
@ -317,133 +331,129 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
#pragma warning disable SYSLIB0014
|
||||
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
|
||||
using (WebClient client = new WebClient())
|
||||
using WebClient client = new();
|
||||
#pragma warning restore SYSLIB0014
|
||||
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
{
|
||||
webClients.Add(client);
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
||||
}
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
client.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
if (args.Cancelled)
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
|
||||
}
|
||||
webClients[index].Dispose();
|
||||
|
||||
client.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
{
|
||||
int index = (int)args.UserState;
|
||||
|
||||
if (args.Cancelled)
|
||||
{
|
||||
webClients[index].Dispose();
|
||||
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
list[index] = args.Result;
|
||||
Interlocked.Increment(ref completedRequests);
|
||||
|
||||
if (Equals(completedRequests, ConnectionCount))
|
||||
{
|
||||
byte[] mergedFileBytes = new byte[_buildSize];
|
||||
for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
|
||||
{
|
||||
Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
|
||||
destinationOffset += list[connectionIndex].Length;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
client.DownloadDataAsync(new Uri(downloadUrl), i);
|
||||
}
|
||||
catch (WebException ex)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, ex.Message);
|
||||
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
|
||||
|
||||
for (int j = 0; j < webClients.Count; j++)
|
||||
{
|
||||
webClients[j].CancelAsync();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
using (HttpClient client = new HttpClient())
|
||||
using HttpClient client = new();
|
||||
// We do not want to timeout while downloading
|
||||
client.Timeout = TimeSpan.FromDays(1);
|
||||
|
||||
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
||||
{
|
||||
// We do not want to timeout while downloading
|
||||
client.Timeout = TimeSpan.FromDays(1);
|
||||
using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
|
||||
|
||||
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
|
||||
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
|
||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||
long byteWritten = 0;
|
||||
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
|
||||
while (true)
|
||||
{
|
||||
using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
|
||||
int readSize = remoteFileStream.Read(buffer);
|
||||
|
||||
if (readSize == 0)
|
||||
{
|
||||
long totalBytes = response.Content.Headers.ContentLength.Value;
|
||||
long byteWritten = 0;
|
||||
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
|
||||
while (true)
|
||||
{
|
||||
int readSize = remoteFileStream.Read(buffer);
|
||||
|
||||
if (readSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
byteWritten += readSize;
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
byteWritten += readSize;
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
}
|
||||
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@ -454,8 +464,11 @@ namespace Ryujinx.Modules
|
||||
|
||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile));
|
||||
worker.Name = "Updater.SingleThreadWorker";
|
||||
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
||||
{
|
||||
Name = "Updater.SingleThreadWorker"
|
||||
};
|
||||
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
@ -483,72 +496,70 @@ namespace Ryujinx.Modules
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
using (Stream inStream = File.OpenRead(updateFile))
|
||||
using (Stream gzipStream = new GZipInputStream(inStream))
|
||||
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
await Task.Run(() =>
|
||||
TarEntry tarEntry;
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
TarEntry entry = tarEntry;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
});
|
||||
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
TarEntry entry = tarEntry;
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream inStream = File.OpenRead(updateFile))
|
||||
using (ZipFile zipFile = new ZipFile(inStream))
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
await Task.Run(() =>
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Delete downloaded zip
|
||||
@ -577,7 +588,7 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, string.Format(LocaleManager.Instance[LocaleKeys.UpdaterRenameFailed], file));
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,21 +605,24 @@ namespace Ryujinx.Modules
|
||||
|
||||
SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
|
||||
|
||||
UpdateSuccessful = true;
|
||||
_updateSuccessful = true;
|
||||
|
||||
taskDialog.Hide();
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
|
||||
public static bool CanUpdate(bool showWarnings)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -618,8 +632,12 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -629,8 +647,12 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -642,18 +664,27 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (ReleaseInformation.IsFlatHubBuild())
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.s
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
@ -677,7 +708,7 @@ namespace Ryujinx.Modules
|
||||
|
||||
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
|
||||
{
|
||||
var total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
|
||||
foreach (string directory in Directory.GetDirectories(root))
|
||||
{
|
||||
string dirName = Path.GetFileName(directory);
|
||||
@ -694,6 +725,7 @@ namespace Ryujinx.Modules
|
||||
foreach (string file in Directory.GetFiles(root))
|
||||
{
|
||||
count++;
|
||||
|
||||
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
|
@ -14,10 +14,8 @@ using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
@ -35,48 +33,6 @@ namespace Ryujinx.Ava
|
||||
|
||||
private const uint MB_ICONWARNING = 0x30;
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
static void RegisterMimeTypes()
|
||||
{
|
||||
if (ReleaseInformation.IsFlatHubBuild())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
|
||||
|
||||
if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
|
||||
{
|
||||
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
|
||||
using Process mimeProcess = new();
|
||||
|
||||
mimeProcess.StartInfo.FileName = "xdg-mime";
|
||||
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";
|
||||
|
||||
mimeProcess.Start();
|
||||
mimeProcess.WaitForExit();
|
||||
|
||||
if (mimeProcess.ExitCode != 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
|
||||
return;
|
||||
}
|
||||
|
||||
using Process updateMimeProcess = new();
|
||||
|
||||
updateMimeProcess.StartInfo.FileName = "update-mime-database";
|
||||
updateMimeProcess.StartInfo.Arguments = mimeDbPath;
|
||||
|
||||
updateMimeProcess.Start();
|
||||
updateMimeProcess.WaitForExit();
|
||||
|
||||
if (updateMimeProcess.ExitCode != 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Version = ReleaseInformation.GetVersion();
|
||||
@ -139,12 +95,6 @@ namespace Ryujinx.Ava
|
||||
// Initialize the logger system.
|
||||
LoggerModule.Initialize();
|
||||
|
||||
// Register mime types on linux.
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
RegisterMimeTypes();
|
||||
}
|
||||
|
||||
// Initialize Discord integration.
|
||||
DiscordIntegrationModule.Initialize();
|
||||
|
||||
|
@ -29,17 +29,12 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
|
||||
{
|
||||
string playerCount = args.PlayerCountMin == args.PlayerCountMax
|
||||
? args.PlayerCountMin.ToString()
|
||||
: $"{args.PlayerCountMin}-{args.PlayerCountMax}";
|
||||
|
||||
LocaleKeys key = args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange;
|
||||
|
||||
string message = string.Format(LocaleManager.Instance[key],
|
||||
playerCount,
|
||||
args.SupportedStyles,
|
||||
string.Join(", ", args.SupportedPlayers),
|
||||
args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
|
||||
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||
args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange,
|
||||
args.PlayerCountMin == args.PlayerCountMax ? args.PlayerCountMin.ToString() : $"{args.PlayerCountMin}-{args.PlayerCountMax}",
|
||||
args.SupportedStyles,
|
||||
string.Join(", ", args.SupportedPlayers),
|
||||
args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
|
||||
|
||||
return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle], message);
|
||||
}
|
||||
@ -92,7 +87,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageDialogErrorExceptionMessage], ex));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
|
||||
|
||||
dialogCloseEvent.Set();
|
||||
}
|
||||
@ -126,7 +121,8 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = true;
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage], ex));
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -181,7 +177,8 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
catch (Exception ex)
|
||||
{
|
||||
dialogCloseEvent.Set();
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogErrorAppletErrorExceptionMessage], ex));
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_hiddenTextBox.Clear();
|
||||
_parent.ViewModel.RendererControl.Focus();
|
||||
_parent.ViewModel.RendererHostControl.Focus();
|
||||
|
||||
_parent = null;
|
||||
});
|
||||
|
@ -139,14 +139,16 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
else if (_inputMin > 0 && _inputMax == int.MaxValue)
|
||||
{
|
||||
Error.IsVisible = true;
|
||||
Error.Text = string.Format(LocaleManager.Instance[LocaleKeys.SwkbdMinCharacters], _inputMin);
|
||||
|
||||
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
|
||||
|
||||
_checkLength = length => _inputMin <= length;
|
||||
}
|
||||
else
|
||||
{
|
||||
Error.IsVisible = true;
|
||||
Error.Text = string.Format(LocaleManager.Instance[LocaleKeys.SwkbdMinRangeCharacters], _inputMin, _inputMax);
|
||||
|
||||
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
|
||||
|
||||
_checkLength = length => _inputMin <= length && length <= _inputMax;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
Focusable="True">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuFlyout x:Key="GameContextMenu">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
@ -22,14 +22,17 @@
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
||||
<Separator />
|
||||
|
@ -1,9 +1,7 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibHac.Common;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class GameGridView : UserControl
|
||||
{
|
||||
private ApplicationData _selectedApplication;
|
||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||
{
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||
}
|
||||
|
||||
public GameGridView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
_selectedApplication = listBox.SelectedItem as ApplicationData;
|
||||
|
||||
(DataContext as MainWindowViewModel).GridSelectedApplication = _selectedApplication;
|
||||
(DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationData SelectedApplication => _selectedApplication;
|
||||
|
||||
public GameGridView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||
}
|
||||
|
||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
if (sender is ContextMenu menu)
|
||||
{
|
||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,17 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
Focusable="True"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuFlyout x:Key="GameContextMenu">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
@ -21,14 +21,17 @@
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
IsEnabled="{Binding EnabledBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
|
||||
<Separator />
|
||||
@ -130,7 +133,8 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="150" />
|
||||
<ColumnDefinition Width="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
@ -141,30 +145,54 @@
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Margin="0,0,5,0"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
FontWeight="Bold"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Developer}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5" >
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
Text="{Binding TitleId}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Developer}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Version}"
|
||||
Text="{Binding FileExtension}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
|
@ -1,9 +1,7 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibHac.Common;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class GameListView : UserControl
|
||||
{
|
||||
private ApplicationData _selectedApplication;
|
||||
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
|
||||
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
|
||||
|
||||
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
|
||||
{
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
add { AddHandler(ApplicationOpenedEvent, value); }
|
||||
remove { RemoveHandler(ApplicationOpenedEvent, value); }
|
||||
}
|
||||
|
||||
public GameListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
if (sender is ListBox listBox)
|
||||
{
|
||||
_selectedApplication = listBox.SelectedItem as ApplicationData;
|
||||
|
||||
(DataContext as MainWindowViewModel).ListSelectedApplication = _selectedApplication;
|
||||
(DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData;
|
||||
}
|
||||
}
|
||||
|
||||
public ApplicationData SelectedApplication => _selectedApplication;
|
||||
|
||||
public GameListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text;
|
||||
}
|
||||
|
||||
private void MenuBase_OnMenuOpened(object sender, EventArgs e)
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
{
|
||||
if (sender is ContextMenu menu)
|
||||
{
|
||||
bool canHaveUserSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
bool canHaveDeviceSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
bool canHaveBcatSave = !Utilities.IsZeros(selection.ControlHolder.ByteSpan) && selection.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
((menu.Items as AvaloniaList<object>)[2] as MenuItem).IsEnabled = canHaveUserSave;
|
||||
((menu.Items as AvaloniaList<object>)[3] as MenuItem).IsEnabled = canHaveDeviceSave;
|
||||
((menu.Items as AvaloniaList<object>)[4] as MenuItem).IsEnabled = canHaveBcatSave;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,127 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
{
|
||||
public partial class RendererHost : UserControl, IDisposable
|
||||
{
|
||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
||||
private EmbeddedWindow _currentWindow;
|
||||
|
||||
public bool IsVulkan { get; private set; }
|
||||
|
||||
public RendererHost(GraphicsDebugLevel graphicsDebugLevel)
|
||||
{
|
||||
_graphicsDebugLevel = graphicsDebugLevel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public RendererHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void CreateOpenGL()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
_currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel);
|
||||
Initialize();
|
||||
|
||||
IsVulkan = false;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||
Content = _currentWindow;
|
||||
}
|
||||
|
||||
public void CreateVulkan()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
_currentWindow = new VulkanEmbeddedWindow();
|
||||
Initialize();
|
||||
|
||||
IsVulkan = true;
|
||||
}
|
||||
|
||||
public OpenGLContextBase GetContext()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
return openGlEmbeddedWindow.Context;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||
{
|
||||
SizeChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||
{
|
||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void MakeCurrent()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.MakeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeCurrent(SwappableNativeWindowBase window)
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.MakeCurrent(window);
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapBuffers()
|
||||
{
|
||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||
{
|
||||
openGlEmbeddedWindow.SwapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler<EventArgs> RendererInitialized;
|
||||
public event Action<object, Size> SizeChanged;
|
||||
public void Dispose()
|
||||
{
|
||||
if (_currentWindow != null)
|
||||
{
|
||||
_currentWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||
_currentWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api)
|
||||
{
|
||||
return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow)
|
||||
? vulkanEmbeddedWindow.CreateSurface(instance)
|
||||
: default;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using SPB.Graphics;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
|
||||
{
|
||||
public AvaloniaGlxContext(IntPtr handle)
|
||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
||||
{
|
||||
ContextHandle = handle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using SPB.Graphics;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
|
||||
{
|
||||
public AvaloniaWglContext(IntPtr handle)
|
||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
||||
{
|
||||
ContextHandle = handle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public class EmbeddedWindow : NativeControlHost
|
||||
{
|
||||
private WindowProc _wndProcDelegate;
|
||||
private string _className;
|
||||
|
||||
protected GLXWindow X11Window { get; set; }
|
||||
protected IntPtr WindowHandle { get; set; }
|
||||
protected IntPtr X11Display { get; set; }
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
public event EventHandler<Size> SizeChanged;
|
||||
|
||||
protected virtual void OnWindowDestroyed() { }
|
||||
protected virtual void OnWindowDestroying()
|
||||
{
|
||||
WindowHandle = IntPtr.Zero;
|
||||
X11Display = IntPtr.Zero;
|
||||
NsView = IntPtr.Zero;
|
||||
MetalLayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public EmbeddedWindow()
|
||||
{
|
||||
var stateObserverable = this.GetObservable(BoundsProperty);
|
||||
|
||||
stateObserverable.Subscribe(StateChanged);
|
||||
|
||||
Initialized += NativeEmbeddedWindow_Initialized;
|
||||
}
|
||||
|
||||
public virtual void OnWindowCreated() { }
|
||||
|
||||
private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
OnWindowCreated();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
WindowCreated?.Invoke(this, WindowHandle);
|
||||
});
|
||||
}
|
||||
|
||||
private void StateChanged(Rect rect)
|
||||
{
|
||||
SizeChanged?.Invoke(this, rect.Size);
|
||||
_updateBoundsCallback?.Invoke(rect);
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return CreateLinux(parent);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return CreateWin32(parent);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return CreateMacOs(parent);
|
||||
}
|
||||
|
||||
return base.CreateNativeControlCore(parent);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
OnWindowDestroying();
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
DestroyLinux();
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
DestroyWin32(control);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
DestroyMacOS();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
}
|
||||
|
||||
OnWindowDestroyed();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||
{
|
||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||
|
||||
return new PlatformHandle(WindowHandle, "X11");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
IPlatformHandle CreateWin32(IPlatformHandle parent)
|
||||
{
|
||||
_className = "NativeWindow-" + Guid.NewGuid();
|
||||
_wndProcDelegate = WndProc;
|
||||
var wndClassEx = new WNDCLASSEX
|
||||
{
|
||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||
hInstance = GetModuleHandle(null),
|
||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CS_OWNDC,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = CreateArrowCursor()
|
||||
};
|
||||
|
||||
var atom = RegisterClassEx(ref wndClassEx);
|
||||
|
||||
var handle = CreateWindowEx(
|
||||
0,
|
||||
_className,
|
||||
"NativeWindow",
|
||||
WindowStyles.WS_CHILD,
|
||||
0,
|
||||
0,
|
||||
640,
|
||||
480,
|
||||
parent.Handle,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero);
|
||||
|
||||
WindowHandle = handle;
|
||||
|
||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||
|
||||
return new PlatformHandle(WindowHandle, "HWND");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
||||
var root = VisualRoot as Window;
|
||||
bool isLeft = false;
|
||||
switch (msg)
|
||||
{
|
||||
case WindowsMessages.LBUTTONDOWN:
|
||||
case WindowsMessages.RBUTTONDOWN:
|
||||
isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||
this.RaiseEvent(new PointerPressedEventArgs(
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed),
|
||||
KeyModifiers.None));
|
||||
break;
|
||||
case WindowsMessages.LBUTTONUP:
|
||||
case WindowsMessages.RBUTTONUP:
|
||||
isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||
this.RaiseEvent(new PointerReleasedEventArgs(
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased),
|
||||
KeyModifiers.None,
|
||||
isLeft ? MouseButton.Left : MouseButton.Right));
|
||||
break;
|
||||
case WindowsMessages.MOUSEMOVE:
|
||||
this.RaiseEvent(new PointerEventArgs(
|
||||
PointerMovedEvent,
|
||||
this,
|
||||
new Pointer(0, PointerType.Mouse, true),
|
||||
root,
|
||||
this.TranslatePoint(point, root).Value,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||
KeyModifiers.None));
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOs(IPlatformHandle parent)
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
}
|
||||
|
||||
void DestroyLinux()
|
||||
{
|
||||
X11Window?.Dispose();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
void DestroyWin32(IPlatformHandle handle)
|
||||
{
|
||||
DestroyWindow(handle.Handle);
|
||||
UnregisterClass(_className, GetModuleHandle(null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
using Avalonia.OpenGL;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal static class IGlContextExtension
|
||||
{
|
||||
public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context)
|
||||
{
|
||||
var handle = (IntPtr)context.GetType().GetProperty("Handle").GetValue(context);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return new AvaloniaWglContext(handle);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return new AvaloniaGlxContext(handle);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
65
Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
Normal file
65
Ryujinx.Ava/UI/Helpers/NotificationHelper.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public static class NotificationHelper
|
||||
{
|
||||
private const int MaxNotifications = 4;
|
||||
private const int NotificationDelayInMs = 5000;
|
||||
|
||||
private static WindowNotificationManager _notificationManager;
|
||||
|
||||
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
|
||||
private static readonly BlockingCollection<Notification> _notifications = new();
|
||||
|
||||
public static void SetNotificationManager(Window host)
|
||||
{
|
||||
_notificationManager = new WindowNotificationManager(host)
|
||||
{
|
||||
Position = NotificationPosition.BottomRight,
|
||||
MaxItems = MaxNotifications,
|
||||
Margin = new Thickness(0, 0, 15, 40)
|
||||
};
|
||||
|
||||
_notificationManager.TemplateApplied += (sender, args) =>
|
||||
{
|
||||
_templateAppliedEvent.Set();
|
||||
};
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_templateAppliedEvent.WaitOne();
|
||||
|
||||
foreach (var notification in _notifications.GetConsumingEnumerable())
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_notificationManager.Show(notification);
|
||||
});
|
||||
|
||||
await Task.Delay(NotificationDelayInMs / MaxNotifications);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
|
||||
{
|
||||
var delay = waitingExit ? TimeSpan.FromMilliseconds(0) : TimeSpan.FromMilliseconds(NotificationDelayInMs);
|
||||
|
||||
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
|
||||
}
|
||||
|
||||
public static void ShowError(string message)
|
||||
{
|
||||
Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -76,11 +76,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : "";
|
||||
|
||||
var result = await ContentDialogHelper.CreateInfoDialog(
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogMessage], errorCode, GetErrorTitle(error)),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)),
|
||||
GetErrorDescription(error) + (isInSetupGuide
|
||||
? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage]
|
||||
: ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogTitle], errorCode));
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode));
|
||||
|
||||
if (result == UserResult.Ok)
|
||||
{
|
||||
|
@ -1,52 +0,0 @@
|
||||
using Avalonia.Platform;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.Metal;
|
||||
using SPB.Platform.Win32;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public class VulkanEmbeddedWindow : EmbeddedWindow
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_window = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window));
|
||||
}
|
||||
}
|
||||
}
|
@ -3,23 +3,17 @@ using Ryujinx.Ava.Common.Locale;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
internal class TitleUpdateModel
|
||||
public class TitleUpdateModel
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool IsNoUpdate { get; }
|
||||
public ApplicationControlProperty Control { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public string Label => IsNoUpdate
|
||||
? LocaleManager.Instance[LocaleKeys.NoUpdate]
|
||||
: string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString(),
|
||||
Path);
|
||||
public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
|
||||
|
||||
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false)
|
||||
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||
{
|
||||
Control = control;
|
||||
Path = path;
|
||||
IsNoUpdate = isNoUpdate;
|
||||
}
|
||||
}
|
||||
}
|
266
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
266
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
@ -0,0 +1,266 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using SPB.Graphics;
|
||||
using SPB.Platform;
|
||||
using SPB.Platform.GLX;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
public class EmbeddedWindow : NativeControlHost
|
||||
{
|
||||
private WindowProc _wndProcDelegate;
|
||||
private string _className;
|
||||
|
||||
protected GLXWindow X11Window { get; set; }
|
||||
|
||||
protected IntPtr WindowHandle { get; set; }
|
||||
protected IntPtr X11Display { get; set; }
|
||||
protected IntPtr NsView { get; set; }
|
||||
protected IntPtr MetalLayer { get; set; }
|
||||
|
||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||
|
||||
public event EventHandler<IntPtr> WindowCreated;
|
||||
public event EventHandler<Size> SizeChanged;
|
||||
|
||||
public EmbeddedWindow()
|
||||
{
|
||||
this.GetObservable(BoundsProperty).Subscribe(StateChanged);
|
||||
|
||||
Initialized += OnNativeEmbeddedWindowCreated;
|
||||
}
|
||||
|
||||
public virtual void OnWindowCreated() { }
|
||||
|
||||
protected virtual void OnWindowDestroyed() { }
|
||||
|
||||
protected virtual void OnWindowDestroying()
|
||||
{
|
||||
WindowHandle = IntPtr.Zero;
|
||||
X11Display = IntPtr.Zero;
|
||||
NsView = IntPtr.Zero;
|
||||
MetalLayer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e)
|
||||
{
|
||||
OnWindowCreated();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
WindowCreated?.Invoke(this, WindowHandle);
|
||||
});
|
||||
}
|
||||
|
||||
private void StateChanged(Rect rect)
|
||||
{
|
||||
SizeChanged?.Invoke(this, rect.Size);
|
||||
_updateBoundsCallback?.Invoke(rect);
|
||||
}
|
||||
|
||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return CreateLinux(control);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return CreateWin32(control);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return CreateMacOS();
|
||||
}
|
||||
|
||||
return base.CreateNativeControlCore(control);
|
||||
}
|
||||
|
||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||
{
|
||||
OnWindowDestroying();
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
DestroyLinux();
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
DestroyWin32(control);
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
DestroyMacOS();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.DestroyNativeControlCore(control);
|
||||
}
|
||||
|
||||
OnWindowDestroyed();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
private IPlatformHandle CreateLinux(IPlatformHandle control)
|
||||
{
|
||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||
{
|
||||
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle));
|
||||
X11Window.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||
}
|
||||
|
||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||
|
||||
return new PlatformHandle(WindowHandle, "X11");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
IPlatformHandle CreateWin32(IPlatformHandle control)
|
||||
{
|
||||
_className = "NativeWindow-" + Guid.NewGuid();
|
||||
|
||||
_wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (VisualRoot != null)
|
||||
{
|
||||
if (msg == WindowsMessages.LBUTTONDOWN ||
|
||||
msg == WindowsMessages.RBUTTONDOWN ||
|
||||
msg == WindowsMessages.LBUTTONUP ||
|
||||
msg == WindowsMessages.RBUTTONUP ||
|
||||
msg == WindowsMessages.MOUSEMOVE)
|
||||
{
|
||||
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
||||
Pointer pointer = new(0, PointerType.Mouse, true);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case WindowsMessages.LBUTTONDOWN:
|
||||
case WindowsMessages.RBUTTONDOWN:
|
||||
{
|
||||
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
||||
|
||||
var evnt = new PointerPressedEventArgs(
|
||||
this,
|
||||
pointer,
|
||||
VisualRoot,
|
||||
rootVisualPosition,
|
||||
(ulong)Environment.TickCount64,
|
||||
properties,
|
||||
KeyModifiers.None);
|
||||
|
||||
RaiseEvent(evnt);
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowsMessages.LBUTTONUP:
|
||||
case WindowsMessages.RBUTTONUP:
|
||||
{
|
||||
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
||||
|
||||
var evnt = new PointerReleasedEventArgs(
|
||||
this,
|
||||
pointer,
|
||||
VisualRoot,
|
||||
rootVisualPosition,
|
||||
(ulong)Environment.TickCount64,
|
||||
properties,
|
||||
KeyModifiers.None,
|
||||
isLeft ? MouseButton.Left : MouseButton.Right);
|
||||
|
||||
RaiseEvent(evnt);
|
||||
|
||||
break;
|
||||
}
|
||||
case WindowsMessages.MOUSEMOVE:
|
||||
{
|
||||
var evnt = new PointerEventArgs(
|
||||
PointerMovedEvent,
|
||||
this,
|
||||
pointer,
|
||||
VisualRoot,
|
||||
rootVisualPosition,
|
||||
(ulong)Environment.TickCount64,
|
||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||
KeyModifiers.None);
|
||||
|
||||
RaiseEvent(evnt);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||
};
|
||||
|
||||
WNDCLASSEX wndClassEx = new()
|
||||
{
|
||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||
hInstance = GetModuleHandle(null),
|
||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||
style = ClassStyles.CS_OWNDC,
|
||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||
hCursor = CreateArrowCursor()
|
||||
};
|
||||
|
||||
RegisterClassEx(ref wndClassEx);
|
||||
|
||||
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WS_CHILD, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||
|
||||
return new PlatformHandle(WindowHandle, "HWND");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
IPlatformHandle CreateMacOS()
|
||||
{
|
||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||
|
||||
NsView = nsView;
|
||||
|
||||
return new PlatformHandle(nsView, "NSView");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Linux")]
|
||||
void DestroyLinux()
|
||||
{
|
||||
X11Window?.Dispose();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
void DestroyWin32(IPlatformHandle handle)
|
||||
{
|
||||
DestroyWindow(handle.Handle);
|
||||
UnregisterClass(_className, GetModuleHandle(null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
void DestroyMacOS()
|
||||
{
|
||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using SPB.Graphics;
|
||||
using SPB.Graphics.OpenGL;
|
||||
using SPB.Platform;
|
||||
@ -7,26 +10,20 @@ using SPB.Platform.WGL;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
public class OpenGLEmbeddedWindow : EmbeddedWindow
|
||||
public class EmbeddedWindowOpenGL : EmbeddedWindow
|
||||
{
|
||||
private readonly int _major;
|
||||
private readonly int _minor;
|
||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
||||
private SwappableNativeWindowBase _window;
|
||||
|
||||
public OpenGLContextBase Context { get; set; }
|
||||
|
||||
public OpenGLEmbeddedWindow(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
||||
{
|
||||
_major = major;
|
||||
_minor = minor;
|
||||
_graphicsDebugLevel = graphicsDebugLevel;
|
||||
}
|
||||
public EmbeddedWindowOpenGL() { }
|
||||
|
||||
protected override void OnWindowDestroying()
|
||||
{
|
||||
Context.Dispose();
|
||||
|
||||
base.OnWindowDestroying();
|
||||
}
|
||||
|
||||
@ -48,19 +45,20 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
}
|
||||
|
||||
var flags = OpenGLContextFlags.Compat;
|
||||
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
|
||||
if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None)
|
||||
{
|
||||
flags |= OpenGLContextFlags.Debug;
|
||||
}
|
||||
|
||||
Context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, _major, _minor, flags);
|
||||
var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
|
||||
|
||||
Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags);
|
||||
|
||||
Context.Initialize(_window);
|
||||
Context.MakeCurrent(_window);
|
||||
|
||||
var bindingsContext = new OpenToolkitBindingsContext(Context.GetProcAddress);
|
||||
GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress));
|
||||
|
||||
GL.LoadBindings(bindingsContext);
|
||||
Context.MakeCurrent(null);
|
||||
}
|
||||
|
||||
@ -76,7 +74,14 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
public void SwapBuffers()
|
||||
{
|
||||
_window.SwapBuffers();
|
||||
_window?.SwapBuffers();
|
||||
}
|
||||
|
||||
public void InitializeBackgroundContext(IRenderer renderer)
|
||||
{
|
||||
(renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context));
|
||||
|
||||
MakeCurrent();
|
||||
}
|
||||
}
|
||||
}
|
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using SPB.Platform.Metal;
|
||||
using SPB.Platform.Win32;
|
||||
using SPB.Platform.X11;
|
||||
using SPB.Windowing;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
public class EmbeddedWindowVulkan : EmbeddedWindow
|
||||
{
|
||||
public SurfaceKHR CreateSurface(Instance instance)
|
||||
{
|
||||
NativeWindowBase nativeWindowBase;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
|
||||
}
|
||||
|
||||
public SurfaceKHR CreateSurface(Instance instance, Vk api)
|
||||
{
|
||||
return CreateSurface(instance);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
using OpenTK;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
internal class OpenToolkitBindingsContext : IBindingsContext
|
||||
internal class OpenTKBindingsContext : IBindingsContext
|
||||
{
|
||||
private readonly Func<string, IntPtr> _getProcAddress;
|
||||
|
||||
public OpenToolkitBindingsContext(Func<string, IntPtr> getProcAddress)
|
||||
public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress)
|
||||
{
|
||||
_getProcAddress = getProcAddress;
|
||||
}
|
@ -6,6 +6,6 @@
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.UI.Controls.RendererHost"
|
||||
x:Class="Ryujinx.Ava.UI.Renderer.RendererHost"
|
||||
Focusable="True">
|
||||
</UserControl>
|
||||
</UserControl>
|
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
@ -0,0 +1,68 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
public partial class RendererHost : UserControl, IDisposable
|
||||
{
|
||||
public readonly EmbeddedWindow EmbeddedWindow;
|
||||
|
||||
public event EventHandler<EventArgs> WindowCreated;
|
||||
public event Action<object, Size> SizeChanged;
|
||||
|
||||
public RendererHost()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||
{
|
||||
EmbeddedWindow = new EmbeddedWindowOpenGL();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmbeddedWindow = new EmbeddedWindowVulkan();
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||
EmbeddedWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||
|
||||
Content = EmbeddedWindow;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (EmbeddedWindow != null)
|
||||
{
|
||||
EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||
EmbeddedWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||
{
|
||||
SizeChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||
{
|
||||
WindowCreated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,17 +5,17 @@ using SPB.Graphics.OpenGL;
|
||||
using SPB.Platform;
|
||||
using SPB.Windowing;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
namespace Ryujinx.Ava.UI.Renderer
|
||||
{
|
||||
class SPBOpenGLContext : IOpenGLContext
|
||||
{
|
||||
private OpenGLContextBase _context;
|
||||
private NativeWindowBase _window;
|
||||
private readonly OpenGLContextBase _context;
|
||||
private readonly NativeWindowBase _window;
|
||||
|
||||
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
||||
{
|
||||
_context = context;
|
||||
_window = window;
|
||||
_window = window;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -32,12 +32,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||
{
|
||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||
|
||||
context.Initialize(window);
|
||||
context.MakeCurrent(window);
|
||||
|
||||
GL.LoadBindings(new OpenToolkitBindingsContext(context.GetProcAddress));
|
||||
GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress));
|
||||
|
||||
context.MakeCurrent(null);
|
||||
|
@ -81,10 +81,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public string Developers
|
||||
{
|
||||
get => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
|
||||
}
|
||||
public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
|
||||
|
||||
public AboutWindowViewModel()
|
||||
{
|
||||
|
@ -3,6 +3,8 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Bcat;
|
||||
using LibHac.Tools.Fs;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
@ -435,7 +437,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (str.Length > MaxSize)
|
||||
{
|
||||
return str.Substring(0, MaxSize - Ellipsis.Length) + Ellipsis;
|
||||
return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}";
|
||||
}
|
||||
|
||||
return str;
|
||||
@ -717,7 +719,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileErrorMessage], ProfileName));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -5,14 +5,17 @@ using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Renderer;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@ -87,7 +90,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private float _volume;
|
||||
private string _backendText;
|
||||
|
||||
private bool _canUpdate;
|
||||
private bool _canUpdate = true;
|
||||
private Cursor _cursor;
|
||||
private string _title;
|
||||
private string _currentEmulatedGamePath;
|
||||
@ -176,11 +179,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool CanUpdate
|
||||
{
|
||||
get => _canUpdate;
|
||||
get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false);
|
||||
set
|
||||
{
|
||||
_canUpdate = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
@ -342,6 +344,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnabledUserSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
|
||||
public bool EnabledDeviceSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
|
||||
public bool EnabledBcatSaveDirectory => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
public string LoadHeading
|
||||
{
|
||||
get => _loadHeading;
|
||||
@ -675,6 +683,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
get => ConsoleHelper.SetConsoleWindowStateSupported;
|
||||
}
|
||||
|
||||
public bool ManageFileTypesVisible
|
||||
{
|
||||
get => FileAssociationHelper.IsTypeAssociationSupported;
|
||||
}
|
||||
|
||||
public ObservableCollection<ApplicationData> Applications
|
||||
{
|
||||
get => _applications;
|
||||
@ -733,19 +746,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (ConfigurationState.Instance.Ui.GridSize)
|
||||
return ConfigurationState.Instance.Ui.GridSize.Value switch
|
||||
{
|
||||
case 1:
|
||||
return 78;
|
||||
case 2:
|
||||
return 100;
|
||||
case 3:
|
||||
return 120;
|
||||
case 4:
|
||||
return 140;
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
1 => 78,
|
||||
2 => 100,
|
||||
3 => 120,
|
||||
4 => 140,
|
||||
_ => 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -753,19 +761,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (ConfigurationState.Instance.Ui.GridSize)
|
||||
return ConfigurationState.Instance.Ui.GridSize.Value switch
|
||||
{
|
||||
case 1:
|
||||
return 120;
|
||||
case 2:
|
||||
return ShowNames ? 210 : 150;
|
||||
case 3:
|
||||
return ShowNames ? 240 : 180;
|
||||
case 4:
|
||||
return ShowNames ? 280 : 220;
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
1 => 120,
|
||||
2 => ShowNames ? 210 : 150,
|
||||
3 => ShowNames ? 240 : 180,
|
||||
4 => ShowNames ? 280 : 220,
|
||||
_ => 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -870,7 +873,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public Action<bool> SwitchToGameControl { get; private set; }
|
||||
public Action<Control> SetMainContent { get; private set; }
|
||||
public TopLevel TopLevel { get; private set; }
|
||||
public RendererHost RendererControl { get; private set; }
|
||||
public RendererHost RendererHostControl { get; private set; }
|
||||
public bool IsClosing { get; set; }
|
||||
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
|
||||
public IHostUiHandler UiHandler { get; internal set; }
|
||||
@ -947,20 +950,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (firmwareVersion == null)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage], filename));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string dialogTitle = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle], firmwareVersion.VersionString);
|
||||
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString);
|
||||
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString);
|
||||
|
||||
SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion();
|
||||
|
||||
string dialogMessage = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage], firmwareVersion.VersionString);
|
||||
|
||||
if (currentVersion != null)
|
||||
{
|
||||
dialogMessage += string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage], currentVersion.VersionString);
|
||||
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString);
|
||||
}
|
||||
|
||||
dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage];
|
||||
@ -993,7 +994,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
waitingDialog.Close();
|
||||
|
||||
string message = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage], firmwareVersion.VersionString);
|
||||
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString);
|
||||
|
||||
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
|
||||
@ -1063,7 +1064,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case LoadState.Loaded:
|
||||
LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName);
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
||||
IsLoadingIndeterminate = true;
|
||||
CacheLoadStatus = "";
|
||||
break;
|
||||
@ -1079,7 +1080,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case ShaderCacheLoadingState.Loaded:
|
||||
LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName);
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
||||
IsLoadingIndeterminate = true;
|
||||
CacheLoadStatus = "";
|
||||
break;
|
||||
@ -1091,35 +1092,27 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}));
|
||||
}
|
||||
|
||||
private void OpenSaveDirectory(in SaveDataFilter filter, ApplicationData data, ulong titleId)
|
||||
{
|
||||
ApplicationHelper.OpenSaveDir(in filter, titleId, data.ControlHolder, data.TitleName);
|
||||
}
|
||||
|
||||
private async void ExtractLogo()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ExtractRomFs()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Data, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ExtractExeFs()
|
||||
{
|
||||
var selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path);
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Code, SelectedApplication.Path, SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1144,7 +1137,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private void InitializeGame()
|
||||
{
|
||||
RendererControl.RendererInitialized += GlRenderer_Created;
|
||||
RendererHostControl.WindowCreated += RendererHost_Created;
|
||||
|
||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||
AppHost.AppExit += AppHost_AppExit;
|
||||
@ -1203,7 +1196,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private void GlRenderer_Created(object sender, EventArgs e)
|
||||
private void RendererHost_Created(object sender, EventArgs e)
|
||||
{
|
||||
ShowLoading(false);
|
||||
|
||||
@ -1333,10 +1326,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeLanguage(object obj)
|
||||
public void ChangeLanguage(object languageCode)
|
||||
{
|
||||
LocaleManager.Instance.LoadDefaultLanguage();
|
||||
LocaleManager.Instance.LoadLanguage((string)obj);
|
||||
LocaleManager.Instance.LoadLanguage((string)languageCode);
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode;
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
}
|
||||
|
||||
public async void ManageProfiles()
|
||||
@ -1374,7 +1372,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionMessage], selection.TitleName),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, selection.TitleName),
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
@ -1401,7 +1399,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionErrorMessage], file.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1438,7 +1436,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
|
||||
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.DialogShaderDeletionMessage], selection.TitleName),
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, selection.TitleName),
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
@ -1463,7 +1461,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionErrorMessage], directory.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, directory.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1476,62 +1474,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.ShaderCachePurgeError], file.Name, e));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.ShaderCachePurgeError, file.Name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenDeviceSaveDirectory()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||
{
|
||||
async void Action()
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(Action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenBcatSaveDirectory()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||
{
|
||||
async void Action()
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(Action);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleFavorite()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
@ -1550,37 +1498,45 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void OpenUserSaveDirectory()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
OpenSaveDirectory(SaveDataType.Account, userId: new UserId((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low));
|
||||
}
|
||||
|
||||
public void OpenDeviceSaveDirectory()
|
||||
{
|
||||
OpenSaveDirectory(SaveDataType.Device, userId: default);
|
||||
}
|
||||
|
||||
public void OpenBcatSaveDirectory()
|
||||
{
|
||||
OpenSaveDirectory(SaveDataType.Bcat, userId: default);
|
||||
}
|
||||
|
||||
private void OpenSaveDirectory(SaveDataType saveDataType, UserId userId)
|
||||
{
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
Task.Run(() =>
|
||||
if (!ulong.TryParse(SelectedApplication.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||
{
|
||||
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
async void Action()
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
||||
}
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogRyujinxErrorMessage], LocaleManager.Instance[LocaleKeys.DialogInvalidTitleIdErrorMessage]);
|
||||
});
|
||||
|
||||
Dispatcher.UIThread.Post(Action);
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveDataType, userId, saveDataId: default, index: default);
|
||||
|
||||
UserId userId = new((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low);
|
||||
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
|
||||
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
|
||||
});
|
||||
ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, SelectedApplication.ControlHolder, SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenModsDirectory()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
|
||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
|
||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -1588,12 +1544,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void OpenSdModsDirectory()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
|
||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
|
||||
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -1601,37 +1555,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public async void OpenTitleUpdateManager()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
await new TitleUpdateWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
|
||||
}
|
||||
await TitleUpdateWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenDownloadableContentManager()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
|
||||
}
|
||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OpenCheatManager()
|
||||
{
|
||||
ApplicationData selection = SelectedApplication;
|
||||
if (selection != null)
|
||||
if (SelectedApplication != null)
|
||||
{
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
await new CheatWindow(VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(desktop.MainWindow);
|
||||
}
|
||||
await new CheatWindow(VirtualFileSystem, SelectedApplication.TitleId, SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1645,7 +1587,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
StatusBarProgressMaximum = 0;
|
||||
StatusBarProgressValue = 0;
|
||||
|
||||
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
|
||||
});
|
||||
|
||||
ReloadGameList?.Invoke();
|
||||
@ -1735,18 +1677,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
PrepareLoadScreen();
|
||||
|
||||
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||
{
|
||||
RendererControl.CreateOpenGL();
|
||||
}
|
||||
else
|
||||
{
|
||||
RendererControl.CreateVulkan();
|
||||
}
|
||||
RendererHostControl = new RendererHost();
|
||||
|
||||
AppHost = new AppHost(
|
||||
RendererControl,
|
||||
RendererHostControl,
|
||||
InputManager,
|
||||
path,
|
||||
VirtualFileSystem,
|
||||
@ -1767,8 +1701,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
|
||||
CanUpdate = false;
|
||||
LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], AppHost.Device.Application.TitleName) : titleName;
|
||||
TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
|
||||
|
||||
LoadHeading = TitleName = titleName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Application.TitleName);
|
||||
TitleName = AppHost.Device.Application.TitleName;
|
||||
}
|
||||
|
||||
SwitchToRenderer(startFullscreen);
|
||||
|
||||
@ -1787,9 +1727,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
SwitchToGameControl(startFullscreen);
|
||||
|
||||
SetMainContent(RendererControl);
|
||||
SetMainContent(RendererHostControl);
|
||||
|
||||
RendererControl.Focus();
|
||||
RendererHostControl.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1819,14 +1759,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion,
|
||||
version.VersionString);
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString);
|
||||
|
||||
hasApplet = version.Major > 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
|
||||
}
|
||||
|
||||
IsAppletMenuActive = hasApplet;
|
||||
@ -1857,8 +1796,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
HandleRelaunch();
|
||||
});
|
||||
|
||||
RendererControl.RendererInitialized -= GlRenderer_Created;
|
||||
RendererControl = null;
|
||||
RendererHostControl.WindowCreated -= RendererHost_Created;
|
||||
RendererHostControl = null;
|
||||
|
||||
SelectedIcon = null;
|
||||
|
||||
|
250
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
250
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
@ -0,0 +1,250 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels;
|
||||
|
||||
public class TitleUpdateViewModel : BaseModel
|
||||
{
|
||||
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||
public readonly string _titleUpdateJsonPath;
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||
private AvaloniaList<object> _views = new();
|
||||
private object _selectedUpdate;
|
||||
|
||||
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||
{
|
||||
get => _titleUpdates;
|
||||
set
|
||||
{
|
||||
_titleUpdates = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<object> Views
|
||||
{
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public object SelectedUpdate
|
||||
{
|
||||
get => _selectedUpdate;
|
||||
set
|
||||
{
|
||||
_selectedUpdate = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
LoadUpdates();
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
// NOTE: Save the list again to remove leftovers.
|
||||
Save();
|
||||
|
||||
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||
|
||||
SelectedUpdate = selected;
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void SortUpdates()
|
||||
{
|
||||
var list = TitleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
Views.Clear();
|
||||
Views.Add(new BaseModel());
|
||||
Views.AddRange(list);
|
||||
|
||||
if (SelectedUpdate == null)
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||
{
|
||||
if (Views.Count > 1)
|
||||
{
|
||||
SelectedUpdate = Views[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedUpdate = Views[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveUpdate(TitleUpdateModel update)
|
||||
{
|
||||
TitleUpdates.Remove(update);
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in TitleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
if (update == SelectedUpdate)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
}
|
||||
}
|
@ -17,8 +17,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private ObservableCollection<SaveModel> _views = new();
|
||||
private AccountManager _accountManager;
|
||||
|
||||
public string SaveManagerHeading =>
|
||||
string.Format(LocaleManager.Instance[LocaleKeys.SaveManagerHeading], _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
||||
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
|
||||
|
||||
public int SortIndex
|
||||
{
|
||||
|
@ -77,8 +77,7 @@
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}">
|
||||
</MenuItem>
|
||||
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Click="OpenSettings"
|
||||
@ -141,6 +140,10 @@
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem
|
||||
@ -157,4 +160,4 @@
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -3,6 +3,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
@ -10,6 +11,7 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -163,9 +165,35 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
}
|
||||
}
|
||||
|
||||
private async void InstallFileTypes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (FileAssociationHelper.Install())
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage],
|
||||
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (FileAssociationHelper.Uninstall())
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage],
|
||||
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
public async void CheckForUpdates(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (Updater.CanUpdate(true, Window))
|
||||
if (Updater.CanUpdate(true))
|
||||
{
|
||||
await Updater.BeginParse(Window, true);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@
|
||||
VerticalAlignment="Stretch">
|
||||
<ListBox
|
||||
Name="SaveList"
|
||||
VirtualizationMode="None"
|
||||
Items="{Binding Views}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
@ -116,6 +117,9 @@
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SaveModel">
|
||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
LoadedCheats = new AvaloniaList<CheatsList>();
|
||||
|
||||
Heading = string.Format(LocaleManager.Instance[LocaleKeys.CheatWindowHeading], titleName, titleId.ToUpper());
|
||||
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
private void PrintHeading()
|
||||
{
|
||||
Heading.Text = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
|
||||
Heading.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DlcWindowHeading, _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
|
||||
}
|
||||
|
||||
private void LoadDownloadableContents()
|
||||
@ -133,7 +133,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, containerPath));
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, containerPath));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@
|
||||
<StackPanel Grid.Row="0" IsVisible="False">
|
||||
<helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<helpers:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<helpers:HotKeyControl Name="FullscreenHotKeyMacOS" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<helpers:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
|
||||
<helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
|
||||
</StackPanel>
|
||||
|
@ -104,6 +104,8 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
|
||||
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
|
||||
ViewModel.ReloadGameList += ReloadGameList;
|
||||
|
||||
NotificationHelper.SetNotificationManager(this);
|
||||
}
|
||||
|
||||
private void IsActiveChanged(bool obj)
|
||||
@ -146,7 +148,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
|
||||
{
|
||||
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
@ -271,7 +273,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
ViewModel.LoadApplication(_launchPath, _startFullscreen);
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||
{
|
||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
{
|
||||
@ -327,10 +329,11 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
public void LoadHotKeys()
|
||||
{
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
||||
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
||||
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
|
||||
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
|
||||
HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
|
||||
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
|
||||
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
|
||||
}
|
||||
|
||||
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
|
||||
@ -415,7 +418,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
ViewModel.StatusBarProgressMaximum = 0;
|
||||
ViewModel.StatusBarProgressValue = 0;
|
||||
|
||||
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
|
||||
});
|
||||
|
||||
ReloadGameList();
|
||||
|
@ -1,115 +1,135 @@
|
||||
<window:StyleableWindow
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
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"
|
||||
Width="600"
|
||||
Height="400"
|
||||
MinWidth="600"
|
||||
MinHeight="400"
|
||||
MaxWidth="600"
|
||||
MaxHeight="400"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
Width="500"
|
||||
Height="300"
|
||||
mc:Ignorable="d"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:TitleUpdateViewModel"
|
||||
Focusable="True">
|
||||
<Grid Margin="15">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Name="Heading"
|
||||
Grid.Row="1"
|
||||
MaxWidth="500"
|
||||
Margin="20,15,20,20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="18"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="5"
|
||||
Grid.Row="0"
|
||||
Margin="0 0 0 24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding _titleUpdates}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<RadioButton
|
||||
Padding="8,0"
|
||||
VerticalContentAlignment="Center"
|
||||
GroupName="Update"
|
||||
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
||||
<Label
|
||||
Margin="0"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5"
|
||||
Padding="2.5">
|
||||
<ListBox
|
||||
VirtualizationMode="None"
|
||||
Background="Transparent"
|
||||
SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}"
|
||||
Items="{Binding Views}">
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate
|
||||
DataType="models:TitleUpdateModel">
|
||||
<Panel Margin="10">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Label}" />
|
||||
<StackPanel
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding Label}"
|
||||
FontSize="12" />
|
||||
</RadioButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Click="OpenLocation">
|
||||
<ui:SymbolIcon
|
||||
Symbol="OpenFolder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Click="RemoveUpdate">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Cancel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
<DataTemplate
|
||||
DataType="viewModels:BaseModel">
|
||||
<Panel
|
||||
Height="33"
|
||||
Margin="10">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{locale:Locale NoUpdate}" />
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Margin="0"
|
||||
<Panel
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
Command="{ReflectionBinding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveSelected}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding RemoveAll}">
|
||||
Click="RemoveAll">
|
||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Save}">
|
||||
Click="Save">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Close}">
|
||||
Click="Close">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</window:StyleableWindow>
|
||||
</UserControl>
|
@ -1,271 +1,99 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Path = System.IO.Path;
|
||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||
using System.Threading.Tasks;
|
||||
using Button = Avalonia.Controls.Button;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class TitleUpdateWindow : StyleableWindow
|
||||
public partial class TitleUpdateWindow : UserControl
|
||||
{
|
||||
private readonly string _titleUpdateJsonPath;
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
|
||||
private VirtualFileSystem _virtualFileSystem { get; }
|
||||
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
|
||||
|
||||
private ulong _titleId { get; }
|
||||
private string _titleName { get; }
|
||||
public TitleUpdateViewModel ViewModel;
|
||||
|
||||
public TitleUpdateWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
||||
}
|
||||
|
||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
|
||||
|
||||
_titleId = titleId;
|
||||
_titleName = titleName;
|
||||
|
||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||
|
||||
try
|
||||
{
|
||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||
{
|
||||
Selected = "",
|
||||
Paths = new List<string>()
|
||||
};
|
||||
}
|
||||
|
||||
DataContext = this;
|
||||
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId, titleName);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
||||
|
||||
LoadUpdates();
|
||||
PrintHeading();
|
||||
}
|
||||
|
||||
private void PrintHeading()
|
||||
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||
{
|
||||
Heading.Text = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], _titleUpdates.Count - 1, _titleName, _titleId.ToString("X16"));
|
||||
}
|
||||
|
||||
private void LoadUpdates()
|
||||
{
|
||||
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
||||
|
||||
foreach (string path in _titleUpdateWindowData.Paths)
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
AddUpdate(path);
|
||||
}
|
||||
|
||||
if (_titleUpdateWindowData.Selected == "")
|
||||
{
|
||||
_titleUpdates[0].IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
||||
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
|
||||
|
||||
foreach (TitleUpdateModel update in enabled)
|
||||
{
|
||||
update.IsEnabled = false;
|
||||
}
|
||||
|
||||
if (selected != null)
|
||||
{
|
||||
selected.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
}
|
||||
|
||||
private void AddUpdate(string path)
|
||||
{
|
||||
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
|
||||
{
|
||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
ApplicationControlProperty controlData = new();
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
|
||||
foreach (var update in _titleUpdates)
|
||||
{
|
||||
update.IsEnabled = false;
|
||||
}
|
||||
|
||||
_titleUpdates.Last().IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
||||
{
|
||||
if (removeSelectedOnly)
|
||||
{
|
||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
||||
}
|
||||
|
||||
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
||||
|
||||
SortUpdates();
|
||||
PrintHeading();
|
||||
}
|
||||
|
||||
public void RemoveSelected()
|
||||
{
|
||||
RemoveUpdates(true);
|
||||
}
|
||||
|
||||
public void RemoveAll()
|
||||
{
|
||||
RemoveUpdates();
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
OpenFileDialog dialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||
AllowMultiple = true
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = "",
|
||||
Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
|
||||
Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16"))
|
||||
};
|
||||
|
||||
dialog.Filters.Add(new FileDialogFilter
|
||||
{
|
||||
Name = "NSP",
|
||||
Extensions = { "nsp" }
|
||||
});
|
||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||
|
||||
string[] files = await dialog.ShowAsync(this);
|
||||
contentDialog.Styles.Add(bottomBorder);
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
foreach (string file in files)
|
||||
{
|
||||
AddUpdate(file);
|
||||
}
|
||||
}
|
||||
|
||||
SortUpdates();
|
||||
PrintHeading();
|
||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||
}
|
||||
|
||||
private void SortUpdates()
|
||||
private void Close(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var list = _titleUpdates.ToList();
|
||||
|
||||
list.Sort((first, second) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||
});
|
||||
|
||||
_titleUpdates.Clear();
|
||||
_titleUpdates.AddRange(list);
|
||||
((ContentDialog)Parent).Hide();
|
||||
}
|
||||
|
||||
public void Save()
|
||||
public void Save(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Clear();
|
||||
ViewModel.Save();
|
||||
|
||||
_titleUpdateWindowData.Selected = "";
|
||||
|
||||
foreach (TitleUpdateModel update in _titleUpdates)
|
||||
{
|
||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
||||
|
||||
if (update.IsEnabled)
|
||||
{
|
||||
_titleUpdateWindowData.Selected = update.Path;
|
||||
}
|
||||
}
|
||||
|
||||
using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
||||
{
|
||||
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
||||
}
|
||||
|
||||
if (Owner is MainWindow window)
|
||||
if (VisualRoot is MainWindow window)
|
||||
{
|
||||
window.ViewModel.LoadApplications();
|
||||
}
|
||||
|
||||
Close();
|
||||
((ContentDialog)Parent).Hide();
|
||||
}
|
||||
|
||||
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
if (button.DataContext is TitleUpdateModel model)
|
||||
{
|
||||
OpenHelper.LocateFile(model.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveUpdate(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveAll(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TitleUpdates.Clear();
|
||||
|
||||
ViewModel.SortUpdates();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@ -6,7 +7,7 @@ namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static UInt128 FromHex(string hex)
|
||||
{
|
||||
return new UInt128((ulong)Convert.ToInt64(hex.Substring(0, 16), 16), (ulong)Convert.ToInt64(hex.Substring(16), 16));
|
||||
return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber));
|
||||
}
|
||||
|
||||
public static UInt128 CreateRandom()
|
||||
|
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
@ -0,0 +1,470 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
class AddressSpace : IDisposable
|
||||
{
|
||||
private const ulong PageSize = 0x1000;
|
||||
|
||||
private const int DefaultBlockAlignment = 1 << 20;
|
||||
|
||||
private enum MappingType : byte
|
||||
{
|
||||
None,
|
||||
Private,
|
||||
Shared
|
||||
}
|
||||
|
||||
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public MappingType Type { get; private set; }
|
||||
|
||||
public Mapping(ulong address, ulong size, MappingType type)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public Mapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
Mapping left = new Mapping(Address, leftSize, Type);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void UpdateState(MappingType newType)
|
||||
{
|
||||
Type = newType;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(Mapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>
|
||||
{
|
||||
public ulong Address { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
|
||||
|
||||
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
PrivateAllocation = privateAllocation;
|
||||
}
|
||||
|
||||
public PrivateMapping Split(ulong splitAddress)
|
||||
{
|
||||
ulong leftSize = splitAddress - Address;
|
||||
ulong rightSize = EndAddress - splitAddress;
|
||||
|
||||
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
|
||||
|
||||
PrivateMapping left = new PrivateMapping(Address, leftSize, leftAllocation);
|
||||
|
||||
Address = splitAddress;
|
||||
Size = rightSize;
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation)
|
||||
{
|
||||
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||
mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||
PrivateAllocation = newAllocation;
|
||||
}
|
||||
|
||||
public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock)
|
||||
{
|
||||
if (PrivateAllocation.IsValid)
|
||||
{
|
||||
baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||
mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||
PrivateAllocation.Dispose();
|
||||
}
|
||||
|
||||
PrivateAllocation = default;
|
||||
}
|
||||
|
||||
public void Extend(ulong sizeDelta)
|
||||
{
|
||||
Size += sizeDelta;
|
||||
}
|
||||
|
||||
public int CompareTo(PrivateMapping other)
|
||||
{
|
||||
if (Address < other.Address)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (Address <= other.EndAddress - 1UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
|
||||
private readonly IntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||
private readonly IntrusiveRedBlackTree<PrivateMapping> _privateTree;
|
||||
|
||||
private readonly object _treeLock;
|
||||
|
||||
private readonly bool _supports4KBPages;
|
||||
|
||||
public MemoryBlock Base { get; }
|
||||
public MemoryBlock Mirror { get; }
|
||||
|
||||
public AddressSpace(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages)
|
||||
{
|
||||
if (!supports4KBPages)
|
||||
{
|
||||
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap);
|
||||
_mappingTree = new IntrusiveRedBlackTree<Mapping>();
|
||||
_privateTree = new IntrusiveRedBlackTree<PrivateMapping>();
|
||||
_treeLock = new object();
|
||||
|
||||
_mappingTree.Add(new Mapping(0UL, asSize, MappingType.None));
|
||||
_privateTree.Add(new PrivateMapping(0UL, asSize, default));
|
||||
}
|
||||
|
||||
_backingMemory = backingMemory;
|
||||
_supports4KBPages = supports4KBPages;
|
||||
|
||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
|
||||
Base = new MemoryBlock(asSize, asFlags);
|
||||
Mirror = new MemoryBlock(asSize, asFlags);
|
||||
}
|
||||
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
if (_supports4KBPages)
|
||||
{
|
||||
Base.MapView(_backingMemory, pa, va, size);
|
||||
Mirror.MapView(_backingMemory, pa, va, size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_treeLock)
|
||||
{
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
bool isAligned = ((va | pa | size) & (alignment - 1)) == 0;
|
||||
|
||||
if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned)
|
||||
{
|
||||
Update(va, pa, size, MappingType.Private);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The update method assumes that shared mappings are already aligned.
|
||||
|
||||
if (!flags.HasFlag(MemoryMapFlags.Private))
|
||||
{
|
||||
if ((va & (alignment - 1)) != (pa & (alignment - 1)))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned.");
|
||||
}
|
||||
|
||||
ulong endAddress = va + size;
|
||||
va = BitUtils.AlignDown(va, alignment);
|
||||
pa = BitUtils.AlignDown(pa, alignment);
|
||||
size = BitUtils.AlignUp(endAddress, alignment) - va;
|
||||
}
|
||||
|
||||
Update(va, pa, size, MappingType.Shared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
if (_supports4KBPages)
|
||||
{
|
||||
Base.UnmapView(_backingMemory, va, size);
|
||||
Mirror.UnmapView(_backingMemory, va, size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_treeLock)
|
||||
{
|
||||
Update(va, 0UL, size, MappingType.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update(ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None));
|
||||
|
||||
Update(map, va, pa, size, type);
|
||||
}
|
||||
|
||||
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.Address < va)
|
||||
{
|
||||
_mappingTree.Add(map.Split(va));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddress)
|
||||
{
|
||||
Mapping newMap = map.Split(endAddress);
|
||||
_mappingTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MappingType.None:
|
||||
if (map.Type == MappingType.Shared)
|
||||
{
|
||||
ulong startOffset = map.Address - va;
|
||||
ulong mapVa = va + startOffset;
|
||||
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||
ulong mapEndAddress = mapVa + mapSize;
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
mapVa = BitUtils.AlignDown(mapVa, alignment);
|
||||
mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment);
|
||||
|
||||
mapSize = mapEndAddress - mapVa;
|
||||
|
||||
Base.UnmapView(_backingMemory, mapVa, mapSize);
|
||||
Mirror.UnmapView(_backingMemory, mapVa, mapSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnmapPrivate(va, size);
|
||||
}
|
||||
break;
|
||||
case MappingType.Private:
|
||||
if (map.Type == MappingType.Shared)
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
MapPrivate(va, size);
|
||||
}
|
||||
break;
|
||||
case MappingType.Shared:
|
||||
if (map.Type != MappingType.None)
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong startOffset = map.Address - va;
|
||||
ulong mapPa = pa + startOffset;
|
||||
ulong mapVa = va + startOffset;
|
||||
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||
|
||||
Base.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||
Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
map.UpdateState(type);
|
||||
map = TryCoalesce(map);
|
||||
|
||||
if (map.EndAddress >= endAddress)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private Mapping TryCoalesce(Mapping map)
|
||||
{
|
||||
Mapping previousMap = map.Predecessor;
|
||||
Mapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_mappingTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_mappingTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(Mapping left, Mapping right)
|
||||
{
|
||||
return left.Type == right.Type;
|
||||
}
|
||||
|
||||
private void MapPrivate(ulong va, ulong size)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
|
||||
ulong vaAligned = BitUtils.AlignDown(va, alignment);
|
||||
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
|
||||
|
||||
ulong sizeAligned = endAddressAligned - vaAligned;
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (!map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize()));
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnmapPrivate(ulong va, ulong size)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
ulong alignment = MemoryBlock.GetPageSize();
|
||||
|
||||
// Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use.
|
||||
ulong vaAligned = BitUtils.AlignUp(va, alignment);
|
||||
ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment);
|
||||
|
||||
if (endAddressAligned <= vaAligned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong alignedSize = endAddressAligned - vaAligned;
|
||||
|
||||
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||
|
||||
for (; map != null; map = map.Successor)
|
||||
{
|
||||
if (map.PrivateAllocation.IsValid)
|
||||
{
|
||||
if (map.Address < vaAligned)
|
||||
{
|
||||
_privateTree.Add(map.Split(vaAligned));
|
||||
}
|
||||
|
||||
if (map.EndAddress > endAddressAligned)
|
||||
{
|
||||
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||
_privateTree.Add(newMap);
|
||||
map = newMap;
|
||||
}
|
||||
|
||||
map.Unmap(Base, Mirror);
|
||||
map = TryCoalesce(map);
|
||||
}
|
||||
|
||||
if (map.EndAddress >= endAddressAligned)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PrivateMapping TryCoalesce(PrivateMapping map)
|
||||
{
|
||||
PrivateMapping previousMap = map.Predecessor;
|
||||
PrivateMapping nextMap = map.Successor;
|
||||
|
||||
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||
{
|
||||
previousMap.Extend(map.Size);
|
||||
_privateTree.Remove(map);
|
||||
map = previousMap;
|
||||
}
|
||||
|
||||
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||
{
|
||||
map.Extend(nextMap.Size);
|
||||
_privateTree.Remove(nextMap);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
|
||||
{
|
||||
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_privateMemoryAllocator?.Dispose();
|
||||
Base.Dispose();
|
||||
Mirror.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,5 +7,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
||||
|
||||
public ulong GetPageSize() => MemoryBlock.GetPageSize();
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ namespace Ryujinx.Cpu.Jit
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => true;
|
||||
|
||||
/// <summary>
|
||||
/// Address space width in bits.
|
||||
/// </summary>
|
||||
@ -76,7 +79,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
@ -91,9 +94,16 @@ namespace Ryujinx.Cpu.Jit
|
||||
pa += PageSize;
|
||||
remainingSize -= PageSize;
|
||||
}
|
||||
|
||||
Tracking.Map(oVa, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
@ -378,6 +388,32 @@ namespace Ryujinx.Cpu.Jit
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return Enumerable.Empty<HostMemoryRange>();
|
||||
}
|
||||
|
||||
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||
if (guestRegions == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var regions = new HostMemoryRange[guestRegions.Count];
|
||||
|
||||
for (int i = 0; i < regions.Length; i++)
|
||||
{
|
||||
var guestRegion = guestRegions[i];
|
||||
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
@ -386,6 +422,11 @@ namespace Ryujinx.Cpu.Jit
|
||||
return Enumerable.Empty<MemoryRange>();
|
||||
}
|
||||
|
||||
return GetPhysicalRegionsImpl(va, size);
|
||||
}
|
||||
|
||||
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||
{
|
||||
return null;
|
||||
|
@ -5,6 +5,7 @@ using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@ -37,20 +38,21 @@ namespace Ryujinx.Cpu.Jit
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
private readonly bool _unsafeMode;
|
||||
|
||||
private readonly MemoryBlock _addressSpace;
|
||||
private readonly MemoryBlock _addressSpaceMirror;
|
||||
private readonly AddressSpace _addressSpace;
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly MemoryBlock _backingMemory;
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
|
||||
private readonly ulong[] _pageBitmap;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
|
||||
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
public IntPtr PageTablePointer => _addressSpace.Pointer;
|
||||
public IntPtr PageTablePointer => _addressSpace.Base.Pointer;
|
||||
|
||||
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
||||
|
||||
@ -67,7 +69,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||
{
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_unsafeMode = unsafeMode;
|
||||
@ -86,13 +87,10 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||
|
||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||
_addressSpace = new AddressSpace(backingMemory, asSize, Supports4KBPages);
|
||||
|
||||
_addressSpace = new MemoryBlock(asSize, asFlags);
|
||||
_addressSpaceMirror = new MemoryBlock(asSize, asFlags);
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace, _addressSpaceMirror, Tracking);
|
||||
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -145,18 +143,23 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size)
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
||||
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
|
||||
_addressSpace.Map(va, pa, size, flags);
|
||||
AddMapping(va, size);
|
||||
PtMap(va, pa, size);
|
||||
|
||||
Tracking.Map(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Unmap(ulong va, ulong size)
|
||||
{
|
||||
@ -167,8 +170,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
RemoveMapping(va, size);
|
||||
PtUnmap(va, size);
|
||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
||||
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
|
||||
_addressSpace.Unmap(va, size);
|
||||
}
|
||||
|
||||
private void PtMap(ulong va, ulong pa, ulong size)
|
||||
@ -201,7 +203,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
|
||||
|
||||
return _addressSpaceMirror.Read<T>(va);
|
||||
return _addressSpace.Mirror.Read<T>(va);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
@ -241,7 +243,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
AssertMapped(va, (ulong)data.Length);
|
||||
|
||||
_addressSpaceMirror.Read(va, data);
|
||||
_addressSpace.Mirror.Read(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
@ -260,7 +262,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
|
||||
|
||||
_addressSpaceMirror.Write(va, value);
|
||||
_addressSpace.Mirror.Write(va, value);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
@ -278,7 +280,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)data.Length, write: true);
|
||||
|
||||
_addressSpaceMirror.Write(va, data);
|
||||
_addressSpace.Mirror.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
@ -296,7 +298,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
AssertMapped(va, (ulong)data.Length);
|
||||
|
||||
_addressSpaceMirror.Write(va, data);
|
||||
_addressSpace.Mirror.Write(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
@ -314,7 +316,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||
|
||||
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
|
||||
Span<byte> target = _addressSpace.Mirror.GetSpan(va, data.Length);
|
||||
bool changed = !data.SequenceEqual(target);
|
||||
|
||||
if (changed)
|
||||
@ -347,7 +349,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
AssertMapped(va, (ulong)size);
|
||||
}
|
||||
|
||||
return _addressSpaceMirror.GetSpan(va, size);
|
||||
return _addressSpace.Mirror.GetSpan(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -362,7 +364,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
AssertMapped(va, (ulong)size);
|
||||
}
|
||||
|
||||
return _addressSpaceMirror.GetWritableRegion(va, size);
|
||||
return _addressSpace.Mirror.GetWritableRegion(va, size);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -370,7 +372,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||
|
||||
return ref _addressSpaceMirror.GetRef<T>(va);
|
||||
return ref _addressSpace.Mirror.GetRef<T>(va);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -454,6 +456,14 @@ namespace Ryujinx.Cpu.Jit
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||
{
|
||||
AssertValidAddressAndSize(va, size);
|
||||
|
||||
return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpace.Mirror.GetPointer(va, size), size), 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||
{
|
||||
@ -692,7 +702,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
_ => MemoryPermission.None
|
||||
};
|
||||
|
||||
_addressSpace.Reprotect(va, size, protection, false);
|
||||
_addressSpace.Base.Reprotect(va, size, protection, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -799,7 +809,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
protected override void Destroy()
|
||||
{
|
||||
_addressSpace.Dispose();
|
||||
_addressSpaceMirror.Dispose();
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
|
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
struct PrivateMemoryAllocation : IDisposable
|
||||
{
|
||||
private readonly PrivateMemoryAllocator _owner;
|
||||
private readonly PrivateMemoryAllocator.Block _block;
|
||||
|
||||
public bool IsValid => _owner != null;
|
||||
public MemoryBlock Memory => _block?.Memory;
|
||||
public ulong Offset { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public PrivateMemoryAllocation(
|
||||
PrivateMemoryAllocator owner,
|
||||
PrivateMemoryAllocator.Block block,
|
||||
ulong offset,
|
||||
ulong size)
|
||||
{
|
||||
_owner = owner;
|
||||
_block = block;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public (PrivateMemoryAllocation, PrivateMemoryAllocation) Split(ulong splitOffset)
|
||||
{
|
||||
PrivateMemoryAllocation left = new PrivateMemoryAllocation(_owner, _block, Offset, splitOffset);
|
||||
PrivateMemoryAllocation right = new PrivateMemoryAllocation(_owner, _block, Offset + splitOffset, Size - splitOffset);
|
||||
|
||||
return (left, right);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_owner.Free(_block, Offset, Size);
|
||||
}
|
||||
}
|
||||
}
|
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
@ -0,0 +1,268 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
class PrivateMemoryAllocator : PrivateMemoryAllocatorImpl<PrivateMemoryAllocator.Block>
|
||||
{
|
||||
public const ulong InvalidOffset = ulong.MaxValue;
|
||||
|
||||
public class Block : IComparable<Block>
|
||||
{
|
||||
public MemoryBlock Memory { get; private set; }
|
||||
public ulong Size { get; }
|
||||
|
||||
private struct Range : IComparable<Range>
|
||||
{
|
||||
public ulong Offset { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public Range(ulong offset, ulong size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public int CompareTo(Range other)
|
||||
{
|
||||
return Offset.CompareTo(other.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Range> _freeRanges;
|
||||
|
||||
public Block(MemoryBlock memory, ulong size)
|
||||
{
|
||||
Memory = memory;
|
||||
Size = size;
|
||||
_freeRanges = new List<Range>
|
||||
{
|
||||
new Range(0, size)
|
||||
};
|
||||
}
|
||||
|
||||
public ulong Allocate(ulong size, ulong alignment)
|
||||
{
|
||||
for (int i = 0; i < _freeRanges.Count; i++)
|
||||
{
|
||||
var range = _freeRanges[i];
|
||||
|
||||
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
||||
ulong sizeDelta = alignedOffset - range.Offset;
|
||||
ulong usableSize = range.Size - sizeDelta;
|
||||
|
||||
if (sizeDelta < range.Size && usableSize >= size)
|
||||
{
|
||||
_freeRanges.RemoveAt(i);
|
||||
|
||||
if (sizeDelta != 0)
|
||||
{
|
||||
InsertFreeRange(range.Offset, sizeDelta);
|
||||
}
|
||||
|
||||
ulong endOffset = range.Offset + range.Size;
|
||||
ulong remainingSize = endOffset - (alignedOffset + size);
|
||||
if (remainingSize != 0)
|
||||
{
|
||||
InsertFreeRange(endOffset - remainingSize, remainingSize);
|
||||
}
|
||||
|
||||
return alignedOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidOffset;
|
||||
}
|
||||
|
||||
public void Free(ulong offset, ulong size)
|
||||
{
|
||||
InsertFreeRangeComingled(offset, size);
|
||||
}
|
||||
|
||||
private void InsertFreeRange(ulong offset, ulong size)
|
||||
{
|
||||
var range = new Range(offset, size);
|
||||
int index = _freeRanges.BinarySearch(range);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_freeRanges.Insert(index, range);
|
||||
}
|
||||
|
||||
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
||||
{
|
||||
ulong endOffset = offset + size;
|
||||
var range = new Range(offset, size);
|
||||
int index = _freeRanges.BinarySearch(range);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset)
|
||||
{
|
||||
endOffset = _freeRanges[index].Offset + _freeRanges[index].Size;
|
||||
_freeRanges.RemoveAt(index);
|
||||
}
|
||||
|
||||
if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset)
|
||||
{
|
||||
offset = _freeRanges[index - 1].Offset;
|
||||
_freeRanges.RemoveAt(--index);
|
||||
}
|
||||
|
||||
range = new Range(offset, endOffset - offset);
|
||||
|
||||
_freeRanges.Insert(index, range);
|
||||
}
|
||||
|
||||
public bool IsTotallyFree()
|
||||
{
|
||||
if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size)
|
||||
{
|
||||
Debug.Assert(_freeRanges[0].Offset == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int CompareTo(Block other)
|
||||
{
|
||||
return Size.CompareTo(other.Size);
|
||||
}
|
||||
|
||||
public virtual void Destroy()
|
||||
{
|
||||
Memory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags)
|
||||
{
|
||||
}
|
||||
|
||||
public PrivateMemoryAllocation Allocate(ulong size, ulong alignment)
|
||||
{
|
||||
var allocation = Allocate(size, alignment, CreateBlock);
|
||||
|
||||
return new PrivateMemoryAllocation(this, allocation.Block, allocation.Offset, allocation.Size);
|
||||
}
|
||||
|
||||
private Block CreateBlock(MemoryBlock memory, ulong size)
|
||||
{
|
||||
return new Block(memory, size);
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateMemoryAllocatorImpl<T> : IDisposable where T : PrivateMemoryAllocator.Block
|
||||
{
|
||||
private const ulong InvalidOffset = ulong.MaxValue;
|
||||
|
||||
public struct Allocation
|
||||
{
|
||||
public T Block { get; }
|
||||
public ulong Offset { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public Allocation(T block, ulong offset, ulong size)
|
||||
{
|
||||
Block = block;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<T> _blocks;
|
||||
|
||||
private readonly int _blockAlignment;
|
||||
private readonly MemoryAllocationFlags _allocationFlags;
|
||||
|
||||
public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags)
|
||||
{
|
||||
_blocks = new List<T>();
|
||||
_blockAlignment = blockAlignment;
|
||||
_allocationFlags = allocationFlags;
|
||||
}
|
||||
|
||||
protected Allocation Allocate(ulong size, ulong alignment, Func<MemoryBlock, ulong, T> createBlock)
|
||||
{
|
||||
// Ensure we have a sane alignment value.
|
||||
if ((ulong)(int)alignment != alignment || (int)alignment <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
var block = _blocks[i];
|
||||
|
||||
if (block.Size >= size)
|
||||
{
|
||||
ulong offset = block.Allocate(size, alignment);
|
||||
if (offset != InvalidOffset)
|
||||
{
|
||||
return new Allocation(block, offset, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
|
||||
|
||||
var memory = new MemoryBlock(blockAlignedSize, _allocationFlags);
|
||||
var newBlock = createBlock(memory, blockAlignedSize);
|
||||
|
||||
InsertBlock(newBlock);
|
||||
|
||||
ulong newBlockOffset = newBlock.Allocate(size, alignment);
|
||||
Debug.Assert(newBlockOffset != InvalidOffset);
|
||||
|
||||
return new Allocation(newBlock, newBlockOffset, size);
|
||||
}
|
||||
|
||||
public void Free(PrivateMemoryAllocator.Block block, ulong offset, ulong size)
|
||||
{
|
||||
block.Free(offset, size);
|
||||
|
||||
if (block.IsTotallyFree())
|
||||
{
|
||||
for (int i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
if (_blocks[i] == block)
|
||||
{
|
||||
_blocks.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
block.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertBlock(T block)
|
||||
{
|
||||
int index = _blocks.BinarySearch(block);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_blocks.Insert(index, block);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
_blocks[i].Destroy();
|
||||
}
|
||||
|
||||
_blocks.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@ -30,207 +28,115 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
|
||||
public static int GetMaxCommandSize()
|
||||
{
|
||||
Assembly assembly = typeof(CommandHelper).Assembly;
|
||||
|
||||
IEnumerable<Type> commands = assembly.GetTypes().Where(type => typeof(IGALCommand).IsAssignableFrom(type) && type.IsValueType);
|
||||
|
||||
int maxSize = commands.Max(command =>
|
||||
{
|
||||
MethodInfo method = typeof(Unsafe).GetMethod(nameof(Unsafe.SizeOf));
|
||||
MethodInfo generic = method.MakeGenericMethod(command);
|
||||
int size = (int)generic.Invoke(null, null);
|
||||
|
||||
return size;
|
||||
});
|
||||
|
||||
InitLookup();
|
||||
|
||||
return maxSize + 1; // 1 byte reserved for command size.
|
||||
return InitLookup() + 1; // 1 byte reserved for command size.
|
||||
}
|
||||
|
||||
private static void InitLookup()
|
||||
private static int InitLookup()
|
||||
{
|
||||
_lookup[(int)CommandType.Action] = (memory, threaded, renderer) =>
|
||||
ActionCommand.Run(ref GetCommand<ActionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateBuffer] = (memory, threaded, renderer) =>
|
||||
CreateBufferCommand.Run(ref GetCommand<CreateBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateProgram] = (memory, threaded, renderer) =>
|
||||
CreateProgramCommand.Run(ref GetCommand<CreateProgramCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateSampler] = (memory, threaded, renderer) =>
|
||||
CreateSamplerCommand.Run(ref GetCommand<CreateSamplerCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateSync] = (memory, threaded, renderer) =>
|
||||
CreateSyncCommand.Run(ref GetCommand<CreateSyncCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CreateTexture] = (memory, threaded, renderer) =>
|
||||
CreateTextureCommand.Run(ref GetCommand<CreateTextureCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.GetCapabilities] = (memory, threaded, renderer) =>
|
||||
GetCapabilitiesCommand.Run(ref GetCommand<GetCapabilitiesCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.PreFrame] = (memory, threaded, renderer) =>
|
||||
PreFrameCommand.Run(ref GetCommand<PreFrameCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ReportCounter] = (memory, threaded, renderer) =>
|
||||
ReportCounterCommand.Run(ref GetCommand<ReportCounterCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ResetCounter] = (memory, threaded, renderer) =>
|
||||
ResetCounterCommand.Run(ref GetCommand<ResetCounterCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.UpdateCounters] = (memory, threaded, renderer) =>
|
||||
UpdateCountersCommand.Run(ref GetCommand<UpdateCountersCommand>(memory), threaded, renderer);
|
||||
int maxCommandSize = 0;
|
||||
|
||||
_lookup[(int)CommandType.BufferDispose] = (memory, threaded, renderer) =>
|
||||
BufferDisposeCommand.Run(ref GetCommand<BufferDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BufferGetData] = (memory, threaded, renderer) =>
|
||||
BufferGetDataCommand.Run(ref GetCommand<BufferGetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BufferSetData] = (memory, threaded, renderer) =>
|
||||
BufferSetDataCommand.Run(ref GetCommand<BufferSetDataCommand>(memory), threaded, renderer);
|
||||
void Register<T>(CommandType commandType) where T : unmanaged, IGALCommand, IGALCommand<T>
|
||||
{
|
||||
maxCommandSize = Math.Max(maxCommandSize, Unsafe.SizeOf<T>());
|
||||
_lookup[(int)commandType] = (memory, threaded, renderer) => T.Run(ref GetCommand<T>(memory), threaded, renderer);
|
||||
}
|
||||
|
||||
_lookup[(int)CommandType.CounterEventDispose] = (memory, threaded, renderer) =>
|
||||
CounterEventDisposeCommand.Run(ref GetCommand<CounterEventDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CounterEventFlush] = (memory, threaded, renderer) =>
|
||||
CounterEventFlushCommand.Run(ref GetCommand<CounterEventFlushCommand>(memory), threaded, renderer);
|
||||
Register<ActionCommand>(CommandType.Action);
|
||||
Register<CreateBufferCommand>(CommandType.CreateBuffer);
|
||||
Register<CreateProgramCommand>(CommandType.CreateProgram);
|
||||
Register<CreateSamplerCommand>(CommandType.CreateSampler);
|
||||
Register<CreateSyncCommand>(CommandType.CreateSync);
|
||||
Register<CreateTextureCommand>(CommandType.CreateTexture);
|
||||
Register<GetCapabilitiesCommand>(CommandType.GetCapabilities);
|
||||
Register<PreFrameCommand>(CommandType.PreFrame);
|
||||
Register<ReportCounterCommand>(CommandType.ReportCounter);
|
||||
Register<ResetCounterCommand>(CommandType.ResetCounter);
|
||||
Register<UpdateCountersCommand>(CommandType.UpdateCounters);
|
||||
|
||||
_lookup[(int)CommandType.ProgramDispose] = (memory, threaded, renderer) =>
|
||||
ProgramDisposeCommand.Run(ref GetCommand<ProgramDisposeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ProgramGetBinary] = (memory, threaded, renderer) =>
|
||||
ProgramGetBinaryCommand.Run(ref GetCommand<ProgramGetBinaryCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ProgramCheckLink] = (memory, threaded, renderer) =>
|
||||
ProgramCheckLinkCommand.Run(ref GetCommand<ProgramCheckLinkCommand>(memory), threaded, renderer);
|
||||
Register<BufferDisposeCommand>(CommandType.BufferDispose);
|
||||
Register<BufferGetDataCommand>(CommandType.BufferGetData);
|
||||
Register<BufferSetDataCommand>(CommandType.BufferSetData);
|
||||
|
||||
_lookup[(int)CommandType.SamplerDispose] = (memory, threaded, renderer) =>
|
||||
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer);
|
||||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||
|
||||
_lookup[(int)CommandType.TextureCopyTo] = (memory, threaded, renderer) =>
|
||||
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToScaled] = (memory, threaded, renderer) =>
|
||||
TextureCopyToScaledCommand.Run(ref GetCommand<TextureCopyToScaledCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCopyToSlice] = (memory, threaded, renderer) =>
|
||||
TextureCopyToSliceCommand.Run(ref GetCommand<TextureCopyToSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureCreateView] = (memory, threaded, renderer) =>
|
||||
TextureCreateViewCommand.Run(ref GetCommand<TextureCreateViewCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureGetData] = (memory, threaded, renderer) =>
|
||||
TextureGetDataCommand.Run(ref GetCommand<TextureGetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureGetDataSlice] = (memory, threaded, renderer) =>
|
||||
TextureGetDataSliceCommand.Run(ref GetCommand<TextureGetDataSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureRelease] = (memory, threaded, renderer) =>
|
||||
TextureReleaseCommand.Run(ref GetCommand<TextureReleaseCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetData] = (memory, threaded, renderer) =>
|
||||
TextureSetDataCommand.Run(ref GetCommand<TextureSetDataCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetDataSlice] = (memory, threaded, renderer) =>
|
||||
TextureSetDataSliceCommand.Run(ref GetCommand<TextureSetDataSliceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetDataSliceRegion] = (memory, threaded, renderer) =>
|
||||
TextureSetDataSliceRegionCommand.Run(ref GetCommand<TextureSetDataSliceRegionCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureSetStorage] = (memory, threaded, renderer) =>
|
||||
TextureSetStorageCommand.Run(ref GetCommand<TextureSetStorageCommand>(memory), threaded, renderer);
|
||||
Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
|
||||
Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
|
||||
Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
|
||||
|
||||
_lookup[(int)CommandType.WindowPresent] = (memory, threaded, renderer) =>
|
||||
WindowPresentCommand.Run(ref GetCommand<WindowPresentCommand>(memory), threaded, renderer);
|
||||
Register<SamplerDisposeCommand>(CommandType.SamplerDispose);
|
||||
|
||||
_lookup[(int)CommandType.Barrier] = (memory, threaded, renderer) =>
|
||||
BarrierCommand.Run(ref GetCommand<BarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.BeginTransformFeedback] = (memory, threaded, renderer) =>
|
||||
BeginTransformFeedbackCommand.Run(ref GetCommand<BeginTransformFeedbackCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearBuffer] = (memory, threaded, renderer) =>
|
||||
ClearBufferCommand.Run(ref GetCommand<ClearBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearRenderTargetColor] = (memory, threaded, renderer) =>
|
||||
ClearRenderTargetColorCommand.Run(ref GetCommand<ClearRenderTargetColorCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.ClearRenderTargetDepthStencil] = (memory, threaded, renderer) =>
|
||||
ClearRenderTargetDepthStencilCommand.Run(ref GetCommand<ClearRenderTargetDepthStencilCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CommandBufferBarrier] = (memory, threaded, renderer) =>
|
||||
CommandBufferBarrierCommand.Run(ref GetCommand<CommandBufferBarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.CopyBuffer] = (memory, threaded, renderer) =>
|
||||
CopyBufferCommand.Run(ref GetCommand<CopyBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DispatchCompute] = (memory, threaded, renderer) =>
|
||||
DispatchComputeCommand.Run(ref GetCommand<DispatchComputeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.Draw] = (memory, threaded, renderer) =>
|
||||
DrawCommand.Run(ref GetCommand<DrawCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexed] = (memory, threaded, renderer) =>
|
||||
DrawIndexedCommand.Run(ref GetCommand<DrawIndexedCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexedIndirect] = (memory, threaded, renderer) =>
|
||||
DrawIndexedIndirectCommand.Run(ref GetCommand<DrawIndexedIndirectCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndexedIndirectCount] = (memory, threaded, renderer) =>
|
||||
DrawIndexedIndirectCountCommand.Run(ref GetCommand<DrawIndexedIndirectCountCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndirect] = (memory, threaded, renderer) =>
|
||||
DrawIndirectCommand.Run(ref GetCommand<DrawIndirectCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawIndirectCount] = (memory, threaded, renderer) =>
|
||||
DrawIndirectCountCommand.Run(ref GetCommand<DrawIndirectCountCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.DrawTexture] = (memory, threaded, renderer) =>
|
||||
DrawTextureCommand.Run(ref GetCommand<DrawTextureCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.EndHostConditionalRendering] = (memory, threaded, renderer) =>
|
||||
EndHostConditionalRenderingCommand.Run(renderer);
|
||||
_lookup[(int)CommandType.EndTransformFeedback] = (memory, threaded, renderer) =>
|
||||
EndTransformFeedbackCommand.Run(ref GetCommand<EndTransformFeedbackCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetAlphaTest] = (memory, threaded, renderer) =>
|
||||
SetAlphaTestCommand.Run(ref GetCommand<SetAlphaTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetBlendState] = (memory, threaded, renderer) =>
|
||||
SetBlendStateCommand.Run(ref GetCommand<SetBlendStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthBias] = (memory, threaded, renderer) =>
|
||||
SetDepthBiasCommand.Run(ref GetCommand<SetDepthBiasCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthClamp] = (memory, threaded, renderer) =>
|
||||
SetDepthClampCommand.Run(ref GetCommand<SetDepthClampCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthMode] = (memory, threaded, renderer) =>
|
||||
SetDepthModeCommand.Run(ref GetCommand<SetDepthModeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetDepthTest] = (memory, threaded, renderer) =>
|
||||
SetDepthTestCommand.Run(ref GetCommand<SetDepthTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetFaceCulling] = (memory, threaded, renderer) =>
|
||||
SetFaceCullingCommand.Run(ref GetCommand<SetFaceCullingCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetFrontFace] = (memory, threaded, renderer) =>
|
||||
SetFrontFaceCommand.Run(ref GetCommand<SetFrontFaceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetStorageBuffers] = (memory, threaded, renderer) =>
|
||||
SetStorageBuffersCommand.Run(ref GetCommand<SetStorageBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTransformFeedbackBuffers] = (memory, threaded, renderer) =>
|
||||
SetTransformFeedbackBuffersCommand.Run(ref GetCommand<SetTransformFeedbackBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetUniformBuffers] = (memory, threaded, renderer) =>
|
||||
SetUniformBuffersCommand.Run(ref GetCommand<SetUniformBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetImage] = (memory, threaded, renderer) =>
|
||||
SetImageCommand.Run(ref GetCommand<SetImageCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetIndexBuffer] = (memory, threaded, renderer) =>
|
||||
SetIndexBufferCommand.Run(ref GetCommand<SetIndexBufferCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetLineParameters] = (memory, threaded, renderer) =>
|
||||
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetLogicOpState] = (memory, threaded, renderer) =>
|
||||
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetMultisampleState] = (memory, threaded, renderer) =>
|
||||
SetMultisampleStateCommand.Run(ref GetCommand<SetMultisampleStateCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPatchParameters] = (memory, threaded, renderer) =>
|
||||
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPointParameters] = (memory, threaded, renderer) =>
|
||||
SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPolygonMode] = (memory, threaded, renderer) =>
|
||||
SetPolygonModeCommand.Run(ref GetCommand<SetPolygonModeCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPrimitiveRestart] = (memory, threaded, renderer) =>
|
||||
SetPrimitiveRestartCommand.Run(ref GetCommand<SetPrimitiveRestartCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetPrimitiveTopology] = (memory, threaded, renderer) =>
|
||||
SetPrimitiveTopologyCommand.Run(ref GetCommand<SetPrimitiveTopologyCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetProgram] = (memory, threaded, renderer) =>
|
||||
SetProgramCommand.Run(ref GetCommand<SetProgramCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRasterizerDiscard] = (memory, threaded, renderer) =>
|
||||
SetRasterizerDiscardCommand.Run(ref GetCommand<SetRasterizerDiscardCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargetColorMasks] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetColorMasksCommand.Run(ref GetCommand<SetRenderTargetColorMasksCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargetScale] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetScaleCommand.Run(ref GetCommand<SetRenderTargetScaleCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetRenderTargets] = (memory, threaded, renderer) =>
|
||||
SetRenderTargetsCommand.Run(ref GetCommand<SetRenderTargetsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetScissor] = (memory, threaded, renderer) =>
|
||||
SetScissorsCommand.Run(ref GetCommand<SetScissorsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetStencilTest] = (memory, threaded, renderer) =>
|
||||
SetStencilTestCommand.Run(ref GetCommand<SetStencilTestCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetTextureAndSampler] = (memory, threaded, renderer) =>
|
||||
SetTextureAndSamplerCommand.Run(ref GetCommand<SetTextureAndSamplerCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetUserClipDistance] = (memory, threaded, renderer) =>
|
||||
SetUserClipDistanceCommand.Run(ref GetCommand<SetUserClipDistanceCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetVertexAttribs] = (memory, threaded, renderer) =>
|
||||
SetVertexAttribsCommand.Run(ref GetCommand<SetVertexAttribsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetVertexBuffers] = (memory, threaded, renderer) =>
|
||||
SetVertexBuffersCommand.Run(ref GetCommand<SetVertexBuffersCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.SetViewports] = (memory, threaded, renderer) =>
|
||||
SetViewportsCommand.Run(ref GetCommand<SetViewportsCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureBarrier] = (memory, threaded, renderer) =>
|
||||
TextureBarrierCommand.Run(ref GetCommand<TextureBarrierCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TextureBarrierTiled] = (memory, threaded, renderer) =>
|
||||
TextureBarrierTiledCommand.Run(ref GetCommand<TextureBarrierTiledCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TryHostConditionalRendering] = (memory, threaded, renderer) =>
|
||||
TryHostConditionalRenderingCommand.Run(ref GetCommand<TryHostConditionalRenderingCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.TryHostConditionalRenderingFlush] = (memory, threaded, renderer) =>
|
||||
TryHostConditionalRenderingFlushCommand.Run(ref GetCommand<TryHostConditionalRenderingFlushCommand>(memory), threaded, renderer);
|
||||
_lookup[(int)CommandType.UpdateRenderScale] = (memory, threaded, renderer) =>
|
||||
UpdateRenderScaleCommand.Run(ref GetCommand<UpdateRenderScaleCommand>(memory), threaded, renderer);
|
||||
Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
|
||||
Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
|
||||
Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
|
||||
Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
|
||||
Register<TextureGetDataCommand>(CommandType.TextureGetData);
|
||||
Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
|
||||
Register<TextureReleaseCommand>(CommandType.TextureRelease);
|
||||
Register<TextureSetDataCommand>(CommandType.TextureSetData);
|
||||
Register<TextureSetDataSliceCommand>(CommandType.TextureSetDataSlice);
|
||||
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||
|
||||
Register<WindowPresentCommand>(CommandType.WindowPresent);
|
||||
|
||||
Register<BarrierCommand>(CommandType.Barrier);
|
||||
Register<BeginTransformFeedbackCommand>(CommandType.BeginTransformFeedback);
|
||||
Register<ClearBufferCommand>(CommandType.ClearBuffer);
|
||||
Register<ClearRenderTargetColorCommand>(CommandType.ClearRenderTargetColor);
|
||||
Register<ClearRenderTargetDepthStencilCommand>(CommandType.ClearRenderTargetDepthStencil);
|
||||
Register<CommandBufferBarrierCommand>(CommandType.CommandBufferBarrier);
|
||||
Register<CopyBufferCommand>(CommandType.CopyBuffer);
|
||||
Register<DispatchComputeCommand>(CommandType.DispatchCompute);
|
||||
Register<DrawCommand>(CommandType.Draw);
|
||||
Register<DrawIndexedCommand>(CommandType.DrawIndexed);
|
||||
Register<DrawIndexedIndirectCommand>(CommandType.DrawIndexedIndirect);
|
||||
Register<DrawIndexedIndirectCountCommand>(CommandType.DrawIndexedIndirectCount);
|
||||
Register<DrawIndirectCommand>(CommandType.DrawIndirect);
|
||||
Register<DrawIndirectCountCommand>(CommandType.DrawIndirectCount);
|
||||
Register<DrawTextureCommand>(CommandType.DrawTexture);
|
||||
Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
|
||||
Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
|
||||
Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
|
||||
Register<SetBlendStateCommand>(CommandType.SetBlendState);
|
||||
Register<SetDepthBiasCommand>(CommandType.SetDepthBias);
|
||||
Register<SetDepthClampCommand>(CommandType.SetDepthClamp);
|
||||
Register<SetDepthModeCommand>(CommandType.SetDepthMode);
|
||||
Register<SetDepthTestCommand>(CommandType.SetDepthTest);
|
||||
Register<SetFaceCullingCommand>(CommandType.SetFaceCulling);
|
||||
Register<SetFrontFaceCommand>(CommandType.SetFrontFace);
|
||||
Register<SetStorageBuffersCommand>(CommandType.SetStorageBuffers);
|
||||
Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
|
||||
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
||||
Register<SetImageCommand>(CommandType.SetImage);
|
||||
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
||||
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
||||
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
||||
Register<SetMultisampleStateCommand>(CommandType.SetMultisampleState);
|
||||
Register<SetPatchParametersCommand>(CommandType.SetPatchParameters);
|
||||
Register<SetPointParametersCommand>(CommandType.SetPointParameters);
|
||||
Register<SetPolygonModeCommand>(CommandType.SetPolygonMode);
|
||||
Register<SetPrimitiveRestartCommand>(CommandType.SetPrimitiveRestart);
|
||||
Register<SetPrimitiveTopologyCommand>(CommandType.SetPrimitiveTopology);
|
||||
Register<SetProgramCommand>(CommandType.SetProgram);
|
||||
Register<SetRasterizerDiscardCommand>(CommandType.SetRasterizerDiscard);
|
||||
Register<SetRenderTargetColorMasksCommand>(CommandType.SetRenderTargetColorMasks);
|
||||
Register<SetRenderTargetScaleCommand>(CommandType.SetRenderTargetScale);
|
||||
Register<SetRenderTargetsCommand>(CommandType.SetRenderTargets);
|
||||
Register<SetScissorsCommand>(CommandType.SetScissor);
|
||||
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
||||
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
||||
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
||||
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
||||
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
||||
Register<SetViewportsCommand>(CommandType.SetViewports);
|
||||
Register<TextureBarrierCommand>(CommandType.TextureBarrier);
|
||||
Register<TextureBarrierTiledCommand>(CommandType.TextureBarrierTiled);
|
||||
Register<TryHostConditionalRenderingCommand>(CommandType.TryHostConditionalRendering);
|
||||
Register<TryHostConditionalRenderingFlushCommand>(CommandType.TryHostConditionalRenderingFlush);
|
||||
Register<UpdateRenderScaleCommand>(CommandType.UpdateRenderScale);
|
||||
|
||||
return maxCommandSize;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct BarrierCommand : IGALCommand
|
||||
struct BarrierCommand : IGALCommand, IGALCommand<BarrierCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.Barrier;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct BeginTransformFeedbackCommand : IGALCommand
|
||||
struct BeginTransformFeedbackCommand : IGALCommand, IGALCommand<BeginTransformFeedbackCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.BeginTransformFeedback;
|
||||
private PrimitiveTopology _topology;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
{
|
||||
struct BufferDisposeCommand : IGALCommand
|
||||
struct BufferDisposeCommand : IGALCommand, IGALCommand<BufferDisposeCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.BufferDispose;
|
||||
private BufferHandle _buffer;
|
||||
|
@ -3,7 +3,7 @@ using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
{
|
||||
struct BufferGetDataCommand : IGALCommand
|
||||
struct BufferGetDataCommand : IGALCommand, IGALCommand<BufferGetDataCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.BufferGetData;
|
||||
private BufferHandle _buffer;
|
||||
|
@ -3,7 +3,7 @@ using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
{
|
||||
struct BufferSetDataCommand : IGALCommand
|
||||
struct BufferSetDataCommand : IGALCommand, IGALCommand<BufferSetDataCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.BufferSetData;
|
||||
private BufferHandle _buffer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct ClearBufferCommand : IGALCommand
|
||||
struct ClearBufferCommand : IGALCommand, IGALCommand<ClearBufferCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearBuffer;
|
||||
private BufferHandle _destination;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct ClearRenderTargetColorCommand : IGALCommand
|
||||
struct ClearRenderTargetColorCommand : IGALCommand, IGALCommand<ClearRenderTargetColorCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetColor;
|
||||
private int _index;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct ClearRenderTargetDepthStencilCommand : IGALCommand
|
||||
struct ClearRenderTargetDepthStencilCommand : IGALCommand, IGALCommand<ClearRenderTargetDepthStencilCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
|
||||
private int _layer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct CommandBufferBarrierCommand : IGALCommand
|
||||
struct CommandBufferBarrierCommand : IGALCommand, IGALCommand<CommandBufferBarrierCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.CommandBufferBarrier;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct CopyBufferCommand : IGALCommand
|
||||
struct CopyBufferCommand : IGALCommand, IGALCommand<CopyBufferCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.CopyBuffer;
|
||||
private BufferHandle _source;
|
||||
|
@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
||||
{
|
||||
struct CounterEventDisposeCommand : IGALCommand
|
||||
struct CounterEventDisposeCommand : IGALCommand, IGALCommand<CounterEventDisposeCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.CounterEventDispose;
|
||||
private TableRef<ThreadedCounterEvent> _event;
|
||||
|
@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
|
||||
{
|
||||
struct CounterEventFlushCommand : IGALCommand
|
||||
struct CounterEventFlushCommand : IGALCommand, IGALCommand<CounterEventFlushCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.CounterEventFlush;
|
||||
private TableRef<ThreadedCounterEvent> _event;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DispatchComputeCommand : IGALCommand
|
||||
struct DispatchComputeCommand : IGALCommand, IGALCommand<DispatchComputeCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DispatchCompute;
|
||||
private int _groupsX;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawIndexedCommand : IGALCommand
|
||||
struct DrawIndexedCommand : IGALCommand, IGALCommand<DrawIndexedCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DrawIndexed;
|
||||
private int _indexCount;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawCommand : IGALCommand
|
||||
struct DrawCommand : IGALCommand, IGALCommand<DrawCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.Draw;
|
||||
private int _vertexCount;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawIndexedIndirectCommand : IGALCommand
|
||||
struct DrawIndexedIndirectCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DrawIndexedIndirect;
|
||||
private BufferRange _indirectBuffer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawIndexedIndirectCountCommand : IGALCommand
|
||||
struct DrawIndexedIndirectCountCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCountCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DrawIndexedIndirectCount;
|
||||
private BufferRange _indirectBuffer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawIndirectCommand : IGALCommand
|
||||
struct DrawIndirectCommand : IGALCommand, IGALCommand<DrawIndirectCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DrawIndirect;
|
||||
private BufferRange _indirectBuffer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct DrawIndirectCountCommand : IGALCommand
|
||||
struct DrawIndirectCountCommand : IGALCommand, IGALCommand<DrawIndirectCountCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.DrawIndirectCount;
|
||||
private BufferRange _indirectBuffer;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user