Compare commits

...

43 Commits

Author SHA1 Message Date
d076339e3e Add Ryujinx license file to builds (#4057) 2022-12-07 18:20:18 +01:00
837836431d nuget: bump System.Drawing.Common from 6.0.0 to 7.0.0 (#4024)
Bumps [System.Drawing.Common](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.Drawing.Common
  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-12-07 16:24:34 +00:00
9f555db5cd hle: Do not add disabled AoC item to the list (#4044)
* hle: Do not add disabled AoC item to the list

We currently add all AoC items to a list in `ContentManager` and the enable check is only done when FS service ask for the data. Which is wrong. It causes an issue in MK8D which doesn't boot even if you have disabled a not updated DLC.

I've fixed it by not adding the disabled AoC item to the list, I've removed some duplicate code too.
There is still an edge case because we currently don't check the AoC Item version, but that should be fixed later since now MK8D throw an error if the DLC isn't updated.

* remove useless "enabled"
2022-12-07 15:00:28 +01:00
bf7fa60dfc Fix struct layout packing (#4039) 2022-12-07 02:04:01 +00:00
752b93d3b7 gtk: Fixes warnings about obsolete components (#4049)
* gtk: Fixes warnings about obsolete components

* remove wrong using
2022-12-07 01:49:37 +01:00
f23b2878cc Shader: Add fallback for LDG from "ube" buffer ranges. (#4027)
We have a conversion from LDG on the compute shader to a special constant buffer binding that's used to exceed hardware limits on compute, but it was only running if the byte offset could be identified. The fallback that checks all of the bindings at runtime only checks the storage buffers.

This PR adds checking ube ranges to the LoadGlobal fallback. This extends the changes in #4011 to only check ube entries which are accessed by the shader.

Fixes particles affected by the wind in The Legend of Zelda: Breath of the Wild. May fix other weird issues with compute shaders in some games.

Try a bunch of games and drivers to make sure they don't blow up loading constants willynilly from searchable buffers.
2022-12-06 23:15:44 +00:00
e211c3f00a UI: Add Metal surface creation for MoltenVK (#3980)
* Initial implementation of metal surface across UIs

* Fix SDL2 on windows

* Update Ryujinx/Ryujinx.csproj

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

* Address Feedback

Co-authored-by: Mary-nyan <thog@protonmail.com>
2022-12-06 19:00:25 -03:00
d3709a753f nuget: bump XamlNameReferenceGenerator from 1.4.2 to 1.5.1 (#4026)
Bumps [XamlNameReferenceGenerator](https://github.com/avaloniaui/Avalonia.NameGenerator) from 1.4.2 to 1.5.1.
- [Release notes](https://github.com/avaloniaui/Avalonia.NameGenerator/releases)
- [Commits](https://github.com/avaloniaui/Avalonia.NameGenerator/compare/1.4.2...1.5.1)

---
updated-dependencies:
- dependency-name: XamlNameReferenceGenerator
  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-12-06 19:00:08 +01:00
ab676d58ea nuget: bump System.IdentityModel.Tokens.Jwt from 6.25.0 to 6.25.1 (#4043)
Bumps [System.IdentityModel.Tokens.Jwt](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 6.25.0 to 6.25.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/6.25.0...6.25.1)

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

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-12-06 18:01:21 +01:00
2372c194f1 ava: Cleanup Input classes (#4042)
* ava: Cleanup Input classes

This PR just cleanup all Input classes for consistencies.

* Addresses TSRBerry's feedback
2022-12-06 15:32:14 +00:00
40311310d1 amadeus: Add missing compressor effect from REV11 (#4010)
* amadeus: Add missing compressor effect from REV11

This was in my reversing notes but seems I completely forgot to
implement it

Also took the opportunity to simplify the Limiter effect a bit.

* Remove some outdated comment

* Address gdkchan's comments
2022-12-06 15:04:25 +01:00
dde9bb5c69 Fix storage buffer access when match fails (#4037)
* Fix storage buffer access when match fails

* Shader cache version bump
2022-12-06 03:36:54 +00:00
266338a7c9 Change default Vsync toggle hotkey to F1 instead of Tab (#3995) 2022-12-06 02:09:26 +00:00
90156eea4c nuget: bump Microsoft.CodeAnalysis.CSharp from 4.2.0 to 4.4.0 (#4025)
Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/compare/v4.2.0...Visual-Studio-2019-Version-16.0-Preview-4.4)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  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-12-06 01:51:33 +00:00
071c01c235 Fix Sorting Regression (#4032)
* Fix sorting regression + Remove unsued sort

* Fix GTK

* Attempt 2 to fix GTK

* Whoopsie

* Fix whitspace
2022-12-06 01:40:06 +00:00
de06ffb0f7 Fix shaders with global memory access from unknown locations (#4029)
* Fix shaders with global memory access from unknown locations

* Shader cache version bump
2022-12-06 01:09:24 +00:00
8a7de35e3f Update 'OpenGL Log Level' to 'Graphics Backend Log Level' (#3996)
* Update 'OpenGL Log Level' to 'Graphics Backend Log Level'

* update other locals and change keys
2022-12-06 00:48:41 +00:00
121296834a Ava GUI: Several UI Fixes (#3991)
* Fix accessability violations in ListView

* Use accent colour for favourite star

* Hide progress bar when its done

* App Data Formating

- Added space before storage unit
- Changed so minutes have 0 decimals, and hours and days have 1

* Fix theming

* Fix mismatched corner radius

* Fix acceability violations in GridView

* More consistency between Grid and List View

* Fix margin

* Let whitespace defocus controls
2022-12-05 22:04:18 +00:00
bbb24d8c7e Restrict shader storage buffer search when match fails (#4011)
* Restrict storage buffer search when match fails

* Shader cache version bump
2022-12-05 19:11:32 +00:00
4da44e09cb Make structs readonly when applicable (#4002)
* Make all structs readonly when applicable. It should reduce amount of needless defensive copies

* Make structs with trivial boilerplate equality code record structs

* Remove unnecessary readonly modifiers from TextureCreateInfo

* Make BitMap structs readonly too
2022-12-05 14:47:39 +01:00
ae13f0ab4d misc: Fix obsolete warnings in Ryujinx.Graphics.Vulkan (#4020)
Was caused by some merges after the Silk.NET update
2022-12-05 12:57:11 +00:00
a2a35f1be6 nuget: bump Microsoft.NET.Test.Sdk from 16.8.0 to 17.4.0 (#3900)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.8.0 to 17.4.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.8.0...v17.4.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  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-12-05 08:17:40 +01:00
aedfadaaf7 Add InfoType.MesosphereCurrentProcess (#3792)
* Add InfoType.MesosphereCurrentProcess

* Make outHandle inlined

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

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-12-04 19:46:02 +00:00
5c0fb0cec3 ui: Disallow checking for updates while emulation active (#3886)
* Disallow updating while game is running

* Reflected change on Avalonia

* Git has gone wonky

* Fix accidental indent
2022-12-04 20:17:11 +01:00
17a1cab5d2 Allow SNorm buffer texture formats on Vulkan (#3957)
* Allow SNorm buffer texture formats on Vulkan

* Shader cache version bump
2022-12-04 15:36:03 -03:00
73aed239c3 Implement non-MS to MS copies with draws (#3958)
* Implement non-MS to MS copies with draws, simplify MS to non-MS copies and supports any host sample count

* Remove unused program
2022-12-04 15:07:11 -03:00
9ac66336a2 GPU: Use lazy checks for specialization state (#4004)
* GPU: Use lazy checks for specialization state

This PR adds a new class, the SpecializationStateUpdater, that allows elements of specialization state to be updated individually, and signal the state is checked when it changes between draws, instead of building and checking it on every draw. This also avoids building spec state when

Most state updates have been moved behind the shader state update, so that their specialization state updates make it in before shaders are fetched.

Downside: Fields in GpuChannelGraphicsState are no longer readonly. To counteract copies that might be caused this I pass it as `ref` when possible, though maybe `in` would be better? Not really sure about the quirks of `in` and the difference probably won't show on a benchmark.

The result is around 2 extra FPS on SMO in the usual spot. Not much right now, but it will remove costs when we're doing more expensive specialization checks, such as fragment output type specialization for macos. It may also help more on other games with more draws.

* Address Feedback

* Oops
2022-12-04 18:41:17 +01:00
4965681e06 GPU: Swap bindings array instead of copying (#4003)
* GPU: Swap bindings array instead of copying

Reduces work on UpdateShaderState. Now the cost is a few reference moves for arrays, rather than copying data.

Downside: bindings arrays are no longer readonly.

* Micro optimisation

* Add missing docs

* Address Feedback
2022-12-04 18:18:40 +01:00
3868a00206 Use source generated regular expressions (#4005) 2022-12-04 00:43:23 +00:00
933e5144a9 Query Available RAM on macOS (#4000) 2022-12-04 00:25:07 +00:00
73a42c85c4 Add crowdin badge and information in readme (#3990)
* Add crowdin bagde and informations in readme.

* Update README.md

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

* Update README.md

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-12-02 23:57:38 +01:00
39ba11054b Update Crowdin configuration file 2022-12-02 15:22:21 +01:00
c250e3392c Fix using in Ava 2022-12-02 15:08:57 +01:00
e56b069081 Update Crowdin configuration file 2022-12-02 14:54:56 +01:00
204c031fef SDL2Driver: Invoke dispatcher on main thread (#3818) 2022-12-02 14:37:22 +01:00
d9053bbe37 Avalonia - Save Manager (#3476)
* Add save manager to account selector

* add fallback to app metadata for titlename if app is not in gamelist

* Allow recovering lost accounts
2022-12-02 13:16:43 +00:00
c25e8427aa amadeus: Fix wrong SendCommands logic (#3969)
* amadeus: Fix wrong SendCommands logic

Might help with audio desync, might cause audio stutters, will see!

* Address gdkchan's comment
2022-12-02 13:01:19 +00:00
21a081b185 Add back locales removed in #3955 (#3983)
Add back `SettingsButtonSave` & `SettingsButtonClose` removed in #3955

Fixes #3982
2022-12-02 12:46:18 +00:00
b540ea80d1 Ava GUI: Make Dialogue More Intuitive (#3955)
* Adjust button position and locales

* Shortcuts + Highlight default action

* Update Locales - Corrections Welcome

* Move `Apply` button back to right side

* OS Reactive Button layout

* Fix reversed boolean :)

* Fix accented button styling
2022-12-02 03:31:21 +01:00
d692a9b83e Revert "nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)"
This reverts commit 9677ddaa5d.

SixLabors.ImageShar switched to a shady and vague license starting with 2.x
without mentioning it on their changelog.

As a result we are staying on 1.x (licensed under Apache-2) and will
seak an alternative package.
2022-12-01 23:06:55 +01:00
9677ddaa5d nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3 (#3976)
* nuget: bump SixLabors.ImageSharp from 1.0.4 to 2.1.3

Bumps [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 1.0.4 to 2.1.3.
- [Release notes](https://github.com/SixLabors/ImageSharp/releases)
- [Commits](https://github.com/SixLabors/ImageSharp/compare/v1.0.4...v2.1.3)

---
updated-dependencies:
- dependency-name: SixLabors.ImageSharp
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Update for 2.x changes

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mary <mary@mary.zone>
2022-12-01 21:54:41 +00:00
ce92e8cd04 chore: Update Silk.NET to 2.16.0 (#3953) 2022-12-01 19:11:56 +01:00
456fc04007 Better SDL2 Audio Init Error Logging (#3967)
* Better SDL2 Audio Init Error Logging

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

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

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

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

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

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

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

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

* Update Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs

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

* Update SDL2HardwareDeviceDriver.cs

* Update SDL2HardwareDeviceDriver.cs

Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-12-01 17:53:13 +01:00
328 changed files with 4304 additions and 2388 deletions

View File

@ -89,6 +89,7 @@ csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
csharp_style_prefer_readonly_struct = true
# Code-block preferences
csharp_prefer_braces = true:silent

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.RegisterAllocators
{
struct AllocationResult
readonly struct AllocationResult
{
public int IntUsedRegisters { get; }
public int VecUsedRegisters { get; }

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
private class ParallelCopy
{
private struct Copy
private readonly struct Copy
{
public Register Dest { get; }
public Register Source { get; }

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
class HybridAllocator : IRegisterAllocator
{
private struct BlockInfo
private readonly struct BlockInfo
{
public bool HasCall { get; }

View File

@ -3,7 +3,7 @@ using System;
namespace ARMeilleure.CodeGen.RegisterAllocators
{
struct RegisterMasks
readonly struct RegisterMasks
{
public int IntAvailableRegisters { get; }
public int VecAvailableRegisters { get; }

View File

@ -1,6 +1,6 @@
namespace ARMeilleure.CodeGen.X86
{
struct IntrinsicInfo
readonly struct IntrinsicInfo
{
public X86Instruction Inst { get; }
public IntrinsicType Type { get; }

View File

@ -2,7 +2,7 @@ using ARMeilleure.Instructions;
namespace ARMeilleure.Decoders
{
struct InstDescriptor
readonly struct InstDescriptor
{
public static InstDescriptor Undefined => new InstDescriptor(InstName.Und, InstEmit.Und);

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.Decoders
private const int FastLookupSize = 0x1000;
private struct InstInfo
private readonly struct InstInfo
{
public int Mask { get; }
public int Value { get; }

View File

@ -6,7 +6,7 @@ namespace ARMeilleure.Diagnostics
{
static class Symbols
{
private struct RangedSymbol
private readonly struct RangedSymbol
{
public readonly ulong Start;
public readonly ulong End;

View File

@ -3,7 +3,7 @@ using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.IntermediateRepresentation
{
struct PhiOperation
readonly struct PhiOperation
{
private readonly Operation _operation;

View File

@ -2,7 +2,7 @@ using System;
namespace ARMeilleure.IntermediateRepresentation
{
struct Register : IEquatable<Register>
readonly struct Register : IEquatable<Register>
{
public int Index { get; }

View File

@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
namespace ARMeilleure.Translation.Cache
{
struct CacheEntry : IComparable<CacheEntry>
readonly struct CacheEntry : IComparable<CacheEntry>
{
public int Offset { get; }
public int Size { get; }

View File

@ -6,7 +6,7 @@ namespace ARMeilleure.Translation.Cache
{
class CacheMemoryAllocator
{
private struct MemoryBlock : IComparable<MemoryBlock>
private readonly struct MemoryBlock : IComparable<MemoryBlock>
{
public int Offset { get; }
public int Size { get; }

View File

@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
namespace ARMeilleure.Translation
{
struct CompilerContext
readonly struct CompilerContext
{
public ControlFlowGraph Cfg { get; }

View File

@ -14,7 +14,7 @@ namespace ARMeilleure.Translation
private const int RegsCount = 32;
private const int RegsMask = RegsCount - 1;
private struct RegisterMask : IEquatable<RegisterMask>
private readonly struct RegisterMask : IEquatable<RegisterMask>
{
public long IntMask => Mask.GetElement(0);
public long VecMask => Mask.GetElement(1);

View File

@ -293,7 +293,7 @@ namespace ARMeilleure.Translation
}
}
private struct Range
private readonly struct Range
{
public ulong Start { get; }
public ulong End { get; }

View File

@ -21,6 +21,10 @@
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
alt="">
</a>
<a href="https://crwd.in/ryujinx">
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
alt="">
</a>
<a href="https://discord.com/invite/VkQYXAZ">
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
alt="Discord">
@ -48,6 +52,8 @@ See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ry
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
## Latest build
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**

View File

@ -1,5 +1,6 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using Ryujinx.SDL2.Common;
using System;
@ -112,6 +113,9 @@ namespace Ryujinx.Audio.Backends.SDL2
if (device == 0)
{
Logger.Error?.Print(LogClass.Application,
$"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
return 0;
}
@ -119,6 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
if (!isValid)
{
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
SDL_CloseAudioDevice(device);
return 0;

View File

@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
namespace SoundIOSharp
{
public struct SoundIOChannelLayout
public readonly struct SoundIOChannelLayout
{
public static int BuiltInCount
{

View File

@ -1,6 +1,6 @@
namespace SoundIOSharp
{
public struct SoundIOSampleRateRange
public readonly struct SoundIOSampleRateRange
{
internal SoundIOSampleRateRange(int min, int max)
{

View File

@ -48,6 +48,11 @@ namespace Ryujinx.Audio.Renderer.Common
/// <summary>
/// Effect to capture mixes (via auxiliary buffers).
/// </summary>
CaptureBuffer
CaptureBuffer,
/// <summary>
/// Effect applying a compressor filter (DRC).
/// </summary>
Compressor,
}
}

View File

@ -14,6 +14,7 @@ namespace Ryujinx.Audio.Renderer.Common
Reverb3d,
PcmFloat,
Limiter,
CaptureBuffer
CaptureBuffer,
Compressor
}
}

View File

@ -116,6 +116,11 @@ namespace Ryujinx.Audio.Renderer.Dsp
};
}
public bool HasRemainingCommands(int sessionId)
{
return _sessionCommandList[sessionId] != null;
}
public void Signal()
{
_mailbox.SendMessage(MailboxMessage.RenderStart);

View File

@ -31,6 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
LimiterVersion1,
LimiterVersion2,
GroupedBiquadFilter,
CaptureBuffer
CaptureBuffer,
Compressor
}
}

View File

@ -0,0 +1,173 @@
using System;
using System.Diagnostics;
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
public class CompressorCommand : ICommand
{
private const int FixedPointPrecision = 15;
public bool Enabled { get; set; }
public int NodeId { get; }
public CommandType CommandType => CommandType.Compressor;
public uint EstimatedProcessingTime { get; set; }
public CompressorParameter Parameter => _parameter;
public Memory<CompressorState> State { get; }
public ushort[] OutputBufferIndices { get; }
public ushort[] InputBufferIndices { get; }
public bool IsEffectEnabled { get; }
private CompressorParameter _parameter;
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
Enabled = true;
NodeId = nodeId;
_parameter = parameter;
State = state;
IsEffectEnabled = isEnabled;
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
for (int i = 0; i < _parameter.ChannelCount; i++)
{
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
}
}
public void Process(CommandList context)
{
ref CompressorState state = ref State.Span[0];
if (IsEffectEnabled)
{
if (_parameter.Status == Server.Effect.UsageState.Invalid)
{
state = new CompressorState(ref _parameter);
}
else if (_parameter.Status == Server.Effect.UsageState.New)
{
state.UpdateParameter(ref _parameter);
}
}
ProcessCompressor(context, ref state);
}
private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
{
Debug.Assert(_parameter.IsChannelCountValid());
if (IsEffectEnabled && _parameter.IsChannelCountValid())
{
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
float unknown4 = state.Unknown4;
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
for (int i = 0; i < _parameter.ChannelCount; i++)
{
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
}
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
{
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
{
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
}
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
float z = 0.0f;
bool unknown10OutOfRange = false;
if (newMean < 1.0e-10f)
{
z = 1.0f;
unknown10OutOfRange = state.Unknown10 < -100.0f;
}
if (y >= state.Unknown10 || unknown10OutOfRange)
{
float tmpGain;
if (y >= state.Unknown14)
{
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
}
else
{
tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
}
z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
}
float unknown4New = z;
float compressionEmaAlpha;
if ((unknown4 - z) <= 0.08f)
{
compressionEmaAlpha = Parameter.ReleaseCoefficient;
if ((unknown4 - z) >= -0.08f)
{
if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
{
unknown4New = unknown4;
}
compressionEmaAlpha = previousCompressionEmaAlpha;
}
}
else
{
compressionEmaAlpha = Parameter.AttackCoefficient;
}
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
{
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
}
unknown4 = unknown4New;
previousCompressionEmaAlpha = compressionEmaAlpha;
}
state.InputMovingAverage = inputMovingAverage;
state.Unknown4 = unknown4;
state.CompressionGainAverage = compressionGainAverage;
state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
}
else
{
for (int i = 0; i < Parameter.ChannelCount; i++)
{
if (InputBufferIndices[i] != OutputBufferIndices[i])
{
context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
}
}
}
}
}
}

View File

@ -90,32 +90,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;

View File

@ -101,32 +101,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
float inputCoefficient = Parameter.ReleaseCoefficient;
if (sampleInputMax > state.DectectorAverage[channelIndex])
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
{
inputCoefficient = Parameter.AttackCoefficient;
}
state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
float attenuation = 1.0f;
if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
if (detectorValue > Parameter.Threshold)
{
attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
attenuation = Parameter.Threshold / detectorValue;
}
float outputCoefficient = Parameter.ReleaseCoefficient;
if (state.CompressionGain[channelIndex] > attenuation)
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
{
outputCoefficient = Parameter.AttackCoefficient;
}
state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
float outputSample = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
@ -144,7 +143,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
}
}
}

View File

@ -0,0 +1,26 @@
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp.Effect
{
public struct ExponentialMovingAverage
{
private float _mean;
public ExponentialMovingAverage(float mean)
{
_mean = mean;
}
public float Read()
{
return _mean;
}
public float Update(float value, float alpha)
{
_mean += alpha * (value - _mean);
return _mean;
}
}
}

View File

@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Renderer.Dsp
return (float)value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float ConvertFloat(float value, int qBits)
{
return value / (1 << qBits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ToFixed(float value, int qBits)
{

View File

@ -1,4 +1,5 @@
using System;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Dsp
@ -46,6 +47,53 @@ namespace Ryujinx.Audio.Renderer.Dsp
return MathF.Pow(10, x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Log10(float x)
{
// NOTE: Nintendo uses an approximation of log10, we don't.
// As such, we support the same ranges as Nintendo to avoid unexpected behaviours.
return MathF.Pow(10, MathF.Max(x, 1.0e-10f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float MeanSquare(ReadOnlySpan<float> inputs)
{
float res = 0.0f;
foreach (float input in inputs)
{
res += (input * input);
}
res /= inputs.Length;
return res;
}
/// <summary>
/// Map decibel to linear.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value/returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinear(float db)
{
return MathF.Pow(10.0f, db / 20.0f);
}
/// <summary>
/// Map decibel to linear in [0, 2] range.
/// </summary>
/// <param name="db">The decibel value to convert</param>
/// <returns>Converted linear value in [0, 2] range</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DecibelToLinearExtended(float db)
{
float tmp = MathF.Log2(DecibelToLinear(db));
return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float DegreesToRadians(float degrees)
{

View File

@ -0,0 +1,51 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class CompressorState
{
public ExponentialMovingAverage InputMovingAverage;
public float Unknown4;
public ExponentialMovingAverage CompressionGainAverage;
public float CompressorGainReduction;
public float Unknown10;
public float Unknown14;
public float PreviousCompressionEmaAlpha;
public float MakeupGain;
public float OutputGain;
public CompressorState(ref CompressorParameter parameter)
{
InputMovingAverage = new ExponentialMovingAverage(0.0f);
Unknown4 = 1.0f;
CompressionGainAverage = new ExponentialMovingAverage(1.0f);
UpdateParameter(ref parameter);
}
public void UpdateParameter(ref CompressorParameter parameter)
{
float threshold = parameter.Threshold;
float ratio = 1.0f / parameter.Ratio;
float attackCoefficient = parameter.AttackCoefficient;
float makeupGain;
if (parameter.MakeupGainEnabled)
{
makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f;
}
else
{
makeupGain = 0.0f;
}
PreviousCompressionEmaAlpha = attackCoefficient;
MakeupGain = makeupGain;
CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax;
Unknown10 = threshold - 1.5f;
Unknown14 = threshold + 1.5f;
OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain);
}
}
}

View File

@ -1,3 +1,4 @@
using Ryujinx.Audio.Renderer.Dsp.Effect;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@ -5,20 +6,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
public class LimiterState
{
public float[] DectectorAverage;
public float[] CompressionGain;
public ExponentialMovingAverage[] DetectorAverage;
public ExponentialMovingAverage[] CompressionGainAverage;
public float[] DelayedSampleBuffer;
public int[] DelayedSampleBufferPosition;
public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
{
DectectorAverage = new float[parameter.ChannelCount];
CompressionGain = new float[parameter.ChannelCount];
DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount];
CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount];
DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
DelayedSampleBufferPosition = new int[parameter.ChannelCount];
DectectorAverage.AsSpan().Fill(0.0f);
CompressionGain.AsSpan().Fill(1.0f);
DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f));
CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f));
DelayedSampleBufferPosition.AsSpan().Fill(0);
DelayedSampleBuffer.AsSpan().Fill(0.0f);

View File

@ -0,0 +1,115 @@
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.Compressor"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CompressorParameter
{
/// <summary>
/// The input channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Input;
/// <summary>
/// The output channel indices that will be used by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
public Array6<byte> Output;
/// <summary>
/// The maximum number of channels supported.
/// </summary>
public ushort ChannelCountMax;
/// <summary>
/// The total channel count used.
/// </summary>
public ushort ChannelCount;
/// <summary>
/// The target sample rate.
/// </summary>
/// <remarks>This is in kHz.</remarks>
public int SampleRate;
/// <summary>
/// The threshold.
/// </summary>
public float Threshold;
/// <summary>
/// The compressor ratio.
/// </summary>
public float Ratio;
/// <summary>
/// The attack time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int AttackTime;
/// <summary>
/// The release time.
/// <remarks>This is in microseconds.</remarks>
/// </summary>
public int ReleaseTime;
/// <summary>
/// The input gain.
/// </summary>
public float InputGain;
/// <summary>
/// The attack coefficient.
/// </summary>
public float AttackCoefficient;
/// <summary>
/// The release coefficient.
/// </summary>
public float ReleaseCoefficient;
/// <summary>
/// The output gain.
/// </summary>
public float OutputGain;
/// <summary>
/// The current usage status of the effect on the client side.
/// </summary>
public UsageState Status;
/// <summary>
/// Indicate if the makeup gain should be used.
/// </summary>
[MarshalAs(UnmanagedType.I1)]
public bool MakeupGainEnabled;
/// <summary>
/// Reserved/padding.
/// </summary>
private Array2<byte> _reserved;
/// <summary>
/// Check if the <see cref="ChannelCount"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCount"/> is valid.</returns>
public bool IsChannelCountValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCount);
}
/// <summary>
/// Check if the <see cref="ChannelCountMax"/> is valid.
/// </summary>
/// <returns>Returns true if the <see cref="ChannelCountMax"/> is valid.</returns>
public bool IsChannelCountMaxValid()
{
return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax);
}
}
}

View File

@ -670,14 +670,21 @@ namespace Ryujinx.Audio.Renderer.Server
{
_terminationEvent.Reset();
GenerateCommandList(out CommandList commands);
if (!_manager.Processor.HasRemainingCommands(_sessionId))
{
GenerateCommandList(out CommandList commands);
_manager.Processor.Send(_sessionId,
commands,
GetMaxAllocatedTimeForDsp(),
_appletResourceId);
_manager.Processor.Send(_sessionId,
commands,
GetMaxAllocatedTimeForDsp(),
_appletResourceId);
_systemEvent.Signal();
_systemEvent.Signal();
}
else
{
_isDspRunningBehind = true;
}
}
else
{

View File

@ -44,7 +44,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%.
///
///
/// </summary>
/// <remarks>This was added in system update 6.0.0</remarks>
public const int Revision5 = 5 << 24;
@ -93,6 +93,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// REV11:
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
/// A new effect was added: Compressor. This effect is effectively implemented with a DRC.
/// 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>

View File

@ -469,6 +469,18 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
{
if (parameter.IsChannelCountValid())
{
CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
}
/// <summary>
/// Generate a new <see cref="VolumeCommand"/>.
/// </summary>

View File

@ -606,6 +606,17 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
{
Debug.Assert(effect.Type == EffectType.Compressor);
_commandBuffer.GenerateCompressorEffect(bufferOffset,
effect.Parameter,
effect.State,
effect.IsEnabled,
nodeId);
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@ -650,6 +661,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
break;
case EffectType.Compressor:
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}

View File

@ -179,5 +179,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@ -543,5 +543,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@ -747,5 +747,10 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
public virtual uint Estimate(CompressorCommand command)
{
return 0;
}
}
}

View File

@ -232,5 +232,79 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
}
public override uint Estimate(CompressorCommand command)
{
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
if (_sampleCount == 160)
{
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 34431;
case 2:
return 44253;
case 4:
return 63827;
case 6:
return 83361;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)630.12f;
case 2:
return (uint)638.27f;
case 4:
return (uint)705.86f;
case 6:
return (uint)782.02f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
if (command.Enabled)
{
switch (command.Parameter.ChannelCount)
{
case 1:
return 51095;
case 2:
return 65693;
case 4:
return 95383;
case 6:
return 124510;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
else
{
switch (command.Parameter.ChannelCount)
{
case 1:
return (uint)840.14f;
case 2:
return (uint)826.1f;
case 4:
return (uint)901.88f;
case 6:
return (uint)965.29f;
default:
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
}
}
}

View File

@ -262,6 +262,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Limiter;
case EffectType.CaptureBuffer:
return PerformanceDetailType.CaptureBuffer;
case EffectType.Compressor:
return PerformanceDetailType.Compressor;
default:
throw new NotImplementedException($"{Type}");
}

View File

@ -0,0 +1,67 @@
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Server.Effect
{
/// <summary>
/// Server state for a compressor effect.
/// </summary>
public class CompressorEffect : BaseEffect
{
/// <summary>
/// The compressor parameter.
/// </summary>
public CompressorParameter Parameter;
/// <summary>
/// The compressor state.
/// </summary>
public Memory<CompressorState> State { get; }
/// <summary>
/// Create a new <see cref="CompressorEffect"/>.
/// </summary>
public CompressorEffect()
{
State = new CompressorState[1];
}
public override EffectType TargetEffectType => EffectType.Compressor;
public override ulong GetWorkBuffer(int index)
{
return GetSingleBuffer();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
{
// Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised.
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
{
Debug.Assert(IsTypeValid(ref parameter));
UpdateParameterBase(ref parameter);
Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled;
updateErrorInfo = new BehaviourParameter.ErrorInfo();
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
Parameter.Status = UsageState.Enabled;
}
}
}

View File

@ -35,5 +35,6 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(LimiterCommandVersion2 command);
uint Estimate(GroupedBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
}
}

View File

@ -240,6 +240,10 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.CaptureBuffer:
effect = new CaptureBufferEffect();
break;
case EffectType.Compressor:
effect = new CompressorEffect();
break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}

View File

@ -125,7 +125,7 @@ namespace Ryujinx.Ava
_inputManager = inputManager;
_accountManager = accountManager;
_userChannelPersistence = userChannelPersistence;
_renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs",
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:",
"SettingsTabLoggingDeveloperOptions": "Entwickleroptionen (WARNUNG: Beeinträchtigt die Leistung)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL Logstufe:",
"SettingsTabLoggingOpenglLogLevelNone": "Keine",
"SettingsTabLoggingOpenglLogLevelError": "Fehler",
"SettingsTabLoggingOpenglLogLevelPerformance": "Verlangsamungen",
"SettingsTabLoggingOpenglLogLevelAll": "Alle",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle",
"SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log",
"SettingsTabInput": "Eingabe",
"SettingsTabInputEnableDockedMode": "Docked Modus",
"SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff",
"SettingsButtonSave": "Speichern",
"SettingsButtonClose": "Schließen",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Abbrechen",
"SettingsButtonApply": "Übernehmen",
"ControllerSettingsPlayer": "Spieler",
"ControllerSettingsPlayer1": "Spieler 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS",
"SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:",
"SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)",
"SettingsTabLoggingOpenglLogLevel": "Επίπεδο Καταγραφής OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Κανένα",
"SettingsTabLoggingOpenglLogLevelError": "Σφάλμα",
"SettingsTabLoggingOpenglLogLevelPerformance": "Επιβραδύνσεις",
"SettingsTabLoggingOpenglLogLevelAll": "Όλα",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα",
"SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων",
"SettingsTabInput": "Χειρισμός",
"SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode",
"SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο",
"SettingsButtonSave": "Αποθήκευση",
"SettingsButtonClose": "Κλείσιμο",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Ακύρωση",
"SettingsButtonApply": "Εφαρμογή",
"ControllerSettingsPlayer": "Παίχτης",
"ControllerSettingsPlayer1": "Παίχτης 1",

View File

@ -157,17 +157,19 @@
"SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs",
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:",
"SettingsTabLoggingDeveloperOptions": "Developer Options (WARNING: Will reduce performance)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL Log Level:",
"SettingsTabLoggingOpenglLogLevelNone": "None",
"SettingsTabLoggingOpenglLogLevelError": "Error",
"SettingsTabLoggingOpenglLogLevelPerformance": "Slowdowns",
"SettingsTabLoggingOpenglLogLevelAll": "All",
"SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "None",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Error",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "All",
"SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs",
"SettingsTabInput": "Input",
"SettingsTabInputEnableDockedMode": "Docked Mode",
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
"SettingsButtonSave": "Save",
"SettingsButtonClose": "Close",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancel",
"SettingsButtonApply": "Apply",
"ControllerSettingsPlayer": "Player",
"ControllerSettingsPlayer1": "Player 1",
@ -594,7 +596,18 @@
"RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?",
"SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:",
"SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:",
"VolumeShort": "Vol",
"SettingsEnableMacroHLE": "Enable Macro HLE",
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure."
"SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.",
"VolumeShort": "Vol",
"UserProfilesManageSaves": "Manage Saves",
"DeleteUserSave": "Do you want to delete user save for this game?",
"IrreversibleActionNote": "This action is not reversible.",
"SaveManagerHeading": "Manage Saves for {0}",
"SaveManagerTitle": "Save Manager",
"Name": "Name",
"Size": "Size",
"Search": "Search",
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
"Recover": "Recover",
"UserProfilesRecoverHeading" : "Saves were found for the following accounts"
}

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access",
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:",
"SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)",
"SettingsTabLoggingOpenglLogLevel": "Nivel de registro de OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Nada",
"SettingsTabLoggingOpenglLogLevelError": "Errores",
"SettingsTabLoggingOpenglLogLevelPerformance": "Ralentizaciones",
"SettingsTabLoggingOpenglLogLevelAll": "Todo",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Errores",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo",
"SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug",
"SettingsTabInput": "Entrada",
"SettingsTabInputEnableDockedMode": "Modo dock/TV",
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
"SettingsButtonSave": "Guardar",
"SettingsButtonClose": "Cerrar",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancelar",
"SettingsButtonApply": "Aplicar",
"ControllerSettingsPlayer": "Jugador",
"ControllerSettingsPlayer1": "Jugador 1",

View File

@ -150,17 +150,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers",
"SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:",
"SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)",
"SettingsTabLoggingOpenglLogLevel": "Niveau des journaux OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Aucun",
"SettingsTabLoggingOpenglLogLevelError": "Erreur",
"SettingsTabLoggingOpenglLogLevelPerformance": "Ralentissements",
"SettingsTabLoggingOpenglLogLevelAll": "Tout",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout",
"SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug",
"SettingsTabInput": "Contrôles",
"SettingsTabInputEnableDockedMode": "Active le mode station d'accueil",
"SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier",
"SettingsButtonSave": "Enregistrer",
"SettingsButtonClose": "Fermer",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Annuler",
"SettingsButtonApply": "Appliquer",
"ControllerSettingsPlayer": "Joueur",
"ControllerSettingsPlayer1": "Joueur 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs",
"SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:",
"SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)",
"SettingsTabLoggingOpenglLogLevel": "Livello di log OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Nessuno",
"SettingsTabLoggingOpenglLogLevelError": "Errore",
"SettingsTabLoggingOpenglLogLevelPerformance": "Rallentamenti",
"SettingsTabLoggingOpenglLogLevelAll": "Tutto",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Errore",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto",
"SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug",
"SettingsTabInput": "Input",
"SettingsTabInputEnableDockedMode": "Attiva modalità TV",
"SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera",
"SettingsButtonSave": "Salva",
"SettingsButtonClose": "Chiudi",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancella",
"SettingsButtonApply": "Applica",
"ControllerSettingsPlayer": "Giocatore",
"ControllerSettingsPlayer1": "Giocatore 1",
@ -556,8 +557,8 @@
"SettingsSelectThemeFileDialogTitle" : "Seleziona file del tema",
"SettingsXamlThemeFile" : "File del tema xaml",
"SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:",
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:"
"AvatarWindowTitle": "Gestisci account - Avatar"
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:",
"AvatarWindowTitle": "Gestisci account - Avatar",
"Amiibo": "Amiibo",
"Unknown": "Sconosciuto",
"Usage": "Utilizzo",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効",
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:",
"SettingsTabLoggingDeveloperOptions": "開発者オプション (警告: パフォーマンスが低下します)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL ログレベル:",
"SettingsTabLoggingOpenglLogLevelNone": "なし",
"SettingsTabLoggingOpenglLogLevelError": "エラー",
"SettingsTabLoggingOpenglLogLevelPerformance": "パフォーマンス低下",
"SettingsTabLoggingOpenglLogLevelAll": "すべて",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "なし",
"SettingsTabLoggingGraphicsBackendLogLevelError": "エラー",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて",
"SettingsTabLoggingEnableDebugLogs": "デバッグログを有効",
"SettingsTabInput": "入力",
"SettingsTabInputEnableDockedMode": "ドッキングモード",
"SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス",
"SettingsButtonSave": "セーブ",
"SettingsButtonClose": "閉じる",
"SettingsButtonOk": "オーケー",
"SettingsButtonCancel": "キャンセル",
"SettingsButtonApply": "適用",
"ControllerSettingsPlayer": "プレイヤー",
"ControllerSettingsPlayer1": "プレイヤー 1",

View File

@ -156,17 +156,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Fs 액세스 로그 켜기",
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs 전역 액세스 로그 모드 :",
"SettingsTabLoggingDeveloperOptions": "개발자 옵션 (경고 : 성능이 저하됩니다.)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL 로그 수준 :",
"SettingsTabLoggingOpenglLogLevelNone": "없음",
"SettingsTabLoggingOpenglLogLevelError": "오류",
"SettingsTabLoggingOpenglLogLevelPerformance": "감속",
"SettingsTabLoggingOpenglLogLevelAll": "모두",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "없음",
"SettingsTabLoggingGraphicsBackendLogLevelError": "오류",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "감속",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "모두",
"SettingsTabLoggingEnableDebugLogs": "디버그 로그 사용",
"SettingsTabInput": "입력",
"SettingsTabInputEnableDockedMode": "도킹 모드 활성화",
"SettingsTabInputDirectKeyboardAccess": "직접 키보드 액세스",
"SettingsButtonSave": "구하다",
"SettingsButtonClose": "출구",
"SettingsButtonOk": "좋아",
"SettingsButtonCancel": "취소",
"SettingsButtonApply": "적용하다",
"ControllerSettingsPlayer": "플레이어",
"ControllerSettingsPlayer1": "플레이어 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików",
"SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:",
"SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)",
"SettingsTabLoggingOpenglLogLevel": "Poziom Logów OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Żadne",
"SettingsTabLoggingOpenglLogLevelError": "Błędy",
"SettingsTabLoggingOpenglLogLevelPerformance": "Spowolnienia",
"SettingsTabLoggingOpenglLogLevelAll": "Wszystkie",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie",
"SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania",
"SettingsTabInput": "Sterowanie",
"SettingsTabInputEnableDockedMode": "Tryb Zadokowany",
"SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury",
"SettingsButtonSave": "Zapisz",
"SettingsButtonClose": "Zamknij",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Anuluj",
"SettingsButtonApply": "Zastosuj",
"ControllerSettingsPlayer": "Gracz",
"ControllerSettingsPlayer1": "Gracz 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos",
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:",
"SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)",
"SettingsTabLoggingOpenglLogLevel": "Nível de log do OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Nenhum",
"SettingsTabLoggingOpenglLogLevelError": "Erro",
"SettingsTabLoggingOpenglLogLevelPerformance": "Lentidão",
"SettingsTabLoggingOpenglLogLevelAll": "Todos",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Erro",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos",
"SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração",
"SettingsTabInput": "Controle",
"SettingsTabInputEnableDockedMode": "Habilitar modo TV",
"SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado",
"SettingsButtonSave": "Salvar",
"SettingsButtonClose": "Fechar",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Cancelar",
"SettingsButtonApply": "Aplicar",
"ControllerSettingsPlayer": "Jogador",
"ControllerSettingsPlayer1": "Jogador 1",

View File

@ -156,17 +156,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа Fs",
"SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа Fs:",
"SettingsTabLoggingDeveloperOptions": "Параметры разработчика (ВНИМАНИЕ: снизит производительность)",
"SettingsTabLoggingOpenglLogLevel": "Уровень журнала OpenGL:",
"SettingsTabLoggingOpenglLogLevelNone": "Ничего",
"SettingsTabLoggingOpenglLogLevelError": "Ошибка",
"SettingsTabLoggingOpenglLogLevelPerformance": "Замедления",
"SettingsTabLoggingOpenglLogLevelAll": "Всё",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё",
"SettingsTabLoggingEnableDebugLogs": "Включить журналы отладки",
"SettingsTabInput": "Управление",
"SettingsTabInputEnableDockedMode": "Включить режим закрепления",
"SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры",
"SettingsButtonSave": "Сохранить",
"SettingsButtonClose": "Закрыть",
"SettingsButtonOk": "OK",
"SettingsButtonCancel": "Отмена",
"SettingsButtonApply": "Применить",
"ControllerSettingsPlayer": "Игрок",
"ControllerSettingsPlayer1": "Игрок 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir",
"SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:",
"SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL Log Seviyesi:",
"SettingsTabLoggingOpenglLogLevelNone": "H",
"SettingsTabLoggingOpenglLogLevelError": "Hata",
"SettingsTabLoggingOpenglLogLevelPerformance": "Yavaşlamalar",
"SettingsTabLoggingOpenglLogLevelAll": "Her Şey",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiç",
"SettingsTabLoggingGraphicsBackendLogLevelError": "Hata",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "Her Şey",
"SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir",
"SettingsTabInput": "Giriş Yöntemi",
"SettingsTabInputEnableDockedMode": "Docked Modunu Etkinleştir",
"SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi",
"SettingsButtonSave": "Kaydet",
"SettingsButtonClose": "Kapat",
"SettingsButtonOk": "Tamam",
"SettingsButtonCancel": "İptal",
"SettingsButtonApply": "Uygula",
"ControllerSettingsPlayer": "Oyuncu",
"ControllerSettingsPlayer1": "Oyuncu 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "记录文件访问",
"SettingsTabLoggingFsGlobalAccessLogMode": "记录全局文件访问模式:",
"SettingsTabLoggingDeveloperOptions": "开发者选项 (警告: 会降低性能)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL日志级别:",
"SettingsTabLoggingOpenglLogLevelNone": "",
"SettingsTabLoggingOpenglLogLevelError": "错误",
"SettingsTabLoggingOpenglLogLevelPerformance": "减速",
"SettingsTabLoggingOpenglLogLevelAll": "全部",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "",
"SettingsTabLoggingGraphicsBackendLogLevelError": "错误",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "全部",
"SettingsTabLoggingEnableDebugLogs": "启用调试日志",
"SettingsTabInput": "输入",
"SettingsTabInputEnableDockedMode": "主机模式",
"SettingsTabInputDirectKeyboardAccess": "直通键盘控制",
"SettingsButtonSave": "保存",
"SettingsButtonClose": "关闭",
"SettingsButtonOk": "批准",
"SettingsButtonCancel": "取消",
"SettingsButtonApply": "应用",
"ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1",

View File

@ -157,17 +157,18 @@
"SettingsTabLoggingEnableFsAccessLogs": "記錄檔案存取",
"SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:",
"SettingsTabLoggingDeveloperOptions": "開發者選項 (警告: 會降低效能)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL 日誌級別:",
"SettingsTabLoggingOpenglLogLevelNone": "",
"SettingsTabLoggingOpenglLogLevelError": "錯誤",
"SettingsTabLoggingOpenglLogLevelPerformance": "減速",
"SettingsTabLoggingOpenglLogLevelAll": "全部",
"SettingsTabLoggingGraphicsBackendLogLevelNone": "",
"SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤",
"SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速",
"SettingsTabLoggingGraphicsBackendLogLevelAll": "全部",
"SettingsTabLoggingEnableDebugLogs": "啟用除錯日誌",
"SettingsTabInput": "輸入",
"SettingsTabInputEnableDockedMode": "Docked 模式",
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
"SettingsButtonSave": "儲存",
"SettingsButtonClose": "關閉",
"SettingsButtonOk": "嘛好",
"SettingsButtonCancel": "取消",
"SettingsButtonApply": "套用",
"ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1",

View File

@ -41,6 +41,9 @@
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<SolidColorBrush x:Key="ControlFillColorSecondaryBrush" Color="{StaticResource ControlFillColorSecondary}" />
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlFillColorSecondaryBrush" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
@ -55,5 +58,7 @@
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
</Styles.Resources>
</Styles>

View File

@ -50,5 +50,7 @@
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
</Styles.Resources>
</Styles>

View File

@ -1,7 +1,6 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Height="2000" Padding="20">
@ -269,13 +268,15 @@
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<sys:Double x:Key="ScrollBarThickness">15</sys:Double>
<sys:Double x:Key="FontSizeSmall">8</sys:Double>
<sys:Double x:Key="FontSizeNormal">10</sys:Double>
<sys:Double x:Key="FontSize">12</sys:Double>
<sys:Double x:Key="FontSizeLarge">15</sys:Double>
<sys:Double x:Key="ControlContentThemeFontSize">13</sys:Double>
<x:Double x:Key="ScrollBarThickness">15</x:Double>
<x:Double x:Key="FontSizeSmall">8</x:Double>
<x:Double x:Key="FontSizeNormal">10</x:Double>
<x:Double x:Key="FontSize">12</x:Double>
<x:Double x:Key="FontSizeLarge">15</x:Double>
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
<x:Double x:Key="MenuItemHeight">26</x:Double>
<x:Double x:Key="TabItemMinHeight">28</x:Double>
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</Styles.Resources>
</Styles>

View File

@ -113,6 +113,11 @@ namespace Ryujinx.Ava.Common
return;
}
OpenSaveDir(saveDataId);
}
public static void OpenSaveDir(ulong saveDataId)
{
string saveRootPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataId:x16}");
if (!Directory.Exists(saveRootPath))

View File

@ -0,0 +1,127 @@
using System;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;
using Avalonia;
namespace Ryujinx.Ava.Ui.Helper
{
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
[SupportedOSPlatform("macos")]
static class MetalHelper
{
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
private struct Selector
{
public readonly IntPtr NativePtr;
public unsafe Selector(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
NativePtr = sel_registerName(data);
}
public static implicit operator Selector(string value) => new Selector(value);
}
private static unsafe IntPtr GetClass(string value)
{
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
byte* data = stackalloc byte[size];
fixed (char* pValue = value)
{
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
}
return objc_getClass(data);
}
private struct NSPoint
{
public double X;
public double Y;
public NSPoint(double x, double y)
{
X = x;
Y = y;
}
}
private struct NSRect
{
public NSPoint Pos;
public NSPoint Size;
public NSRect(double x, double y, double width, double height)
{
Pos = new NSPoint(x, y);
Size = new NSPoint(width, height);
}
}
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
{
// Create a new CAMetalLayer.
IntPtr layerClass = GetClass("CAMetalLayer");
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
objc_msgSend(metalLayer, "init");
// Create a child NSView to render into.
IntPtr nsViewClass = GetClass("NSView");
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
objc_msgSend(child, "setWantsLayer:", (byte)1);
objc_msgSend(child, "setLayer:", metalLayer);
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
updateBounds = (Rect rect) => {
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
};
nsView = child;
return metalLayer;
}
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
{
// TODO
}
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr sel_registerName(byte* data);
[DllImport(LibObjCImport)]
private static unsafe extern IntPtr objc_getClass(byte* data);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
[DllImport(LibObjCImport)]
private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
[DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
}
}

View File

@ -4,7 +4,6 @@ using Ryujinx.Input;
using System;
using System.Collections.Generic;
using System.Numerics;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key;
@ -13,30 +12,37 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboard : IKeyboard
{
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver;
private readonly AvaloniaKeyboardDriver _driver;
private StandardKeyboardInputConfig _configuration;
private readonly object _userMappingLock = new();
private StandardKeyboardInputConfig _configuration;
private bool HasConfiguration => _configuration != null;
public string Id { get; }
public string Id { get; }
public string Name { get; }
public bool IsConnected => true;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_driver = driver;
Id = id;
Name = name;
_buttonsUserMapping = new List<ButtonMappingEntry>();
}
public void Dispose() { }
_driver = driver;
Id = id;
Name = name;
}
public KeyboardStateSnapshot GetKeyboardStateSnapshot()
{
@ -46,11 +52,11 @@ namespace Ryujinx.Ava.Input
public GamepadStateSnapshot GetMappedStateSnapshot()
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (!HasConfiguration)
if (_configuration == null)
{
return result;
}
@ -62,17 +68,17 @@ namespace Ryujinx.Ava.Input
continue;
}
// Do not touch state of the button already pressed
// NOTE: Do not touch state of the button already pressed.
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
}
@ -114,29 +120,29 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Clear();
// Left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
// Left JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
// Right JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
}
}
@ -190,16 +196,6 @@ namespace Ryujinx.Ava.Input
_driver?.ResetKeys();
}
private class ButtonMappingEntry
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
}
public void Dispose() { }
}
}

View File

@ -4,7 +4,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Input;
using System;
using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key;
using Key = Ryujinx.Input.Key;
@ -13,24 +12,23 @@ namespace Ryujinx.Ava.Input
internal class AvaloniaKeyboardDriver : IGamepadDriver
{
private static readonly string[] _keyboardIdentifers = new string[1] { "0" };
private readonly Control _control;
private readonly Control _control;
private readonly HashSet<AvaKey> _pressedKeys;
public event EventHandler<KeyEventArgs> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease;
public event EventHandler<string> TextInput;
public string DriverName => "Avalonia";
public event EventHandler<string> TextInput;
public string DriverName => "AvaloniaKeyboardDriver";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control)
{
_control = control;
_control = control;
_pressedKeys = new HashSet<AvaKey>();
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.KeyDown += OnKeyPress;
_control.KeyUp += OnKeyRelease;
_control.TextInput += Control_TextInput;
}
@ -41,21 +39,16 @@ namespace Ryujinx.Ava.Input
public event Action<string> OnGamepadConnected
{
add { }
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
add { }
remove { }
}
public void Dispose()
{
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (!_keyboardIdentifers[0].Equals(id))
@ -70,15 +63,13 @@ namespace Ryujinx.Ava.Input
{
if (disposing)
{
_control.KeyUp -= OnKeyPress;
_control.KeyUp -= OnKeyPress;
_control.KeyDown -= OnKeyRelease;
}
}
protected void OnKeyPress(object sender, KeyEventArgs args)
{
AvaKey key = args.Key;
_pressedKeys.Add(args.Key);
KeyPressed?.Invoke(this, args);
@ -98,7 +89,7 @@ namespace Ryujinx.Ava.Input
return false;
}
AvaloniaMappingHelper.TryGetAvaKey(key, out var nativeKey);
AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out var nativeKey);
return _pressedKeys.Contains(nativeKey);
}
@ -107,5 +98,10 @@ namespace Ryujinx.Ava.Input
{
_pressedKeys.Clear();
}
public void Dispose()
{
Dispose(true);
}
}
}

View File

@ -5,7 +5,7 @@ using AvaKey = Avalonia.Input.Key;
namespace Ryujinx.Ava.Input
{
internal static class AvaloniaMappingHelper
internal static class AvaloniaKeyboardMappingHelper
{
private static readonly AvaKey[] _keyMapping = new AvaKey[(int)Key.Count]
{
@ -149,11 +149,11 @@ namespace Ryujinx.Ava.Input
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
static AvaloniaMappingHelper()
static AvaloniaKeyboardMappingHelper()
{
var inputKeys = Enum.GetValues(typeof(Key));
// Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>();
foreach (var key in inputKeys)
@ -167,15 +167,13 @@ namespace Ryujinx.Ava.Input
public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{
var keyExist = (int)key < _keyMapping.Length;
avaKey = AvaKey.None;
bool keyExist = (int)key < _keyMapping.Length;
if (keyExist)
{
avaKey = _keyMapping[(int)key];
}
else
{
avaKey = AvaKey.None;
}
return keyExist;
}

View File

@ -10,15 +10,12 @@ namespace Ryujinx.Ava.Input
{
private AvaloniaMouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0";
public string Id => "0";
public string Name => "AvaloniaMouse";
public bool IsConnected => true;
public bool[] Buttons => _driver.PressedButtons;
public bool IsConnected => true;
public GamepadFeaturesFlag Features => throw new NotImplementedException();
public bool[] Buttons => _driver.PressedButtons;
public AvaloniaMouse(AvaloniaMouseDriver driver)
{

View File

@ -11,35 +11,50 @@ namespace Ryujinx.Ava.Input
{
internal class AvaloniaMouseDriver : IGamepadDriver
{
private Control _widget;
private bool _isDisposed;
private Size _size;
private Control _widget;
private bool _isDisposed;
private Size _size;
private readonly Window _window;
public bool[] PressedButtons { get; }
public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; }
public Vector2 Scroll { get; private set; }
public Vector2 Scroll { get; private set; }
public string DriverName => "AvaloniaMouseDriver";
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public AvaloniaMouseDriver(Window window, Control parent)
{
_widget = parent;
_window = window;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerMoved += Parent_PointerMovedEvent;
_widget.PointerPressed += Parent_PointerPressEvent;
_widget.PointerReleased += Parent_PointerReleaseEvent;
_widget.PointerWheelChanged += Parent_ScrollEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerMoved += Parent_PointerMovedEvent;
_window.PointerPressed += Parent_PointerPressEvent;
_window.PointerReleased += Parent_PointerReleaseEvent;
_window.PointerWheelChanged += Parent_ScrollEvent;
PressedButtons = new bool[(int)MouseButton.Count];
_size = new Size((int)parent.Bounds.Width, (int)parent.Bounds.Height);
parent.GetObservable(Control.BoundsProperty).Subscribe(Resized);
parent.GetObservable(Visual.BoundsProperty).Subscribe(Resized);
}
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
private void Resized(Rect rect)
@ -59,14 +74,12 @@ namespace Ryujinx.Ava.Input
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
{
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
PressedButtons[(int)pointerProperties.PointerUpdateKind] = true;
PressedButtons[(int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind] = true;
}
private void Parent_PointerMovedEvent(object o, PointerEventArgs args)
{
var position = args.GetPosition(_widget);
Point position = args.GetPosition(_widget);
CurrentPosition = new Vector2((float)position.X, (float)position.Y);
}
@ -96,22 +109,6 @@ namespace Ryujinx.Ava.Input
return _size;
}
public string DriverName => "Avalonia";
public event Action<string> OnGamepadConnected
{
add { }
remove { }
}
public event Action<string> OnGamepadDisconnected
{
add { }
remove { }
}
public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public IGamepad GetGamepad(string id)
{
return new AvaloniaMouse(this);
@ -126,14 +123,14 @@ namespace Ryujinx.Ava.Input
_isDisposed = true;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerMoved -= Parent_PointerMovedEvent;
_widget.PointerPressed -= Parent_PointerPressEvent;
_widget.PointerReleased -= Parent_PointerReleaseEvent;
_widget.PointerWheelChanged -= Parent_ScrollEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerMoved -= Parent_PointerMovedEvent;
_window.PointerPressed -= Parent_PointerPressEvent;
_window.PointerReleased -= Parent_PointerReleaseEvent;
_window.PointerWheelChanged -= Parent_ScrollEvent;
_widget = null;

View File

@ -1,5 +1,6 @@
using ARMeilleure.Translation.PTC;
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@ -8,6 +9,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.System;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
@ -20,10 +22,11 @@ namespace Ryujinx.Ava
{
internal class Program
{
public static double WindowScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
@ -94,6 +97,9 @@ namespace Ryujinx.Ava
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
ForceDpiAware.Windows();
@ -219,4 +225,4 @@ namespace Ryujinx.Ava
Logger.Shutdown();
}
}
}
}

View File

@ -28,14 +28,16 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.5" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
<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.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.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
@ -57,14 +59,18 @@
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<Content Include="..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="..\distribution\legal\THIRDPARTY.md">
</Content>
<Content Include="..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</ContentWithTargetPath>
</Content>
<Content Include="..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>

View File

@ -68,7 +68,7 @@ namespace Ryujinx.Ava.Ui.Applet
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, Avalonia.Input.KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
{
@ -88,7 +88,7 @@ namespace Ryujinx.Ava.Ui.Applet
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
{
var key = (HidKey)AvaloniaMappingHelper.ToInputKey(e.Key);
var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
{

View File

@ -11,7 +11,8 @@
Height="340"
CanResize="False"
SizeToContent="Height"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="20"
HorizontalAlignment="Stretch"

View File

@ -6,7 +6,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="400"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="20"
HorizontalAlignment="Stretch"

View File

@ -2,11 +2,10 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Ava.Ui.Helper;
using SPB.Graphics;
using SPB.Platform;
using SPB.Platform.GLX;
using SPB.Platform.X11;
using SPB.Windowing;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@ -23,6 +22,10 @@ namespace Ryujinx.Ava.Ui.Controls
protected GLXWindow X11Window { get; set; }
protected IntPtr WindowHandle { get; set; }
protected IntPtr X11Display { get; set; }
protected IntPtr NsView { get; set; }
protected IntPtr MetalLayer { get; set; }
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<IntPtr> WindowCreated;
public event EventHandler<Size> SizeChanged;
@ -58,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Controls
private void StateChanged(Rect rect)
{
SizeChanged?.Invoke(this, rect.Size);
_updateBoundsCallback?.Invoke(rect);
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
@ -70,6 +74,11 @@ namespace Ryujinx.Ava.Ui.Controls
{
return CreateWin32(parent);
}
else if (OperatingSystem.IsMacOS())
{
return CreateMacOs(parent);
}
return base.CreateNativeControlCore(parent);
}
@ -85,6 +94,10 @@ namespace Ryujinx.Ava.Ui.Controls
{
DestroyWin32(control);
}
else if (OperatingSystem.IsMacOS())
{
DestroyMacOS();
}
else
{
base.DestroyNativeControlCore(control);
@ -187,6 +200,16 @@ namespace Ryujinx.Ava.Ui.Controls
return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
}
[SupportedOSPlatform("macos")]
IPlatformHandle CreateMacOs(IPlatformHandle parent)
{
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
NsView = nsView;
return new PlatformHandle(nsView, "NSView");
}
void DestroyLinux()
{
X11Window?.Dispose();
@ -198,5 +221,11 @@ namespace Ryujinx.Ava.Ui.Controls
DestroyWindow(handle.Handle);
UnregisterClass(_className, GetModuleHandle(null));
}
[SupportedOSPlatform("macos")]
void DestroyMacOS()
{
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
}
}
}

View File

@ -10,7 +10,8 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
@ -113,8 +114,8 @@
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
<Style.Animations>
<Animation Duration="0:0:0.7">
<KeyFrame Cue="0%">
@ -132,27 +133,18 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.Styles>
<Style Selector="ui|SymbolIcon.small.icon">
<Setter Property="FontSize" Value="15" />
</Style>
<Style Selector="ui|SymbolIcon.normal.icon">
<Setter Property="FontSize" Value="19" />
</Style>
<Style Selector="ui|SymbolIcon.large.icon">
<Setter Property="FontSize" Value="23" />
</Style>
<Style Selector="ui|SymbolIcon.huge.icon">
<Setter Property="FontSize" Value="26" />
</Style>
</Grid.Styles>
<Border
Margin="0"
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
@ -160,57 +152,41 @@
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<StackPanel
<Panel
Grid.Row="1"
Height="50"
Margin="5"
Margin="0 10 0 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
<TextBlock
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding TitleName}"
TextAlignment="Center"
TextWrapping="Wrap" />
</StackPanel>
</Panel>
</Grid>
</Border>
<ui:SymbolIcon
Margin="5"
Margin="5,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.icon="true"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Foreground="Yellow"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<ui:SymbolIcon
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
Classes.icon="true"
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
Foreground="Black"
IsVisible="{Binding Favorite}"
Symbol="Star" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@ -10,7 +10,8 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
@ -115,8 +116,8 @@
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="CornerRadius" Value="5" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorDark3}" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
<Setter Property="BorderThickness" Value="2"/>
<Style.Animations>
<Animation Duration="0:0:0.7">
<KeyFrame Cue="0%">
@ -134,6 +135,12 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
@ -152,9 +159,6 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Image
Grid.RowSpan="3"
Grid.Column="0"
@ -169,7 +173,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Top"
Orientation="Vertical"
Spacing="5">
Spacing="5" >
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding TitleName}"
@ -214,20 +218,10 @@
Margin="-5,-5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="20"
Foreground="Yellow"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<ui:SymbolIcon
Grid.Row="0"
Grid.Column="0"
Margin="-5,-5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="20"
Foreground="Black"
IsVisible="{Binding Favorite}"
Symbol="Star" />
</Grid>
</Border>
</Grid>

View File

@ -4,7 +4,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="5,10,5,5"
HorizontalAlignment="Stretch"

View File

@ -4,7 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost"
Focusable="True">
<ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
x:Name="ContentFrame" />
</UserControl>

View File

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using LibHac;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
@ -14,6 +15,8 @@ namespace Ryujinx.Ava.Ui.Controls
{
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public VirtualFileSystem VirtualFileSystem { get; }
public HorizonClient HorizonClient { get; }
public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost()
@ -22,10 +25,12 @@ namespace Ryujinx.Ava.Ui.Controls
}
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem)
VirtualFileSystem virtualFileSystem, HorizonClient horizonClient)
{
AccountManager = accountManager;
ContentManager = contentManager;
VirtualFileSystem = virtualFileSystem;
HorizonClient = horizonClient;
ViewModel = new UserProfileViewModel(this);
@ -54,9 +59,10 @@ namespace Ryujinx.Ava.Ui.Controls
ContentFrame.Navigate(sourcePageType, parameter);
}
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager,
VirtualFileSystem ownerVirtualFileSystem, HorizonClient ownerHorizonClient)
{
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem, ownerHorizonClient);
ContentDialog contentDialog = new ContentDialog
{
Title = LocaleManager.Instance["UserProfileWindowTitle"],

View File

@ -4,7 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
Focusable="True">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@ -3,5 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost"
Focusable="True">
</UserControl>

View File

@ -0,0 +1,103 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
Height="400"
Width="550"
x:Class="Ryujinx.Ava.Ui.Controls.SaveManager"
Focusable="True">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<Label Content="{locale:Locale CommonSort}" VerticalAlignment="Center" />
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Name}" />
</ComboBoxItem>
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Size}" />
</ComboBoxItem>
</ComboBox>
<ComboBox SelectedIndex="{Binding OrderIndex}" Width="150">
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale OrderAscending}" />
</ComboBoxItem>
<ComboBoxItem>
<Label VerticalAlignment="Center" HorizontalContentAlignment="Left"
Content="{locale:Locale Descending}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<Grid Grid.Column="1" HorizontalAlignment="Stretch" Margin="10,0, 0, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="{locale:Locale Search}" VerticalAlignment="Center"/>
<TextBox Margin="5,0,0,0" Grid.Column="1" HorizontalAlignment="Stretch" Text="{Binding Search}"/>
</Grid>
</Grid>
<Border Grid.Row="1" Margin="0,5" BorderThickness="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox Name="SaveList" Items="{Binding View}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="models:SaveModel">
<Grid HorizontalAlignment="Stretch" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Border Height="42" Margin="2" Width="42" Padding="10"
IsVisible="{Binding !InGameList}">
<ui:SymbolIcon Symbol="Help" FontSize="30" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<Image IsVisible="{Binding InGameList}"
Margin="2"
Width="42"
Height="42"
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
<TextBlock MaxLines="3" Width="320" Margin="5" TextWrapping="Wrap"
Text="{Binding Title}" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Grid.Column="1" Spacing="10" HorizontalAlignment="Right"
Orientation="Horizontal">
<Label Content="{Binding SizeString}" IsVisible="{Binding SizeAvailable}"
VerticalAlignment="Center" HorizontalAlignment="Right" />
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
MinWidth="0" MinHeight="0" Name="OpenLocation" Command="{Binding OpenLocation}">
<ui:SymbolIcon Symbol="OpenFolder" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
<Button VerticalAlignment="Center" HorizontalAlignment="Right" Padding="10"
MinWidth="0" MinHeight="0" Name="Delete" Command="{Binding Delete}">
<ui:SymbolIcon Symbol="Delete" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,160 @@
using Avalonia.Controls;
using DynamicData;
using DynamicData.Binding;
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class SaveManager : UserControl
{
private readonly UserProfile _userProfile;
private readonly HorizonClient _horizonClient;
private readonly VirtualFileSystem _virtualFileSystem;
private int _sortIndex;
private int _orderIndex;
private ObservableCollection<SaveModel> _view = new ObservableCollection<SaveModel>();
private string _search;
public ObservableCollection<SaveModel> Saves { get; set; } = new ObservableCollection<SaveModel>();
public ObservableCollection<SaveModel> View
{
get => _view;
set => _view = value;
}
public int SortIndex
{
get => _sortIndex;
set
{
_sortIndex = value;
Sort();
}
}
public int OrderIndex
{
get => _orderIndex;
set
{
_orderIndex = value;
Sort();
}
}
public string Search
{
get => _search;
set
{
_search = value;
Sort();
}
}
public SaveManager()
{
InitializeComponent();
}
public SaveManager(UserProfile userProfile, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
{
_userProfile = userProfile;
_horizonClient = horizonClient;
_virtualFileSystem = virtualFileSystem;
InitializeComponent();
DataContext = this;
Task.Run(LoadSaves);
}
public void LoadSaves()
{
Saves.Clear();
var saveDataFilter = SaveDataFilter.Make(programId: default, saveType: SaveDataType.Account,
new UserId((ulong)_userProfile.UserId.High, (ulong)_userProfile.UserId.Low), saveDataId: default, index: default);
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
while (true)
{
saveDataIterator.Get.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
if (readCount == 0)
{
break;
}
for (int i = 0; i < readCount; i++)
{
var save = saveDataInfo[i];
if (save.ProgramId.Value != 0)
{
var saveModel = new SaveModel(save, _horizonClient, _virtualFileSystem);
Saves.Add(saveModel);
saveModel.DeleteAction = () => { Saves.Remove(saveModel); };
}
Sort();
}
}
}
private void Sort()
{
Saves.AsObservableChangeSet()
.Filter(Filter)
.Sort(GetComparer())
.Bind(out var view).AsObservableList();
_view.Clear();
_view.AddRange(view);
}
private IComparer<SaveModel> GetComparer()
{
switch (SortIndex)
{
case 0:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
case 1:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
default:
return null;
}
}
private bool Filter(object arg)
{
if (arg is SaveModel save)
{
return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower());
}
return false;
}
}
}

View File

@ -8,7 +8,8 @@
Title="Ryujinx - Waiting"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<Grid
Margin="20"
HorizontalAlignment="Stretch"

View File

@ -10,8 +10,10 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
Margin="0"
MinWidth="500"
Padding="0"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
@ -63,7 +65,7 @@
HorizontalAlignment="Stretch"
MaxLength="{Binding MaxProfileNameLength}"
Text="{Binding Name}" />
<TextBlock Text="{Locale:Locale UserProfilesUserId}" />
<TextBlock Name="IdText" Text="{Locale:Locale UserProfilesUserId}" />
<TextBlock Name="IdLabel" Text="{Binding UserId}" />
</StackPanel>
<StackPanel

View File

@ -36,15 +36,8 @@ namespace Ryujinx.Ava.Ui.Controls
case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = args.isNewUser;
if (!_isNewUser)
{
_profile = args.profile;
TempProfile = new TempProfile(_profile);
}
else
{
TempProfile = new TempProfile();
}
_profile = args.profile;
TempProfile = new TempProfile(_profile);
_parent = args.parent;
break;
@ -53,7 +46,8 @@ namespace Ryujinx.Ava.Ui.Controls
DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser;
IdLabel.IsVisible = !_isNewUser;
IdLabel.IsVisible = _profile != null;
IdText.IsVisible = _profile != null;
ChangePictureButton.IsVisible = !_isNewUser;
}
}
@ -87,7 +81,7 @@ namespace Ryujinx.Ava.Ui.Controls
return;
}
if (_profile != null)
if (_profile != null && !_isNewUser)
{
_profile.Name = TempProfile.Name;
_profile.Image = TempProfile.Image;
@ -97,7 +91,7 @@ namespace Ryujinx.Ava.Ui.Controls
}
else if (_isNewUser)
{
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image, TempProfile.UserId);
}
else
{

View File

@ -0,0 +1,71 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
MinWidth="500"
MinHeight="400"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
x:Class="Ryujinx.Ava.Ui.Controls.UserRecoverer"
Focusable="True">
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Margin="5"
Height="30"
Width="50"
MinWidth="50"
HorizontalAlignment="Left"
Command="{Binding GoBack}">
<ui:SymbolIcon Symbol="Back"/>
</Button>
<TextBlock Grid.Row="1"
Text="{Locale:Locale UserProfilesRecoverHeading}"/>
<ListBox
Margin="5"
Grid.Row="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Items="{Binding LostProfiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding UserId}"
TextAlignment="Left"
TextWrapping="Wrap" />
<Button Grid.Column="1"
HorizontalAlignment="Right"
Command="{Binding Recover}"
CommandParameter="{Binding}"
Content="{Locale:Locale Recover}"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>

View File

@ -0,0 +1,44 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserRecoverer : UserControl
{
private UserProfileViewModel _viewModel;
private NavigationDialogHost _parent;
public UserRecoverer()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfileViewModel viewModel))arg.Parameter;
_viewModel = args.viewModel;
_parent = args.parent;
break;
}
DataContext = _viewModel;
}
}
}
}

View File

@ -10,8 +10,10 @@
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
d:DesignHeight="450"
MinWidth="500"
d:DesignWidth="800"
mc:Ignorable="d">
mc:Ignorable="d"
Focusable="True">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
@ -25,6 +27,7 @@
</Grid.RowDefinitions>
<ListBox
Margin="5"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
DoubleTapped="ProfilesList_DoubleTapped"
@ -88,21 +91,56 @@
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel
<Grid
Grid.Row="1"
Margin="10,0"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<Button Command="{Binding AddUser}" Content="{Locale:Locale UserProfilesAddNewProfile}" />
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button
HorizontalAlignment="Stretch"
Grid.Row="0"
Grid.Column="0"
Margin="2"
Command="{Binding AddUser}"
Content="{Locale:Locale UserProfilesAddNewProfile}" />
<Button
HorizontalAlignment="Stretch"
Grid.Row="0"
Margin="2"
Grid.Column="1"
Command="{Binding EditUser}"
Content="{Locale:Locale UserProfilesEditProfile}"
IsEnabled="{Binding IsSelectedProfiledEditable}" />
<Button
HorizontalAlignment="Stretch"
Grid.Row="1"
Grid.Column="0"
Margin="2"
Content="{Locale:Locale UserProfilesManageSaves}"
Command="{Binding ManageSaves}" />
<Button
HorizontalAlignment="Stretch"
Grid.Row="1"
Grid.Column="1"
Margin="2"
Command="{Binding DeleteUser}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}"
IsEnabled="{Binding IsSelectedProfileDeletable}" />
</StackPanel>
<Button
HorizontalAlignment="Stretch"
Grid.Row="2"
Grid.ColumnSpan="2"
Grid.Column="0"
Margin="2"
Command="{Binding RecoverLostAccounts}"
Content="{Locale:Locale UserProfilesRecoverLostAccounts}" />
</Grid>
</Grid>
</UserControl>

View File

@ -3,6 +3,7 @@ using Ryujinx.Ava.Ui.Controls;
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using SPB.Platform.GLX;
using SPB.Platform.Metal;
using SPB.Platform.Win32;
using SPB.Platform.X11;
using SPB.Windowing;
@ -37,6 +38,10 @@ namespace Ryujinx.Ava.Ui
{
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
}
else if (OperatingSystem.IsMacOS())
{
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
}
else
{
throw new PlatformNotSupportedException();

View File

@ -1,45 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class FileSizeSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).TimePlayed;
string bValue = (y as ApplicationData).TimePlayed;
if (aValue[^3..] == "GiB")
{
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
}
else
{
aValue = aValue[0..^3];
}
if (bValue[^3..] == "GiB")
{
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
}
else
{
bValue = bValue[0..^3];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1;
}
else
{
return 0;
}
}
}
}

View File

@ -1,50 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
internal class FileSizeSortComparer : IComparer<ApplicationData>
{
public FileSizeSortComparer() { }
public FileSizeSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
private int _order;
public int Compare(ApplicationData x, ApplicationData y)
{
string aValue = x.FileSize;
string bValue = y.FileSize;
if (aValue[^3..] == "GiB")
{
aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
}
else
{
aValue = aValue[0..^3];
}
if (bValue[^3..] == "GiB")
{
bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
}
else
{
bValue = bValue[0..^3];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1 * _order;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1 * _order;
}
else
{
return 0;
}
}
}
}

View File

@ -1,66 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections.Generic;
namespace Ryujinx.Ava.Ui.Models.Generic
{
internal class TimePlayedSortComparer : IComparer<ApplicationData>
{
public TimePlayedSortComparer() { }
public TimePlayedSortComparer(bool isAscending) { _order = isAscending ? 1 : -1; }
private int _order;
public int Compare(ApplicationData x, ApplicationData y)
{
string aValue = x.TimePlayed;
string bValue = y.TimePlayed;
if (aValue.Length > 4 && aValue[^4..] == "mins")
{
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
}
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
{
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
}
else if (aValue.Length > 4 && aValue[^4..] == "days")
{
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
}
else
{
aValue = aValue[0..^1];
}
if (bValue.Length > 4 && bValue[^4..] == "mins")
{
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
}
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
{
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
}
else if (bValue.Length > 4 && bValue[^4..] == "days")
{
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
}
else
{
bValue = bValue[0..^1];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1 * _order;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1 * _order;
}
else
{
return 0;
}
}
}
}

View File

@ -1,27 +0,0 @@
using Ryujinx.Ui.App.Common;
using System;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class LastPlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).LastPlayed;
string bValue = (y as ApplicationData).LastPlayed;
if (aValue == "Never")
{
aValue = DateTime.UnixEpoch.ToString();
}
if (bValue == "Never")
{
bValue = DateTime.UnixEpoch.ToString();
}
return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
}
}
}

View File

@ -0,0 +1,122 @@
using LibHac;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.Ncm;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Models
{
public class SaveModel : BaseModel
{
private readonly HorizonClient _horizonClient;
private long _size;
public Action DeleteAction { get; set; }
public ulong SaveId { get; }
public ProgramId TitleId { get; }
public string TitleIdString => $"{TitleId.Value:X16}";
public UserId UserId { get; }
public bool InGameList { get; }
public string Title { get; }
public byte[] Icon { get; }
public long Size
{
get => _size; set
{
_size = value;
SizeAvailable = true;
OnPropertyChanged();
OnPropertyChanged(nameof(SizeString));
OnPropertyChanged(nameof(SizeAvailable));
}
}
public bool SizeAvailable { get; set; }
public string SizeString => $"{((float)_size * 0.000000954):0.###}MB";
public SaveModel(SaveDataInfo info, HorizonClient horizonClient, VirtualFileSystem virtualFileSystem)
{
_horizonClient = horizonClient;
SaveId = info.SaveDataId;
TitleId = info.ProgramId;
UserId = info.UserId;
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.TitleId.ToUpper() == TitleIdString);
InGameList = appData != null;
if (InGameList)
{
Icon = appData.Icon;
Title = appData.TitleName;
}
else
{
var appMetadata = MainWindow.MainWindowViewModel.ApplicationLibrary.LoadAndSaveMetaData(TitleIdString);
Title = appMetadata.Title ?? TitleIdString;
}
Task.Run(() =>
{
var saveRoot = System.IO.Path.Combine(virtualFileSystem.GetNandPath(), $"user/save/{info.SaveDataId:x16}");
long total_size = GetDirectorySize(saveRoot);
long GetDirectorySize(string path)
{
long size = 0;
if (Directory.Exists(path))
{
var directories = Directory.GetDirectories(path);
foreach (var directory in directories)
{
size += GetDirectorySize(directory);
}
var files = Directory.GetFiles(path);
foreach (var file in files)
{
size += new FileInfo(file).Length;
}
}
return size;
}
Size = total_size;
});
}
public void OpenLocation()
{
ApplicationHelper.OpenSaveDir(SaveId);
}
public async void Delete()
{
var result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DeleteUserSave"],
LocaleManager.Instance["IrreversibleActionNote"],
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"], "");
if (result == UserResult.Yes)
{
_horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, SaveId);
DeleteAction?.Invoke();
}
}
}
}

View File

@ -45,9 +45,12 @@ namespace Ryujinx.Ava.Ui.Models
{
_profile = profile;
Image = profile.Image;
Name = profile.Name;
UserId = profile.UserId;
if (_profile != null)
{
Image = profile.Image;
Name = profile.Name;
UserId = profile.UserId;
}
}
public TempProfile(){}

View File

@ -1,61 +0,0 @@
using Ryujinx.Ui.App.Common;
using System.Collections;
namespace Ryujinx.Ava.Ui.Models
{
internal class TimePlayedSortComparer : IComparer
{
public int Compare(object x, object y)
{
string aValue = (x as ApplicationData).TimePlayed;
string bValue = (y as ApplicationData).TimePlayed;
if (aValue.Length > 4 && aValue[^4..] == "mins")
{
aValue = (float.Parse(aValue[0..^5]) * 60).ToString();
}
else if (aValue.Length > 3 && aValue[^3..] == "hrs")
{
aValue = (float.Parse(aValue[0..^4]) * 3600).ToString();
}
else if (aValue.Length > 4 && aValue[^4..] == "days")
{
aValue = (float.Parse(aValue[0..^5]) * 86400).ToString();
}
else
{
aValue = aValue[0..^1];
}
if (bValue.Length > 4 && bValue[^4..] == "mins")
{
bValue = (float.Parse(bValue[0..^5]) * 60).ToString();
}
else if (bValue.Length > 3 && bValue[^3..] == "hrs")
{
bValue = (float.Parse(bValue[0..^4]) * 3600).ToString();
}
else if (bValue.Length > 4 && bValue[^4..] == "days")
{
bValue = (float.Parse(bValue[0..^5]) * 86400).ToString();
}
else
{
bValue = bValue[0..^1];
}
if (float.Parse(aValue) > float.Parse(bValue))
{
return -1;
}
else if (float.Parse(bValue) > float.Parse(aValue))
{
return 1;
}
else
{
return 0;
}
}
}
}

View File

@ -1,3 +1,4 @@
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Profile = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
@ -7,6 +8,7 @@ namespace Ryujinx.Ava.Ui.Models
public class UserProfile : BaseModel
{
private readonly Profile _profile;
private readonly NavigationDialogHost _owner;
private byte[] _image;
private string _name;
private UserId _userId;
@ -41,9 +43,10 @@ namespace Ryujinx.Ava.Ui.Models
}
}
public UserProfile(Profile profile)
public UserProfile(Profile profile, NavigationDialogHost owner)
{
_profile = profile;
_owner = owner;
Image = profile.Image;
Name = profile.Name;
@ -57,5 +60,10 @@ namespace Ryujinx.Ava.Ui.Models
OnPropertyChanged(nameof(IsOpened));
OnPropertyChanged(nameof(Name));
}
public void Recover(UserProfile userProfile)
{
_owner.Navigate(typeof(UserEditor), (_owner, userProfile, true));
}
}
}

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