Compare commits

..

25 Commits

Author SHA1 Message Date
gdkchan
8750b90a7f Ensure that vertex attribute buffer index is valid on GPU (#3942)
* Ensure that vertex attribute buffer index is valid on GPU

* Remove vertex buffer validation code from OpenGL

* Remove some fields that are no longer necessary
2022-11-30 18:06:40 -03:00
dependabot[bot]
af01100050 nuget: bump System.Management from 6.0.0 to 7.0.0 (#3949)
Bumps [System.Management](https://github.com/dotnet/runtime) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: System.Management
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>
2022-11-29 23:30:33 +01:00
EmulationFanatic
c0821fee1f Update README.MD (#3946)
Updates the readme to include usage of Metal via MoltenVK, updated game compatibility statistics.
2022-11-29 17:20:26 +01:00
merry
a5c2aead67 ConcurrentBitmap: Use Interlocked Or/And (#3937) 2022-11-29 13:47:57 +00:00
Mary-nyan
d41c95dcff chore: Update OpenTK to 4.7.5 (#3944) 2022-11-29 13:32:40 +00:00
TSRBerry
fbf2b09706 ava: Make dialogs using an overlay window work on Linux (#3938) 2022-11-29 06:33:46 +01:00
riperiperi
1fc0f569de GPU: Always draw polygon topology as triangle fan (#3932)
Polygon topology wasn't really supported and would only work on OpenGL on drivers that haven't removed it. As an alternative, this PR makes all cases of polygon topology use triangle fan. The topology type and transform feedback type have not been changed, as I don't think geo shader/tfb should be used with polygons.

The OpenGL spec states:
Only convex polygons are guaranteed to be drawn correctly by the GL.

For convex polygons, triangle fan is equivalent to polygon. I imagine this is probably how it works on device, as this get-out-of-jail-free card is too enticing to pass up.

This fixes the stat display in Pokemon S/V.
2022-11-28 19:18:22 -03:00
Mary-nyan
dff138229c amadeus: Fixes and initial 15.0.0 support (#3908)
* amadeus: Allow OOB read of GC-ADPCM coefficients

Fixes "Ninja Gaiden Sigma 2" and possibly "NINJA GAIDEN 3: Razor's Edge"

* amadeus: Fix wrong variable usage in delay effect

We should transform the delay line values, not the input.

* amadeus: Update GroupedBiquadFilterCommand documentation

* amadeus: Simplify PoolMapper alignment checks

* amadeus: Update Surround delay effect matrix to REV11

* amadeus: Add drop parameter support and use 32 bits integers for estimate time

Also implement accurate ExecuteAudioRendererRendering stub.

* Address gdkchan's comments

* Address gdkchan's other comments

* Address gdkchan's comment
2022-11-28 08:28:45 +01:00
Ac_K
472119c8da sfdnsres; Fix deserializer of AddrInfoSerialized when addresses are empty (#3924) 2022-11-28 02:53:57 +01:00
Mary-nyan
1865ea87e5 bsd: Fix eventfd broken logic (#3647)
* bsd: Fix eventfd broken logic

This commit fix eventfd logic being broken.

The following changes were made:
- EventFd IPC definition had argument inverted
- EventFd events weren't fired correctly
- Poll logic was wrong and unfinished for eventfd
- Reintroduce workaround from #3385 but in a safer way, and spawn 4
  threads.

* ipc: Rework a bit for multithreads

* Clean up debug logs

* Make server thread yield when managed lock isn't availaible

* Fix replyTargetHandle not being added in the proper locking scope

* Simplify some scopes

* Address gdkchan's comments

* Revert IPC workaround for now

* Reintroduce the EventFileDescriptor workaround
2022-11-27 20:18:05 +00:00
&Olga
18b61aff59 Unbreak bug_report.md (#3915)
* Unbreak bug_report.md

* Update bug_report.md
2022-11-27 20:11:51 +00:00
Luminoso-256
cb22629ac1 HLE: fix small issue in IPsmSession (#3909) 2022-11-27 01:10:42 +00:00
TSRBerry
6f0f99ee2b Avalonia: Fix OpenGL crashing on Linux (#3902)
* ava: Fix OpenGL crashing on Linux

Fixes a regression from #3901

* Fix formatting
2022-11-26 12:06:53 +01:00
TSRBerry
70f2da8fdf ava: Fix invisible vulkan window on Linux (#3901)
Co-authored-by: emmauss <emmausssss@gmail.com>

Co-authored-by: emmauss <emmausssss@gmail.com>
2022-11-25 17:40:44 +00:00
Ac_K
5d3ef7761b ava: Refactor Title Update Manager window (#3898)
* ava: Refactor TitleUpdate Manager window

* Update locale
2022-11-25 17:55:08 +01:00
riperiperi
476b4683cf Fix CB0 alignment with addresses used for 8/16-bit LDG/STG (#3897)
This replacement is meant to be done with the original identified byteOffset, not the one assigned later on by the below conditionals (that already has the constant offset added, for instance).

This fixes videos being pixelated in Xenoblade 3, and other regressions that might have happened since #3847.
2022-11-25 14:39:03 +00:00
Mary-nyan
5fb5079730 chore: Update Avalonia related dependencies (#3891) 2022-11-25 13:27:41 +00:00
Ac_K
3fbacd0f49 ava: Rework DLC Manager, Add various fixes and cleanup (#3896)
* Fixes Everything Part.2

* Change sorting, fix remove and heading
2022-11-25 12:41:34 +01:00
dependabot[bot]
7aa6abc120 nuget: bump SharpZipLib from 1.3.3 to 1.4.1 (#3893)
Bumps [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) from 1.3.3 to 1.4.1.
- [Release notes](https://github.com/icsharpcode/SharpZipLib/releases)
- [Changelog](https://github.com/icsharpcode/SharpZipLib/blob/master/docs/Changes.txt)
- [Commits](https://github.com/icsharpcode/SharpZipLib/compare/v1.3.3...v1.4.1)

---
updated-dependencies:
- dependency-name: SharpZipLib
  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>
2022-11-24 20:30:17 +01:00
Mary-nyan
548bfd60a2 chore: Update Ryujinx.SDL2-CS to 2.24.2 (#3892)
* chore: Update Ryujinx.SDL2-CS to 2.24.2

* Disable SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS
2022-11-24 20:13:16 +01:00
riperiperi
65778a6b78 GPU: Don't trigger uploads for redundant buffer updates (#3828)
* Initial implementation

* Actually do The Thing

* Add remark about performance to IVirtualMemoryManager
2022-11-24 15:50:15 +01:00
Mary-nyan
f4e879a1e6 Reduce usage of Marshal.PtrToStructure and Marshal.StructureToPtr (#3805)
* common: Make BinaryReaderExtensions Read & Write take unamanged types

This allows us to not rely on Marshal.PtrToStructure and Marshal.StructureToPtr for those.

* common: Make MemoryHelper Read & Write takes unamanged types

* Update Marshal.SizeOf => Unsafe.SizeOf when appropriate and start moving software applet to unmanaged types
2022-11-24 15:26:29 +01:00
Ac_K
a1ddaa2736 ui: Fixes disposing on GTK/Avalonia and Firmware Messages on Avalonia (#3885)
* ui: Only wait on _exitEvent when MainLoop is active under GTK

This fixes a dispose issue under Horizon/GTK, we don't check if the ApplicationClient is null so it throw NCE. We don't check if the main loop is active and waiting an event which is set in the main loop... So that could lead to a freeze.

Everything works fine in GTK now.

Related issue: https://github.com/Ryujinx/Ryujinx/issues/3873

As a side note, same kind of issue appear in Avalonia UI too. Firmware's popup doesn't show anything and the emulator just freeze.

* TSRBerry's change

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

* Fix Avalonia crashing/freezing

* Add Avalonia OpenGL fixes

* Fix firmware popup on windows

* Fixes everything

* Add _initialized bool to VulkanRenderer and OpenGL Window

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-11-24 15:08:27 +01:00
Mary-nyan
008286b79f Ryujinx.Ava: Add missing redefinition of app name (#3890)
Before this, Ryujinx would possibly report as "Avalonia Application".
2022-11-24 14:52:39 +01:00
gdkchan
a0c77f8d11 Fix NRE on Avalonia for error applets with unknown error message (#3888) 2022-11-24 09:31:00 +01:00
113 changed files with 1118 additions and 808 deletions

View File

@@ -1,6 +1,6 @@
--- ---
name: Bug Report name: Bug Report
about: Something doesn't work correctly in Ryujinx. Note that game-specific issues should be instead posted on the Game Compatibility List at https://github.com/Ryujinx/Ryujinx-Games-List, unless it is a provable regression. about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
#assignees: #assignees:
--- ---

View File

@@ -36,7 +36,7 @@
## Compatibility ## Compatibility
As of October 2022, Ryujinx has been tested on approximately 3,700 titles; over 3,500 boot past menus and into gameplay, with roughly 3,000 of those being considered playable. As of November 2022, Ryujinx has been tested on approximately 3,800 titles; over 3,600 boot past menus and into gameplay, with roughly 3,200 of those being considered playable.
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already! You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
## Usage ## Usage
@@ -90,7 +90,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
- **GPU** - **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum) or Vulkan APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI. The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
- **Input** - **Input**

View File

@@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.OpenAL" Version="4.7.2" /> <PackageReference Include="OpenTK.OpenAL" Version="4.7.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Common.Logging;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -71,6 +72,19 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (short)value; return (short)value;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{
if ((uint)index > (uint)coefficients.Length)
{
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
}
return coefficients[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext) public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
{ {
@@ -84,8 +98,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
byte coefficientIndex = (byte)((predScale >> 4) & 0xF); byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
short history0 = loopContext.History0; short history0 = loopContext.History0;
short history1 = loopContext.History1; short history1 = loopContext.History1;
short coefficient0 = coefficients[coefficientIndex * 2 + 0]; short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
short coefficient1 = coefficients[coefficientIndex * 2 + 1]; short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset); int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset); int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
@@ -109,8 +123,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
coefficientIndex = (byte)((predScale >> 4) & 0xF); coefficientIndex = (byte)((predScale >> 4) & 0xF);
coefficient0 = coefficients[coefficientIndex * 2 + 0]; coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
coefficient1 = coefficients[coefficientIndex * 2 + 1]; coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
nibbles += 2; nibbles += 2;

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.AdpcmDataSourceVersion1; public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }
public uint SampleRate { get; } public uint SampleRate { get; }

View File

@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.AuxiliaryBuffer; public CommandType CommandType => CommandType.AuxiliaryBuffer;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint InputBufferIndex { get; } public uint InputBufferIndex { get; }
public uint OutputBufferIndex { get; } public uint OutputBufferIndex { get; }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.BiquadFilter; public CommandType CommandType => CommandType.BiquadFilter;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public Memory<BiquadFilterState> BiquadFilterState { get; } public Memory<BiquadFilterState> BiquadFilterState { get; }
public int InputBufferIndex { get; } public int InputBufferIndex { get; }

View File

@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CaptureBuffer; public CommandType CommandType => CommandType.CaptureBuffer;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint InputBufferIndex { get; } public uint InputBufferIndex { get; }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CircularBufferSink; public CommandType CommandType => CommandType.CircularBufferSink;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort[] Input { get; } public ushort[] Input { get; }
public uint InputCount { get; } public uint InputCount { get; }

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.ClearMixBuffer; public CommandType CommandType => CommandType.ClearMixBuffer;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ClearMixBufferCommand(int nodeId) public ClearMixBufferCommand(int nodeId)
{ {

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.CopyMixBuffer; public CommandType CommandType => CommandType.CopyMixBuffer;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType { get; } public CommandType CommandType { get; }
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }
public uint SampleRate { get; } public uint SampleRate { get; }

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Delay; public CommandType CommandType => CommandType.Delay;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public DelayParameter Parameter => _parameter; public DelayParameter Parameter => _parameter;
public Memory<DelayState> State { get; } public Memory<DelayState> State { get; }
@@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
} }
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping. DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount) private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
{ {
const ushort channelCount = 1;
float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision); float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
@@ -70,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float temp = input * inGain + delayLineValue * feedbackGain; float temp = input * inGain + delayLineValue * feedbackGain;
state.UpdateLowPassFilter(ref temp, 1); state.UpdateLowPassFilter(ref temp, channelCount);
outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64; outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
} }
@@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Y = state.DelayLines[1].Read(), Y = state.DelayLines[1].Read(),
}; };
Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain; Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount); state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
@@ -148,7 +148,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
W = state.DelayLines[3].Read() W = state.DelayLines[3].Read()
}; };
Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain; Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount); state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
@@ -171,12 +171,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain); 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
for (int i = 0; i < sampleCount; i++) for (int i = 0; i < sampleCount; i++)
{ {
@@ -200,7 +200,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
U = state.DelayLines[5].Read() U = state.DelayLines[5].Read()
}; };
Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain; Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount); state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DepopForMixBuffers; public CommandType CommandType => CommandType.DepopForMixBuffers;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint MixBufferOffset { get; } public uint MixBufferOffset { get; }

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DepopPrepare; public CommandType CommandType => CommandType.DepopPrepare;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint MixBufferCount { get; } public uint MixBufferCount { get; }

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DeviceSink; public CommandType CommandType => CommandType.DeviceSink;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public string DeviceName { get; } public string DeviceName { get; }

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.DownMixSurroundToStereo; public CommandType CommandType => CommandType.DownMixSurroundToStereo;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort[] InputBufferIndices { get; } public ushort[] InputBufferIndices { get; }
public ushort[] OutputBufferIndices { get; } public ushort[] OutputBufferIndices { get; }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.GroupedBiquadFilter; public CommandType CommandType => CommandType.GroupedBiquadFilter;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
private BiquadFilterParameter[] _parameters; private BiquadFilterParameter[] _parameters;
private Memory<BiquadFilterState> _biquadFilterStates; private Memory<BiquadFilterState> _biquadFilterStates;
@@ -47,9 +47,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
} }
} }
// NOTE: Nintendo also implements a hot path for double biquad filters, but no generic path when the command definition suggests it could be done. // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
// As such we currently only implement a generic path for simplicity. // As such we currently only implement a generic path for simplicity for double biquad.
// TODO: Implement double biquad filters fast path.
if (_parameters.Length == 1) if (_parameters.Length == 1)
{ {
BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType { get; } public CommandType CommandType { get; }
public ulong EstimatedProcessingTime { get; } public uint EstimatedProcessingTime { get; }
public void Process(CommandList context); public void Process(CommandList context);

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.LimiterVersion1; public CommandType CommandType => CommandType.LimiterVersion1;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter; public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; } public Memory<LimiterState> State { get; }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.LimiterVersion2; public CommandType CommandType => CommandType.LimiterVersion2;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public LimiterParameter Parameter => _parameter; public LimiterParameter Parameter => _parameter;
public Memory<LimiterState> State { get; } public Memory<LimiterState> State { get; }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Mix; public CommandType CommandType => CommandType.Mix;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.MixRamp; public CommandType CommandType => CommandType.MixRamp;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.MixRampGrouped; public CommandType CommandType => CommandType.MixRampGrouped;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint MixBufferCount { get; } public uint MixBufferCount { get; }

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1; public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }
public uint SampleRate { get; } public uint SampleRate { get; }

View File

@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1; public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }
public uint SampleRate { get; } public uint SampleRate { get; }

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Performance; public CommandType CommandType => CommandType.Performance;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public PerformanceEntryAddresses PerformanceEntryAddresses { get; } public PerformanceEntryAddresses PerformanceEntryAddresses { get; }

View File

@@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Reverb3d; public CommandType CommandType => CommandType.Reverb3d;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Reverb; public CommandType CommandType => CommandType.Reverb;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ReverbParameter Parameter => _parameter; public ReverbParameter Parameter => _parameter;
public Memory<ReverbState> State { get; } public Memory<ReverbState> State { get; }

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Upsample; public CommandType CommandType => CommandType.Upsample;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public uint BufferCount { get; } public uint BufferCount { get; }
public uint InputBufferIndex { get; } public uint InputBufferIndex { get; }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.Volume; public CommandType CommandType => CommandType.Volume;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public CommandType CommandType => CommandType.VolumeRamp; public CommandType CommandType => CommandType.VolumeRamp;
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
public ushort InputBufferIndex { get; } public ushort InputBufferIndex { get; }
public ushort OutputBufferIndex { get; } public ushort OutputBufferIndex { get; }

View File

@@ -28,6 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
private object _lock = new object(); private object _lock = new object();
private AudioRendererRenderingDevice _renderingDevice;
private AudioRendererExecutionMode _executionMode; private AudioRendererExecutionMode _executionMode;
private IWritableEvent _systemEvent; private IWritableEvent _systemEvent;
private ManualResetEvent _terminationEvent; private ManualResetEvent _terminationEvent;
@@ -63,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
private uint _renderingTimeLimitPercent; private uint _renderingTimeLimitPercent;
private bool _voiceDropEnabled; private bool _voiceDropEnabled;
private uint _voiceDropCount; private uint _voiceDropCount;
private float _voiceDropParameter;
private bool _isDspRunningBehind; private bool _isDspRunningBehind;
private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
@@ -95,6 +97,7 @@ namespace Ryujinx.Audio.Renderer.Server
_totalElapsedTicksUpdating = 0; _totalElapsedTicksUpdating = 0;
_sessionId = 0; _sessionId = 0;
_voiceDropParameter = 1.0f;
} }
public ResultCode Initialize( public ResultCode Initialize(
@@ -130,6 +133,7 @@ namespace Ryujinx.Audio.Renderer.Server
_upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
_appletResourceId = appletResourceId; _appletResourceId = appletResourceId;
_memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
_renderingDevice = parameter.RenderingDevice;
_executionMode = parameter.ExecutionMode; _executionMode = parameter.ExecutionMode;
_sessionId = sessionId; _sessionId = sessionId;
MemoryManager = memoryManager; MemoryManager = memoryManager;
@@ -337,6 +341,7 @@ namespace Ryujinx.Audio.Renderer.Server
_processHandle = processHandle; _processHandle = processHandle;
_elapsedFrameCount = 0; _elapsedFrameCount = 0;
_voiceDropParameter = 1.0f;
switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion()) switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
{ {
@@ -515,7 +520,7 @@ namespace Ryujinx.Audio.Renderer.Server
return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency); return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
} }
private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp) private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
{ {
int i; int i;
@@ -584,7 +589,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
command.Enabled = false; command.Enabled = false;
voicesEstimatedTime -= (long)command.EstimatedProcessingTime; voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
} }
} }
} }
@@ -618,13 +623,13 @@ namespace Ryujinx.Audio.Renderer.Server
_voiceContext.Sort(); _voiceContext.Sort();
commandGenerator.GenerateVoices(); commandGenerator.GenerateVoices();
long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime; uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
commandGenerator.GenerateSubMixes(); commandGenerator.GenerateSubMixes();
commandGenerator.GenerateFinalMixes(); commandGenerator.GenerateFinalMixes();
commandGenerator.GenerateSinks(); commandGenerator.GenerateSinks();
long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime; uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
if (_voiceDropEnabled) if (_voiceDropEnabled)
{ {
@@ -856,5 +861,26 @@ namespace Ryujinx.Audio.Renderer.Server
} }
} }
} }
public void SetVoiceDropParameter(float voiceDropParameter)
{
_voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
}
public float GetVoiceDropParameter()
{
return _voiceDropParameter;
}
public ResultCode ExecuteAudioRendererRendering()
{
if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
{
// NOTE: Here Nintendo aborts with this error code, we don't want that.
return ResultCode.InvalidExecutionContextOperation;
}
return ResultCode.UnsupportedOperation;
}
} }
} }

View File

@@ -94,8 +94,9 @@ namespace Ryujinx.Audio.Renderer.Server
/// REV11: /// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer. /// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes. /// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
/// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
/// </summary> /// </summary>
/// <remarks>This was added in system update 14.0.0</remarks> /// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
public const int Revision11 = 11 << 24; public const int Revision11 = 11 << 24;
/// <summary> /// <summary>

View File

@@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary> /// <summary>
/// The estimated total processing time. /// The estimated total processing time.
/// </summary> /// </summary>
public ulong EstimatedProcessingTime { get; set; } public uint EstimatedProcessingTime { get; set; }
/// <summary> /// <summary>
/// The command list that is populated by the <see cref="CommandBuffer"/>. /// The command list that is populated by the <see cref="CommandBuffer"/>.

View File

@@ -263,12 +263,12 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
return UpdateResult.Success; return UpdateResult.Success;
} }
if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0) if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0)
{ {
return UpdateResult.InvalidParameter; return UpdateResult.InvalidParameter;
} }
if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0) if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0)
{ {
return UpdateResult.InvalidParameter; return UpdateResult.InvalidParameter;
} }

View File

@@ -17,5 +17,6 @@ namespace Ryujinx.Audio
InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId,
} }
} }

View File

@@ -21,6 +21,8 @@ namespace Ryujinx.Ava
{ {
public override void Initialize() public override void Initialize()
{ {
Name = $"Ryujinx {Program.Version}";
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }

View File

@@ -349,7 +349,10 @@ namespace Ryujinx.Ava
_isActive = false; _isActive = false;
if (_renderingThread.IsAlive)
{
_renderingThread.Join(); _renderingThread.Join();
}
DisplaySleep.Restore(); DisplaySleep.Restore();
@@ -417,7 +420,6 @@ namespace Ryujinx.Ava
public async Task<bool> LoadGuestApplication() public async Task<bool> LoadGuestApplication()
{ {
InitializeSwitchInstance(); InitializeSwitchInstance();
MainWindow.UpdateGraphicsConfig(); MainWindow.UpdateGraphicsConfig();
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -428,17 +430,16 @@ namespace Ryujinx.Ava
{ {
if (userError == UserError.NoFirmware) if (userError == UserError.NoFirmware)
{ {
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
firmwareVersion.VersionString);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"],
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], ""); string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
"");
if (result != UserResult.Yes) if (result != UserResult.Yes)
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -447,8 +448,7 @@ namespace Ryujinx.Ava
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -461,11 +461,9 @@ namespace Ryujinx.Ava
_parent.RefreshFirmwareStatus(); _parent.RefreshFirmwareStatus();
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString), string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message, string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogOk"], LocaleManager.Instance["InputDialogOk"],
"", "",
LocaleManager.Instance["RyujinxInfo"]); LocaleManager.Instance["RyujinxInfo"]);
@@ -473,9 +471,7 @@ namespace Ryujinx.Ava
} }
else else
{ {
Dispatcher.UIThread.Post(async () => await await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
Device.Dispose(); Device.Dispose();
return false; return false;
@@ -514,7 +510,7 @@ namespace Ryujinx.Ava
} }
else if (File.Exists(ApplicationPath)) else if (File.Exists(ApplicationPath))
{ {
switch (System.IO.Path.GetExtension(ApplicationPath).ToLowerInvariant()) switch (Path.GetExtension(ApplicationPath).ToLowerInvariant())
{ {
case ".xci": case ".xci":
{ {

View File

@@ -410,6 +410,8 @@
"DlcManagerTableHeadingContainerPathLabel": "Container Path", "DlcManagerTableHeadingContainerPathLabel": "Container Path",
"DlcManagerTableHeadingFullPathLabel": "Full Path", "DlcManagerTableHeadingFullPathLabel": "Full Path",
"DlcManagerRemoveAllButton": "Remove All", "DlcManagerRemoveAllButton": "Remove All",
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language", "MenuBarOptionsChangeLanguage": "Change Language",
"CommonSort": "Sort", "CommonSort": "Sort",
"CommonShowNames": "Show Names", "CommonShowNames": "Show Names",
@@ -562,12 +564,12 @@
"Writable": "Writable", "Writable": "Writable",
"SelectDlcDialogTitle": "Select DLC files", "SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files", "SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "Manage User Profiles", "UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Manage Game Cheats", "CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Game DLC", "DlcWindowTitle": "Downloadable Content Manager",
"UpdateWindowTitle": "Manage Game Updates", "UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel", "Cancel": "Cancel",
"Save": "Save", "Save": "Save",
@@ -575,7 +577,7 @@
"UserProfilesSetProfileImage": "Set Profile Image", "UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required", "UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set", "UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]", "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
"UserProfilesName": "Name:", "UserProfilesName": "Name:",

View File

@@ -28,7 +28,6 @@ namespace Ryujinx.Ava
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; } public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; } public static RenderTimer RenderTimer { get; private set; }
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
@@ -43,7 +42,7 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{ {
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING); _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
} }
PreviewerDetached = true; PreviewerDetached = true;
@@ -73,7 +72,7 @@ namespace Ryujinx.Ava
EnableMultitouch = true, EnableMultitouch = true,
UseWgl = false, UseWgl = false,
AllowEglInitialization = false, AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8f, CompositionBackdropCornerRadius = 8.0f,
}) })
.UseSkia() .UseSkia()
.AfterSetup(_ => .AfterSetup(_ =>
@@ -122,12 +121,10 @@ namespace Ryujinx.Ava
PrintSystemInfo(); PrintSystemInfo();
// Enable OGL multithreading on the driver, when available. // Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
// Check if keys exists. // Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
if (!hasSystemProdKeys)
{ {
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{ {
@@ -197,8 +194,7 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print(); SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels(); Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{ {

View File

@@ -19,25 +19,25 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" /> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.15" /> <PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg" Version="0.10.14" /> <PackageReference Include="Avalonia.Svg" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" /> <PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" /> <PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" /> <PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" /> <PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" /> <PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="OpenTK.Core" Version="4.7.2" /> <PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="SPB" Version="0.0.4-build28" /> <PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.3.3" /> <PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup> </ItemGroup>

View File

@@ -168,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Applet
object response = await msgDialog.Run(); object response = await msgDialog.Run();
if (response != null && buttons.Length > 1 && (int)response != buttons.Length - 1) if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
{ {
showDetails = true; showDetails = true;
} }

View File

@@ -127,9 +127,16 @@ namespace Ryujinx.Ava.Ui.Controls
contentDialog.PrimaryButtonClick += deferCloseAction; contentDialog.PrimaryButtonClick += deferCloseAction;
} }
await contentDialog.ShowAsync(ContentDialogPlacement.Popup); if (useOverlay)
{
await contentDialog.ShowAsync(overlay, ContentDialogPlacement.Popup);
overlay?.Close(); overlay!.Close();
}
else
{
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}
} }
if (useOverlay) if (useOverlay)

View File

@@ -6,8 +6,8 @@ using SPB.Graphics;
using SPB.Platform; using SPB.Platform;
using SPB.Platform.GLX; using SPB.Platform.GLX;
using SPB.Platform.X11; using SPB.Platform.X11;
using SPB.Windowing;
using System; using System;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,12 +15,12 @@ using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
public unsafe class EmbeddedWindow : NativeControlHost public class EmbeddedWindow : NativeControlHost
{ {
private WindowProc _wndProcDelegate; private WindowProc _wndProcDelegate;
private string _className; private string _className;
protected GLXWindow X11Window { get; private set; } protected GLXWindow X11Window { get; set; }
protected IntPtr WindowHandle { get; set; } protected IntPtr WindowHandle { get; set; }
protected IntPtr X11Display { get; set; } protected IntPtr X11Display { get; set; }
@@ -94,19 +94,17 @@ namespace Ryujinx.Ava.Ui.Controls
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
IPlatformHandle CreateLinux(IPlatformHandle parent) protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
{ {
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow; X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
WindowHandle = X11Window.WindowHandle.RawHandle; WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle; X11Display = X11Window.DisplayHandle.RawHandle;
return new PlatformHandle(WindowHandle, "X11"); return new PlatformHandle(WindowHandle, "X11");
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
unsafe IPlatformHandle CreateWin32(IPlatformHandle parent) IPlatformHandle CreateWin32(IPlatformHandle parent)
{ {
_className = "NativeWindow-" + Guid.NewGuid(); _className = "NativeWindow-" + Guid.NewGuid();
_wndProcDelegate = WndProc; _wndProcDelegate = WndProc;
@@ -142,7 +140,7 @@ namespace Ryujinx.Ava.Ui.Controls
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
{ {
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF); var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
var root = VisualRoot as Window; var root = VisualRoot as Window;

View File

@@ -69,12 +69,12 @@ namespace Ryujinx.Ava.Ui.Controls
public void MakeCurrent() public void MakeCurrent()
{ {
Context.MakeCurrent(_window); Context?.MakeCurrent(_window);
} }
public void MakeCurrent(NativeWindowBase window) public void MakeCurrent(NativeWindowBase window)
{ {
Context.MakeCurrent(window); Context?.MakeCurrent(window);
} }
public void SwapBuffers() public void SwapBuffers()

View File

@@ -1,10 +1,13 @@
using Avalonia.Platform;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan; using SPB.Graphics.Vulkan;
using SPB.Platform.GLX;
using SPB.Platform.Win32; using SPB.Platform.Win32;
using SPB.Platform.X11; using SPB.Platform.X11;
using SPB.Windowing; using SPB.Windowing;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Ava.Ui namespace Ryujinx.Ava.Ui
{ {
@@ -12,6 +15,18 @@ namespace Ryujinx.Ava.Ui
{ {
private NativeWindowBase _window; private NativeWindowBase _window;
[SupportedOSPlatform("linux")]
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
{
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
X11Window.Hide();
return new PlatformHandle(WindowHandle, "X11");
}
public SurfaceKHR CreateSurface(Instance instance) public SurfaceKHR CreateSurface(Instance instance)
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
@@ -20,7 +35,7 @@ namespace Ryujinx.Ava.Ui
} }
else if (OperatingSystem.IsLinux()) else if (OperatingSystem.IsLinux())
{ {
_window = X11Window; _window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
} }
else else
{ {

View File

@@ -1,8 +1,22 @@
namespace Ryujinx.Ava.Ui.Models using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Models
{ {
public class DownloadableContentModel public class DownloadableContentModel : BaseModel
{ {
public bool Enabled { get; set; } private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
OnPropertyChanged();
}
}
public string TitleId { get; } public string TitleId { get; }
public string ContainerPath { get; } public string ContainerPath { get; }
public string FullPath { get; } public string FullPath { get; }

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
@@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _loadHeading; private string _loadHeading;
private string _cacheLoadStatus; private string _cacheLoadStatus;
private string _searchText; private string _searchText;
private Timer _searchTimer;
private string _dockedStatusText; private string _dockedStatusText;
private string _fifoStatusText; private string _fifoStatusText;
private string _gameStatusText; private string _gameStatusText;
@@ -115,10 +117,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
_searchText = value; _searchText = value;
RefreshView(); _searchTimer?.Dispose();
_searchTimer = new Timer(TimerCallback, null, 1000, 0);
} }
} }
private void TimerCallback(object obj)
{
RefreshView();
_searchTimer.Dispose();
_searchTimer = null;
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
{ {
get => _appsObservableList; get => _appsObservableList;
@@ -207,15 +219,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
get get
{ {
switch (Glyph) return Glyph switch
{ {
case Glyph.List: Glyph.List => _owner.GameList.SelectedApplication,
return _owner.GameList.SelectedApplication; Glyph.Grid => _owner.GameGrid.SelectedApplication,
case Glyph.Grid: _ => null,
return _owner.GameGrid.SelectedApplication; };
default:
return null;
}
} }
} }
@@ -408,6 +417,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
_owner.AppHost.Device.SetVolume(_volume); _owner.AppHost.Device.SetVolume(_volume);
} }
OnPropertyChanged(nameof(VolumeStatusText)); OnPropertyChanged(nameof(VolumeStatusText));
OnPropertyChanged(nameof(VolumeMuted)); OnPropertyChanged(nameof(VolumeMuted));
OnPropertyChanged(); OnPropertyChanged();
@@ -477,38 +487,36 @@ namespace Ryujinx.Ava.Ui.ViewModels
internal void Sort(bool isAscending) internal void Sort(bool isAscending)
{ {
IsAscending = isAscending; IsAscending = isAscending;
RefreshView(); RefreshView();
} }
internal void Sort(ApplicationSort sort) internal void Sort(ApplicationSort sort)
{ {
SortMode = sort; SortMode = sort;
RefreshView(); RefreshView();
} }
private IComparer<ApplicationData> GetComparer() private IComparer<ApplicationData> GetComparer()
{ {
switch (SortMode) return SortMode switch
{ {
case ApplicationSort.LastPlayed: ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
return new Models.Generic.LastPlayedSortComparer(IsAscending); ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
case ApplicationSort.FileSize: ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
return new Models.Generic.FileSizeSortComparer(IsAscending); ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
case ApplicationSort.TotalTimePlayed: : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
return new Models.Generic.TimePlayedSortComparer(IsAscending); ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
case ApplicationSort.Title: : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName); ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
case ApplicationSort.Favorite: : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite); ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
case ApplicationSort.Developer: : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer); ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
case ApplicationSort.FileType: : SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension); _ => null,
case ApplicationSort.Path: };
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
default:
return null;
}
} }
private void RefreshView() private void RefreshView()
@@ -624,27 +632,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
get get
{ {
switch (SortMode) return SortMode switch
{ {
case ApplicationSort.Title: ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"],
return LocaleManager.Instance["GameListHeaderApplication"]; ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"],
case ApplicationSort.Developer: ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"],
return LocaleManager.Instance["GameListHeaderDeveloper"]; ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"],
case ApplicationSort.LastPlayed: ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"],
return LocaleManager.Instance["GameListHeaderLastPlayed"]; ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"],
case ApplicationSort.TotalTimePlayed: ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"],
return LocaleManager.Instance["GameListHeaderTimePlayed"]; ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"],
case ApplicationSort.FileType: _ => string.Empty,
return LocaleManager.Instance["GameListHeaderFileExtension"]; };
case ApplicationSort.FileSize:
return LocaleManager.Instance["GameListHeaderFileSize"];
case ApplicationSort.Path:
return LocaleManager.Instance["GameListHeaderPath"];
case ApplicationSort.Favorite:
return LocaleManager.Instance["CommonFavorite"];
}
return string.Empty;
} }
} }
@@ -668,6 +667,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_showUikey); set get => KeyGesture.Parse(_showUikey); set
{ {
_showUikey = value.ToString(); _showUikey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -677,6 +677,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_screenshotkey); set get => KeyGesture.Parse(_screenshotkey); set
{ {
_screenshotkey = value.ToString(); _screenshotkey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -686,6 +687,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_pauseKey); set get => KeyGesture.Parse(_pauseKey); set
{ {
_pauseKey = value.ToString(); _pauseKey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -768,6 +770,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
StatusBarProgressValue = e.NumAppsLoaded; StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressMaximum = e.NumAppsFound; StatusBarProgressMaximum = e.NumAppsFound;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum); LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
@@ -792,9 +795,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
await Dispatcher.UIThread.InvokeAsync(() => await Dispatcher.UIThread.InvokeAsync(() =>
{ {
Applications.Clear(); Applications.Clear();
_owner.LoadProgressBar.IsVisible = true; _owner.LoadProgressBar.IsVisible = true;
StatusBarProgressMaximum = 0; StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0; StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0); LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
}); });
@@ -878,10 +883,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None); ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
} }
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{ {
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None); ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
} }
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{ {
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None); PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
@@ -941,9 +948,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_lastFullscreenToggle = Environment.TickCount64; _lastFullscreenToggle = Environment.TickCount64;
WindowState state = _owner.WindowState; if (_owner.WindowState == WindowState.FullScreen)
if (state == WindowState.FullScreen)
{ {
_owner.WindowState = WindowState.Normal; _owner.WindowState = WindowState.Normal;
@@ -971,8 +976,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
if (IsGameRunning) if (IsGameRunning)
{ {
ConfigurationState.Instance.System.EnableDockedMode.Value = ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
!ConfigurationState.Instance.System.EnableDockedMode.Value;
} }
} }
@@ -985,6 +989,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
else if (IsGameRunning) else if (IsGameRunning)
{ {
await Task.Delay(100); await Task.Delay(100);
_owner.AppHost?.ShowExitPrompt(); _owner.AppHost?.ShowExitPrompt();
} }
} }
@@ -994,6 +999,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager); _owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
await _owner.SettingsWindow.ShowDialog(_owner); await _owner.SettingsWindow.ShowDialog(_owner);
LoadConfigurableHotKeys(); LoadConfigurableHotKeys();
} }
@@ -1004,9 +1010,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenAboutWindow() public async void OpenAboutWindow()
{ {
AboutWindow window = new(); await new AboutWindow().ShowDialog(_owner);
await window.ShowDialog(_owner);
} }
public void ChangeLanguage(object obj) public void ChangeLanguage(object obj)
@@ -1065,14 +1069,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenUserSaveDirectory() public void OpenUserSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1082,8 +1084,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
return; return;
} }
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low); UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default); SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
}); });
} }
@@ -1091,8 +1093,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ToggleFavorite() public void ToggleFavorite()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
selection.Favorite = !selection.Favorite; selection.Favorite = !selection.Favorite;
@@ -1108,8 +1109,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenModsDirectory() public void OpenModsDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath(); string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
@@ -1121,7 +1121,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenSdModsDirectory() public void OpenSdModsDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
@@ -1134,12 +1134,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenPtcDirectory() public void OpenPtcDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu"); string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0"); string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1"); string backupPath = Path.Combine(ptcDir, "1");
@@ -1156,8 +1154,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgePtcCache() public async void PurgePtcCache()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0")); DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
@@ -1165,7 +1162,10 @@ 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["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new(); List<FileInfo> cacheFiles = new();
@@ -1198,8 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenShaderCacheDirectory() public void OpenShaderCacheDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"); string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
@@ -1220,18 +1219,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgeShaderCache() public async void PurgeShaderCache()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader")); DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>(); List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new List<FileInfo>(); List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists) if (shaderCacheDir.Exists)
{ {
@@ -1279,38 +1280,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenTitleUpdateManager() public async void OpenTitleUpdateManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
TitleUpdateWindow titleUpdateManager = await new TitleUpdateWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await titleUpdateManager.ShowDialog(_owner);
} }
} }
public async void OpenDownloadableContentManager() public async void OpenDownloadableContentManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
await downloadableContentManager.ShowDialog(_owner);
} }
} }
public async void OpenCheatManager() public async void OpenCheatManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName); await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
await cheatManager.ShowDialog(_owner);
} }
} }
@@ -1321,13 +1312,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
return; return;
} }
var application = _owner.AppHost.Device.Application; ApplicationLoader application = _owner.AppHost.Device.Application;
if (application != null) if (application != null)
{ {
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName); await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner);
await cheatManager.ShowDialog(_owner);
_owner.AppHost.Device.EnableCheats(); _owner.AppHost.Device.EnableCheats();
} }
@@ -1335,14 +1323,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenDeviceSaveDirectory() public void OpenDeviceSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1360,14 +1346,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenBcatSaveDirectory() public void OpenBcatSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1420,12 +1404,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.Close(); _owner.Close();
} }
private async Task HandleFirmwareInstallation(string path) private async Task HandleFirmwareInstallation(string filename)
{ {
try try
{ {
string filename = path;
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename); SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null) if (firmwareVersion == null)
@@ -1437,7 +1419,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString); string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion(); SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString); string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
@@ -1480,11 +1461,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString); string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message); Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache. // Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists) if (miiEditorCacheFolder.Exists)
{ {
@@ -1514,8 +1496,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (LibHac.Common.Keys.MissingKeyException ex) catch (LibHac.Common.Keys.MissingKeyException ex)
{ {
Logger.Error?.Print(LogClass.Application, ex.ToString()); Logger.Error?.Print(LogClass.Application, ex.ToString());
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner)); Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -3,45 +3,92 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="800"
Height="500"
MinWidth="800"
MinHeight="500"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height" SizeToContent="Height"
Width="600" MinHeight="500" Height="500"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
MinWidth="600"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Name="DownloadableContentGrid" Margin="15"> <Grid Name="DownloadableContentGrid" Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Name="Heading"
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap" TextAlignment="Center"
Text="{Binding Heading}" TextWrapping="Wrap" />
TextAlignment="Center" /> <DockPanel
<Border
Grid.Row="2" Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</DockPanel>
<Border
Grid.Row="3"
Margin="5" Margin="5"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<DataGrid <DataGrid
Name="DlcDataGrid"
MinHeight="200" MinHeight="200"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
Items="{Binding DownloadableContents}" Items="{Binding _downloadableContents}"
SelectionMode="Extended"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<DataGrid.Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(1)">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</Styles>
</DataGrid.Styles>
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn Width="90"> <DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
@@ -49,7 +96,7 @@
<CheckBox <CheckBox
Width="50" Width="50"
MinWidth="40" MinWidth="40"
HorizontalAlignment="Right" HorizontalAlignment="Center"
IsChecked="{Binding Enabled}" /> IsChecked="{Binding Enabled}" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
@@ -57,35 +104,27 @@
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" /> <TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header> </DataGridTemplateColumn.Header>
</DataGridTemplateColumn> </DataGridTemplateColumn>
<DataGridTextColumn <DataGridTextColumn Width="140" Binding="{Binding TitleId}">
Width="190"
Binding="{Binding TitleId}"
CanUserResize="True">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" /> <TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn <DataGridTextColumn Width="280" Binding="{Binding FullPath}">
Width="*"
Binding="{Binding ContainerPath}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding FullPath}"
CanUserResize="True">
<DataGridTextColumn.Header> <DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" /> <TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header> </DataGridTextColumn.Header>
</DataGridTextColumn> </DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</ScrollViewer>
</Border> </Border>
<DockPanel <DockPanel
Grid.Row="3" Grid.Row="4"
Margin="0" Margin="0"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left"> <DockPanel Margin="0" HorizontalAlignment="Left">

View File

@@ -18,6 +18,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
@@ -29,12 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
private readonly List<DownloadableContentContainer> _downloadableContentContainerList; private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath; private readonly string _downloadableContentJsonPath;
public VirtualFileSystem VirtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>(); private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
public ulong TitleId { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); private ulong _titleId { get; }
private string _titleName { get; }
public DownloadableContentManagerWindow() public DownloadableContentManagerWindow()
{ {
@@ -42,14 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
} }
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
VirtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
TitleId = titleId; _downloadableContents = new AvaloniaList<DownloadableContentModel>();
TitleName = titleName;
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
@@ -66,9 +68,24 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadDownloadableContents(); LoadDownloadableContents();
PrintHeading();
}
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
} }
private void LoadDownloadableContents() private void LoadDownloadableContents()
@@ -79,20 +96,20 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
VirtualFileSystem.ImportTickets(pfs); _virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{ {
using var ncaFile = new UniqueRef<IFile>(); using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null) if (nca != null)
{ {
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath, downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath, downloadableContentNca.FullPath,
downloadableContentNca.Enabled)); downloadableContentNca.Enabled));
@@ -105,11 +122,11 @@ namespace Ryujinx.Ava.Ui.Windows
Save(); Save();
} }
private Nca TryCreateNca(IStorage ncaStorage, string containerPath) private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{ {
try try
{ {
return new Nca(VirtualFileSystem.KeySet, ncaStorage); return new Nca(_virtualFileSystem.KeySet, ncaStorage);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -124,26 +141,25 @@ namespace Ryujinx.Ava.Ui.Windows
private async Task AddDownloadableContent(string path) private async Task AddDownloadableContent(string path)
{ {
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{ {
return; return;
} }
using (FileStream containerFile = File.OpenRead(path)) using FileStream containerFile = File.OpenRead(path);
{
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false; bool containsDownloadableContent = false;
VirtualFileSystem.ImportTickets(pfs); _virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{ {
using var ncaFile = new UniqueRef<IFile>(); using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null) if (nca == null)
{ {
continue; continue;
@@ -151,12 +167,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (nca.Header.ContentType == NcaContentType.PublicData) if (nca.Header.ContentType == NcaContentType.PublicData)
{ {
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId) if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{ {
break; break;
} }
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true)); _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true; containsDownloadableContent = true;
} }
@@ -167,18 +183,31 @@ namespace Ryujinx.Ava.Ui.Windows
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
} }
} }
}
private void RemoveDownloadableContents(bool removeSelectedOnly = false) private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{ {
if (removeSelectedOnly) if (removeSelectedOnly)
{ {
DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList()); AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
} }
else else
{ {
DownloadableContents.Clear(); _downloadableContents.Clear();
} }
PrintHeading();
} }
public void RemoveSelected() public void RemoveSelected()
@@ -191,6 +220,22 @@ namespace Ryujinx.Ava.Ui.Windows
RemoveDownloadableContents(); RemoveDownloadableContents();
} }
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add() public async void Add()
{ {
OpenFileDialog dialog = new OpenFileDialog() OpenFileDialog dialog = new OpenFileDialog()
@@ -214,6 +259,8 @@ namespace Ryujinx.Ava.Ui.Windows
await AddDownloadableContent(file); await AddDownloadableContent(file);
} }
} }
PrintHeading();
} }
public void Save() public void Save()
@@ -222,7 +269,7 @@ namespace Ryujinx.Ava.Ui.Windows
DownloadableContentContainer container = default; DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents) foreach (DownloadableContentModel downloadableContent in _downloadableContents)
{ {
if (container.ContainerPath != downloadableContent.ContainerPath) if (container.ContainerPath != downloadableContent.ContainerPath)
{ {

View File

@@ -90,8 +90,8 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version}"; Title = $"Ryujinx {Program.Version}";
Height = Height / Program.WindowScaleFactor; Height /= Program.WindowScaleFactor;
Width = Width / Program.WindowScaleFactor; Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@@ -251,9 +251,12 @@ namespace Ryujinx.Ava.Ui.Windows
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this); AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
if (!AppHost.LoadGuestApplication().Result) Dispatcher.UIThread.Post(async () =>
{
if (!await AppHost.LoadGuestApplication())
{ {
AppHost.DisposeContext(); AppHost.DisposeContext();
AppHost = null;
return; return;
} }
@@ -264,11 +267,13 @@ namespace Ryujinx.Ava.Ui.Windows
SwitchToGameControl(startFullscreen); SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path; _currentEmulatedGamePath = path;
Thread gameThread = new Thread(InitializeGame)
Thread gameThread = new(InitializeGame)
{ {
Name = "GUI.WindowThread" Name = "GUI.WindowThread"
}; };
gameThread.Start(); gameThread.Start();
});
} }
private void InitializeGame() private void InitializeGame()
@@ -518,10 +523,7 @@ namespace Ryujinx.Ava.Ui.Windows
public static void UpdateGraphicsConfig() public static void UpdateGraphicsConfig()
{ {
int resScale = ConfigurationState.Instance.Graphics.ResScale; GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy; GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath; GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache; GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
@@ -546,10 +548,12 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{ {
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed); if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds; double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero); appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
}); });
} }

View File

@@ -75,7 +75,7 @@
Spacing="10"> Spacing="10">
<ListBox <ListBox
Name="GameList" Name="GameList"
MinHeight="150" MinHeight="250"
Items="{Binding GameDirectories}" /> Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>

View File

@@ -16,7 +16,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone; using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
@@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows
_currentAssigner.Cancel(); _currentAssigner.Cancel();
_currentAssigner = null; _currentAssigner = null;
button.IsChecked = false; button.IsChecked = false;
} }
} }
@@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
if (e.SelectedItem is NavigationViewItem navitem) if (e.SelectedItem is NavigationViewItem navitem)
{ {
switch (navitem.Tag.ToString()) NavPanel.Content = navitem.Tag.ToString() switch
{ {
case "UiPage": "UiPage" => UiPage,
NavPanel.Content = UiPage; "InputPage" => InputPage,
break; "HotkeysPage" => HotkeysPage,
case "InputPage": "SystemPage" => SystemPage,
NavPanel.Content = InputPage; "CpuPage" => CpuPage,
break; "GraphicsPage" => GraphicsPage,
case "HotkeysPage": "AudioPage" => AudioPage,
NavPanel.Content = HotkeysPage; "NetworkPage" => NetworkPage,
break; "LoggingPage" => LoggingPage,
case "SystemPage": _ => throw new NotImplementedException()
NavPanel.Content = SystemPage; };
break;
case "CpuPage":
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
}
} }
} }
@@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e) private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{ {
List<string> selected = new(GameList.SelectedItems.Cast<string>()); int oldIndex = GameList.SelectedIndex;
foreach (string path in selected) foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{ {
ViewModel.GameDirectories.Remove(path); ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true; ViewModel.DirectoryChanged = true;
} }
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
} }
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -214,7 +202,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveButton_Clicked(object sender, RoutedEventArgs e) private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{ {
SaveSettings(); SaveSettings();
Close(); Close();
} }
@@ -232,7 +219,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveSettings() private void SaveSettings()
{ {
ViewModel.SaveSettings(); ViewModel.SaveSettings();
ControllerSettings?.SaveCurrentProfile(); ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged) if (Owner is MainWindow window && ViewModel.DirectoryChanged)
@@ -246,8 +232,10 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
ControllerSettings.Dispose(); ControllerSettings.Dispose();
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
base.OnClosed(e); base.OnClosed(e);
} }
} }

View File

@@ -3,13 +3,17 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
SizeToContent="Height" Width="600"
Width="600" MinHeight="500" Height="500" Height="400"
WindowStartupLocation="CenterOwner"
MinWidth="600" MinWidth="600"
MinHeight="400"
MaxWidth="600"
MaxHeight="400"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Margin="15"> <Grid Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -19,15 +23,15 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Name="Heading"
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap" TextAlignment="Center"
Text="{Binding Heading}" TextWrapping="Wrap" />
TextAlignment="Center" />
<Border <Border
Grid.Row="2" Grid.Row="2"
Margin="5" Margin="5"
@@ -36,8 +40,6 @@
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer <ScrollViewer
Width="550"
MinHeight="200"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
@@ -45,11 +47,19 @@
Margin="10" Margin="10"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Items="{Binding TitleUpdates}"> Items="{Binding _titleUpdates}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}"> <RadioButton
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" /> Padding="8,0"
VerticalContentAlignment="Center"
GroupName="Update"
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
<Label
Margin="0"
VerticalAlignment="Center"
Content="{Binding Label}"
FontSize="12" />
</RadioButton> </RadioButton>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
private readonly string _titleUpdateJsonPath; private readonly string _titleUpdateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>(); private ulong _titleId { get; }
public string TitleId { get; } private string _titleName { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
public TitleUpdateWindow() public TitleUpdateWindow()
{ {
@@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
} }
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
VirtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
TitleId = titleId; _titleUpdates = new AvaloniaList<TitleUpdateModel>();
TitleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); _titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try try
{ {
@@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadUpdates(); LoadUpdates();
PrintHeading();
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
} }
private void LoadUpdates() private void LoadUpdates()
{ {
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); _titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
foreach (string path in _titleUpdateWindowData.Paths) foreach (string path in _titleUpdateWindowData.Paths)
{ {
@@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (_titleUpdateWindowData.Selected == "") if (_titleUpdateWindowData.Selected == "")
{ {
TitleUpdates[0].IsEnabled = true; _titleUpdates[0].IsEnabled = true;
} }
else else
{ {
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
foreach (TitleUpdateModel update in enabled) foreach (TitleUpdateModel update in enabled)
{ {
@@ -111,26 +117,31 @@ namespace Ryujinx.Ava.Ui.Windows
private void AddUpdate(string path) private void AddUpdate(string path)
{ {
if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path)) if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
{ {
using (FileStream file = new(path, FileMode.Open, FileAccess.Read)) using FileStream file = new(path, FileMode.Open, FileAccess.Read);
{
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
try try
{ {
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null) if (controlNca != null && patchNca != null)
{ {
ApplicationControlProperty controlData = new ApplicationControlProperty(); ApplicationControlProperty controlData = new();
using var nacpFile = new UniqueRef<IFile>(); using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure(); controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path)); _titleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in _titleUpdates)
{
update.IsEnabled = false;
}
_titleUpdates.Last().IsEnabled = true;
} }
else else
{ {
@@ -149,22 +160,22 @@ namespace Ryujinx.Ava.Ui.Windows
} }
} }
} }
}
private void RemoveUpdates(bool removeSelectedOnly = false) private void RemoveUpdates(bool removeSelectedOnly = false)
{ {
if (removeSelectedOnly) if (removeSelectedOnly)
{ {
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); _titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
} }
else else
{ {
TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList()); _titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
} }
TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true; _titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
SortUpdates(); SortUpdates();
PrintHeading();
} }
public void RemoveSelected() public void RemoveSelected()
@@ -179,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows
public async void Add() public async void Add()
{ {
OpenFileDialog dialog = new OpenFileDialog() OpenFileDialog dialog = new()
{ {
Title = LocaleManager.Instance["SelectUpdateDialogTitle"], Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
AllowMultiple = true AllowMultiple = true
@@ -202,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows
} }
SortUpdates(); SortUpdates();
PrintHeading();
} }
private void SortUpdates() private void SortUpdates()
{ {
var list = TitleUpdates.ToList(); var list = _titleUpdates.ToList();
list.Sort((first, second) => list.Sort((first, second) =>
{ {
@@ -222,8 +234,8 @@ namespace Ryujinx.Ava.Ui.Windows
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
}); });
TitleUpdates.Clear(); _titleUpdates.Clear();
TitleUpdates.AddRange(list); _titleUpdates.AddRange(list);
} }
public void Save() public void Save()
@@ -232,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData.Selected = ""; _titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates) foreach (TitleUpdateModel update in _titleUpdates)
{ {
_titleUpdateWindowData.Paths.Add(update.Path); _titleUpdateWindowData.Paths.Add(update.Path);

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Common namespace Ryujinx.Common
@@ -7,49 +8,15 @@ namespace Ryujinx.Common
public static class BinaryReaderExtensions public static class BinaryReaderExtensions
{ {
public unsafe static T ReadStruct<T>(this BinaryReader reader) public unsafe static T ReadStruct<T>(this BinaryReader reader)
where T : struct where T : unmanaged
{ {
int size = Marshal.SizeOf<T>(); return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
public unsafe static T[] ReadStructArray<T>(this BinaryReader reader, int count)
where T : struct
{
int size = Marshal.SizeOf<T>();
T[] result = new T[count];
for (int i = 0; i < count; i++)
{
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
result[i] = Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
return result;
} }
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value) public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : struct where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
writer.Write(data); writer.Write(data);
} }

View File

@@ -8,7 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MsgPack.Cli" Version="1.0.1" /> <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" /> <PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" /> <PackageReference Include="System.Management" Version="7.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data); WriteImpl(va, data);
} }
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return false;
}
SignalMemoryTracking(va, (ulong)data.Length, false);
if (IsContiguousAndMapped(va, data.Length))
{
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
else
{
WriteImpl(va, data);
return true;
}
}
/// <summary> /// <summary>
/// Writes data to CPU mapped memory. /// Writes data to CPU mapped memory.
/// </summary> /// </summary>

View File

@@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit
} }
} }
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
try
{
SignalMemoryTracking(va, (ulong)data.Length, false);
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return true;
}
}
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {

View File

@@ -1,6 +1,7 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -23,34 +24,18 @@ namespace Ryujinx.Cpu
} }
} }
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : struct public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); return MemoryMarshal.Cast<byte, T>(memory.GetSpan(position, Unsafe.SizeOf<T>()))[0];
byte[] data = new byte[size];
memory.Read(position, data);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
} }
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : struct public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : unmanaged
{ {
long size = Marshal.SizeOf<T>(); ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
memory.Write(position, data); memory.Write(position, data);
return (ulong)size; return (ulong)data.Length;
} }
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1) public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)

View File

@@ -422,7 +422,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
// Stop the GPU thread. // Stop the GPU thread.
_disposed = true; _disposed = true;
if (_gpuThread != null && _gpuThread.IsAlive)
{
_gpuThread.Join(); _gpuThread.Join();
}
// Dispose the renderer. // Dispose the renderer.
_baseRenderer.Dispose(); _baseRenderer.Dispose();

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
class ConstantBufferUpdater class ConstantBufferUpdater
{ {
private const int UniformDataCacheSize = 512;
private readonly GpuChannel _channel; private readonly GpuChannel _channel;
private readonly DeviceStateWithShadow<ThreedClassState> _state; private readonly DeviceStateWithShadow<ThreedClassState> _state;
@@ -16,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private ulong _ubBeginCpuAddress = 0; private ulong _ubBeginCpuAddress = 0;
private ulong _ubFollowUpAddress = 0; private ulong _ubFollowUpAddress = 0;
private ulong _ubByteCount = 0; private ulong _ubByteCount = 0;
private int _ubIndex = 0;
private int[] _ubData = new int[UniformDataCacheSize];
/// <summary> /// <summary>
/// Creates a new instance of the constant buffer updater. /// Creates a new instance of the constant buffer updater.
@@ -108,9 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_ubFollowUpAddress != 0) if (_ubFollowUpAddress != 0)
{ {
var memoryManager = _channel.MemoryManager; var memoryManager = _channel.MemoryManager;
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
{
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount); memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
}
_ubFollowUpAddress = 0; _ubFollowUpAddress = 0;
_ubIndex = 0;
} }
} }
@@ -124,7 +135,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset; ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
if (_ubFollowUpAddress != address) if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
{ {
FlushUboDirty(); FlushUboDirty();
@@ -132,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address); _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
} }
var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1)); _ubData[_ubIndex++] = argument;
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
_ubFollowUpAddress = address + 4; _ubFollowUpAddress = address + 4;
_ubByteCount += 4; _ubByteCount += 4;
@@ -153,7 +163,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong size = (ulong)data.Length * 4; ulong size = (ulong)data.Length * 4;
if (_ubFollowUpAddress != address) if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
{ {
FlushUboDirty(); FlushUboDirty();
@@ -161,8 +171,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address); _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
} }
var byteData = MemoryMarshal.Cast<int, byte>(data); data.CopyTo(_ubData.AsSpan(_ubIndex));
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); _ubIndex += data.Length;
_ubFollowUpAddress = address + size; _ubFollowUpAddress = address + size;
_ubByteCount += size; _ubByteCount += size;

View File

@@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private bool _vsUsesDrawParameters; private bool _vsUsesDrawParameters;
private bool _vtgWritesRtLayer; private bool _vtgWritesRtLayer;
private byte _vsClipDistancesWritten; private byte _vsClipDistancesWritten;
private uint _vbEnableMask;
private bool _prevDrawIndexed; private bool _prevDrawIndexed;
private bool _prevDrawIndirect; private bool _prevDrawIndirect;
@@ -76,6 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
nameof(ThreedClassState.VertexBufferState), nameof(ThreedClassState.VertexBufferState),
nameof(ThreedClassState.VertexBufferEndAddress)), nameof(ThreedClassState.VertexBufferEndAddress)),
// Must be done after vertex buffer updates.
new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)), new StateUpdateCallbackEntry(UpdateVertexAttribState, nameof(ThreedClassState.VertexAttribState)),
new StateUpdateCallbackEntry(UpdateBlendState, new StateUpdateCallbackEntry(UpdateBlendState,
@@ -852,12 +854,23 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary> /// </summary>
private void UpdateVertexAttribState() private void UpdateVertexAttribState()
{ {
uint vbEnableMask = _vbEnableMask;
Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs]; Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs];
for (int index = 0; index < Constants.TotalVertexAttribs; index++) for (int index = 0; index < Constants.TotalVertexAttribs; index++)
{ {
var vertexAttrib = _state.State.VertexAttribState[index]; var vertexAttrib = _state.State.VertexAttribState[index];
int bufferIndex = vertexAttrib.UnpackBufferIndex();
if ((vbEnableMask & (1u << bufferIndex)) == 0)
{
// Using a vertex buffer that doesn't exist is invalid, so let's use a dummy attribute for those cases.
vertexAttribs[index] = new VertexAttribDescriptor(0, 0, true, Format.R32G32B32A32Float);
continue;
}
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format)) if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
{ {
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}."); Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
@@ -866,7 +879,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
} }
vertexAttribs[index] = new VertexAttribDescriptor( vertexAttribs[index] = new VertexAttribDescriptor(
vertexAttrib.UnpackBufferIndex(), bufferIndex,
vertexAttrib.UnpackOffset(), vertexAttrib.UnpackOffset(),
vertexAttrib.UnpackIsConstant(), vertexAttrib.UnpackIsConstant(),
format); format);
@@ -954,6 +967,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
bool drawIndexed = _drawState.DrawIndexed; bool drawIndexed = _drawState.DrawIndexed;
bool drawIndirect = _drawState.DrawIndirect; bool drawIndirect = _drawState.DrawIndirect;
uint vbEnableMask = 0;
for (int index = 0; index < Constants.TotalVertexBuffers; index++) for (int index = 0; index < Constants.TotalVertexBuffers; index++)
{ {
@@ -971,6 +985,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong address = vertexBuffer.Address.Pack(); ulong address = vertexBuffer.Address.Pack();
if (_channel.MemoryManager.IsMapped(address))
{
vbEnableMask |= 1u << index;
}
int stride = vertexBuffer.UnpackStride(); int stride = vertexBuffer.UnpackStride();
bool instanced = _state.State.VertexBufferInstanced[index]; bool instanced = _state.State.VertexBufferInstanced[index];
@@ -1017,6 +1036,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor); _pipeline.VertexBuffers[index] = new BufferPipelineDescriptor(_channel.MemoryManager.IsMapped(address), stride, divisor);
_channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor); _channel.BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
} }
if (_vbEnableMask != vbEnableMask)
{
_vbEnableMask = vbEnableMask;
UpdateVertexAttribState();
}
} }
/// <summary> /// <summary>

View File

@@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
WriteImpl(range, data, _cpuMemory.WriteUntracked); WriteImpl(range, data, _cpuMemory.WriteUntracked);
} }
/// <summary>
/// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
/// </summary>
/// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
/// <param name="address">Address to write into</param>
/// <param name="data">Data to be written</param>
/// <returns>True if the data was changed, false otherwise</returns>
public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan<byte> data)
{
return _cpuMemory.WriteWithRedundancyCheck(address, data);
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data); private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary> /// <summary>

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3868; private const uint CodeGenVersion = 3897;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@@ -331,7 +331,7 @@ namespace Ryujinx.Graphics.OpenGL
case PrimitiveTopology.QuadStrip: case PrimitiveTopology.QuadStrip:
return PrimitiveType.QuadStrip; return PrimitiveType.QuadStrip;
case PrimitiveTopology.Polygon: case PrimitiveTopology.Polygon:
return PrimitiveType.Polygon; return PrimitiveType.TriangleFan;
case PrimitiveTopology.LinesAdjacency: case PrimitiveTopology.LinesAdjacency:
return PrimitiveType.LinesAdjacency; return PrimitiveType.LinesAdjacency;
case PrimitiveTopology.LineStripAdjacency: case PrimitiveTopology.LineStripAdjacency:

View File

@@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" /> <PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -10,13 +10,9 @@ namespace Ryujinx.Graphics.OpenGL
{ {
public int Handle { get; private set; } public int Handle { get; private set; }
private bool _needsAttribsUpdate;
private readonly VertexAttribDescriptor[] _vertexAttribs; private readonly VertexAttribDescriptor[] _vertexAttribs;
private readonly VertexBufferDescriptor[] _vertexBuffers; private readonly VertexBufferDescriptor[] _vertexBuffers;
private int _vertexAttribsCount;
private int _vertexBuffersCount;
private int _minVertexCount; private int _minVertexCount;
private uint _vertexAttribsInUse; private uint _vertexAttribsInUse;
@@ -76,9 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
_vertexBuffers[bindingIndex] = vb; _vertexBuffers[bindingIndex] = vb;
} }
_vertexBuffersCount = bindingIndex;
_minVertexCount = minVertexCount; _minVertexCount = minVertexCount;
_needsAttribsUpdate = true;
} }
public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs) public void SetVertexAttributes(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
@@ -131,8 +125,6 @@ namespace Ryujinx.Graphics.OpenGL
_vertexAttribs[index] = attrib; _vertexAttribs[index] = attrib;
} }
_vertexAttribsCount = index;
for (; index < Constants.MaxVertexAttribs; index++) for (; index < Constants.MaxVertexAttribs; index++)
{ {
DisableVertexAttrib(index); DisableVertexAttrib(index);
@@ -160,13 +152,11 @@ namespace Ryujinx.Graphics.OpenGL
public void PreDraw(int vertexCount) public void PreDraw(int vertexCount)
{ {
LimitVertexBuffers(vertexCount); LimitVertexBuffers(vertexCount);
Validate();
} }
public void PreDrawVbUnbounded() public void PreDrawVbUnbounded()
{ {
UnlimitVertexBuffers(); UnlimitVertexBuffers();
Validate();
} }
public void LimitVertexBuffers(int vertexCount) public void LimitVertexBuffers(int vertexCount)
@@ -252,36 +242,6 @@ namespace Ryujinx.Graphics.OpenGL
_vertexBuffersLimited = 0; _vertexBuffersLimited = 0;
} }
public void Validate()
{
for (int attribIndex = 0; attribIndex < _vertexAttribsCount; attribIndex++)
{
VertexAttribDescriptor attrib = _vertexAttribs[attribIndex];
if (!attrib.IsZero)
{
if ((uint)attrib.BufferIndex >= _vertexBuffersCount)
{
DisableVertexAttrib(attribIndex);
continue;
}
if (_vertexBuffers[attrib.BufferIndex].Buffer.Handle == BufferHandle.Null)
{
DisableVertexAttrib(attribIndex);
continue;
}
if (_needsAttribsUpdate)
{
EnableVertexAttrib(attribIndex);
}
}
}
_needsAttribsUpdate = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnableVertexAttrib(int index) private void EnableVertexAttrib(int index)
{ {

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
private const int TextureCount = 3; private const int TextureCount = 3;
private readonly OpenGLRenderer _renderer; private readonly OpenGLRenderer _renderer;
private bool _initialized;
private int _width; private int _width;
private int _height; private int _height;
private int _copyFramebufferHandle; private int _copyFramebufferHandle;
@@ -179,6 +181,7 @@ namespace Ryujinx.Graphics.OpenGL
public void InitializeBackgroundContext(IOpenGLContext baseContext) public void InitializeBackgroundContext(IOpenGLContext baseContext)
{ {
BackgroundContext = new BackgroundContextWorker(baseContext); BackgroundContext = new BackgroundContextWorker(baseContext);
_initialized = true;
} }
public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
@@ -193,6 +196,11 @@ namespace Ryujinx.Graphics.OpenGL
public void Dispose() public void Dispose()
{ {
if (!_initialized)
{
return;
}
BackgroundContext.Dispose(); BackgroundContext.Dispose();
if (_copyFramebufferHandle != 0) if (_copyFramebufferHandle != 0)

View File

@@ -128,6 +128,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) : GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) :
(null, 0); (null, 0);
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (byteOffset == null) if (byteOffset == null)
{ {
Operand baseAddrLow = Cbuf(0, baseAddressCbOffset); Operand baseAddrLow = Cbuf(0, baseAddressCbOffset);
@@ -156,11 +161,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
byteOffset = offset; byteOffset = offset;
} }
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (isStg16Or8) if (isStg16Or8)
{ {
return byteOffset; return byteOffset;

View File

@@ -180,6 +180,7 @@ namespace Ryujinx.Graphics.Vulkan
GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency, GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency, GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList, GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
GAL.PrimitiveTopology.Polygon => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan,
GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."), GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."), GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
_ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList) _ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)

View File

@@ -328,7 +328,8 @@ namespace Ryujinx.Graphics.Vulkan
IndexBufferPattern pattern = _topology switch IndexBufferPattern pattern = _topology switch
{ {
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern, GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern, GAL.PrimitiveTopology.TriangleFan or
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_topology}") _ => throw new NotSupportedException($"Unsupported topology: {_topology}")
}; };
@@ -359,7 +360,8 @@ namespace Ryujinx.Graphics.Vulkan
pattern = _topology switch pattern = _topology switch
{ {
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern, GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern, GAL.PrimitiveTopology.TriangleFan or
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
_ => throw new NotSupportedException($"Unsupported topology: {_topology}") _ => throw new NotSupportedException($"Unsupported topology: {_topology}")
}; };
} }

View File

@@ -13,7 +13,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.2" /> <PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
<PackageReference Include="shaderc.net" Version="0.1.0" /> <PackageReference Include="shaderc.net" Version="0.1.0" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />

View File

@@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Device _device; private Device _device;
private WindowBase _window; private WindowBase _window;
private bool _initialized;
internal FormatCapabilities FormatCapabilities { get; private set; } internal FormatCapabilities FormatCapabilities { get; private set; }
internal HardwareCapabilities Capabilities; internal HardwareCapabilities Capabilities;
@@ -266,6 +268,8 @@ namespace Ryujinx.Graphics.Vulkan
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex); LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
_window = new Window(this, _surface, _physicalDevice, _device); _window = new Window(this, _surface, _physicalDevice, _device);
_initialized = true;
} }
public BufferHandle CreateBuffer(int size) public BufferHandle CreateBuffer(int size)
@@ -573,6 +577,11 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Dispose() public unsafe void Dispose()
{ {
if (!_initialized)
{
return;
}
CommandBufferPool.Dispose(); CommandBufferPool.Dispose();
BackgroundResources.Dispose(); BackgroundResources.Dispose();
_counters.Dispose(); _counters.Dispose();

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
@@ -9,6 +10,7 @@ using Ryujinx.Memory;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -78,13 +80,13 @@ namespace Ryujinx.HLE.HOS.Applets
var launchParams = _normalSession.Pop(); var launchParams = _normalSession.Pop();
var keyboardConfig = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop();
_isBackground = keyboardConfig.Length == Marshal.SizeOf<SoftwareKeyboardInitialize>(); _isBackground = keyboardConfig.Length == Unsafe.SizeOf<SoftwareKeyboardInitialize>();
if (_isBackground) if (_isBackground)
{ {
// Initialize the keyboard applet in background mode. // Initialize the keyboard applet in background mode.
_keyboardBackgroundInitialize = ReadStruct<SoftwareKeyboardInitialize>(keyboardConfig); _keyboardBackgroundInitialize = MemoryMarshal.Read<SoftwareKeyboardInitialize>(keyboardConfig);
_backgroundState = InlineKeyboardState.Uninitialized; _backgroundState = InlineKeyboardState.Uninitialized;
if (_device.UiHandler == null) if (_device.UiHandler == null)
@@ -342,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Applets
else else
{ {
int wordsCount = reader.ReadInt32(); int wordsCount = reader.ReadInt32();
int wordSize = Marshal.SizeOf<SoftwareKeyboardUserWord>(); int wordSize = Unsafe.SizeOf<SoftwareKeyboardUserWord>();
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (wordsCount > MaxUserWords) if (wordsCount > MaxUserWords)
@@ -359,8 +361,7 @@ namespace Ryujinx.HLE.HOS.Applets
for (int word = 0; word < wordsCount; word++) for (int word = 0; word < wordsCount; word++)
{ {
byte[] wordData = reader.ReadBytes(wordSize); _keyboardBackgroundUserWords[word] = reader.ReadStruct<SoftwareKeyboardUserWord>();
_keyboardBackgroundUserWords[word] = ReadStruct<SoftwareKeyboardUserWord>(wordData);
} }
} }
} }
@@ -369,27 +370,25 @@ namespace Ryujinx.HLE.HOS.Applets
case InlineKeyboardRequest.SetCustomizeDic: case InlineKeyboardRequest.SetCustomizeDic:
// Read the custom dic data. // Read the custom dic data.
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardCustomizeDic>()) if (remaining != Unsafe.SizeOf<SoftwareKeyboardCustomizeDic>())
{ {
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes"); Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes");
} }
else else
{ {
var keyboardDicData = reader.ReadBytes((int)remaining); _keyboardBackgroundDic = reader.ReadStruct<SoftwareKeyboardCustomizeDic>();
_keyboardBackgroundDic = ReadStruct<SoftwareKeyboardCustomizeDic>(keyboardDicData);
} }
break; break;
case InlineKeyboardRequest.SetCustomizedDictionaries: case InlineKeyboardRequest.SetCustomizedDictionaries:
// Read the custom dictionaries data. // Read the custom dictionaries data.
remaining = stream.Length - stream.Position; remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardDictSet>()) if (remaining != Unsafe.SizeOf<SoftwareKeyboardDictSet>())
{ {
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes"); Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes");
} }
else else
{ {
var keyboardDictData = reader.ReadBytes((int)remaining); _keyboardBackgroundDictSet = reader.ReadStruct<SoftwareKeyboardDictSet>();
_keyboardBackgroundDictSet = ReadStruct<SoftwareKeyboardDictSet>(keyboardDictData);
} }
break; break;
case InlineKeyboardRequest.Calc: case InlineKeyboardRequest.Calc:

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// A structure used by SetCustomizeDic request to software keyboard. /// A structure used by SetCustomizeDic request to software keyboard.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Size = 0x70)]
struct SoftwareKeyboardCustomizeDic struct SoftwareKeyboardCustomizeDic
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 112)] // Unknown
public byte[] Unknown;
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
@@ -21,8 +22,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// Array of word entries in the buffer. /// Array of word entries in the buffer.
/// </summary> /// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)] public Array24<ulong> Entries;
public ulong[] Entries;
/// <summary> /// <summary>
/// Number of used entries in the Entries field. /// Number of used entries in the Entries field.

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary> /// <summary>
/// A structure used by SetUserWordInfo request to the software keyboard. /// A structure used by SetUserWordInfo request to the software keyboard.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)] [StructLayout(LayoutKind.Sequential, Size = 0x64)]
struct SoftwareKeyboardUserWord struct SoftwareKeyboardUserWord
{ {
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] // Unknown
public byte[] Unknown;
} }
} }

View File

@@ -479,7 +479,10 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose(); AudioRendererManager.Dispose();
if (LibHacHorizonManager.ApplicationClient != null)
{
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
}
KernelContext.Dispose(); KernelContext.Dispose();
} }

View File

@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
public ResultCode ExecuteAudioRendererRendering() public ResultCode ExecuteAudioRendererRendering()
{ {
throw new NotImplementedException(); return (ResultCode)_impl.ExecuteAudioRendererRendering();
} }
public uint GetMixBufferCount() public uint GetMixBufferCount()
@@ -108,5 +108,15 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
_impl.Dispose(); _impl.Dispose();
} }
} }
public void SetVoiceDropParameter(float voiceDropParameter)
{
_impl.SetVoiceDropParameter(voiceDropParameter);
}
public float GetVoiceDropParameter()
{
return _impl.GetVoiceDropParameter();
}
} }
} }

View File

@@ -172,6 +172,35 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
return result; return result;
} }
[CommandHipc(11)] // 3.0.0+
// ExecuteAudioRendererRendering()
public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
{
return _impl.ExecuteAudioRendererRendering();
}
[CommandHipc(12)] // 15.0.0+
// SetVoiceDropParameter(f32 voiceDropParameter)
public ResultCode SetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = context.RequestData.ReadSingle();
_impl.SetVoiceDropParameter(voiceDropParameter);
return ResultCode.Success;
}
[CommandHipc(13)] // 15.0.0+
// GetVoiceDropParameter() -> f32 voiceDropParameter
public ResultCode GetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = _impl.GetVoiceDropParameter();
context.ResponseData.Write(voiceDropParameter);
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
if (isDisposing) if (isDisposing)

View File

@@ -16,5 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
void SetRenderingTimeLimit(uint percent); void SetRenderingTimeLimit(uint percent);
uint GetRenderingTimeLimit(); uint GetRenderingTimeLimit();
ResultCode ExecuteAudioRendererRendering(); ResultCode ExecuteAudioRendererRendering();
void SetVoiceDropParameter(float voiceDropParameter);
float GetVoiceDropParameter();
} }
} }

View File

@@ -1,9 +1,12 @@
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
{ {
[StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)] [StructLayout(LayoutKind.Sequential, Pack = 0x8)]
struct UserPresence struct UserPresence
{ {
public UserId UserId; public UserId UserId;
@@ -13,15 +16,20 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool SamePresenceGroupApplication; public bool SamePresenceGroupApplication;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)] public Array3<byte> Unknown;
public char[] Unknown; private AppKeyValueStorageHolder _appKeyValueStorage;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)] public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
public char[] AppKeyValueStorage;
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
private struct AppKeyValueStorageHolder
{
public const int Size = 0xC0;
}
public override string ToString() public override string ToString()
{ {
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}"; return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {Encoding.ASCII.GetString(AppKeyValueStorage)} }}";
} }
} }
} }

View File

@@ -236,23 +236,14 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
ulong position = context.Request.PtrBuff[0].Position; ulong position = context.Request.PtrBuff[0].Position;
ulong size = context.Request.PtrBuff[0].Size; ulong size = context.Request.PtrBuff[0].Size;
byte[] bufferContent = new byte[size]; ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
context.Memory.Read(position, bufferContent);
if (uuid.IsNull) if (uuid.IsNull)
{ {
return ResultCode.InvalidArgument; return ResultCode.InvalidArgument;
} }
int elementCount = bufferContent.Length / Marshal.SizeOf<UserPresence>(); Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent)))
{
UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray<UserPresence>(elementCount);
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray });
}
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -1,15 +1,13 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
{ {
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x10)] [StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NotificationInfo struct NotificationInfo
{ {
public NotificationEventType Type; public NotificationEventType Type;
private Array4<byte> _padding;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)]
public char[] Padding;
public long NetworkUserIdPlaceholder; public long NetworkUserIdPlaceholder;
} }
} }

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Ptm.Psm
{ {
if (_stateChangeEventHandle == -1) if (_stateChangeEventHandle == -1)
{ {
KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle); KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle);
if (resultCode != KernelResult.Success) if (resultCode != KernelResult.Success)
{ {

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
@@ -75,7 +76,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++) for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++)
{ {
MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Marshal.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value); MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Unsafe.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
} }
context.ResponseData.Write(filteredApplicationPlayStatistics.Count()); context.ResponseData.Write(filteredApplicationPlayStatistics.Count());

View File

@@ -315,6 +315,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
} }
} }
if (updateCount > 0)
{
break;
}
// If we are here, that mean nothing was availaible, sleep for 50ms // If we are here, that mean nothing was availaible, sleep for 50ms
context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000);
} }
@@ -972,11 +977,12 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
} }
[CommandHipc(31)] // 7.0.0+ [CommandHipc(31)] // 7.0.0+
// EventFd(u64 initval, nn::socket::EventFdFlags flags) -> (i32 ret, u32 bsd_errno) // EventFd(nn::socket::EventFdFlags flags, u64 initval) -> (i32 ret, u32 bsd_errno)
public ResultCode EventFd(ServiceCtx context) public ResultCode EventFd(ServiceCtx context)
{ {
ulong initialValue = context.RequestData.ReadUInt64();
EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
ulong initialValue = context.RequestData.ReadUInt64();
EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags); EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags);

View File

@@ -26,8 +26,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value = value; _value = value;
_flags = flags; _flags = flags;
WriteEvent = new ManualResetEvent(true); WriteEvent = new ManualResetEvent(false);
ReadEvent = new ManualResetEvent(true); ReadEvent = new ManualResetEvent(false);
UpdateEventStates();
} }
public int Refcount { get; set; } public int Refcount { get; set; }
@@ -38,6 +39,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
ReadEvent.Dispose(); ReadEvent.Dispose();
} }
private void ResetEventStates()
{
WriteEvent.Reset();
ReadEvent.Reset();
}
private void UpdateEventStates()
{
if (_value > 0)
{
ReadEvent.Set();
}
if (_value != uint.MaxValue - 1)
{
WriteEvent.Set();
}
}
public LinuxError Read(out int readSize, Span<byte> buffer) public LinuxError Read(out int readSize, Span<byte> buffer)
{ {
if (buffer.Length < sizeof(ulong)) if (buffer.Length < sizeof(ulong))
@@ -47,10 +67,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EINVAL; return LinuxError.EINVAL;
} }
ReadEvent.Reset();
lock (_lock) lock (_lock)
{ {
ResetEventStates();
ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0]; ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0];
if (_value == 0) if (_value == 0)
@@ -66,6 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
readSize = 0; readSize = 0;
UpdateEventStates();
return LinuxError.EAGAIN; return LinuxError.EAGAIN;
} }
} }
@@ -85,8 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value = 0; _value = 0;
} }
ReadEvent.Set(); UpdateEventStates();
return LinuxError.SUCCESS; return LinuxError.SUCCESS;
} }
} }
@@ -100,10 +120,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EINVAL; return LinuxError.EINVAL;
} }
WriteEvent.Reset();
lock (_lock) lock (_lock)
{ {
ResetEventStates();
if (_value > _value + count) if (_value > _value + count)
{ {
if (Blocking) if (Blocking)
@@ -114,6 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
writeSize = 0; writeSize = 0;
UpdateEventStates();
return LinuxError.EAGAIN; return LinuxError.EAGAIN;
} }
} }
@@ -123,8 +144,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value += count; _value += count;
Monitor.Pulse(_lock); Monitor.Pulse(_lock);
WriteEvent.Set(); UpdateEventStates();
return LinuxError.SUCCESS; return LinuxError.SUCCESS;
} }
} }

View File

@@ -68,20 +68,37 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
for (int i = 0; i < events.Count; i++) for (int i = 0; i < events.Count; i++)
{ {
PollEventTypeMask outputEvents = 0;
PollEvent evnt = events[i]; PollEvent evnt = events[i];
EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor;
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || if (socket.ReadEvent.WaitOne(0))
evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
&& socket.ReadEvent.WaitOne(0))
{ {
waiters.Add(socket.ReadEvent); if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input))
{
outputEvents |= PollEventTypeMask.Input;
} }
if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
{
outputEvents |= PollEventTypeMask.UrgentInput;
}
}
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
&& socket.WriteEvent.WaitOne(0)) && socket.WriteEvent.WaitOne(0))
{ {
waiters.Add(socket.WriteEvent); outputEvents |= PollEventTypeMask.Output;
}
if (outputEvents != 0)
{
evnt.Data.OutputEvents = outputEvents;
updatedCount++;
} }
} }
} }

View File

@@ -566,7 +566,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
private static List<AddrInfoSerialized> DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size) private static List<AddrInfoSerialized> DeserializeAddrInfos(IVirtualMemoryManager memory, ulong address, ulong size)
{ {
List<AddrInfoSerialized> result = new List<AddrInfoSerialized>(); List<AddrInfoSerialized> result = new();
ReadOnlySpan<byte> data = memory.GetSpan(address, (int)size); ReadOnlySpan<byte> data = memory.GetSpan(address, (int)size);
@@ -606,9 +606,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
} }
// NOTE: 0 = Any // NOTE: 0 = Any
AddrInfoSerializedHeader header = new AddrInfoSerializedHeader(ip, 0); AddrInfoSerializedHeader header = new(ip, 0);
AddrInfo4 addr = new AddrInfo4(ip, (short)port); AddrInfo4 addr = new(ip, (short)port);
AddrInfoSerialized info = new AddrInfoSerialized(header, addr, null, hostEntry.HostName); AddrInfoSerialized info = new(header, addr, null, hostEntry.HostName);
data = info.Write(data); data = info.Write(data);
} }

View File

@@ -14,6 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
public byte Family; public byte Family;
public short Port; public short Port;
public Array4<byte> Address; public Array4<byte> Address;
public Array8<byte> Padding;
public AddrInfo4(IPAddress address, short port) public AddrInfo4(IPAddress address, short port)
{ {

View File

@@ -35,7 +35,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
AddrInfo4? socketAddress = null; AddrInfo4? socketAddress = null;
Array4<byte>? rawIPv4Address = null; Array4<byte>? rawIPv4Address = null;
string canonicalName = null; string canonicalName;
buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..]; buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..];
@@ -50,6 +50,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
Debug.Assert(header.Magic == SfdnsresContants.AddrInfoMagic); Debug.Assert(header.Magic == SfdnsresContants.AddrInfoMagic);
if (header.AddressLength == 0)
{
rest = buffer;
return null;
}
if (header.Family == (int)AddressFamily.InterNetwork) if (header.Family == (int)AddressFamily.InterNetwork)
{ {
socketAddress = MemoryMarshal.Read<AddrInfo4>(buffer); socketAddress = MemoryMarshal.Read<AddrInfo4>(buffer);

View File

@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Time.TimeZone; using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.Clock namespace Ryujinx.HLE.HOS.Services.Time.Clock
@@ -16,14 +17,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public CalendarAdditionalInfo NetworkCalendarAdditionalTime; public CalendarAdditionalInfo NetworkCalendarAdditionalTime;
public SteadyClockTimePoint SteadyClockTimePoint; public SteadyClockTimePoint SteadyClockTimePoint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] private LocationNameStorageHolder _locationName;
public char[] LocationName;
public Span<byte> LocationName => MemoryMarshal.Cast<LocationNameStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size));
[MarshalAs(UnmanagedType.I1)] [MarshalAs(UnmanagedType.I1)]
public bool IsAutomaticCorrectionEnabled; public bool IsAutomaticCorrectionEnabled;
public byte Type; public byte Type;
public ushort Unknown; public ushort Unknown;
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)]
private struct LocationNameStorageHolder
{
public const int Size = 0x24;
}
public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context) public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context)
{ {
currentTime = 0; currentTime = 0;

View File

@@ -8,7 +8,9 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time namespace Ryujinx.HLE.HOS.Services.Time
{ {
@@ -281,7 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{ {
byte type = context.RequestData.ReadByte(); byte type = context.RequestData.ReadByte();
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ClockSnapshot>()); context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf<ClockSnapshot>());
context.RequestData.BaseStream.Position += 7; context.RequestData.BaseStream.Position += 7;
@@ -372,12 +374,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
return result; return result;
} }
char[] tzName = deviceLocationName.ToCharArray(); ReadOnlySpan<byte> tzName = Encoding.ASCII.GetBytes(deviceLocationName);
char[] locationName = new char[0x24];
Array.Copy(tzName, locationName, tzName.Length); tzName.CopyTo(clockSnapshot.LocationName);
clockSnapshot.LocationName = locationName;
result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext); result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
@@ -414,7 +413,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc) private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc)
{ {
Debug.Assert(ipcDesc.Size == (ulong)Marshal.SizeOf<ClockSnapshot>()); Debug.Assert(ipcDesc.Size == (ulong)Unsafe.SizeOf<ClockSnapshot>());
byte[] temp = new byte[ipcDesc.Size]; byte[] temp = new byte[ipcDesc.Size];

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.Utilities;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@@ -890,14 +891,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
long streamLength = reader.BaseStream.Length; long streamLength = reader.BaseStream.Length;
if (streamLength < Marshal.SizeOf<TzifHeader>()) if (streamLength < Unsafe.SizeOf<TzifHeader>())
{ {
return false; return false;
} }
TzifHeader header = reader.ReadStruct<TzifHeader>(); TzifHeader header = reader.ReadStruct<TzifHeader>();
streamLength -= Marshal.SizeOf<TzifHeader>(); streamLength -= Unsafe.SizeOf<TzifHeader>();
int ttisGMTCount = Detzcode32(header.TtisGMTCount); int ttisGMTCount = Detzcode32(header.TtisGMTCount);
int ttisSTDCount = Detzcode32(header.TtisSTDCount); int ttisSTDCount = Detzcode32(header.TtisSTDCount);

View File

@@ -11,7 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.Core" Version="4.7.2" /> <PackageReference Include="OpenTK.Core" Version="4.7.5" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
</ItemGroup> </ItemGroup>

View File

@@ -40,20 +40,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
return (IntPtr)surfaceHandle; return (IntPtr)surfaceHandle;
} }
// TODO: Fix this in SDL2-CS.
[DllImport("SDL2", EntryPoint = "SDL_Vulkan_GetInstanceExtensions", CallingConvention = CallingConvention.Cdecl)]
public static extern SDL_bool SDL_Vulkan_GetInstanceExtensions_Workaround(IntPtr window, out uint count, IntPtr names);
public unsafe string[] GetRequiredInstanceExtensions() public unsafe string[] GetRequiredInstanceExtensions()
{ {
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE) if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE)
{ {
IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount]; IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount];
string[] extensions = new string[(int)extensionsCount]; string[] extensions = new string[(int)extensionsCount];
fixed (IntPtr* rawExtensionsPtr = rawExtensions) fixed (IntPtr* rawExtensionsPtr = rawExtensions)
{ {
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE) if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
{ {
for (int i = 0; i < extensions.Length; i++) for (int i = 0; i < extensions.Length; i++)
{ {

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices; using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol namespace Ryujinx.Input.Motion.CemuHook.Protocol
{ {
@@ -8,9 +9,7 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public MessageType Type; public MessageType Type;
public SubscriberType SubscriberType; public SubscriberType SubscriberType;
public byte Slot; public byte Slot;
public Array6<byte> MacAddress;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress;
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -27,11 +26,8 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public uint DPadAnalog; public uint DPadAnalog;
public ulong MainButtonsAnalog; public ulong MainButtonsAnalog;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public Array6<byte> Touch1;
public byte[] Touch1; public Array6<byte> Touch2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch2;
public ulong MotionTimestamp; public ulong MotionTimestamp;
public float AccelerometerX; public float AccelerometerX;

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