Compare commits

..

24 Commits

Author SHA1 Message Date
a1a4771ac1 Remove use of GetFunctionPointerForDelegate to get JIT cache function pointer (#4337)
* Remove use of GetFunctionPointerForDelegate to get JIT cache function pointer

* Rename FuncPtr to FuncPointer
2023-01-23 22:37:53 +00:00
2fd819613f SPIR-V: Change BitfieldExtract and BitfieldInsert for SPIRV-Cross (#4336)
* SPIR-V: Change BitfieldExtract and BitfieldInsert types to make Metal MSL compiler happy

* Shader cache version bump
2023-01-23 19:20:40 -03:00
ad6ff6ce99 GUI: Add option to register file types (#4250)
* Add FileAssociationHelper.cs

* Add register file types option to gtk

* Add register file types option to avalonia

* Add Windows support to FileAssociationHelper.cs

* linux: Add uninstall support for file types

* Ignore .glade~ backup files

* Rename Register/Unregister methods

* gtk: Add manage file types submenu

* ava: Add manage file types submenu

* windows: Add uninstall support for file types

* Don't invert uninstall condition (formatting change)

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Add IsTypesRegisteredWindows & Fix Windows install function

* Add AreMimeTypesRegisteredLinux()

* Fix wrong indention

Co-authored-by: AcK77 <acoustik666@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-22 17:39:00 +00:00
dc30d94852 Handle parsing of corrupt Config.json and prevent crash on launch (#4309)
* Handle parsing of corrupt Config.json and prevent crash on launch

* Implement a cleaner solution to handle empty json object
2023-01-22 17:04:33 +01:00
4f293f8cbe Arm64: Simplify TryEncodeBitMask and use for constants (#4328)
* Arm64: Simplify TryEncodeBitMask

* CodeGenerator: Use TryEncodeBitMask in GenerateConstantCopy

* Ptc: Bump version
2023-01-22 14:15:49 +00:00
32a1cd83fd AvaloniaKeyboardDriver: Swallow TextInput events to avoid bell (#4320) 2023-01-22 11:21:52 +01:00
e3d0ccf8d5 Allow setting texture data from 1x to fix some textures resetting randomly (#2860)
* Allow setting texture data from 1x to fix some textures resetting randomly

Expected targets:

- Deltarune 1+2
- Crash Team Racing
- Those new pokemon games idk

* Allow scaling of MSAA textures, propagate scale on copy.

* Fix Rebase

Oops

* Automatic disable

* A bit more aggressive

* Without the debug log

* Actually decrement the score when writing.
2023-01-22 02:03:30 +00:00
c14844d12c Ava UI: Various Fixes (#4326)
* Ava UI: Various Fixes

* use WriteAllBytes
2023-01-22 01:42:55 +01:00
7fea26e97e Remove use of reflection on GAL multithreading (#4287)
* Introduce new IGALCommand<T> interface and use it

* Remove use of reflection on GAL multithreading

* Unmanaged constraint
2023-01-22 01:07:43 +01:00
7b7f62c776 nuget: bump Microsoft.CodeAnalysis.Analyzers from 3.3.3 to 3.3.4 (#4310)
* nuget: bump Microsoft.CodeAnalysis.Analyzers from 3.3.3 to 3.3.4

Bumps [Microsoft.CodeAnalysis.Analyzers](https://github.com/dotnet/roslyn-analyzers) from 3.3.3 to 3.3.4.
- [Release notes](https://github.com/dotnet/roslyn-analyzers/releases)
- [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/main/PostReleaseActivities.md)
- [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v3.3.3...v3.3.4)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fixes warning

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-21 20:04:39 +00:00
423dbc8888 Use volatile read/writes for GAL threading (#4327) 2023-01-21 19:49:55 +00:00
6adf15e479 Implement CSET and CSETP shader instructions (#4318)
* Implement CSET and CSETP shader instructions

* Shader cache version bump

* Fix CC.HI
2023-01-21 12:18:05 -03:00
2747f12591 nuget: bump System.IdentityModel.Tokens.Jwt from 6.25.1 to 6.26.0 (#4322)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.25.1 to 6.26.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/commits)

---
updated-dependencies:
- dependency-name: System.IdentityModel.Tokens.Jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-21 05:24:57 +01:00
a47824f961 Ava UI: Add Notifications and Cleanup (#4275)
* Ava UI: Add Notifications and Cleanup

* Revert notifications on ErrorDialog

* remove unused code from game list views

* Fix cast
2023-01-21 02:57:37 +01:00
8474d52778 Ava UI: Fix string.Format issues in Locale (#4305)
* Ava UI: Fix `string.Format` issues in Locale

* LoacLanguage everytime now

* Apply suggestions from code review

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* fix UpdateAndGetDynamicValue

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-01-21 02:06:19 +01:00
Phi
dd7a924596 Catch Profile.json parse to prevent crash on launch (#3393)
* Catch Profile.json parse to prevent crash on launch

* Update Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

Co-authored-by: PhiZero <wolkan.craanen@gmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-21 01:36:57 +01:00
a76eaf9a9a Ava UI: Add Control+Cmd+F HotKey for Mac OS (#4317)
* Ava UI: Add Control+Cmd+F HotKey for Mac OS

* fix aligned

* Remove comment from code
2023-01-20 22:18:01 +01:00
009e6bcd1b Audio: Implement PCM24 output (#4321) 2023-01-20 21:46:13 +01:00
eb2cc159fa Ava UI: Fixes and cleanup Updater (#4269)
* ava: Fixes and cleanup Updater

* _updateSuccessful
2023-01-20 21:30:21 +01:00
bb89e36fd8 Vulkan: Destroy old swapchain on swapchain recreation (#3889)
* Destroy old swapchain on swapchain recreation

* vkDeviceWaitIdle before DestroySwapchain

* Update Ryujinx.Graphics.Vulkan/Window.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Avoid unsafe code on RecreateSwapchain()

* Destroying old Swapchain on a queue.

* Cleanup and fix on destroying old Swapchain.

* Update Ryujinx.Graphics.Vulkan/Window.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Ryujinx.Graphics.Vulkan/Window.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Ryujinx.Graphics.Vulkan/Window.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Window.cs

Done.

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-19 21:31:25 -03:00
de3134adbe Vulkan: Explicitly enable precise occlusion queries (#4292)
The only guarantee of the occlusion query type in Vulkan is that it will be zero when no samples pass, and non-zero when any samples pass. Of course, most GPUs implement this by just placing the # of samples in the result and calling it a day. However, this lax restriction means that GPUs could just report a boolean (1/0) or report a value after one is recorded, but before all samples have been counted.

MoltenVK falls in the first category - by default it only reports 1/0 for occlusion queries. Thankfully, there is a feature and flag that you can use to force compatible drivers to provide a "precise" query result, that being the real # of samples passed.

Should fix ink collision in Splatoon 2/3 on MoltenVK.
2023-01-19 00:30:42 +00:00
36d53819a4 NativeSignalHandler: Fix write flag (#4306)
* NativeSignalHandler: Fix write flag

* address comments
2023-01-19 00:13:17 +00:00
ae4324032a Optimize string memory usage. Use Spans and StringBuilders where possible (#3933)
* Optimize string memory usage. Use ReadOnlySpan<char> and StringBuilder where possible.

* Fix copypaste error

* Code generator review fixes

* Use if statement instead of switch

* Code style fixes

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Another code style fix

* Styling fix

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Styling fix

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-18 22:25:16 +00:00
f449895e6d HOS: Load RomFs by pid (#4301)
We currently loading only one RomFs at a time, which could be wrong if one day we want to load more than one guest at time.
This PR fixes that by loading romfs by pid.
2023-01-18 13:50:42 +00:00
193 changed files with 2112 additions and 1704 deletions

3
.gitignore vendored
View File

@ -170,3 +170,6 @@ launchSettings.json
# NetCore Publishing Profiles # NetCore Publishing Profiles
PublishProfiles/ PublishProfiles/
# Glade backup files
*.glade~

View File

@ -16,4 +16,10 @@
</ContentWithTargetPath> </ContentWithTargetPath>
</ItemGroup> </ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Ryujinx.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,4 @@
using ARMeilleure.IntermediateRepresentation; using ARMeilleure.IntermediateRepresentation;
using System;
using System.Numerics; using System.Numerics;
namespace ARMeilleure.CodeGen.Arm64 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) 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; 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 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 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. // 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; immN = 0;
immS = 0; immS = 0;
@ -59,79 +61,18 @@ namespace ARMeilleure.CodeGen.Arm64
return false; 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) // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones
{ // in element.
bitLength += CountSequence(value >> bitLength); int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1));
} int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue);
int bitLengthLog2 = BitOperations.Log2((uint)bitLength); // Check the value is repeating; also ensures element size is a power of two.
int bitLengthPow2 = 1 << bitLengthLog2; if (ulong.RotateRight(value, elementSize) != value)
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)
{ {
immN = 0; immN = 0;
immS = 0; immS = 0;
@ -140,34 +81,11 @@ namespace ARMeilleure.CodeGen.Arm64
return false; return false;
} }
immR = rotation; immN = (elementSize >> 6) & 1;
immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f;
// immN indicates that there are no repetitions. immR = (elementSize - rotation) & (elementSize - 1);
// 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;
return true; 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));
}
} }
} }

View File

@ -1303,7 +1303,15 @@ namespace ARMeilleure.CodeGen.Arm64
private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value) 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; int hw = 0;
bool first = true; bool first = true;
@ -1328,10 +1336,6 @@ namespace ARMeilleure.CodeGen.Arm64
value >>= 16; value >>= 16;
} }
} }
else
{
context.Assembler.Mov(dest, Register(ZrRegister, dest.Type));
}
} }
private static void GenerateAtomicCas( private static void GenerateAtomicCas(

View File

@ -48,9 +48,21 @@ namespace ARMeilleure.CodeGen
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns> /// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T Map<T>() public T Map<T>()
{ {
IntPtr codePtr = JitCache.Map(this); return MapWithPointer<T>(out _);
}
return Marshal.GetDelegateForFunctionPointer<T>(codePtr); /// <summary>
/// Maps the <see cref="CompiledFunction"/> onto the <see cref="JitCache"/> and returns a delegate of type
/// <typeparamref name="T"/> pointing to the mapped function.
/// </summary>
/// <typeparam name="T">Type of delegate</typeparam>
/// <param name="codePointer">Pointer to the function code in memory</param>
/// <returns>A delegate of type <typeparamref name="T"/> pointing to the mapped function</returns>
public T MapWithPointer<T>(out IntPtr codePointer)
{
codePointer = JitCache.Map(this);
return Marshal.GetDelegateForFunctionPointer<T>(codePointer);
} }
} }
} }

View File

@ -1339,7 +1339,7 @@ namespace ARMeilleure.Decoders
private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) 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 = MakeOp reversedMakeOp =
(inst, address, opCode) (inst, address, opCode)
=> makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16)); => makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16));
@ -1353,7 +1353,7 @@ namespace ARMeilleure.Decoders
string thumbEncoding = encoding; string thumbEncoding = encoding;
if (thumbEncoding.StartsWith("<<<<")) if (thumbEncoding.StartsWith("<<<<"))
{ {
thumbEncoding = "1110" + thumbEncoding.Substring(4); thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}";
} }
SetT32(thumbEncoding, name, emitter, makeOpT32); SetT32(thumbEncoding, name, emitter, makeOpT32);
} }
@ -1365,19 +1365,19 @@ namespace ARMeilleure.Decoders
string thumbEncoding = encoding; string thumbEncoding = encoding;
if (thumbEncoding.StartsWith("11110100")) if (thumbEncoding.StartsWith("11110100"))
{ {
thumbEncoding = "11111001" + encoding.Substring(8); thumbEncoding = $"11111001{encoding.AsSpan(8)}";
} }
else if (thumbEncoding.StartsWith("1111001x")) else if (thumbEncoding.StartsWith("1111001x"))
{ {
thumbEncoding = "111x1111" + encoding.Substring(8); thumbEncoding = $"111x1111{encoding.AsSpan(8)}";
} }
else if (thumbEncoding.StartsWith("11110010")) else if (thumbEncoding.StartsWith("11110010"))
{ {
thumbEncoding = "11101111" + encoding.Substring(8); thumbEncoding = $"11101111{encoding.AsSpan(8)}";
} }
else if (thumbEncoding.StartsWith("11110011")) else if (thumbEncoding.StartsWith("11110011"))
{ {
thumbEncoding = "11111111" + encoding.Substring(8); thumbEncoding = $"11111111{encoding.AsSpan(8)}";
} }
else else
{ {

View File

@ -191,7 +191,7 @@ namespace ARMeilleure.Instructions
{ {
TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode);
return (ulong)function.FuncPtr.ToInt64(); return (ulong)function.FuncPointer.ToInt64();
} }
public static void InvalidateCacheLine(ulong address) public static void InvalidateCacheLine(ulong address)

View File

@ -109,12 +109,6 @@ namespace ARMeilleure.Signal
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) 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)); _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
if (customSignalHandlerFactory != null) if (customSignalHandlerFactory != null)
@ -251,18 +245,88 @@ namespace ARMeilleure.Signal
return context.Copy(inRegionLocal); 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) private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
{ {
EmitterContext context = new EmitterContext(); EmitterContext context = new EmitterContext();
// (int sig, SigInfo* sigInfo, void* ucontext) // (int sig, SigInfo* sigInfo, void* ucontext)
Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1); 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 faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr);
Operand structWriteOffset = context.Load(OperandType.I64, Const((ulong)signalStructPtr + StructWriteOffset)); Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr);
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 isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.

View File

@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";
@ -183,8 +183,8 @@ namespace ARMeilleure.Translation.PTC
private void PreLoad() private void PreLoad()
{ {
string fileNameActual = string.Concat(CachePathActual, ".cache"); string fileNameActual = $"{CachePathActual}.cache";
string fileNameBackup = string.Concat(CachePathBackup, ".cache"); string fileNameBackup = $"{CachePathBackup}.cache";
FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoActual = new FileInfo(fileNameActual);
FileInfo fileInfoBackup = new FileInfo(fileNameBackup); FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
@ -400,8 +400,8 @@ namespace ARMeilleure.Translation.PTC
try try
{ {
string fileNameActual = string.Concat(CachePathActual, ".cache"); string fileNameActual = $"{CachePathActual}.cache";
string fileNameBackup = string.Concat(CachePathBackup, ".cache"); string fileNameBackup = $"{CachePathBackup}.cache";
FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoActual = new FileInfo(fileNameActual);
@ -745,9 +745,9 @@ namespace ARMeilleure.Translation.PTC
bool highCq) bool highCq)
{ {
var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty); var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty);
var gFunc = cFunc.Map<GuestFunction>(); var gFunc = cFunc.MapWithPointer<GuestFunction>(out IntPtr gFuncPointer);
return new TranslatedFunction(gFunc, callCounter, guestSize, highCq); return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq);
} }
private void UpdateInfo(InfoEntry infoEntry) private void UpdateInfo(InfoEntry infoEntry)

View File

@ -125,8 +125,8 @@ namespace ARMeilleure.Translation.PTC
{ {
_lastHash = default; _lastHash = default;
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info"); string fileNameActual = $"{_ptc.CachePathActual}.info";
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info"); string fileNameBackup = $"{_ptc.CachePathBackup}.info";
FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoActual = new FileInfo(fileNameActual);
FileInfo fileInfoBackup = new FileInfo(fileNameBackup); FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
@ -246,8 +246,8 @@ namespace ARMeilleure.Translation.PTC
{ {
_waitEvent.Reset(); _waitEvent.Reset();
string fileNameActual = string.Concat(_ptc.CachePathActual, ".info"); string fileNameActual = $"{_ptc.CachePathActual}.info";
string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info"); string fileNameBackup = $"{_ptc.CachePathBackup}.info";
FileInfo fileInfoActual = new FileInfo(fileNameActual); FileInfo fileInfoActual = new FileInfo(fileNameActual);

View File

@ -1,6 +1,5 @@
using ARMeilleure.Common; using ARMeilleure.Common;
using System; using System;
using System.Runtime.InteropServices;
namespace ARMeilleure.Translation namespace ARMeilleure.Translation
{ {
@ -8,18 +7,18 @@ namespace ARMeilleure.Translation
{ {
private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected. private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
public IntPtr FuncPointer { get; }
public Counter<uint> CallCounter { get; } public Counter<uint> CallCounter { get; }
public ulong GuestSize { get; } public ulong GuestSize { get; }
public bool HighCq { get; } public bool HighCq { get; }
public IntPtr FuncPtr { get; }
public TranslatedFunction(GuestFunction func, Counter<uint> callCounter, ulong guestSize, bool highCq) public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter<uint> callCounter, ulong guestSize, bool highCq)
{ {
_func = func; _func = func;
FuncPointer = funcPointer;
CallCounter = callCounter; CallCounter = callCounter;
GuestSize = guestSize; GuestSize = guestSize;
HighCq = highCq; HighCq = highCq;
FuncPtr = Marshal.GetFunctionPointerForDelegate(func);
} }
public ulong Execute(State.ExecutionContext context) public ulong Execute(State.ExecutionContext context)

View File

@ -211,7 +211,7 @@ namespace ARMeilleure.Translation
if (oldFunc != func) if (oldFunc != func)
{ {
JitCache.Unmap(func.FuncPtr); JitCache.Unmap(func.FuncPointer);
func = oldFunc; func = oldFunc;
} }
@ -230,7 +230,7 @@ namespace ARMeilleure.Translation
{ {
if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq)) if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq))
{ {
Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPtr); Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer);
} }
} }
@ -292,11 +292,11 @@ namespace ARMeilleure.Translation
_ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
} }
GuestFunction func = compiledFunc.Map<GuestFunction>(); GuestFunction func = compiledFunc.MapWithPointer<GuestFunction>(out IntPtr funcPointer);
Allocators.ResetAll(); Allocators.ResetAll();
return new TranslatedFunction(func, counter, funcSize, highCq); return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq);
} }
private void BackgroundTranslate() private void BackgroundTranslate()
@ -537,7 +537,7 @@ namespace ARMeilleure.Translation
foreach (var func in functions) foreach (var func in functions)
{ {
JitCache.Unmap(func.FuncPtr); JitCache.Unmap(func.FuncPointer);
func.CallCounter?.Dispose(); func.CallCounter?.Dispose();
} }
@ -546,7 +546,7 @@ namespace ARMeilleure.Translation
while (_oldFuncs.TryDequeue(out var kv)) while (_oldFuncs.TryDequeue(out var kv))
{ {
JitCache.Unmap(kv.Value.FuncPtr); JitCache.Unmap(kv.Value.FuncPointer);
kv.Value.CallCounter?.Dispose(); kv.Value.CallCounter?.Dispose();
} }

View File

@ -20,7 +20,7 @@
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" /> <PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageVersion Include="LibHac" Version="0.17.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.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.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="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" /> <PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" /> <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.IO.FileSystem.Primitives" Version="4.3.0" />
<PackageVersion Include="System.Management" Version="7.0.0" /> <PackageVersion Include="System.Management" Version="7.0.0" />
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" /> <PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />

View File

@ -75,9 +75,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
return SampleFormat.PcmFloat; 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)) if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8))
{ {
return SampleFormat.PcmInt8; return SampleFormat.PcmInt8;

View File

@ -58,10 +58,13 @@ namespace Ryujinx.Audio.Backends.CompatLayer
switch (realSampleFormat) switch (realSampleFormat)
{ {
case SampleFormat.PcmInt8: 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; break;
case SampleFormat.PcmInt32: case SampleFormat.PcmInt32:
PcmHelper.Convert(MemoryMarshal.Cast<byte, int>(convertedSamples), samples); PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast<byte, int>(convertedSamples), samples);
break; break;
case SampleFormat.PcmFloat: case SampleFormat.PcmFloat:
PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples); PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast<byte, float>(convertedSamples), samples);

View File

@ -37,19 +37,32 @@ namespace Ryujinx.Audio.Renderer.Dsp
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TOutput ConvertSample<TInput, TOutput>(TInput value) where TInput: INumber<TInput>, IMinMaxValue<TInput> where TOutput : INumber<TOutput>, IMinMaxValue<TOutput> public static void ConvertSampleToPcm8(Span<sbyte> output, ReadOnlySpan<short> input)
{
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>
{ {
for (int i = 0; i < input.Length; i++) 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;
} }
} }

View File

@ -231,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) if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0)
{ {
@ -240,7 +240,7 @@ namespace Ryujinx.Ava
lock (_lockObject) lock (_lockObject)
{ {
DateTime currentTime = DateTime.Now; 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 string directory = AppDataManager.Mode switch
{ {
@ -462,8 +462,7 @@ namespace Ryujinx.Ava
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage], LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString),
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
""); "");
@ -493,10 +492,8 @@ namespace Ryujinx.Ava
_viewModel.RefreshFirmwareStatus(); _viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage], LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString),
firmwareVersion.VersionString), LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage],
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
"", "",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]); LocaleManager.Instance[LocaleKeys.RyujinxInfo]);

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?",
"DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", "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!", "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!",
"DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ",
"DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;",
"DialogDlcLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}",
"DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!",
"DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;",

View File

@ -26,6 +26,9 @@
"MenuBarToolsInstallFirmware": "Install Firmware", "MenuBarToolsInstallFirmware": "Install Firmware",
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
"MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory",
"MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarHelp": "Help", "MenuBarHelp": "Help",
"MenuBarHelpCheckForUpdates": "Check for Updates", "MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About", "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.", "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", "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed",
"DialogFirmwareInstalledMessage": "Firmware {0} was 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", "DialogOpenSettingsWindowLabel": "Open Settings Window",
"DialogControllerAppletTitle": "Controller Applet", "DialogControllerAppletTitle": "Controller Applet",
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
@ -375,7 +382,7 @@
"DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?",
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", "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!", "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.", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?",
@ -619,4 +626,4 @@
"UserProfilesRecoverEmptyList": "No profiles to recover", "UserProfilesRecoverEmptyList": "No profiles to recover",
"UserEditorTitle" : "Edit User", "UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User" "UserEditorTitleCreate" : "Create User"
} }

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?",
"DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.",
"DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", "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!", "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.", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?",
"DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", "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é !", "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.", "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 ?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?",
"DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", "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!", "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.", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか",
"DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.",
"DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?",
"DialogDlcLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}",
"DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!",
"DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?",
"DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.",
"DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?",
"DialogDlcLoadNcaErrorMessage": "{0}입니다. 오류 발생 파일: {1}", "DialogLoadNcaErrorMessage": "{0}입니다. 오류 발생 파일: {1}",
"DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!",
"DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로깅이 활성화되어 있습니다.", "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로깅이 활성화되어 있습니다.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로깅을 비활성화하는 것이 좋습니다. 지금 추적 로깅을 비활성화하겠습니까?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로깅을 비활성화하는 것이 좋습니다. 지금 추적 로깅을 비활성화하겠습니까?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil",
"DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", "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!", "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.", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado",
"DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", "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!", "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.", "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?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль",
"DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?",
"DialogDlcLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}",
"DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!",
"DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz",
"DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", "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!", "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.", "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?", "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?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль",
"DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.",
"DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?",
"DialogDlcLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}",
"DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!",
"DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户",
"DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新",
"DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?",
"DialogDlcLoadNcaErrorMessage": "{0}. 错误的文件: {1}", "DialogLoadNcaErrorMessage": "{0}. 错误的文件: {1}",
"DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC", "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC",
"DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?",

View File

@ -370,7 +370,7 @@
"DialogUserProfileDeletionConfirmMessage": "是否刪除選擇的帳號", "DialogUserProfileDeletionConfirmMessage": "是否刪除選擇的帳號",
"DialogControllerSettingsModifiedConfirmMessage": "目前的輸入預設已更新", "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入預設已更新",
"DialogControllerSettingsModifiedConfirmSubMessage": "要儲存嗎?", "DialogControllerSettingsModifiedConfirmSubMessage": "要儲存嗎?",
"DialogDlcLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}",
"DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC", "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC",
"DialogPerformanceCheckLoggingEnabledMessage": "您啟用了跟蹤日誌,僅供開發人員使用。", "DialogPerformanceCheckLoggingEnabledMessage": "您啟用了跟蹤日誌,僅供開發人員使用。",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用跟蹤日誌記錄。您是否要立即停用?", "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用跟蹤日誌記錄。您是否要立即停用?",

View File

@ -1,4 +1,5 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac; using LibHac;
using LibHac.Account; using LibHac.Account;
@ -12,7 +13,6 @@ using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@ -44,14 +44,11 @@ namespace Ryujinx.Ava.Common
_accountManager = accountManager; _accountManager = accountManager;
} }
private static bool TryFindSaveData(string titleName, ulong titleId, private static bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
BlitStruct<ApplicationControlProperty> controlHolder, in SaveDataFilter filter, out ulong saveDataId)
{ {
saveDataId = default; saveDataId = default;
Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter);
SaveDataSpaceId.User, in filter);
if (ResultFs.TargetNotFound.Includes(result)) if (ResultFs.TargetNotFound.Includes(result))
{ {
ref ApplicationControlProperty control = ref controlHolder.Value; ref ApplicationControlProperty control = ref controlHolder.Value;
@ -68,20 +65,17 @@ namespace Ryujinx.Ava.Common
control.UserAccountSaveDataSize = 0x4000; control.UserAccountSaveDataSize = 0x4000;
control.UserAccountSaveDataJournalSize = 0x4000; control.UserAccountSaveDataJournalSize = 0x4000;
Logger.Warning?.Print(LogClass.Application, 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.");
"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); result = _horizonClient.Fs.EnsureApplicationSaveData(out _, new LibHac.Ncm.ApplicationId(titleId), in control, in user);
if (result.IsFailure()) if (result.IsFailure())
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog( await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageCreateSaveErrorMessage, result.ToStringWithName()));
string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageCreateSaveErrorMessage], result.ToStringWithName()));
}); });
return false; return false;
@ -98,16 +92,15 @@ namespace Ryujinx.Ava.Common
return true; 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; return false;
} }
public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, public static void OpenSaveDir(in SaveDataFilter saveDataFilter, ulong titleId, BlitStruct<ApplicationControlProperty> controlData, string titleName)
BlitStruct<ApplicationControlProperty> controlData, string titleName)
{ {
if (!TryFindSaveData(titleName, titleId, controlData, in saveDataFilter, out ulong saveDataId)) 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, public static async Task ExtractSection(NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
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); string destination = await folderDialog.ShowAsync(_owner);
var cancellationToken = new CancellationTokenSource();
var cancellationToken = new CancellationTokenSource();
if (!string.IsNullOrWhiteSpace(destination)) if (!string.IsNullOrWhiteSpace(destination))
{ {
@ -164,7 +158,7 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( 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], LocaleManager.Instance[LocaleKeys.InputDialogCancel],
@ -175,133 +169,122 @@ namespace Ryujinx.Ava.Common
cancellationToken.Cancel(); 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; PartitionFileSystem pfs;
Nca patchNca = null;
string extension = Path.GetExtension(titleFilePath).ToLower(); if (extension == ".xci")
if (extension == ".nsp" || extension == ".pfs0" || 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()); int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
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 = patchNca = nca;
Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); }
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection()) else
{ {
patchNca = nca; mainNca = nca;
}
else
{
mainNca = nca;
}
} }
} }
} }
else if (extension == ".nca") }
{ else if (extension == ".nca")
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage()); {
} 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, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionMainNcaNotFoundErrorMessage]);
"Extraction failure. The main NCA was not present in the selected file"); });
Dispatcher.UIThread.InvokeAsync(async () =>
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]); Logger.Error?.Print(LogClass.Application, $"LibHac returned error code: {resultCode.Value.ErrorCode}");
});
return;
}
(Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, Dispatcher.UIThread.InvokeAsync(async () =>
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())
{ {
Logger.Error?.Print(LogClass.Application, await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogNcaExtractionCheckLogErrorMessage]);
$"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]);
});
}
} }
else if (resultCode.Value.IsSuccess())
fsClient.Unmount(source.ToU8Span());
fsClient.Unmount(output.ToU8Span());
}
catch (ArgumentException ex)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{ {
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);
});
} }
}); });

View File

@ -20,7 +20,7 @@ namespace Ryujinx.Ava.Common.Locale
ReflectionBindingExtension binding = new($"[{keyToUse}]") ReflectionBindingExtension binding = new($"[{keyToUse}]")
{ {
Mode = BindingMode.OneWay, Mode = BindingMode.OneWay,
Source = LocaleManager.Instance Source = LocaleManager.Instance
}; };

View File

@ -13,56 +13,87 @@ namespace Ryujinx.Ava.Common.Locale
{ {
private const string DefaultLanguageCode = "en_US"; private const string DefaultLanguageCode = "en_US";
private Dictionary<LocaleKeys, string> _localeStrings; private Dictionary<LocaleKeys, string> _localeStrings;
private ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues; private Dictionary<LocaleKeys, string> _localeDefaultStrings;
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
public static LocaleManager Instance { get; } = new LocaleManager(); public static LocaleManager Instance { get; } = new LocaleManager();
public Dictionary<LocaleKeys, string> LocaleStrings { get => _localeStrings; set => _localeStrings = value; }
public LocaleManager() public LocaleManager()
{ {
_localeStrings = new Dictionary<LocaleKeys, string>(); _localeStrings = new Dictionary<LocaleKeys, string>();
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>(); _localeDefaultStrings = new Dictionary<LocaleKeys, string>();
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
Load(); Load();
} }
public void Load() public void Load()
{ {
// Load the system Language Code.
string localeLanguageCode = CultureInfo.CurrentCulture.Name.Replace('-', '_'); 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 (Program.PreviewerDetached)
{ {
if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value)) if (!string.IsNullOrEmpty(ConfigurationState.Instance.Ui.LanguageCode.Value))
{ {
localeLanguageCode = 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(); LoadDefaultLanguage();
if (localeLanguageCode != DefaultLanguageCode) LoadLanguage(localeLanguageCode);
{
LoadLanguage(localeLanguageCode);
}
} }
public string this[LocaleKeys key] public string this[LocaleKeys key]
{ {
get get
{ {
// Check if the locale contains the key.
if (_localeStrings.TryGetValue(key, out string value)) if (_localeStrings.TryGetValue(key, out string value))
{ {
// Check if the localized string needs to be formatted.
if (_dynamicValues.TryGetValue(key, out var dynamicValue)) 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; 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(); return key.ToString();
} }
set 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; _dynamicValues[key] = values;
OnPropertyChanged("Item"); OnPropertyChanged("Item");
return this[key];
} }
public void LoadDefaultLanguage() private void LoadDefaultLanguage()
{ {
LoadLanguage(DefaultLanguageCode); _localeDefaultStrings = LoadJsonLanguage();
} }
public void LoadLanguage(string languageCode) public void LoadLanguage(string languageCode)
{ {
string languageJson = EmbeddedResources.ReadAllText($"Ryujinx.Ava/Assets/Locales/{languageCode}.json"); foreach (var item in LoadJsonLanguage(languageCode))
if (languageJson == null)
{ {
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) foreach (var item in strings)
{ {
if (Enum.TryParse<LocaleKeys>(item.Key, out var key)) if (Enum.TryParse<LocaleKeys>(item.Key, out var key))
{ {
this[key] = item.Value; localeStrings[key] = item.Value;
} }
} }
if (Program.PreviewerDetached) return localeStrings;
{
ConfigurationState.Instance.Ui.LanguageCode.Value = languageCode;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
@ -30,6 +31,7 @@ namespace Ryujinx.Ava.Input
_control.KeyDown += OnKeyPress; _control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease; _control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput; _control.TextInput += Control_TextInput;
_control.AddHandler(InputElement.TextInputEvent, Control_LastChanceTextInput, RoutingStrategies.Bubble);
} }
private void Control_TextInput(object sender, TextInputEventArgs e) private void Control_TextInput(object sender, TextInputEventArgs e)
@ -37,6 +39,12 @@ namespace Ryujinx.Ava.Input
TextInput?.Invoke(this, e.Text); TextInput?.Invoke(this, e.Text);
} }
private void Control_LastChanceTextInput(object sender, TextInputEventArgs e)
{
// Swallow event
e.Handled = true;
}
public event Action<string> OnGamepadConnected public event Action<string> OnGamepadConnected
{ {
add { } add { }

View File

@ -7,9 +7,7 @@ using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Ryujinx.Ava; using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
@ -32,31 +30,29 @@ namespace Ryujinx.Modules
internal static class Updater internal static class Updater
{ {
private const string GitHubApiURL = "https://api.github.com"; private const string GitHubApiURL = "https://api.github.com";
internal static bool Running;
private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish"); 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 _buildVer;
private static string _platformExt; private static string _platformExt;
private static string _buildUrl; 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>(); private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
public static bool UpdateSuccessful { get; private set; } public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
{ {
if (Running) if (_running)
{ {
return; return;
} }
Running = true; _running = true;
mainWindow.ViewModel.CanUpdate = false;
// Detect current platform // Detect current platform
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
@ -82,77 +78,87 @@ namespace Ryujinx.Modules
catch catch
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () => 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; return;
} }
// Get latest version number from GitHub API // Get latest version number from GitHub API
try 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); if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
JObject jsonRoot = JObject.Parse(fetchedJson);
JToken assets = jsonRoot["assets"];
_buildVer = (string)jsonRoot["name"];
foreach (JToken asset in assets)
{ {
string assetName = (string)asset["name"]; _buildUrl = downloadURL;
string assetState = (string)asset["state"];
string downloadURL = (string)asset["browser_download_url"];
if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt)) if (assetState != "uploaded")
{ {
_buildUrl = downloadURL; if (showVersionUpToDate)
if (assetState != "uploaded")
{ {
if (showVersionUpToDate) Dispatcher.UIThread.Post(async () =>
{ {
Dispatcher.UIThread.Post(async () => await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
{ });
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
}
return;
} }
break; _running = false;
}
}
// If build not done, assume no new update are availaible. return;
if (_buildUrl == null) }
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], "");
{ });
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
});
}
return;
} }
_running = false;
return;
} }
} }
catch (Exception exception) catch (Exception exception)
{ {
Logger.Error?.Print(LogClass.Application, exception.Message); Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
}); });
_running = false;
return; return;
} }
@ -163,11 +169,16 @@ namespace Ryujinx.Modules
catch catch
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () => 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; return;
} }
@ -181,8 +192,7 @@ namespace Ryujinx.Modules
}); });
} }
Running = false; _running = false;
mainWindow.ViewModel.CanUpdate = true;
return; return;
} }
@ -210,7 +220,8 @@ namespace Ryujinx.Modules
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
// Show a message asking the user if they want to update // 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], LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
$"{Program.Version} -> {newVersion}"); $"{Program.Version} -> {newVersion}");
@ -218,12 +229,16 @@ namespace Ryujinx.Modules
{ {
UpdateRyujinx(mainWindow, _buildUrl); UpdateRyujinx(mainWindow, _buildUrl);
} }
else
{
_running = false;
}
}); });
} }
private static HttpClient ConstructHttpClient() private static HttpClient ConstructHttpClient()
{ {
HttpClient result = new HttpClient(); HttpClient result = new();
// Required by GitHub to interract with APIs. // Required by GitHub to interract with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0"); 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) 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 // Empty update dir, although it shouldn't ever have anything inside it
if (Directory.Exists(UpdateDir)) if (Directory.Exists(UpdateDir))
@ -245,17 +260,16 @@ namespace Ryujinx.Modules
string updateFile = Path.Combine(UpdateDir, "update.bin"); string updateFile = Path.Combine(UpdateDir, "update.bin");
var taskDialog = new TaskDialog() TaskDialog taskDialog = new()
{ {
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
IconSource = new SymbolIconSource { Symbol = Symbol.Download }, IconSource = new SymbolIconSource { Symbol = Symbol.Download },
Buttons = { }, Buttons = { },
ShowProgressBar = true ShowProgressBar = true,
XamlRoot = parent
}; };
taskDialog.XamlRoot = parent;
taskDialog.Opened += (s, e) => taskDialog.Opened += (s, e) =>
{ {
if (_buildSize >= 0) if (_buildSize >= 0)
@ -270,7 +284,7 @@ namespace Ryujinx.Modules
await taskDialog.ShowAsync(true); await taskDialog.ShowAsync(true);
if (UpdateSuccessful) if (_updateSuccessful)
{ {
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater], var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
@ -279,7 +293,7 @@ namespace Ryujinx.Modules
if (shouldRestart) if (shouldRestart)
{ {
string ryuName = Path.GetFileName(Environment.ProcessPath); 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)) if (!Path.Exists(ryuExe))
{ {
@ -298,15 +312,15 @@ namespace Ryujinx.Modules
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
{ {
// Multi-Threaded Updater // Multi-Threaded Updater
long chunkSize = _buildSize / ConnectionCount; long chunkSize = _buildSize / ConnectionCount;
long remainderChunk = _buildSize % ConnectionCount; long remainderChunk = _buildSize % ConnectionCount;
int completedRequests = 0; int completedRequests = 0;
int totalProgressPercentage = 0; int totalProgressPercentage = 0;
int[] progressPercentage = new int[ConnectionCount]; int[] progressPercentage = new int[ConnectionCount];
List<byte[]> list = new List<byte[]>(ConnectionCount); List<byte[]> list = new(ConnectionCount);
List<WebClient> webClients = new List<WebClient>(ConnectionCount); List<WebClient> webClients = new(ConnectionCount);
for (int i = 0; i < ConnectionCount; i++) for (int i = 0; i < ConnectionCount; i++)
{ {
@ -317,133 +331,129 @@ namespace Ryujinx.Modules
{ {
#pragma warning disable SYSLIB0014 #pragma warning disable SYSLIB0014
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. // 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 #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}"); webClients[index].Dispose();
}
else
{
client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
}
client.DownloadProgressChanged += (_, args) => taskDialog.Hide();
{
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);
return; 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) 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 using Stream updateFileStream = File.Open(updateFile, FileMode.Create);
client.Timeout = TimeSpan.FromDays(1);
using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result) long totalBytes = response.Content.Headers.ContentLength.Value;
using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result) 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; break;
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);
}
} }
}
InstallUpdate(taskDialog, updateFile); byteWritten += readSize;
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
updateFileStream.Write(buffer, 0, readSize);
}
} }
InstallUpdate(taskDialog, updateFile);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -454,8 +464,11 @@ namespace Ryujinx.Modules
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
{ {
Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile)); Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
worker.Name = "Updater.SingleThreadWorker"; {
Name = "Updater.SingleThreadWorker"
};
worker.Start(); worker.Start();
} }
@ -483,72 +496,70 @@ namespace Ryujinx.Modules
if (OperatingSystem.IsLinux()) if (OperatingSystem.IsLinux())
{ {
using (Stream inStream = File.OpenRead(updateFile)) using Stream inStream = File.OpenRead(updateFile);
using (Stream gzipStream = new GZipInputStream(inStream)) using GZipInputStream gzipStream = new(inStream);
using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII)) using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
await Task.Run(() =>
{ {
await Task.Run(() => TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) != null)
{ {
TarEntry tarEntry; if (tarEntry.IsDirectory) continue;
while ((tarEntry = tarStream.GetNextEntry()) != null)
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using (FileStream outStream = File.OpenWrite(outPath))
{ {
if (tarEntry.IsDirectory) continue; tarStream.CopyEntryContents(outStream);
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);
});
} }
});
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 else
{ {
using (Stream inStream = File.OpenRead(updateFile)) using Stream inStream = File.OpenRead(updateFile);
using (ZipFile zipFile = new ZipFile(inStream)) using ZipFile zipFile = new(inStream);
await Task.Run(() =>
{ {
await Task.Run(() => double count = 0;
foreach (ZipEntry zipEntry in zipFile)
{ {
double count = 0; count++;
foreach (ZipEntry zipEntry in zipFile) 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++; zipStream.CopyTo(outStream);
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);
});
} }
});
} File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() =>
{
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
});
}
});
} }
// Delete downloaded zip // Delete downloaded zip
@ -577,7 +588,7 @@ namespace Ryujinx.Modules
} }
catch 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")); SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
UpdateSuccessful = true; _updateSuccessful = true;
taskDialog.Hide(); 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)
public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
{ {
#if !DISABLE_UPDATER #if !DISABLE_UPDATER
if (RuntimeInformation.OSArchitecture != Architecture.X64) if (RuntimeInformation.OSArchitecture != Architecture.X64)
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage], Dispatcher.UIThread.Post(async () =>
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]); {
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
});
} }
return false; return false;
@ -618,8 +632,12 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], Dispatcher.UIThread.Post(async () =>
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]); {
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
});
} }
return false; return false;
@ -629,8 +647,12 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], Dispatcher.UIThread.Post(async () =>
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]); {
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
});
} }
return false; return false;
@ -642,18 +664,27 @@ namespace Ryujinx.Modules
{ {
if (ReleaseInformation.IsFlatHubBuild()) 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 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; return false;
#endif #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 // NOTE: This method should always reflect the latest build layout.s
private static IEnumerable<string> EnumerateFilesToDelete() private static IEnumerable<string> EnumerateFilesToDelete()
@ -677,7 +708,7 @@ namespace Ryujinx.Modules
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) 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)) foreach (string directory in Directory.GetDirectories(root))
{ {
string dirName = Path.GetFileName(directory); string dirName = Path.GetFileName(directory);
@ -694,6 +725,7 @@ namespace Ryujinx.Modules
foreach (string file in Directory.GetFiles(root)) foreach (string file in Directory.GetFiles(root))
{ {
count++; count++;
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>

View File

@ -14,10 +14,8 @@ using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava namespace Ryujinx.Ava
@ -35,48 +33,6 @@ namespace Ryujinx.Ava
private const uint MB_ICONWARNING = 0x30; 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) public static void Main(string[] args)
{ {
Version = ReleaseInformation.GetVersion(); Version = ReleaseInformation.GetVersion();
@ -139,12 +95,6 @@ namespace Ryujinx.Ava
// Initialize the logger system. // Initialize the logger system.
LoggerModule.Initialize(); LoggerModule.Initialize();
// Register mime types on linux.
if (OperatingSystem.IsLinux())
{
RegisterMimeTypes();
}
// Initialize Discord integration. // Initialize Discord integration.
DiscordIntegrationModule.Initialize(); DiscordIntegrationModule.Initialize();

View File

@ -29,17 +29,12 @@ namespace Ryujinx.Ava.UI.Applet
public bool DisplayMessageDialog(ControllerAppletUiArgs args) public bool DisplayMessageDialog(ControllerAppletUiArgs args)
{ {
string playerCount = args.PlayerCountMin == args.PlayerCountMax string message = LocaleManager.Instance.UpdateAndGetDynamicValue(
? args.PlayerCountMin.ToString() args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange,
: $"{args.PlayerCountMin}-{args.PlayerCountMax}"; args.PlayerCountMin == args.PlayerCountMax ? args.PlayerCountMin.ToString() : $"{args.PlayerCountMin}-{args.PlayerCountMax}",
args.SupportedStyles,
LocaleKeys key = args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange; string.Join(", ", args.SupportedPlayers),
args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
string message = string.Format(LocaleManager.Instance[key],
playerCount,
args.SupportedStyles,
string.Join(", ", args.SupportedPlayers),
args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle], message); return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.DialogControllerAppletTitle], message);
} }
@ -92,7 +87,7 @@ namespace Ryujinx.Ava.UI.Applet
} }
catch (Exception ex) catch (Exception ex)
{ {
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogMessageDialogErrorExceptionMessage], ex)); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
dialogCloseEvent.Set(); dialogCloseEvent.Set();
} }
@ -126,7 +121,8 @@ namespace Ryujinx.Ava.UI.Applet
catch (Exception ex) catch (Exception ex)
{ {
error = true; error = true;
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage], ex));
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
} }
finally finally
{ {
@ -181,7 +177,8 @@ namespace Ryujinx.Ava.UI.Applet
catch (Exception ex) catch (Exception ex)
{ {
dialogCloseEvent.Set(); dialogCloseEvent.Set();
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogErrorAppletErrorExceptionMessage], ex));
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
} }
}); });

View File

@ -139,14 +139,16 @@ namespace Ryujinx.Ava.UI.Controls
else if (_inputMin > 0 && _inputMax == int.MaxValue) else if (_inputMin > 0 && _inputMax == int.MaxValue)
{ {
Error.IsVisible = true; Error.IsVisible = true;
Error.Text = string.Format(LocaleManager.Instance[LocaleKeys.SwkbdMinCharacters], _inputMin);
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
_checkLength = length => _inputMin <= length; _checkLength = length => _inputMin <= length;
} }
else else
{ {
Error.IsVisible = true; 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; _checkLength = length => _inputMin <= length && length <= _inputMax;
} }

View File

@ -14,7 +14,7 @@
Focusable="True"> Focusable="True">
<UserControl.Resources> <UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" /> <helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> <MenuFlyout x:Key="GameContextMenu">
<MenuItem <MenuItem
Command="{Binding ToggleFavorite}" Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
@ -22,14 +22,17 @@
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenUserSaveDirectory}" Command="{Binding OpenUserSaveDirectory}"
IsEnabled="{Binding EnabledUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDeviceSaveDirectory}" Command="{Binding OpenDeviceSaveDirectory}"
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenBcatSaveDirectory}" Command="{Binding OpenBcatSaveDirectory}"
IsEnabled="{Binding EnabledBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator /> <Separator />

View File

@ -1,9 +1,7 @@
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using LibHac.Common;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
{ {
public partial class GameGridView : UserControl public partial class GameGridView : UserControl
{ {
private ApplicationData _selectedApplication;
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{ {
add { AddHandler(ApplicationOpenedEvent, value); } add { AddHandler(ApplicationOpenedEvent, value); }
remove { RemoveHandler(ApplicationOpenedEvent, value); } remove { RemoveHandler(ApplicationOpenedEvent, value); }
} }
public GameGridView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void GameList_DoubleTapped(object sender, RoutedEventArgs args) public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
{ {
if (sender is ListBox listBox) if (sender is ListBox listBox)
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
{ {
if (sender is ListBox listBox) if (sender is ListBox listBox)
{ {
_selectedApplication = listBox.SelectedItem as ApplicationData; (DataContext as MainWindowViewModel).GridSelectedApplication = listBox.SelectedItem as ApplicationData;
(DataContext as MainWindowViewModel).GridSelectedApplication = _selectedApplication;
} }
} }
public ApplicationData SelectedApplication => _selectedApplication;
public GameGridView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{ {
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; (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;
}
}
}
} }
} }

View File

@ -13,7 +13,7 @@
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<helpers:BitmapArrayValueConverter x:Key="ByteImage" /> <helpers:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened"> <MenuFlyout x:Key="GameContextMenu">
<MenuItem <MenuItem
Command="{Binding ToggleFavorite}" Command="{Binding ToggleFavorite}"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
@ -21,14 +21,17 @@
<Separator /> <Separator />
<MenuItem <MenuItem
Command="{Binding OpenUserSaveDirectory}" Command="{Binding OpenUserSaveDirectory}"
IsEnabled="{Binding EnabledUserSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenDeviceSaveDirectory}" Command="{Binding OpenDeviceSaveDirectory}"
IsEnabled="{Binding EnabledDeviceSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Command="{Binding OpenBcatSaveDirectory}" Command="{Binding OpenBcatSaveDirectory}"
IsEnabled="{Binding EnabledBcatSaveDirectory}"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}" Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator /> <Separator />

View File

@ -1,9 +1,7 @@
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using LibHac.Common;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
@ -13,16 +11,25 @@ namespace Ryujinx.Ava.UI.Controls
{ {
public partial class GameListView : UserControl public partial class GameListView : UserControl
{ {
private ApplicationData _selectedApplication;
public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent = public static readonly RoutedEvent<ApplicationOpenedEventArgs> ApplicationOpenedEvent =
RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble); RoutedEvent.Register<GameGridView, ApplicationOpenedEventArgs>(nameof(ApplicationOpened), RoutingStrategies.Bubble);
public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened public event EventHandler<ApplicationOpenedEventArgs> ApplicationOpened
{ {
add { AddHandler(ApplicationOpenedEvent, value); } add { AddHandler(ApplicationOpenedEvent, value); }
remove { RemoveHandler(ApplicationOpenedEvent, value); } remove { RemoveHandler(ApplicationOpenedEvent, value); }
} }
public GameListView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void GameList_DoubleTapped(object sender, RoutedEventArgs args) public void GameList_DoubleTapped(object sender, RoutedEventArgs args)
{ {
if (sender is ListBox listBox) if (sender is ListBox listBox)
@ -38,46 +45,13 @@ namespace Ryujinx.Ava.UI.Controls
{ {
if (sender is ListBox listBox) if (sender is ListBox listBox)
{ {
_selectedApplication = listBox.SelectedItem as ApplicationData; (DataContext as MainWindowViewModel).ListSelectedApplication = listBox.SelectedItem as ApplicationData;
(DataContext as MainWindowViewModel).ListSelectedApplication = _selectedApplication;
} }
} }
public ApplicationData SelectedApplication => _selectedApplication;
public GameListView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e) private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{ {
(DataContext as MainWindowViewModel).SearchText = (sender as TextBox).Text; (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;
}
}
}
} }
} }

View 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);
}
}
}

View File

@ -76,11 +76,11 @@ namespace Ryujinx.Ava.UI.Helpers
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : ""; string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance[LocaleKeys.OpenSetupGuideMessage] : "";
var result = await ContentDialogHelper.CreateInfoDialog( 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 GetErrorDescription(error) + (isInSetupGuide
? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage] ? LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogInfoMessage]
: ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk], : ""), setupButtonLabel, LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Format(LocaleManager.Instance[LocaleKeys.DialogUserErrorDialogTitle], errorCode)); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode));
if (result == UserResult.Ok) if (result == UserResult.Ok)
{ {

View File

@ -8,7 +8,7 @@ namespace Ryujinx.Ava.UI.Models
public ApplicationControlProperty Control { get; } public ApplicationControlProperty Control { get; }
public string Path { get; } public string Path { get; }
public string Label => string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString()); public string Label => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleUpdateVersionLabel, Control.DisplayVersionString.ToString());
public TitleUpdateModel(ApplicationControlProperty control, string path) public TitleUpdateModel(ApplicationControlProperty control, string path)
{ {

View File

@ -140,68 +140,75 @@ namespace Ryujinx.Ava.UI.Renderer
{ {
if (VisualRoot != null) if (VisualRoot != null)
{ {
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value; if (msg == WindowsMessages.LBUTTONDOWN ||
Pointer pointer = new(0, PointerType.Mouse, true); msg == WindowsMessages.RBUTTONDOWN ||
msg == WindowsMessages.LBUTTONUP ||
switch (msg) msg == WindowsMessages.RBUTTONUP ||
msg == WindowsMessages.MOUSEMOVE)
{ {
case WindowsMessages.LBUTTONDOWN: Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
case WindowsMessages.RBUTTONDOWN: Pointer pointer = new(0, PointerType.Mouse, true);
{
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( switch (msg)
this, {
pointer, case WindowsMessages.LBUTTONDOWN:
VisualRoot, case WindowsMessages.RBUTTONDOWN:
rootVisualPosition, {
(ulong)Environment.TickCount64, bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
properties, RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
KeyModifiers.None); PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
RaiseEvent(evnt); var evnt = new PointerPressedEventArgs(
this,
pointer,
VisualRoot,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
KeyModifiers.None);
break; RaiseEvent(evnt);
}
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( break;
this, }
pointer, case WindowsMessages.LBUTTONUP:
VisualRoot, case WindowsMessages.RBUTTONUP:
rootVisualPosition, {
(ulong)Environment.TickCount64, bool isLeft = msg == WindowsMessages.LBUTTONUP;
properties, RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
KeyModifiers.None, PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
isLeft ? MouseButton.Left : MouseButton.Right);
RaiseEvent(evnt); var evnt = new PointerReleasedEventArgs(
this,
pointer,
VisualRoot,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
KeyModifiers.None,
isLeft ? MouseButton.Left : MouseButton.Right);
break; RaiseEvent(evnt);
}
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;
}
case WindowsMessages.MOUSEMOVE:
{
var evnt = new PointerEventArgs(
PointerMovedEvent,
this,
pointer,
VisualRoot,
rootVisualPosition,
(ulong)Environment.TickCount64,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
KeyModifiers.None);
break; RaiseEvent(evnt);
}
break;
}
}
} }
} }

View File

@ -81,10 +81,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public string Developers public string Developers => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.AboutPageDeveloperListMore, "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
{
get => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
}
public AboutWindowViewModel() public AboutWindowViewModel()
{ {

View File

@ -3,6 +3,8 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac.Bcat;
using LibHac.Tools.Fs;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
@ -435,7 +437,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (str.Length > MaxSize) if (str.Length > MaxSize)
{ {
return str.Substring(0, MaxSize - Ellipsis.Length) + Ellipsis; return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}";
} }
return str; 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."); 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; return;
} }

View File

@ -5,8 +5,10 @@ using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Tools.Fs;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
@ -88,7 +90,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private float _volume; private float _volume;
private string _backendText; private string _backendText;
private bool _canUpdate; private bool _canUpdate = true;
private Cursor _cursor; private Cursor _cursor;
private string _title; private string _title;
private string _currentEmulatedGamePath; private string _currentEmulatedGamePath;
@ -177,11 +179,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanUpdate public bool CanUpdate
{ {
get => _canUpdate; get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false);
set set
{ {
_canUpdate = value; _canUpdate = value;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -343,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 public string LoadHeading
{ {
get => _loadHeading; get => _loadHeading;
@ -676,6 +683,11 @@ namespace Ryujinx.Ava.UI.ViewModels
get => ConsoleHelper.SetConsoleWindowStateSupported; get => ConsoleHelper.SetConsoleWindowStateSupported;
} }
public bool ManageFileTypesVisible
{
get => FileAssociationHelper.IsTypeAssociationSupported;
}
public ObservableCollection<ApplicationData> Applications public ObservableCollection<ApplicationData> Applications
{ {
get => _applications; get => _applications;
@ -734,19 +746,14 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
get get
{ {
switch (ConfigurationState.Instance.Ui.GridSize) return ConfigurationState.Instance.Ui.GridSize.Value switch
{ {
case 1: 1 => 78,
return 78; 2 => 100,
case 2: 3 => 120,
return 100; 4 => 140,
case 3: _ => 16,
return 120; };
case 4:
return 140;
default:
return 16;
}
} }
} }
@ -754,19 +761,14 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
get get
{ {
switch (ConfigurationState.Instance.Ui.GridSize) return ConfigurationState.Instance.Ui.GridSize.Value switch
{ {
case 1: 1 => 120,
return 120; 2 => ShowNames ? 210 : 150,
case 2: 3 => ShowNames ? 240 : 180,
return ShowNames ? 210 : 150; 4 => ShowNames ? 280 : 220,
case 3: _ => 16,
return ShowNames ? 240 : 180; };
case 4:
return ShowNames ? 280 : 220;
default:
return 16;
}
} }
} }
@ -948,20 +950,18 @@ namespace Ryujinx.Ava.UI.ViewModels
if (firmwareVersion == null) if (firmwareVersion == null)
{ {
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage], filename)); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename));
return; 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(); SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage], firmwareVersion.VersionString);
if (currentVersion != null) 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]; dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage];
@ -994,7 +994,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
waitingDialog.Close(); 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]); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
@ -1064,7 +1064,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case LoadState.Loaded: case LoadState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = "";
break; break;
@ -1080,7 +1080,7 @@ namespace Ryujinx.Ava.UI.ViewModels
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case ShaderCacheLoadingState.Loaded: case ShaderCacheLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = "";
break; break;
@ -1092,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() private async void ExtractLogo()
{ {
var selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, selection.Path); await ApplicationHelper.ExtractSection(NcaSectionType.Logo, SelectedApplication.Path, SelectedApplication.TitleName);
} }
} }
private async void ExtractRomFs() private async void ExtractRomFs()
{ {
var selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
await ApplicationHelper.ExtractSection(NcaSectionType.Data, selection.Path); await ApplicationHelper.ExtractSection(NcaSectionType.Data, SelectedApplication.Path, SelectedApplication.TitleName);
} }
} }
private async void ExtractExeFs() private async void ExtractExeFs()
{ {
var selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
await ApplicationHelper.ExtractSection(NcaSectionType.Code, selection.Path); await ApplicationHelper.ExtractSection(NcaSectionType.Code, SelectedApplication.Path, SelectedApplication.TitleName);
} }
} }
@ -1334,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)languageCode);
LocaleManager.Instance.LoadLanguage((string)obj);
if (Program.PreviewerDetached)
{
ConfigurationState.Instance.Ui.LanguageCode.Value = (string)languageCode;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
} }
public async void ManageProfiles() public async void ManageProfiles()
@ -1375,7 +1372,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], 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.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
@ -1402,7 +1399,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
catch (Exception e) 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));
} }
} }
} }
@ -1439,7 +1436,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], 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.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
@ -1464,7 +1461,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
catch (Exception e) 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));
} }
} }
} }
@ -1477,62 +1474,12 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
catch (Exception e) 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() public void ToggleFavorite()
{ {
ApplicationData selection = SelectedApplication; ApplicationData selection = SelectedApplication;
@ -1551,37 +1498,45 @@ namespace Ryujinx.Ava.UI.ViewModels
public void OpenUserSaveDirectory() public void OpenUserSaveDirectory()
{ {
ApplicationData selection = SelectedApplication; OpenSaveDirectory(SaveDataType.Account, userId: new UserId((ulong)AccountManager.LastOpenedUser.UserId.High, (ulong)AccountManager.LastOpenedUser.UserId.Low));
if (selection != null) }
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); ApplicationHelper.OpenSaveDir(in saveDataFilter, titleIdNumber, SelectedApplication.ControlHolder, SelectedApplication.TitleName);
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
} }
} }
public void OpenModsDirectory() public void OpenModsDirectory()
{ {
ApplicationData selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath(); string modsBasePath = VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId); string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
} }
@ -1589,12 +1544,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public void OpenSdModsDirectory() public void OpenSdModsDirectory()
{ {
ApplicationData selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath(); string sdModsBasePath = VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId); string titleModsPath = VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, SelectedApplication.TitleId);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
} }
@ -1610,25 +1563,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void OpenDownloadableContentManager() public async void OpenDownloadableContentManager()
{ {
ApplicationData selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
{
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
}
} }
} }
public async void OpenCheatManager() public async void OpenCheatManager()
{ {
ApplicationData selection = SelectedApplication; if (SelectedApplication != null)
if (selection != null)
{ {
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) await new CheatWindow(VirtualFileSystem, SelectedApplication.TitleId, SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
{
await new CheatWindow(VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(desktop.MainWindow);
}
} }
} }
@ -1642,7 +1587,7 @@ namespace Ryujinx.Ava.UI.ViewModels
StatusBarProgressMaximum = 0; StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0; StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
}); });
ReloadGameList?.Invoke(); ReloadGameList?.Invoke();
@ -1756,8 +1701,14 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
CanUpdate = false; 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); SwitchToRenderer(startFullscreen);
@ -1808,14 +1759,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (version != null) if (version != null)
{ {
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString);
version.VersionString);
hasApplet = version.Major > 3; hasApplet = version.Major > 3;
} }
else else
{ {
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
} }
IsAppletMenuActive = hasApplet; IsAppletMenuActive = hasApplet;

View File

@ -21,8 +21,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SpanHelpers = LibHac.Common.SpanHelpers; using System.Text;
using Path = System.IO.Path; using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
namespace Ryujinx.Ava.UI.ViewModels; namespace Ryujinx.Ava.UI.ViewModels;
@ -90,6 +91,8 @@ public class TitleUpdateViewModel : BaseModel
Selected = "", Selected = "",
Paths = new List<string>() Paths = new List<string>()
}; };
Save();
} }
LoadUpdates(); LoadUpdates();
@ -102,6 +105,9 @@ public class TitleUpdateViewModel : BaseModel
AddUpdate(path); AddUpdate(path);
} }
// NOTE: Save the list again to remove leftovers.
Save();
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null); TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
SelectedUpdate = selected; SelectedUpdate = selected;
@ -181,7 +187,7 @@ public class TitleUpdateViewModel : BaseModel
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path)); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
}); });
} }
} }
@ -223,4 +229,22 @@ public class TitleUpdateViewModel : BaseModel
SortUpdates(); 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)));
}
} }

View File

@ -17,8 +17,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private ObservableCollection<SaveModel> _views = new(); private ObservableCollection<SaveModel> _views = new();
private AccountManager _accountManager; private AccountManager _accountManager;
public string SaveManagerHeading => public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
string.Format(LocaleManager.Instance[LocaleKeys.SaveManagerHeading], _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
public int SortIndex public int SortIndex
{ {

View File

@ -77,8 +77,7 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}"> <MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
</MenuItem>
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenSettings" Click="OpenSettings"
@ -141,6 +140,10 @@
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" /> <MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" /> <MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem> </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>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}"> <MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem <MenuItem
@ -157,4 +160,4 @@
</MenuItem> </MenuItem>
</Menu> </Menu>
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View File

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Ncm; using LibHac.Ncm;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@ -10,6 +11,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.Common.Helper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; 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) public async void CheckForUpdates(object sender, RoutedEventArgs e)
{ {
if (Updater.CanUpdate(true, Window)) if (Updater.CanUpdate(true))
{ {
await Updater.BeginParse(Window, true); await Updater.BeginParse(Window, true);
} }

View File

@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Windows
{ {
LoadedCheats = new AvaloniaList<CheatsList>(); LoadedCheats = new AvaloniaList<CheatsList>();
Heading = string.Format(LocaleManager.Instance[LocaleKeys.CheatWindowHeading], titleName, titleId.ToUpper()); Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
InitializeComponent(); InitializeComponent();

View File

@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Windows
private void PrintHeading() 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() private void LoadDownloadableContents()
@ -133,7 +133,7 @@ namespace Ryujinx.Ava.UI.Windows
{ {
Dispatcher.UIThread.InvokeAsync(async () => 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));
}); });
} }

View File

@ -43,6 +43,7 @@
<StackPanel Grid.Row="0" IsVisible="False"> <StackPanel Grid.Row="0" IsVisible="False">
<helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" /> <helpers:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<helpers:HotKeyControl Name="FullscreenHotKey2" 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="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
<helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" /> <helpers:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
</StackPanel> </StackPanel>

View File

@ -104,6 +104,8 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated; ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded; ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.ReloadGameList += ReloadGameList; ViewModel.ReloadGameList += ReloadGameList;
NotificationHelper.SetNotificationManager(this);
} }
private void IsActiveChanged(bool obj) private void IsActiveChanged(bool obj)
@ -146,7 +148,7 @@ namespace Ryujinx.Ava.UI.Windows
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) 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(() => Dispatcher.UIThread.Post(() =>
{ {
@ -271,7 +273,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.LoadApplication(_launchPath, _startFullscreen); 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 => Updater.BeginParse(this, false).ContinueWith(task =>
{ {
@ -327,10 +329,11 @@ namespace Ryujinx.Ava.UI.Windows
public void LoadHotKeys() public void LoadHotKeys()
{ {
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt)); HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11)); HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9)); HotKeyManager.SetHotKey(FullscreenHotKeyMacOS, new KeyGesture(Key.F, KeyModifiers.Control | KeyModifiers.Meta));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
} }
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e) private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
@ -415,7 +418,7 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.StatusBarProgressMaximum = 0; ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0; ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
}); });
ReloadGameList(); ReloadGameList();

View File

@ -42,7 +42,7 @@ namespace Ryujinx.Ava.UI.Windows
SecondaryButtonText = "", SecondaryButtonText = "",
CloseButtonText = "", CloseButtonText = "",
Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName), Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
Title = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], titleName, titleId.ToString("X16")) Title = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.GameUpdateWindowHeading, titleName, titleId.ToString("X16"))
}; };
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
@ -60,24 +60,7 @@ namespace Ryujinx.Ava.UI.Windows
public void Save(object sender, RoutedEventArgs e) public void Save(object sender, RoutedEventArgs e)
{ {
ViewModel._titleUpdateWindowData.Paths.Clear(); ViewModel.Save();
ViewModel._titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in ViewModel.TitleUpdates)
{
ViewModel._titleUpdateWindowData.Paths.Add(update.Path);
if (update == ViewModel.SelectedUpdate)
{
ViewModel._titleUpdateWindowData.Selected = update.Path;
}
}
using (FileStream titleUpdateJsonStream = File.Create(ViewModel._titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
{
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(ViewModel._titleUpdateWindowData, true)));
}
if (VisualRoot is MainWindow window) if (VisualRoot is MainWindow window)
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Globalization;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
{ {
@ -6,7 +7,7 @@ namespace Ryujinx.Common.Utilities
{ {
public static UInt128 FromHex(string hex) 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() public static UInt128 CreateRandom()

View File

@ -7,9 +7,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture; using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window; using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -30,207 +28,115 @@ namespace Ryujinx.Graphics.GAL.Multithreading
public static int GetMaxCommandSize() public static int GetMaxCommandSize()
{ {
Assembly assembly = typeof(CommandHelper).Assembly; return InitLookup() + 1; // 1 byte reserved for command size.
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.
} }
private static void InitLookup() private static int InitLookup()
{ {
_lookup[(int)CommandType.Action] = (memory, threaded, renderer) => int maxCommandSize = 0;
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);
_lookup[(int)CommandType.BufferDispose] = (memory, threaded, renderer) => void Register<T>(CommandType commandType) where T : unmanaged, IGALCommand, IGALCommand<T>
BufferDisposeCommand.Run(ref GetCommand<BufferDisposeCommand>(memory), threaded, renderer); {
_lookup[(int)CommandType.BufferGetData] = (memory, threaded, renderer) => maxCommandSize = Math.Max(maxCommandSize, Unsafe.SizeOf<T>());
BufferGetDataCommand.Run(ref GetCommand<BufferGetDataCommand>(memory), threaded, renderer); _lookup[(int)commandType] = (memory, threaded, renderer) => T.Run(ref GetCommand<T>(memory), threaded, renderer);
_lookup[(int)CommandType.BufferSetData] = (memory, threaded, renderer) => }
BufferSetDataCommand.Run(ref GetCommand<BufferSetDataCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.CounterEventDispose] = (memory, threaded, renderer) => Register<ActionCommand>(CommandType.Action);
CounterEventDisposeCommand.Run(ref GetCommand<CounterEventDisposeCommand>(memory), threaded, renderer); Register<CreateBufferCommand>(CommandType.CreateBuffer);
_lookup[(int)CommandType.CounterEventFlush] = (memory, threaded, renderer) => Register<CreateProgramCommand>(CommandType.CreateProgram);
CounterEventFlushCommand.Run(ref GetCommand<CounterEventFlushCommand>(memory), threaded, renderer); 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) => Register<BufferDisposeCommand>(CommandType.BufferDispose);
ProgramDisposeCommand.Run(ref GetCommand<ProgramDisposeCommand>(memory), threaded, renderer); Register<BufferGetDataCommand>(CommandType.BufferGetData);
_lookup[(int)CommandType.ProgramGetBinary] = (memory, threaded, renderer) => Register<BufferSetDataCommand>(CommandType.BufferSetData);
ProgramGetBinaryCommand.Run(ref GetCommand<ProgramGetBinaryCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.ProgramCheckLink] = (memory, threaded, renderer) =>
ProgramCheckLinkCommand.Run(ref GetCommand<ProgramCheckLinkCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SamplerDispose] = (memory, threaded, renderer) => Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
SamplerDisposeCommand.Run(ref GetCommand<SamplerDisposeCommand>(memory), threaded, renderer); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
_lookup[(int)CommandType.TextureCopyTo] = (memory, threaded, renderer) => Register<ProgramDisposeCommand>(CommandType.ProgramDispose);
TextureCopyToCommand.Run(ref GetCommand<TextureCopyToCommand>(memory), threaded, renderer); Register<ProgramGetBinaryCommand>(CommandType.ProgramGetBinary);
_lookup[(int)CommandType.TextureCopyToScaled] = (memory, threaded, renderer) => Register<ProgramCheckLinkCommand>(CommandType.ProgramCheckLink);
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);
_lookup[(int)CommandType.WindowPresent] = (memory, threaded, renderer) => Register<SamplerDisposeCommand>(CommandType.SamplerDispose);
WindowPresentCommand.Run(ref GetCommand<WindowPresentCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.Barrier] = (memory, threaded, renderer) => Register<TextureCopyToCommand>(CommandType.TextureCopyTo);
BarrierCommand.Run(ref GetCommand<BarrierCommand>(memory), threaded, renderer); Register<TextureCopyToScaledCommand>(CommandType.TextureCopyToScaled);
_lookup[(int)CommandType.BeginTransformFeedback] = (memory, threaded, renderer) => Register<TextureCopyToSliceCommand>(CommandType.TextureCopyToSlice);
BeginTransformFeedbackCommand.Run(ref GetCommand<BeginTransformFeedbackCommand>(memory), threaded, renderer); Register<TextureCreateViewCommand>(CommandType.TextureCreateView);
_lookup[(int)CommandType.ClearBuffer] = (memory, threaded, renderer) => Register<TextureGetDataCommand>(CommandType.TextureGetData);
ClearBufferCommand.Run(ref GetCommand<ClearBufferCommand>(memory), threaded, renderer); Register<TextureGetDataSliceCommand>(CommandType.TextureGetDataSlice);
_lookup[(int)CommandType.ClearRenderTargetColor] = (memory, threaded, renderer) => Register<TextureReleaseCommand>(CommandType.TextureRelease);
ClearRenderTargetColorCommand.Run(ref GetCommand<ClearRenderTargetColorCommand>(memory), threaded, renderer); Register<TextureSetDataCommand>(CommandType.TextureSetData);
_lookup[(int)CommandType.ClearRenderTargetDepthStencil] = (memory, threaded, renderer) => Register<TextureSetDataSliceCommand>(CommandType.TextureSetDataSlice);
ClearRenderTargetDepthStencilCommand.Run(ref GetCommand<ClearRenderTargetDepthStencilCommand>(memory), threaded, renderer); Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
_lookup[(int)CommandType.CommandBufferBarrier] = (memory, threaded, renderer) => Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
CommandBufferBarrierCommand.Run(ref GetCommand<CommandBufferBarrierCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.CopyBuffer] = (memory, threaded, renderer) => Register<WindowPresentCommand>(CommandType.WindowPresent);
CopyBufferCommand.Run(ref GetCommand<CopyBufferCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.DispatchCompute] = (memory, threaded, renderer) => Register<BarrierCommand>(CommandType.Barrier);
DispatchComputeCommand.Run(ref GetCommand<DispatchComputeCommand>(memory), threaded, renderer); Register<BeginTransformFeedbackCommand>(CommandType.BeginTransformFeedback);
_lookup[(int)CommandType.Draw] = (memory, threaded, renderer) => Register<ClearBufferCommand>(CommandType.ClearBuffer);
DrawCommand.Run(ref GetCommand<DrawCommand>(memory), threaded, renderer); Register<ClearRenderTargetColorCommand>(CommandType.ClearRenderTargetColor);
_lookup[(int)CommandType.DrawIndexed] = (memory, threaded, renderer) => Register<ClearRenderTargetDepthStencilCommand>(CommandType.ClearRenderTargetDepthStencil);
DrawIndexedCommand.Run(ref GetCommand<DrawIndexedCommand>(memory), threaded, renderer); Register<CommandBufferBarrierCommand>(CommandType.CommandBufferBarrier);
_lookup[(int)CommandType.DrawIndexedIndirect] = (memory, threaded, renderer) => Register<CopyBufferCommand>(CommandType.CopyBuffer);
DrawIndexedIndirectCommand.Run(ref GetCommand<DrawIndexedIndirectCommand>(memory), threaded, renderer); Register<DispatchComputeCommand>(CommandType.DispatchCompute);
_lookup[(int)CommandType.DrawIndexedIndirectCount] = (memory, threaded, renderer) => Register<DrawCommand>(CommandType.Draw);
DrawIndexedIndirectCountCommand.Run(ref GetCommand<DrawIndexedIndirectCountCommand>(memory), threaded, renderer); Register<DrawIndexedCommand>(CommandType.DrawIndexed);
_lookup[(int)CommandType.DrawIndirect] = (memory, threaded, renderer) => Register<DrawIndexedIndirectCommand>(CommandType.DrawIndexedIndirect);
DrawIndirectCommand.Run(ref GetCommand<DrawIndirectCommand>(memory), threaded, renderer); Register<DrawIndexedIndirectCountCommand>(CommandType.DrawIndexedIndirectCount);
_lookup[(int)CommandType.DrawIndirectCount] = (memory, threaded, renderer) => Register<DrawIndirectCommand>(CommandType.DrawIndirect);
DrawIndirectCountCommand.Run(ref GetCommand<DrawIndirectCountCommand>(memory), threaded, renderer); Register<DrawIndirectCountCommand>(CommandType.DrawIndirectCount);
_lookup[(int)CommandType.DrawTexture] = (memory, threaded, renderer) => Register<DrawTextureCommand>(CommandType.DrawTexture);
DrawTextureCommand.Run(ref GetCommand<DrawTextureCommand>(memory), threaded, renderer); Register<EndHostConditionalRenderingCommand>(CommandType.EndHostConditionalRendering);
_lookup[(int)CommandType.EndHostConditionalRendering] = (memory, threaded, renderer) => Register<EndTransformFeedbackCommand>(CommandType.EndTransformFeedback);
EndHostConditionalRenderingCommand.Run(renderer); Register<SetAlphaTestCommand>(CommandType.SetAlphaTest);
_lookup[(int)CommandType.EndTransformFeedback] = (memory, threaded, renderer) => Register<SetBlendStateCommand>(CommandType.SetBlendState);
EndTransformFeedbackCommand.Run(ref GetCommand<EndTransformFeedbackCommand>(memory), threaded, renderer); Register<SetDepthBiasCommand>(CommandType.SetDepthBias);
_lookup[(int)CommandType.SetAlphaTest] = (memory, threaded, renderer) => Register<SetDepthClampCommand>(CommandType.SetDepthClamp);
SetAlphaTestCommand.Run(ref GetCommand<SetAlphaTestCommand>(memory), threaded, renderer); Register<SetDepthModeCommand>(CommandType.SetDepthMode);
_lookup[(int)CommandType.SetBlendState] = (memory, threaded, renderer) => Register<SetDepthTestCommand>(CommandType.SetDepthTest);
SetBlendStateCommand.Run(ref GetCommand<SetBlendStateCommand>(memory), threaded, renderer); Register<SetFaceCullingCommand>(CommandType.SetFaceCulling);
_lookup[(int)CommandType.SetDepthBias] = (memory, threaded, renderer) => Register<SetFrontFaceCommand>(CommandType.SetFrontFace);
SetDepthBiasCommand.Run(ref GetCommand<SetDepthBiasCommand>(memory), threaded, renderer); Register<SetStorageBuffersCommand>(CommandType.SetStorageBuffers);
_lookup[(int)CommandType.SetDepthClamp] = (memory, threaded, renderer) => Register<SetTransformFeedbackBuffersCommand>(CommandType.SetTransformFeedbackBuffers);
SetDepthClampCommand.Run(ref GetCommand<SetDepthClampCommand>(memory), threaded, renderer); Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
_lookup[(int)CommandType.SetDepthMode] = (memory, threaded, renderer) => Register<SetImageCommand>(CommandType.SetImage);
SetDepthModeCommand.Run(ref GetCommand<SetDepthModeCommand>(memory), threaded, renderer); Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
_lookup[(int)CommandType.SetDepthTest] = (memory, threaded, renderer) => Register<SetLineParametersCommand>(CommandType.SetLineParameters);
SetDepthTestCommand.Run(ref GetCommand<SetDepthTestCommand>(memory), threaded, renderer); Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
_lookup[(int)CommandType.SetFaceCulling] = (memory, threaded, renderer) => Register<SetMultisampleStateCommand>(CommandType.SetMultisampleState);
SetFaceCullingCommand.Run(ref GetCommand<SetFaceCullingCommand>(memory), threaded, renderer); Register<SetPatchParametersCommand>(CommandType.SetPatchParameters);
_lookup[(int)CommandType.SetFrontFace] = (memory, threaded, renderer) => Register<SetPointParametersCommand>(CommandType.SetPointParameters);
SetFrontFaceCommand.Run(ref GetCommand<SetFrontFaceCommand>(memory), threaded, renderer); Register<SetPolygonModeCommand>(CommandType.SetPolygonMode);
_lookup[(int)CommandType.SetStorageBuffers] = (memory, threaded, renderer) => Register<SetPrimitiveRestartCommand>(CommandType.SetPrimitiveRestart);
SetStorageBuffersCommand.Run(ref GetCommand<SetStorageBuffersCommand>(memory), threaded, renderer); Register<SetPrimitiveTopologyCommand>(CommandType.SetPrimitiveTopology);
_lookup[(int)CommandType.SetTransformFeedbackBuffers] = (memory, threaded, renderer) => Register<SetProgramCommand>(CommandType.SetProgram);
SetTransformFeedbackBuffersCommand.Run(ref GetCommand<SetTransformFeedbackBuffersCommand>(memory), threaded, renderer); Register<SetRasterizerDiscardCommand>(CommandType.SetRasterizerDiscard);
_lookup[(int)CommandType.SetUniformBuffers] = (memory, threaded, renderer) => Register<SetRenderTargetColorMasksCommand>(CommandType.SetRenderTargetColorMasks);
SetUniformBuffersCommand.Run(ref GetCommand<SetUniformBuffersCommand>(memory), threaded, renderer); Register<SetRenderTargetScaleCommand>(CommandType.SetRenderTargetScale);
_lookup[(int)CommandType.SetImage] = (memory, threaded, renderer) => Register<SetRenderTargetsCommand>(CommandType.SetRenderTargets);
SetImageCommand.Run(ref GetCommand<SetImageCommand>(memory), threaded, renderer); Register<SetScissorsCommand>(CommandType.SetScissor);
_lookup[(int)CommandType.SetIndexBuffer] = (memory, threaded, renderer) => Register<SetStencilTestCommand>(CommandType.SetStencilTest);
SetIndexBufferCommand.Run(ref GetCommand<SetIndexBufferCommand>(memory), threaded, renderer); Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
_lookup[(int)CommandType.SetLineParameters] = (memory, threaded, renderer) => Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
SetLineParametersCommand.Run(ref GetCommand<SetLineParametersCommand>(memory), threaded, renderer); Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
_lookup[(int)CommandType.SetLogicOpState] = (memory, threaded, renderer) => Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
SetLogicOpStateCommand.Run(ref GetCommand<SetLogicOpStateCommand>(memory), threaded, renderer); Register<SetViewportsCommand>(CommandType.SetViewports);
_lookup[(int)CommandType.SetMultisampleState] = (memory, threaded, renderer) => Register<TextureBarrierCommand>(CommandType.TextureBarrier);
SetMultisampleStateCommand.Run(ref GetCommand<SetMultisampleStateCommand>(memory), threaded, renderer); Register<TextureBarrierTiledCommand>(CommandType.TextureBarrierTiled);
_lookup[(int)CommandType.SetPatchParameters] = (memory, threaded, renderer) => Register<TryHostConditionalRenderingCommand>(CommandType.TryHostConditionalRendering);
SetPatchParametersCommand.Run(ref GetCommand<SetPatchParametersCommand>(memory), threaded, renderer); Register<TryHostConditionalRenderingFlushCommand>(CommandType.TryHostConditionalRenderingFlush);
_lookup[(int)CommandType.SetPointParameters] = (memory, threaded, renderer) => Register<UpdateRenderScaleCommand>(CommandType.UpdateRenderScale);
SetPointParametersCommand.Run(ref GetCommand<SetPointParametersCommand>(memory), threaded, renderer);
_lookup[(int)CommandType.SetPolygonMode] = (memory, threaded, renderer) => return maxCommandSize;
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);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct BarrierCommand : IGALCommand struct BarrierCommand : IGALCommand, IGALCommand<BarrierCommand>
{ {
public CommandType CommandType => CommandType.Barrier; public CommandType CommandType => CommandType.Barrier;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct BeginTransformFeedbackCommand : IGALCommand struct BeginTransformFeedbackCommand : IGALCommand, IGALCommand<BeginTransformFeedbackCommand>
{ {
public CommandType CommandType => CommandType.BeginTransformFeedback; public CommandType CommandType => CommandType.BeginTransformFeedback;
private PrimitiveTopology _topology; private PrimitiveTopology _topology;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
{ {
struct BufferDisposeCommand : IGALCommand struct BufferDisposeCommand : IGALCommand, IGALCommand<BufferDisposeCommand>
{ {
public CommandType CommandType => CommandType.BufferDispose; public CommandType CommandType => CommandType.BufferDispose;
private BufferHandle _buffer; private BufferHandle _buffer;

View File

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
{ {
struct BufferGetDataCommand : IGALCommand struct BufferGetDataCommand : IGALCommand, IGALCommand<BufferGetDataCommand>
{ {
public CommandType CommandType => CommandType.BufferGetData; public CommandType CommandType => CommandType.BufferGetData;
private BufferHandle _buffer; private BufferHandle _buffer;

View File

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
{ {
struct BufferSetDataCommand : IGALCommand struct BufferSetDataCommand : IGALCommand, IGALCommand<BufferSetDataCommand>
{ {
public CommandType CommandType => CommandType.BufferSetData; public CommandType CommandType => CommandType.BufferSetData;
private BufferHandle _buffer; private BufferHandle _buffer;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct ClearBufferCommand : IGALCommand struct ClearBufferCommand : IGALCommand, IGALCommand<ClearBufferCommand>
{ {
public CommandType CommandType => CommandType.ClearBuffer; public CommandType CommandType => CommandType.ClearBuffer;
private BufferHandle _destination; private BufferHandle _destination;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct ClearRenderTargetColorCommand : IGALCommand struct ClearRenderTargetColorCommand : IGALCommand, IGALCommand<ClearRenderTargetColorCommand>
{ {
public CommandType CommandType => CommandType.ClearRenderTargetColor; public CommandType CommandType => CommandType.ClearRenderTargetColor;
private int _index; private int _index;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct ClearRenderTargetDepthStencilCommand : IGALCommand struct ClearRenderTargetDepthStencilCommand : IGALCommand, IGALCommand<ClearRenderTargetDepthStencilCommand>
{ {
public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil; public CommandType CommandType => CommandType.ClearRenderTargetDepthStencil;
private int _layer; private int _layer;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct CommandBufferBarrierCommand : IGALCommand struct CommandBufferBarrierCommand : IGALCommand, IGALCommand<CommandBufferBarrierCommand>
{ {
public CommandType CommandType => CommandType.CommandBufferBarrier; public CommandType CommandType => CommandType.CommandBufferBarrier;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct CopyBufferCommand : IGALCommand struct CopyBufferCommand : IGALCommand, IGALCommand<CopyBufferCommand>
{ {
public CommandType CommandType => CommandType.CopyBuffer; public CommandType CommandType => CommandType.CopyBuffer;
private BufferHandle _source; private BufferHandle _source;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
{ {
struct CounterEventDisposeCommand : IGALCommand struct CounterEventDisposeCommand : IGALCommand, IGALCommand<CounterEventDisposeCommand>
{ {
public CommandType CommandType => CommandType.CounterEventDispose; public CommandType CommandType => CommandType.CounterEventDispose;
private TableRef<ThreadedCounterEvent> _event; private TableRef<ThreadedCounterEvent> _event;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent namespace Ryujinx.Graphics.GAL.Multithreading.Commands.CounterEvent
{ {
struct CounterEventFlushCommand : IGALCommand struct CounterEventFlushCommand : IGALCommand, IGALCommand<CounterEventFlushCommand>
{ {
public CommandType CommandType => CommandType.CounterEventFlush; public CommandType CommandType => CommandType.CounterEventFlush;
private TableRef<ThreadedCounterEvent> _event; private TableRef<ThreadedCounterEvent> _event;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DispatchComputeCommand : IGALCommand struct DispatchComputeCommand : IGALCommand, IGALCommand<DispatchComputeCommand>
{ {
public CommandType CommandType => CommandType.DispatchCompute; public CommandType CommandType => CommandType.DispatchCompute;
private int _groupsX; private int _groupsX;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawIndexedCommand : IGALCommand struct DrawIndexedCommand : IGALCommand, IGALCommand<DrawIndexedCommand>
{ {
public CommandType CommandType => CommandType.DrawIndexed; public CommandType CommandType => CommandType.DrawIndexed;
private int _indexCount; private int _indexCount;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawCommand : IGALCommand struct DrawCommand : IGALCommand, IGALCommand<DrawCommand>
{ {
public CommandType CommandType => CommandType.Draw; public CommandType CommandType => CommandType.Draw;
private int _vertexCount; private int _vertexCount;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawIndexedIndirectCommand : IGALCommand struct DrawIndexedIndirectCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCommand>
{ {
public CommandType CommandType => CommandType.DrawIndexedIndirect; public CommandType CommandType => CommandType.DrawIndexedIndirect;
private BufferRange _indirectBuffer; private BufferRange _indirectBuffer;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawIndexedIndirectCountCommand : IGALCommand struct DrawIndexedIndirectCountCommand : IGALCommand, IGALCommand<DrawIndexedIndirectCountCommand>
{ {
public CommandType CommandType => CommandType.DrawIndexedIndirectCount; public CommandType CommandType => CommandType.DrawIndexedIndirectCount;
private BufferRange _indirectBuffer; private BufferRange _indirectBuffer;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawIndirectCommand : IGALCommand struct DrawIndirectCommand : IGALCommand, IGALCommand<DrawIndirectCommand>
{ {
public CommandType CommandType => CommandType.DrawIndirect; public CommandType CommandType => CommandType.DrawIndirect;
private BufferRange _indirectBuffer; private BufferRange _indirectBuffer;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawIndirectCountCommand : IGALCommand struct DrawIndirectCountCommand : IGALCommand, IGALCommand<DrawIndirectCountCommand>
{ {
public CommandType CommandType => CommandType.DrawIndirectCount; public CommandType CommandType => CommandType.DrawIndirectCount;
private BufferRange _indirectBuffer; private BufferRange _indirectBuffer;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct DrawTextureCommand : IGALCommand struct DrawTextureCommand : IGALCommand, IGALCommand<DrawTextureCommand>
{ {
public CommandType CommandType => CommandType.DrawTexture; public CommandType CommandType => CommandType.DrawTexture;
private TableRef<ITexture> _texture; private TableRef<ITexture> _texture;

View File

@ -1,10 +1,10 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct EndHostConditionalRenderingCommand : IGALCommand struct EndHostConditionalRenderingCommand : IGALCommand, IGALCommand<EndHostConditionalRenderingCommand>
{ {
public CommandType CommandType => CommandType.EndHostConditionalRendering; public CommandType CommandType => CommandType.EndHostConditionalRendering;
public static void Run(IRenderer renderer) public static void Run(ref EndHostConditionalRenderingCommand command, ThreadedRenderer threaded, IRenderer renderer)
{ {
renderer.Pipeline.EndHostConditionalRendering(); renderer.Pipeline.EndHostConditionalRendering();
} }

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{ {
struct EndTransformFeedbackCommand : IGALCommand struct EndTransformFeedbackCommand : IGALCommand, IGALCommand<EndTransformFeedbackCommand>
{ {
public CommandType CommandType => CommandType.EndTransformFeedback; public CommandType CommandType => CommandType.EndTransformFeedback;

View File

@ -4,4 +4,9 @@
{ {
CommandType CommandType { get; } CommandType CommandType { get; }
} }
interface IGALCommand<T> where T : IGALCommand
{
abstract static void Run(ref T command, ThreadedRenderer threaded, IRenderer renderer);
}
} }

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
{ {
struct ProgramCheckLinkCommand : IGALCommand struct ProgramCheckLinkCommand : IGALCommand, IGALCommand<ProgramCheckLinkCommand>
{ {
public CommandType CommandType => CommandType.ProgramCheckLink; public CommandType CommandType => CommandType.ProgramCheckLink;
private TableRef<ThreadedProgram> _program; private TableRef<ThreadedProgram> _program;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
{ {
struct ProgramDisposeCommand : IGALCommand struct ProgramDisposeCommand : IGALCommand, IGALCommand<ProgramDisposeCommand>
{ {
public CommandType CommandType => CommandType.ProgramDispose; public CommandType CommandType => CommandType.ProgramDispose;
private TableRef<ThreadedProgram> _program; private TableRef<ThreadedProgram> _program;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Program
{ {
struct ProgramGetBinaryCommand : IGALCommand struct ProgramGetBinaryCommand : IGALCommand, IGALCommand<ProgramGetBinaryCommand>
{ {
public CommandType CommandType => CommandType.ProgramGetBinary; public CommandType CommandType => CommandType.ProgramGetBinary;
private TableRef<ThreadedProgram> _program; private TableRef<ThreadedProgram> _program;

View File

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct ActionCommand : IGALCommand struct ActionCommand : IGALCommand, IGALCommand<ActionCommand>
{ {
public CommandType CommandType => CommandType.Action; public CommandType CommandType => CommandType.Action;
private TableRef<Action> _action; private TableRef<Action> _action;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct CreateBufferCommand : IGALCommand struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
{ {
public CommandType CommandType => CommandType.CreateBuffer; public CommandType CommandType => CommandType.CreateBuffer;
private BufferHandle _threadedHandle; private BufferHandle _threadedHandle;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources.Programs;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct CreateProgramCommand : IGALCommand struct CreateProgramCommand : IGALCommand, IGALCommand<CreateProgramCommand>
{ {
public CommandType CommandType => CommandType.CreateProgram; public CommandType CommandType => CommandType.CreateProgram;
private TableRef<IProgramRequest> _request; private TableRef<IProgramRequest> _request;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct CreateSamplerCommand : IGALCommand struct CreateSamplerCommand : IGALCommand, IGALCommand<CreateSamplerCommand>
{ {
public CommandType CommandType => CommandType.CreateSampler; public CommandType CommandType => CommandType.CreateSampler;
private TableRef<ThreadedSampler> _sampler; private TableRef<ThreadedSampler> _sampler;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct CreateSyncCommand : IGALCommand struct CreateSyncCommand : IGALCommand, IGALCommand<CreateSyncCommand>
{ {
public CommandType CommandType => CommandType.CreateSync; public CommandType CommandType => CommandType.CreateSync;
private ulong _id; private ulong _id;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct CreateTextureCommand : IGALCommand struct CreateTextureCommand : IGALCommand, IGALCommand<CreateTextureCommand>
{ {
public CommandType CommandType => CommandType.CreateTexture; public CommandType CommandType => CommandType.CreateTexture;
private TableRef<ThreadedTexture> _texture; private TableRef<ThreadedTexture> _texture;

View File

@ -2,7 +2,7 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct GetCapabilitiesCommand : IGALCommand struct GetCapabilitiesCommand : IGALCommand, IGALCommand<GetCapabilitiesCommand>
{ {
public CommandType CommandType => CommandType.GetCapabilities; public CommandType CommandType => CommandType.GetCapabilities;
private TableRef<ResultBox<Capabilities>> _result; private TableRef<ResultBox<Capabilities>> _result;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct PreFrameCommand : IGALCommand struct PreFrameCommand : IGALCommand, IGALCommand<PreFrameCommand>
{ {
public CommandType CommandType => CommandType.PreFrame; public CommandType CommandType => CommandType.PreFrame;

View File

@ -4,7 +4,7 @@ using System;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct ReportCounterCommand : IGALCommand struct ReportCounterCommand : IGALCommand, IGALCommand<ReportCounterCommand>
{ {
public CommandType CommandType => CommandType.ReportCounter; public CommandType CommandType => CommandType.ReportCounter;
private TableRef<ThreadedCounterEvent> _event; private TableRef<ThreadedCounterEvent> _event;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct ResetCounterCommand : IGALCommand struct ResetCounterCommand : IGALCommand, IGALCommand<ResetCounterCommand>
{ {
public CommandType CommandType => CommandType.ResetCounter; public CommandType CommandType => CommandType.ResetCounter;
private CounterType _type; private CounterType _type;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
{ {
struct UpdateCountersCommand : IGALCommand struct UpdateCountersCommand : IGALCommand, IGALCommand<UpdateCountersCommand>
{ {
public CommandType CommandType => CommandType.UpdateCounters; public CommandType CommandType => CommandType.UpdateCounters;

View File

@ -3,7 +3,7 @@ using Ryujinx.Graphics.GAL.Multithreading.Resources;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Sampler
{ {
struct SamplerDisposeCommand : IGALCommand struct SamplerDisposeCommand : IGALCommand, IGALCommand<SamplerDisposeCommand>
{ {
public CommandType CommandType => CommandType.SamplerDispose; public CommandType CommandType => CommandType.SamplerDispose;
private TableRef<ThreadedSampler> _sampler; private TableRef<ThreadedSampler> _sampler;

Some files were not shown because too many files have changed in this diff Show More