Compare commits

...

11 Commits

Author SHA1 Message Date
gdkchan
7b62f7475e Fix AddSessionObj NRE regression (#5875) 2023-11-01 21:47:40 +01:00
gdkchan
841dd56f4c Implement copy dependency for depth and color textures (#4365)
* Implement copy dependency for depth and color textures

* Revert changes added because R32 <-> D32 copies were illegal

* Restore depth alias matches
2023-10-31 19:00:39 -03:00
riperiperi
a16d582a10 [HLE] Remove ServerBase 1ms polling (#5855)
Added a KEvent for each ServerBase which signals whenever a session is added to the _sessions list, which allows it to rerun the ReplyAndReceive with the new session handle.

This greatly reduces the presence of ServerBase on profiles, especially of games that aren't particularly busy. It should also increase responsiveness when adding session objects, as it doesn't take at most 1ms for them to start working.

It also reduces the load on KTimeManager, which could allow it to spin less often. I have noticed that a bunch of games still do 1ms waits (they actually request a bit less than 1ms), so they still end up spinning to the next millisecond. Maybe for waits like this, it could attempt to nudge the timepoints to snap to each other when they're close enough, and also snap to whole millisecond waits when close enough.
2023-10-30 23:26:31 +01:00
gdkchan
9ef0be477b Skip some invalid texture flushes (#5755) 2023-10-30 23:18:28 +01:00
TSRBerry
c14ce4d2a5 Add ldn_mitm as a network client for LDN (#5656)
* Add relevant files from private repo

Hopefully I didn't miss anything.

JsonHelper.cs is a debug only change
I only added line 810-812 in IUserLocalCommunicationService.cs
for the new Spacemeowx2Ldn case.

* Add a small README.md

just for fun

* Add note about NetCoreServer update to 5.1.0

* Fix a few issues

Fix usage of wrong broadcast address
Log warning if empty userstring was received
and don't add them to outNetworkInfo

* Add warning about incompatibility with public LDN version

* Add missing changes from old_master

* Adjust ldn_mitm for Ryujinx/Ryujinx#3805

* ldn: Adapt to changes from #4582

* ldn_mitm: First cleanup iteration

* ldn_mitm: Second cleanup iteration

* Credit spacemeowx2 in README.md

* Address first review comments by AcK

Adhere to Ryujinx coding style
Remove leftover log calls
Change category of a few log calls
Remove leftover debug notes

* Replace return type with void for methods always returning true

* Address first review comments by riperiperi

Purely stylistic changes:
- Adhere to naming style for internal fields
- Improve code formatting

* Throw InvalidOperationException when calling wrong ldn proxy methods

* Add missing newlines in LanDiscovery.Scan()

* Fix Linux not receiving broadcast packets

* Remove ILdnUdpSocket

It's very unlikely that we will ever need a udp client.
Thus we should simplify LanDiscovery initialization
and remove the parameter of InitUdp().

* ldn_mitm: Improve formatting

* fixup! Fix Linux not receiving broadcast packets

By opening the udp server on 'LocalBroadcastAddr'
Linux refused to answer packets going to LocalAddr.
So in order to fix this problem, Linux now opens two LdnProxyUdpServers.

* ldn_mitm: Fix assigning incorrect NodeIds

This just made connecting a lot more reliable! Thanks @riperiperi

* Fix node ids when leaving/joining

* Change NodeId behaviour to work like RyuLdn

* Change timing for accept and network info being reported.

* Wait for connection before sending anything.

* Remove ConnectAsync() from ILdnTcpSocket

* Only broadcast scan responses if we're hosting a network.

* Fix some filters, scan network duplication.

* Fix silly mistake

* Don't die on duplicates, just replace.

* Lock around node updates

These can happen from multiple threads.

* ldn_mitm: Fix namespaces for Types

Improve formatting
Add warning if compression failed

* Add quicker scan, forgetting networks that disappear.

* Always force a network sync when updating AdvertiseData

* Fix TCP frame size being too large for compressed frames

* Allow ldn_mitm to pass -1 id for room localcommunicationids.

* ldn_mitm: Match server socket options

* ldn_mitm: Use correct socket options

* ldn_mitm: Remove TCP broadcast socket options

* config: Rename Spacemeowx2Ldn to LdnMitm

* ldn_mitm: Generate random fake SSID

* ldn_mitm: Adjust logging statements/levels

* ldn_mitm: Add missing Stop() call for udp2

* ldn_mitm: Adjust formatting

* ldn_mitm: Add stub comments and adjust existing ones

* ldn: Add LdnConst class & set tx/rx buffer sizes correctly

* Move LdnConst out of UserServiceCreator

Replace a few values with LdnConsts

* ldn: Adjust namespaces and client names

* ldn_mitm: Adjust formatting

* ldn: Rename RyuLdn to LdnRyu

* Replace LanProtocol.Read() refs with scoped refs

* Add MIT license for ldn_mitm

* Clarify that network interface is also used for LDN

Although it's currently only used by ldn_mitm,
it would probably be more confusing to exclude RyuLdn there.

* Fix giving a station node id 0

* Update Nuget packages

* Remove LdnHelper

* Add update functions for EnableInternetAccess setting

* ldn: Log MultiplayerMode and DisableP2P

* ldn: Adjust namespaces

* Apply formatting

* Conform to Ryujinx code style

* Remove ldn_mitm from THIRDPARTY.md

It shouldn't have been there in the first place.

* Improve formatting

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-10-26 00:32:13 +02:00
jcm
171b46ef49 macOS: Use user-friendly macOS version string (#5838)
* use user-friendly macOS version string rather than kernel version

* add build identifier string

---------

Co-authored-by: jcm <butt@butts.com>
2023-10-25 00:37:13 +02:00
Alex Barney
56fe2ff535 Fix loading tickets from a Sha256PartitionFileSystem (#5844) 2023-10-24 13:26:25 -03:00
Alex Barney
b1f8f868f6 Fix the AOC manager using incorrect paths (#5840)
* Fix the content manager using incorrect path for some AOC NCAs

* Check Results in a few more places in the content manager
2023-10-23 14:34:31 -03:00
Alex Barney
d773d5152e Update to LibHac 0.19.0 (#5831)
* Update to LibHac v0.19.0

- PartitionFileSystem classes now fully match Nintendo's implementation. Current code creating a PartitionFileSystem now need to use the Initialize method.
- Implementing nn::gcsrv and nn::sdmmcsrv now means the FS server now uses that abstraction instead of the old one where we passed in an IDeviceOperator.

* Add GetFileSystemAttribute
2023-10-22 20:30:46 -03:00
gdkchan
33ba170315 Fix NRE on gather operations with depth compare on macOS (#5832) 2023-10-22 20:31:36 +02:00
TSR Berry
638be5f296 Revert "Ava UI: Input Menu Refactor (#4998)"
This reverts commit 49b37550ca.

This currently breaks the GTK GUI.
2023-10-21 15:19:21 +02:00
99 changed files with 2966 additions and 3048 deletions

View File

@@ -18,12 +18,13 @@
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.18.0" />
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />

View File

@@ -141,4 +141,5 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View File

@@ -710,4 +710,4 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
</details>
</details>

View File

@@ -190,6 +190,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
@@ -408,6 +409,11 @@ namespace Ryujinx.Ava
});
}
private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e)
{
Device.Configuration.EnableInternetAccess = e.NewValue;
}
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;

View File

@@ -263,105 +263,6 @@
"ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:",
"ControllerSettingsSave": "Save",
"ControllerSettingsClose": "Close",
"KeyUnknown": "Unknown",
"KeyShiftLeft": "Shift Left",
"KeyShiftRight": "Shift Right",
"KeyControlLeft": "Control Left",
"KeyControlRight": "Control Right",
"KeyAltLeft": "Alt Left",
"KeyAltRight": "Alt Right",
"KeyOptLeft": "⌥ Left",
"KeyOptRight": "⌥ Right",
"KeyWinLeft": "⊞ Left",
"KeyWinRight": "⊞ Right",
"KeyCmdLeft": "⌘ Left",
"KeyCmdRight": "⌘ Right",
"KeyMenu": "Menu",
"KeyUp": "Up",
"KeyDown": "Down",
"KeyLeft": "Left",
"KeyRight": "Right",
"KeyEnter": "Enter",
"KeyEscape": "Escape",
"KeySpace": "Space",
"KeyTab": "Tab",
"KeyBackSpace": "Backspace",
"KeyInsert": "Insert",
"KeyDelete": "Delete",
"KeyPageUp": "Page Up",
"KeyPageDown": "Page Down",
"KeyHome": "Home",
"KeyEnd": "End",
"KeyCapsLock": "Caps Lock",
"KeyScrollLock": "Scroll Lock",
"KeyPrintScreen": "Print Screen",
"KeyPause": "Pause",
"KeyNumLock": "Num Lock",
"KeyClear": "Clear",
"KeyKeypad0": "Keypad 0",
"KeyKeypad1": "Keypad 1",
"KeyKeypad2": "Keypad 2",
"KeyKeypad3": "Keypad 3",
"KeyKeypad4": "Keypad 4",
"KeyKeypad5": "Keypad 5",
"KeyKeypad6": "Keypad 6",
"KeyKeypad7": "Keypad 7",
"KeyKeypad8": "Keypad 8",
"KeyKeypad9": "Keypad 9",
"KeyKeypadDivide": "Keypad Divide",
"KeyKeypadMultiply": "Keypad Multiply",
"KeyKeypadSubtract": "Keypad Subtract",
"KeyKeypadAdd": "Keypad Add",
"KeyKeypadDecimal": "Keypad Decimal",
"KeyKeypadEnter": "Keypad Enter",
"KeyNumber0": "0",
"KeyNumber1": "1",
"KeyNumber2": "2",
"KeyNumber3": "3",
"KeyNumber4": "4",
"KeyNumber5": "5",
"KeyNumber6": "6",
"KeyNumber7": "7",
"KeyNumber8": "8",
"KeyNumber9": "9",
"KeyTilde": "~",
"KeyGrave": "`",
"KeyMinus": "-",
"KeyPlus": "+",
"KeyBracketLeft": "[",
"KeyBracketRight": "]",
"KeySemicolon": ";",
"KeyQuote": "\"",
"KeyComma": ",",
"KeyPeriod": ".",
"KeySlash": "/",
"KeyBackSlash": "\\",
"KeyUnbound": "Unbound",
"GamepadLeftStick": "Left Stick Button",
"GamepadRightStick": "Right Stick Button",
"GamepadLeftShoulder": "Left Shoulder",
"GamepadRightShoulder": "Right Shoulder",
"GamepadLeftTrigger": "Left Trigger",
"GamepadRightTrigger": "Right Trigger",
"GamepadDpadUp": "Up",
"GamepadDpadDown": "Down",
"GamepadDpadLeft": "Left",
"GamepadDpadRight": "Right",
"GamepadMinus": "-",
"GamepadPlus": "+",
"GamepadGuide": "Guide",
"GamepadMisc1": "Misc",
"GamepadPaddle1": "Paddle 1",
"GamepadPaddle2": "Paddle 2",
"GamepadPaddle3": "Paddle 3",
"GamepadPaddle4": "Paddle 4",
"GamepadTouchpad": "Touchpad",
"GamepadSingleLeftTrigger0": "Left Trigger 0",
"GamepadSingleRightTrigger0": "Right Trigger 0",
"GamepadSingleLeftTrigger1": "Left Trigger 1",
"GamepadSingleRightTrigger1": "Right Trigger 1",
"StickLeft": "Left Stick",
"StickRight": "Right Stick",
"UserProfilesSelectedUserProfile": "Selected User Profile:",
"UserProfilesSaveProfileName": "Save Profile Name",
"UserProfilesChangeProfileImage": "Change Profile Image",
@@ -749,7 +650,7 @@
"UserEditorTitle": "Edit User",
"UserEditorTitleCreate": "Create User",
"SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features",
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
"NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub",

View File

@@ -15,7 +15,8 @@
<MenuItem Header="Test 2" />
<MenuItem Header="Test 3">
<MenuItem.Icon>
<CheckBox Margin="0" />
<CheckBox Margin="0"
IsChecked="{ReflectionBinding Checkbox, Mode=TwoWay}" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
@@ -392,4 +393,4 @@
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</Styles.Resources>
</Styles>
</Styles>

View File

@@ -173,7 +173,7 @@ namespace Ryujinx.Ava.Common
string extension = Path.GetExtension(titleFilePath).ToLower();
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
{
PartitionFileSystem pfs;
IFileSystem pfs;
if (extension == ".xci")
{
@@ -181,7 +181,9 @@ namespace Ryujinx.Ava.Common
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
}
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))

View File

@@ -1,8 +1,11 @@
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Helpers
@@ -12,12 +15,12 @@ namespace Ryujinx.Ava.UI.Helpers
internal class ButtonAssignedEventArgs : EventArgs
{
public ToggleButton Button { get; }
public ButtonValue? ButtonValue { get; }
public bool IsAssigned { get; }
public ButtonAssignedEventArgs(ToggleButton button, ButtonValue? buttonValue)
public ButtonAssignedEventArgs(ToggleButton button, bool isAssigned)
{
Button = button;
ButtonValue = buttonValue;
IsAssigned = isAssigned;
}
}
@@ -75,11 +78,15 @@ namespace Ryujinx.Ava.UI.Helpers
await Dispatcher.UIThread.InvokeAsync(() =>
{
ButtonValue? pressedButton = assigner.GetPressedButton();
string pressedButton = assigner.GetPressedButton();
if (_shouldUnbind)
{
pressedButton = null;
SetButtonText(ToggledButton, "Unbound");
}
else if (pressedButton != "")
{
SetButtonText(ToggledButton, pressedButton);
}
_shouldUnbind = false;
@@ -87,8 +94,17 @@ namespace Ryujinx.Ava.UI.Helpers
ToggledButton.IsChecked = false;
ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton));
ButtonAssigned?.Invoke(this, new ButtonAssignedEventArgs(ToggledButton, pressedButton != null));
static void SetButtonText(ToggleButton button, string text)
{
ILogical textBlock = button.GetLogicalDescendants().First(x => x is TextBlock);
if (textBlock != null && textBlock is TextBlock block)
{
block.Text = text;
}
}
});
}

View File

@@ -1,9 +1,7 @@
using Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
@@ -12,158 +10,37 @@ namespace Ryujinx.Ava.UI.Helpers
{
public static KeyValueConverter Instance = new();
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
{
{ Key.Unknown, LocaleKeys.KeyUnknown },
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
{ Key.ControlRight, LocaleKeys.KeyControlRight },
{ Key.AltLeft, OperatingSystem.IsMacOS() ? LocaleKeys.KeyOptLeft : LocaleKeys.KeyAltLeft },
{ Key.AltRight, OperatingSystem.IsMacOS() ? LocaleKeys.KeyOptRight : LocaleKeys.KeyAltRight },
{ Key.WinLeft, OperatingSystem.IsMacOS() ? LocaleKeys.KeyCmdLeft : LocaleKeys.KeyWinLeft },
{ Key.WinRight, OperatingSystem.IsMacOS() ? LocaleKeys.KeyCmdRight : LocaleKeys.KeyWinRight },
{ Key.Up, LocaleKeys.KeyUp },
{ Key.Down, LocaleKeys.KeyDown },
{ Key.Left, LocaleKeys.KeyLeft },
{ Key.Right, LocaleKeys.KeyRight },
{ Key.Enter, LocaleKeys.KeyEnter },
{ Key.Escape, LocaleKeys.KeyEscape },
{ Key.Space, LocaleKeys.KeySpace },
{ Key.Tab, LocaleKeys.KeyTab },
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
{ Key.Insert, LocaleKeys.KeyInsert },
{ Key.Delete, LocaleKeys.KeyDelete },
{ Key.PageUp, LocaleKeys.KeyPageUp },
{ Key.PageDown, LocaleKeys.KeyPageDown },
{ Key.Home, LocaleKeys.KeyHome },
{ Key.End, LocaleKeys.KeyEnd },
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
{ Key.Pause, LocaleKeys.KeyPause },
{ Key.NumLock, LocaleKeys.KeyNumLock },
{ Key.Clear, LocaleKeys.KeyClear },
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
{ Key.Number0, LocaleKeys.KeyNumber0 },
{ Key.Number1, LocaleKeys.KeyNumber1 },
{ Key.Number2, LocaleKeys.KeyNumber2 },
{ Key.Number3, LocaleKeys.KeyNumber3 },
{ Key.Number4, LocaleKeys.KeyNumber4 },
{ Key.Number5, LocaleKeys.KeyNumber5 },
{ Key.Number6, LocaleKeys.KeyNumber6 },
{ Key.Number7, LocaleKeys.KeyNumber7 },
{ Key.Number8, LocaleKeys.KeyNumber8 },
{ Key.Number9, LocaleKeys.KeyNumber9 },
{ Key.Tilde, LocaleKeys.KeyTilde },
{ Key.Grave, LocaleKeys.KeyGrave },
{ Key.Minus, LocaleKeys.KeyMinus },
{ Key.Plus, LocaleKeys.KeyPlus },
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
{ Key.Semicolon, LocaleKeys.KeySemicolon },
{ Key.Quote, LocaleKeys.KeyQuote },
{ Key.Comma, LocaleKeys.KeyComma },
{ Key.Period, LocaleKeys.KeyPeriod },
{ Key.Slash, LocaleKeys.KeySlash },
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
{ Key.Unbound, LocaleKeys.KeyUnbound },
};
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
{
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
{ GamepadInputId.RightStick, LocaleKeys.GamepadRightStick },
{ GamepadInputId.LeftShoulder, LocaleKeys.GamepadLeftShoulder },
{ GamepadInputId.RightShoulder, LocaleKeys.GamepadRightShoulder },
{ GamepadInputId.LeftTrigger, LocaleKeys.GamepadLeftTrigger },
{ GamepadInputId.RightTrigger, LocaleKeys.GamepadRightTrigger },
{ GamepadInputId.DpadUp, LocaleKeys.GamepadDpadUp},
{ GamepadInputId.DpadDown, LocaleKeys.GamepadDpadDown},
{ GamepadInputId.DpadLeft, LocaleKeys.GamepadDpadLeft},
{ GamepadInputId.DpadRight, LocaleKeys.GamepadDpadRight},
{ GamepadInputId.Minus, LocaleKeys.GamepadMinus},
{ GamepadInputId.Plus, LocaleKeys.GamepadPlus},
{ GamepadInputId.Guide, LocaleKeys.GamepadGuide},
{ GamepadInputId.Misc1, LocaleKeys.GamepadMisc1},
{ GamepadInputId.Paddle1, LocaleKeys.GamepadPaddle1},
{ GamepadInputId.Paddle2, LocaleKeys.GamepadPaddle2},
{ GamepadInputId.Paddle3, LocaleKeys.GamepadPaddle3},
{ GamepadInputId.Paddle4, LocaleKeys.GamepadPaddle4},
{ GamepadInputId.Touchpad, LocaleKeys.GamepadTouchpad},
{ GamepadInputId.SingleLeftTrigger0, LocaleKeys.GamepadSingleLeftTrigger0},
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
{ GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
};
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
{
{ StickInputId.Left, LocaleKeys.StickLeft},
{ StickInputId.Right, LocaleKeys.StickRight},
{ StickInputId.Unbound, LocaleKeys.KeyUnbound},
};
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string keyString = "";
if (value is Key key)
if (value == null)
{
if (_keysMap.TryGetValue(key, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = key.ToString();
}
}
else if (value is GamepadInputId gamepadInputId)
{
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = gamepadInputId.ToString();
}
}
else if (value is StickInputId stickInputId)
{
if (_stickInputIdMap.TryGetValue(stickInputId, out LocaleKeys localeKey))
{
keyString = LocaleManager.Instance[localeKey];
}
else
{
keyString = stickInputId.ToString();
}
return null;
}
return keyString;
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
object key = null;
if (value != null)
{
if (targetType == typeof(Key))
{
key = Enum.Parse<Key>(value.ToString());
}
else if (targetType == typeof(GamepadInputId))
{
key = Enum.Parse<GamepadInputId>(value.ToString());
}
else if (targetType == typeof(StickInputId))
{
key = Enum.Parse<StickInputId>(value.ToString());
}
}
return key;
}
}
}

View File

@@ -1,580 +0,0 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using System;
namespace Ryujinx.Ava.UI.Models.Input
{
public class ControllerInputConfig : BaseModel
{
public bool EnableCemuHookMotion { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public int Slot { get; set; }
public int AltSlot { get; set; }
public bool MirrorInput { get; set; }
public int Sensitivity { get; set; }
public double GyroDeadzone { get; set; }
public float WeakRumble { get; set; }
public float StrongRumble { get; set; }
public string Id { get; set; }
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
private StickInputId _leftJoystick;
public StickInputId LeftJoystick
{
get => _leftJoystick;
set
{
_leftJoystick = value;
OnPropertyChanged();
}
}
private bool _leftInvertStickX;
public bool LeftInvertStickX
{
get => _leftInvertStickX;
set
{
_leftInvertStickX = value;
OnPropertyChanged();
}
}
private bool _leftInvertStickY;
public bool LeftInvertStickY
{
get => _leftInvertStickY;
set
{
_leftInvertStickY = value;
OnPropertyChanged();
}
}
private bool _leftRotate90;
public bool LeftRotate90
{
get => _leftRotate90;
set
{
_leftRotate90 = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftStickButton;
public GamepadInputId LeftStickButton
{
get => _leftStickButton;
set
{
_leftStickButton = value;
OnPropertyChanged();
}
}
private StickInputId _rightJoystick;
public StickInputId RightJoystick
{
get => _rightJoystick;
set
{
_rightJoystick = value;
OnPropertyChanged();
}
}
private bool _rightInvertStickX;
public bool RightInvertStickX
{
get => _rightInvertStickX;
set
{
_rightInvertStickX = value;
OnPropertyChanged();
}
}
private bool _rightInvertStickY;
public bool RightInvertStickY
{
get => _rightInvertStickY;
set
{
_rightInvertStickY = value;
OnPropertyChanged();
}
}
private bool _rightRotate90;
public bool RightRotate90
{
get => _rightRotate90;
set
{
_rightRotate90 = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightStickButton;
public GamepadInputId RightStickButton
{
get => _rightStickButton;
set
{
_rightStickButton = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadUp;
public GamepadInputId DpadUp
{
get => _dpadUp;
set
{
_dpadUp = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadDown;
public GamepadInputId DpadDown
{
get => _dpadDown;
set
{
_dpadDown = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadLeft;
public GamepadInputId DpadLeft
{
get => _dpadLeft;
set
{
_dpadLeft = value;
OnPropertyChanged();
}
}
private GamepadInputId _dpadRight;
public GamepadInputId DpadRight
{
get => _dpadRight;
set
{
_dpadRight = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonL;
public GamepadInputId ButtonL
{
get => _buttonL;
set
{
_buttonL = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonMinus;
public GamepadInputId ButtonMinus
{
get => _buttonMinus;
set
{
_buttonMinus = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftButtonSl;
public GamepadInputId LeftButtonSl
{
get => _leftButtonSl;
set
{
_leftButtonSl = value;
OnPropertyChanged();
}
}
private GamepadInputId _leftButtonSr;
public GamepadInputId LeftButtonSr
{
get => _leftButtonSr;
set
{
_leftButtonSr = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonZl;
public GamepadInputId ButtonZl
{
get => _buttonZl;
set
{
_buttonZl = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonA;
public GamepadInputId ButtonA
{
get => _buttonA;
set
{
_buttonA = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonB;
public GamepadInputId ButtonB
{
get => _buttonB;
set
{
_buttonB = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonX;
public GamepadInputId ButtonX
{
get => _buttonX;
set
{
_buttonX = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonY;
public GamepadInputId ButtonY
{
get => _buttonY;
set
{
_buttonY = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonR;
public GamepadInputId ButtonR
{
get => _buttonR;
set
{
_buttonR = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonPlus;
public GamepadInputId ButtonPlus
{
get => _buttonPlus;
set
{
_buttonPlus = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightButtonSl;
public GamepadInputId RightButtonSl
{
get => _rightButtonSl;
set
{
_rightButtonSl = value;
OnPropertyChanged();
}
}
private GamepadInputId _rightButtonSr;
public GamepadInputId RightButtonSr
{
get => _rightButtonSr;
set
{
_rightButtonSr = value;
OnPropertyChanged();
}
}
private GamepadInputId _buttonZr;
public GamepadInputId ButtonZr
{
get => _buttonZr;
set
{
_buttonZr = value;
OnPropertyChanged();
}
}
private float _deadzoneLeft;
public float DeadzoneLeft
{
get => _deadzoneLeft;
set
{
_deadzoneLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _deadzoneRight;
public float DeadzoneRight
{
get => _deadzoneRight;
set
{
_deadzoneRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _rangeLeft;
public float RangeLeft
{
get => _rangeLeft;
set
{
_rangeLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _rangeRight;
public float RangeRight
{
get => _rangeRight;
set
{
_rangeRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private float _triggerThreshold;
public float TriggerThreshold
{
get => _triggerThreshold;
set
{
_triggerThreshold = MathF.Round(value, 3);
OnPropertyChanged();
}
}
private bool _enableMotion;
public bool EnableMotion
{
get => _enableMotion;
set
{
_enableMotion = value;
OnPropertyChanged();
}
}
private bool _enableRumble;
public bool EnableRumble
{
get => _enableRumble;
set
{
_enableRumble = value;
OnPropertyChanged();
}
}
public ControllerInputConfig(InputConfig config)
{
if (config != null)
{
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is not StandardControllerInputConfig controllerInput)
{
return;
}
LeftJoystick = controllerInput.LeftJoyconStick.Joystick;
LeftInvertStickX = controllerInput.LeftJoyconStick.InvertStickX;
LeftInvertStickY = controllerInput.LeftJoyconStick.InvertStickY;
LeftRotate90 = controllerInput.LeftJoyconStick.Rotate90CW;
LeftStickButton = controllerInput.LeftJoyconStick.StickButton;
RightJoystick = controllerInput.RightJoyconStick.Joystick;
RightInvertStickX = controllerInput.RightJoyconStick.InvertStickX;
RightInvertStickY = controllerInput.RightJoyconStick.InvertStickY;
RightRotate90 = controllerInput.RightJoyconStick.Rotate90CW;
RightStickButton = controllerInput.RightJoyconStick.StickButton;
DpadUp = controllerInput.LeftJoycon.DpadUp;
DpadDown = controllerInput.LeftJoycon.DpadDown;
DpadLeft = controllerInput.LeftJoycon.DpadLeft;
DpadRight = controllerInput.LeftJoycon.DpadRight;
ButtonL = controllerInput.LeftJoycon.ButtonL;
ButtonMinus = controllerInput.LeftJoycon.ButtonMinus;
LeftButtonSl = controllerInput.LeftJoycon.ButtonSl;
LeftButtonSr = controllerInput.LeftJoycon.ButtonSr;
ButtonZl = controllerInput.LeftJoycon.ButtonZl;
ButtonA = controllerInput.RightJoycon.ButtonA;
ButtonB = controllerInput.RightJoycon.ButtonB;
ButtonX = controllerInput.RightJoycon.ButtonX;
ButtonY = controllerInput.RightJoycon.ButtonY;
ButtonR = controllerInput.RightJoycon.ButtonR;
ButtonPlus = controllerInput.RightJoycon.ButtonPlus;
RightButtonSl = controllerInput.RightJoycon.ButtonSl;
RightButtonSr = controllerInput.RightJoycon.ButtonSr;
ButtonZr = controllerInput.RightJoycon.ButtonZr;
DeadzoneLeft = controllerInput.DeadzoneLeft;
DeadzoneRight = controllerInput.DeadzoneRight;
RangeLeft = controllerInput.RangeLeft;
RangeRight = controllerInput.RangeRight;
TriggerThreshold = controllerInput.TriggerThreshold;
if (controllerInput.Motion != null)
{
EnableMotion = controllerInput.Motion.EnableMotion;
GyroDeadzone = controllerInput.Motion.GyroDeadzone;
Sensitivity = controllerInput.Motion.Sensitivity;
if (controllerInput.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
DsuServerHost = cemuHook.DsuServerHost;
DsuServerPort = cemuHook.DsuServerPort;
Slot = cemuHook.Slot;
AltSlot = cemuHook.AltSlot;
MirrorInput = cemuHook.MirrorInput;
}
}
if (controllerInput.Rumble != null)
{
EnableRumble = controllerInput.Rumble.EnableRumble;
WeakRumble = controllerInput.Rumble.WeakRumble;
StrongRumble = controllerInput.Rumble.StrongRumble;
}
}
}
public InputConfig GetConfig()
{
var config = new StandardControllerInputConfig
{
Id = Id,
Backend = InputBackendType.GamepadSDL2,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
{
DpadUp = DpadUp,
DpadDown = DpadDown,
DpadLeft = DpadLeft,
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr,
ButtonZl = ButtonZl
},
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
{
ButtonA = ButtonA,
ButtonB = ButtonB,
ButtonX = ButtonX,
ButtonY = ButtonY,
ButtonPlus = ButtonPlus,
ButtonSl = RightButtonSl,
ButtonSr = RightButtonSr,
ButtonR = ButtonR,
ButtonZr = ButtonZr
},
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = LeftJoystick,
InvertStickX = LeftInvertStickX,
InvertStickY = LeftInvertStickY,
Rotate90CW = LeftRotate90,
StickButton = LeftStickButton
},
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = RightJoystick,
InvertStickX = RightInvertStickX,
InvertStickY = RightInvertStickY,
Rotate90CW = RightRotate90,
StickButton = RightStickButton
},
Rumble = new RumbleConfigController
{
EnableRumble = EnableRumble,
WeakRumble = WeakRumble,
StrongRumble = StrongRumble
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,
RangeLeft = RangeLeft,
RangeRight = RangeRight,
TriggerThreshold = TriggerThreshold
};
if (EnableCemuHookMotion)
{
config.Motion = new CemuHookMotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.CemuHook,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity,
DsuServerHost = DsuServerHost,
DsuServerPort = DsuServerPort,
Slot = Slot,
AltSlot = AltSlot,
MirrorInput = MirrorInput
};
}
else
{
config.Motion = new MotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.GamepadDriver,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity
};
}
return config;
}
}
}

View File

@@ -1,422 +0,0 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard;
namespace Ryujinx.Ava.UI.Models.Input
{
public class KeyboardInputConfig : BaseModel
{
public string Id { get; set; }
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
private Key _leftStickUp;
public Key LeftStickUp
{
get => _leftStickUp;
set
{
_leftStickUp = value;
OnPropertyChanged();
}
}
private Key _leftStickDown;
public Key LeftStickDown
{
get => _leftStickDown;
set
{
_leftStickDown = value;
OnPropertyChanged();
}
}
private Key _leftStickLeft;
public Key LeftStickLeft
{
get => _leftStickLeft;
set
{
_leftStickLeft = value;
OnPropertyChanged();
}
}
private Key _leftStickRight;
public Key LeftStickRight
{
get => _leftStickRight;
set
{
_leftStickRight = value;
OnPropertyChanged();
}
}
private Key _leftStickButton;
public Key LeftStickButton
{
get => _leftStickButton;
set
{
_leftStickButton = value;
OnPropertyChanged();
}
}
private Key _rightStickUp;
public Key RightStickUp
{
get => _rightStickUp;
set
{
_rightStickUp = value;
OnPropertyChanged();
}
}
private Key _rightStickDown;
public Key RightStickDown
{
get => _rightStickDown;
set
{
_rightStickDown = value;
OnPropertyChanged();
}
}
private Key _rightStickLeft;
public Key RightStickLeft
{
get => _rightStickLeft;
set
{
_rightStickLeft = value;
OnPropertyChanged();
}
}
private Key _rightStickRight;
public Key RightStickRight
{
get => _rightStickRight;
set
{
_rightStickRight = value;
OnPropertyChanged();
}
}
private Key _rightStickButton;
public Key RightStickButton
{
get => _rightStickButton;
set
{
_rightStickButton = value;
OnPropertyChanged();
}
}
private Key _dpadUp;
public Key DpadUp
{
get => _dpadUp;
set
{
_dpadUp = value;
OnPropertyChanged();
}
}
private Key _dpadDown;
public Key DpadDown
{
get => _dpadDown;
set
{
_dpadDown = value;
OnPropertyChanged();
}
}
private Key _dpadLeft;
public Key DpadLeft
{
get => _dpadLeft;
set
{
_dpadLeft = value;
OnPropertyChanged();
}
}
private Key _dpadRight;
public Key DpadRight
{
get => _dpadRight;
set
{
_dpadRight = value;
OnPropertyChanged();
}
}
private Key _buttonL;
public Key ButtonL
{
get => _buttonL;
set
{
_buttonL = value;
OnPropertyChanged();
}
}
private Key _buttonMinus;
public Key ButtonMinus
{
get => _buttonMinus;
set
{
_buttonMinus = value;
OnPropertyChanged();
}
}
private Key _leftButtonSl;
public Key LeftButtonSl
{
get => _leftButtonSl;
set
{
_leftButtonSl = value;
OnPropertyChanged();
}
}
private Key _leftButtonSr;
public Key LeftButtonSr
{
get => _leftButtonSr;
set
{
_leftButtonSr = value;
OnPropertyChanged();
}
}
private Key _buttonZl;
public Key ButtonZl
{
get => _buttonZl;
set
{
_buttonZl = value;
OnPropertyChanged();
}
}
private Key _buttonA;
public Key ButtonA
{
get => _buttonA;
set
{
_buttonA = value;
OnPropertyChanged();
}
}
private Key _buttonB;
public Key ButtonB
{
get => _buttonB;
set
{
_buttonB = value;
OnPropertyChanged();
}
}
private Key _buttonX;
public Key ButtonX
{
get => _buttonX;
set
{
_buttonX = value;
OnPropertyChanged();
}
}
private Key _buttonY;
public Key ButtonY
{
get => _buttonY;
set
{
_buttonY = value;
OnPropertyChanged();
}
}
private Key _buttonR;
public Key ButtonR
{
get => _buttonR;
set
{
_buttonR = value;
OnPropertyChanged();
}
}
private Key _buttonPlus;
public Key ButtonPlus
{
get => _buttonPlus;
set
{
_buttonPlus = value;
OnPropertyChanged();
}
}
private Key _rightButtonSl;
public Key RightButtonSl
{
get => _rightButtonSl;
set
{
_rightButtonSl = value;
OnPropertyChanged();
}
}
private Key _rightButtonSr;
public Key RightButtonSr
{
get => _rightButtonSr;
set
{
_rightButtonSr = value;
OnPropertyChanged();
}
}
private Key _buttonZr;
public Key ButtonZr
{
get => _buttonZr;
set
{
_buttonZr = value;
OnPropertyChanged();
}
}
public KeyboardInputConfig(InputConfig config)
{
if (config != null)
{
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is not StandardKeyboardInputConfig keyboardConfig)
{
return;
}
LeftStickUp = keyboardConfig.LeftJoyconStick.StickUp;
LeftStickDown = keyboardConfig.LeftJoyconStick.StickDown;
LeftStickLeft = keyboardConfig.LeftJoyconStick.StickLeft;
LeftStickRight = keyboardConfig.LeftJoyconStick.StickRight;
LeftStickButton = keyboardConfig.LeftJoyconStick.StickButton;
RightStickUp = keyboardConfig.RightJoyconStick.StickUp;
RightStickDown = keyboardConfig.RightJoyconStick.StickDown;
RightStickLeft = keyboardConfig.RightJoyconStick.StickLeft;
RightStickRight = keyboardConfig.RightJoyconStick.StickRight;
RightStickButton = keyboardConfig.RightJoyconStick.StickButton;
DpadUp = keyboardConfig.LeftJoycon.DpadUp;
DpadDown = keyboardConfig.LeftJoycon.DpadDown;
DpadLeft = keyboardConfig.LeftJoycon.DpadLeft;
DpadRight = keyboardConfig.LeftJoycon.DpadRight;
ButtonL = keyboardConfig.LeftJoycon.ButtonL;
ButtonMinus = keyboardConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = keyboardConfig.LeftJoycon.ButtonSl;
LeftButtonSr = keyboardConfig.LeftJoycon.ButtonSr;
ButtonZl = keyboardConfig.LeftJoycon.ButtonZl;
ButtonA = keyboardConfig.RightJoycon.ButtonA;
ButtonB = keyboardConfig.RightJoycon.ButtonB;
ButtonX = keyboardConfig.RightJoycon.ButtonX;
ButtonY = keyboardConfig.RightJoycon.ButtonY;
ButtonR = keyboardConfig.RightJoycon.ButtonR;
ButtonPlus = keyboardConfig.RightJoycon.ButtonPlus;
RightButtonSl = keyboardConfig.RightJoycon.ButtonSl;
RightButtonSr = keyboardConfig.RightJoycon.ButtonSr;
ButtonZr = keyboardConfig.RightJoycon.ButtonZr;
}
}
public InputConfig GetConfig()
{
var config = new StandardKeyboardInputConfig
{
Id = Id,
Backend = InputBackendType.WindowKeyboard,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = DpadUp,
DpadDown = DpadDown,
DpadLeft = DpadLeft,
DpadRight = DpadRight,
ButtonL = ButtonL,
ButtonMinus = ButtonMinus,
ButtonZl = ButtonZl,
ButtonSl = LeftButtonSl,
ButtonSr = LeftButtonSr
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = ButtonA,
ButtonB = ButtonB,
ButtonX = ButtonX,
ButtonY = ButtonY,
ButtonPlus = ButtonPlus,
ButtonSl = RightButtonSl,
ButtonSr = RightButtonSr,
ButtonR = ButtonR,
ButtonZr = ButtonZr
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = LeftStickUp,
StickDown = LeftStickDown,
StickRight = LeftStickRight,
StickLeft = LeftStickLeft,
StickButton = LeftStickButton
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = RightStickUp,
StickDown = RightStickDown,
StickLeft = RightStickLeft,
StickRight = RightStickRight,
StickButton = RightStickButton
},
Version = InputConfig.CurrentVersion
};
return config;
}
}
}

View File

@@ -0,0 +1,456 @@
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System;
namespace Ryujinx.Ava.UI.Models
{
internal class InputConfiguration<TKey, TStick> : BaseModel
{
private float _deadzoneRight;
private float _triggerThreshold;
private float _deadzoneLeft;
private double _gyroDeadzone;
private int _sensitivity;
private bool _enableMotion;
private float _weakRumble;
private float _strongRumble;
private float _rangeLeft;
private float _rangeRight;
public InputBackendType Backend { get; set; }
/// <summary>
/// Controller id
/// </summary>
public string Id { get; set; }
/// <summary>
/// Controller's Type
/// </summary>
public ControllerType ControllerType { get; set; }
/// <summary>
/// Player's Index for the controller
/// </summary>
public PlayerIndex PlayerIndex { get; set; }
public TStick LeftJoystick { get; set; }
public bool LeftInvertStickX { get; set; }
public bool LeftInvertStickY { get; set; }
public bool RightRotate90 { get; set; }
public TKey LeftControllerStickButton { get; set; }
public TStick RightJoystick { get; set; }
public bool RightInvertStickX { get; set; }
public bool RightInvertStickY { get; set; }
public bool LeftRotate90 { get; set; }
public TKey RightControllerStickButton { get; set; }
public float DeadzoneLeft
{
get => _deadzoneLeft;
set
{
_deadzoneLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float RangeLeft
{
get => _rangeLeft;
set
{
_rangeLeft = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float DeadzoneRight
{
get => _deadzoneRight;
set
{
_deadzoneRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float RangeRight
{
get => _rangeRight;
set
{
_rangeRight = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public float TriggerThreshold
{
get => _triggerThreshold;
set
{
_triggerThreshold = MathF.Round(value, 3);
OnPropertyChanged();
}
}
public MotionInputBackendType MotionBackend { get; set; }
public TKey ButtonMinus { get; set; }
public TKey ButtonL { get; set; }
public TKey ButtonZl { get; set; }
public TKey LeftButtonSl { get; set; }
public TKey LeftButtonSr { get; set; }
public TKey DpadUp { get; set; }
public TKey DpadDown { get; set; }
public TKey DpadLeft { get; set; }
public TKey DpadRight { get; set; }
public TKey ButtonPlus { get; set; }
public TKey ButtonR { get; set; }
public TKey ButtonZr { get; set; }
public TKey RightButtonSl { get; set; }
public TKey RightButtonSr { get; set; }
public TKey ButtonX { get; set; }
public TKey ButtonB { get; set; }
public TKey ButtonY { get; set; }
public TKey ButtonA { get; set; }
public TKey LeftStickUp { get; set; }
public TKey LeftStickDown { get; set; }
public TKey LeftStickLeft { get; set; }
public TKey LeftStickRight { get; set; }
public TKey LeftKeyboardStickButton { get; set; }
public TKey RightStickUp { get; set; }
public TKey RightStickDown { get; set; }
public TKey RightStickLeft { get; set; }
public TKey RightStickRight { get; set; }
public TKey RightKeyboardStickButton { get; set; }
public int Sensitivity
{
get => _sensitivity;
set
{
_sensitivity = value;
OnPropertyChanged();
}
}
public double GyroDeadzone
{
get => _gyroDeadzone;
set
{
_gyroDeadzone = Math.Round(value, 3);
OnPropertyChanged();
}
}
public bool EnableMotion
{
get => _enableMotion; set
{
_enableMotion = value;
OnPropertyChanged();
}
}
public bool EnableCemuHookMotion { get; set; }
public int Slot { get; set; }
public int AltSlot { get; set; }
public bool MirrorInput { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public bool EnableRumble { get; set; }
public float WeakRumble
{
get => _weakRumble; set
{
_weakRumble = value;
OnPropertyChanged();
}
}
public float StrongRumble
{
get => _strongRumble; set
{
_strongRumble = value;
OnPropertyChanged();
}
}
public InputConfiguration(InputConfig config)
{
if (config != null)
{
Backend = config.Backend;
Id = config.Id;
ControllerType = config.ControllerType;
PlayerIndex = config.PlayerIndex;
if (config is StandardKeyboardInputConfig keyboardConfig)
{
LeftStickUp = (TKey)(object)keyboardConfig.LeftJoyconStick.StickUp;
LeftStickDown = (TKey)(object)keyboardConfig.LeftJoyconStick.StickDown;
LeftStickLeft = (TKey)(object)keyboardConfig.LeftJoyconStick.StickLeft;
LeftStickRight = (TKey)(object)keyboardConfig.LeftJoyconStick.StickRight;
LeftKeyboardStickButton = (TKey)(object)keyboardConfig.LeftJoyconStick.StickButton;
RightStickUp = (TKey)(object)keyboardConfig.RightJoyconStick.StickUp;
RightStickDown = (TKey)(object)keyboardConfig.RightJoyconStick.StickDown;
RightStickLeft = (TKey)(object)keyboardConfig.RightJoyconStick.StickLeft;
RightStickRight = (TKey)(object)keyboardConfig.RightJoyconStick.StickRight;
RightKeyboardStickButton = (TKey)(object)keyboardConfig.RightJoyconStick.StickButton;
ButtonA = (TKey)(object)keyboardConfig.RightJoycon.ButtonA;
ButtonB = (TKey)(object)keyboardConfig.RightJoycon.ButtonB;
ButtonX = (TKey)(object)keyboardConfig.RightJoycon.ButtonX;
ButtonY = (TKey)(object)keyboardConfig.RightJoycon.ButtonY;
ButtonR = (TKey)(object)keyboardConfig.RightJoycon.ButtonR;
RightButtonSl = (TKey)(object)keyboardConfig.RightJoycon.ButtonSl;
RightButtonSr = (TKey)(object)keyboardConfig.RightJoycon.ButtonSr;
ButtonZr = (TKey)(object)keyboardConfig.RightJoycon.ButtonZr;
ButtonPlus = (TKey)(object)keyboardConfig.RightJoycon.ButtonPlus;
DpadUp = (TKey)(object)keyboardConfig.LeftJoycon.DpadUp;
DpadDown = (TKey)(object)keyboardConfig.LeftJoycon.DpadDown;
DpadLeft = (TKey)(object)keyboardConfig.LeftJoycon.DpadLeft;
DpadRight = (TKey)(object)keyboardConfig.LeftJoycon.DpadRight;
ButtonMinus = (TKey)(object)keyboardConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSl;
LeftButtonSr = (TKey)(object)keyboardConfig.LeftJoycon.ButtonSr;
ButtonZl = (TKey)(object)keyboardConfig.LeftJoycon.ButtonZl;
ButtonL = (TKey)(object)keyboardConfig.LeftJoycon.ButtonL;
}
else if (config is StandardControllerInputConfig controllerConfig)
{
LeftJoystick = (TStick)(object)controllerConfig.LeftJoyconStick.Joystick;
LeftInvertStickX = controllerConfig.LeftJoyconStick.InvertStickX;
LeftInvertStickY = controllerConfig.LeftJoyconStick.InvertStickY;
LeftRotate90 = controllerConfig.LeftJoyconStick.Rotate90CW;
LeftControllerStickButton = (TKey)(object)controllerConfig.LeftJoyconStick.StickButton;
RightJoystick = (TStick)(object)controllerConfig.RightJoyconStick.Joystick;
RightInvertStickX = controllerConfig.RightJoyconStick.InvertStickX;
RightInvertStickY = controllerConfig.RightJoyconStick.InvertStickY;
RightRotate90 = controllerConfig.RightJoyconStick.Rotate90CW;
RightControllerStickButton = (TKey)(object)controllerConfig.RightJoyconStick.StickButton;
ButtonA = (TKey)(object)controllerConfig.RightJoycon.ButtonA;
ButtonB = (TKey)(object)controllerConfig.RightJoycon.ButtonB;
ButtonX = (TKey)(object)controllerConfig.RightJoycon.ButtonX;
ButtonY = (TKey)(object)controllerConfig.RightJoycon.ButtonY;
ButtonR = (TKey)(object)controllerConfig.RightJoycon.ButtonR;
RightButtonSl = (TKey)(object)controllerConfig.RightJoycon.ButtonSl;
RightButtonSr = (TKey)(object)controllerConfig.RightJoycon.ButtonSr;
ButtonZr = (TKey)(object)controllerConfig.RightJoycon.ButtonZr;
ButtonPlus = (TKey)(object)controllerConfig.RightJoycon.ButtonPlus;
DpadUp = (TKey)(object)controllerConfig.LeftJoycon.DpadUp;
DpadDown = (TKey)(object)controllerConfig.LeftJoycon.DpadDown;
DpadLeft = (TKey)(object)controllerConfig.LeftJoycon.DpadLeft;
DpadRight = (TKey)(object)controllerConfig.LeftJoycon.DpadRight;
ButtonMinus = (TKey)(object)controllerConfig.LeftJoycon.ButtonMinus;
LeftButtonSl = (TKey)(object)controllerConfig.LeftJoycon.ButtonSl;
LeftButtonSr = (TKey)(object)controllerConfig.LeftJoycon.ButtonSr;
ButtonZl = (TKey)(object)controllerConfig.LeftJoycon.ButtonZl;
ButtonL = (TKey)(object)controllerConfig.LeftJoycon.ButtonL;
DeadzoneLeft = controllerConfig.DeadzoneLeft;
DeadzoneRight = controllerConfig.DeadzoneRight;
RangeLeft = controllerConfig.RangeLeft;
RangeRight = controllerConfig.RangeRight;
TriggerThreshold = controllerConfig.TriggerThreshold;
if (controllerConfig.Motion != null)
{
EnableMotion = controllerConfig.Motion.EnableMotion;
MotionBackend = controllerConfig.Motion.MotionBackend;
GyroDeadzone = controllerConfig.Motion.GyroDeadzone;
Sensitivity = controllerConfig.Motion.Sensitivity;
if (controllerConfig.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
DsuServerHost = cemuHook.DsuServerHost;
DsuServerPort = cemuHook.DsuServerPort;
Slot = cemuHook.Slot;
AltSlot = cemuHook.AltSlot;
MirrorInput = cemuHook.MirrorInput;
}
if (controllerConfig.Rumble != null)
{
EnableRumble = controllerConfig.Rumble.EnableRumble;
WeakRumble = controllerConfig.Rumble.WeakRumble;
StrongRumble = controllerConfig.Rumble.StrongRumble;
}
}
}
}
}
public InputConfiguration()
{
}
public InputConfig GetConfig()
{
if (Backend == InputBackendType.WindowKeyboard)
{
return new StandardKeyboardInputConfig
{
Id = Id,
Backend = Backend,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = (Key)(object)DpadUp,
DpadDown = (Key)(object)DpadDown,
DpadLeft = (Key)(object)DpadLeft,
DpadRight = (Key)(object)DpadRight,
ButtonL = (Key)(object)ButtonL,
ButtonZl = (Key)(object)ButtonZl,
ButtonSl = (Key)(object)LeftButtonSl,
ButtonSr = (Key)(object)LeftButtonSr,
ButtonMinus = (Key)(object)ButtonMinus,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = (Key)(object)ButtonA,
ButtonB = (Key)(object)ButtonB,
ButtonX = (Key)(object)ButtonX,
ButtonY = (Key)(object)ButtonY,
ButtonPlus = (Key)(object)ButtonPlus,
ButtonSl = (Key)(object)RightButtonSl,
ButtonSr = (Key)(object)RightButtonSr,
ButtonR = (Key)(object)ButtonR,
ButtonZr = (Key)(object)ButtonZr,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = (Key)(object)LeftStickUp,
StickDown = (Key)(object)LeftStickDown,
StickRight = (Key)(object)LeftStickRight,
StickLeft = (Key)(object)LeftStickLeft,
StickButton = (Key)(object)LeftKeyboardStickButton,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = (Key)(object)RightStickUp,
StickDown = (Key)(object)RightStickDown,
StickLeft = (Key)(object)RightStickLeft,
StickRight = (Key)(object)RightStickRight,
StickButton = (Key)(object)RightKeyboardStickButton,
},
Version = InputConfig.CurrentVersion,
};
}
if (Backend == InputBackendType.GamepadSDL2)
{
var config = new StandardControllerInputConfig
{
Id = Id,
Backend = Backend,
PlayerIndex = PlayerIndex,
ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
{
DpadUp = (GamepadInputId)(object)DpadUp,
DpadDown = (GamepadInputId)(object)DpadDown,
DpadLeft = (GamepadInputId)(object)DpadLeft,
DpadRight = (GamepadInputId)(object)DpadRight,
ButtonL = (GamepadInputId)(object)ButtonL,
ButtonZl = (GamepadInputId)(object)ButtonZl,
ButtonSl = (GamepadInputId)(object)LeftButtonSl,
ButtonSr = (GamepadInputId)(object)LeftButtonSr,
ButtonMinus = (GamepadInputId)(object)ButtonMinus,
},
RightJoycon = new RightJoyconCommonConfig<GamepadInputId>
{
ButtonA = (GamepadInputId)(object)ButtonA,
ButtonB = (GamepadInputId)(object)ButtonB,
ButtonX = (GamepadInputId)(object)ButtonX,
ButtonY = (GamepadInputId)(object)ButtonY,
ButtonPlus = (GamepadInputId)(object)ButtonPlus,
ButtonSl = (GamepadInputId)(object)RightButtonSl,
ButtonSr = (GamepadInputId)(object)RightButtonSr,
ButtonR = (GamepadInputId)(object)ButtonR,
ButtonZr = (GamepadInputId)(object)ButtonZr,
},
LeftJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = (StickInputId)(object)LeftJoystick,
InvertStickX = LeftInvertStickX,
InvertStickY = LeftInvertStickY,
Rotate90CW = LeftRotate90,
StickButton = (GamepadInputId)(object)LeftControllerStickButton,
},
RightJoyconStick = new JoyconConfigControllerStick<GamepadInputId, StickInputId>
{
Joystick = (StickInputId)(object)RightJoystick,
InvertStickX = RightInvertStickX,
InvertStickY = RightInvertStickY,
Rotate90CW = RightRotate90,
StickButton = (GamepadInputId)(object)RightControllerStickButton,
},
Rumble = new RumbleConfigController
{
EnableRumble = EnableRumble,
WeakRumble = WeakRumble,
StrongRumble = StrongRumble,
},
Version = InputConfig.CurrentVersion,
DeadzoneLeft = DeadzoneLeft,
DeadzoneRight = DeadzoneRight,
RangeLeft = RangeLeft,
RangeRight = RangeRight,
TriggerThreshold = TriggerThreshold,
Motion = EnableCemuHookMotion
? new CemuHookMotionConfigController
{
DsuServerHost = DsuServerHost,
DsuServerPort = DsuServerPort,
Slot = Slot,
AltSlot = AltSlot,
MirrorInput = MirrorInput,
MotionBackend = MotionInputBackendType.CemuHook,
}
: new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
},
};
config.Motion.Sensitivity = Sensitivity;
config.Motion.EnableMotion = EnableMotion;
config.Motion.GyroDeadzone = GyroDeadzone;
return config;
}
return null;
}
}
}

View File

@@ -8,7 +8,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -30,9 +30,9 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Ava.UI.ViewModels.Input
namespace Ryujinx.Ava.UI.ViewModels
{
public class InputViewModel : BaseModel, IDisposable
public class ControllerInputViewModel : BaseModel, IDisposable
{
private const string Disabled = "disabled";
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
@@ -48,7 +48,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private int _controllerNumber;
private string _controllerImage;
private int _device;
private object _configViewModel;
private object _configuration;
private string _profileName;
private bool _isLoaded;
@@ -71,14 +71,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsLeft { get; set; }
public bool IsModified { get; set; }
public event Action NotifyChangesEvent;
public object ConfigViewModel
public object Configuration
{
get => _configViewModel;
get => _configuration;
set
{
_configViewModel = value;
_configuration = value;
OnPropertyChanged();
}
@@ -233,7 +232,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public InputConfig Config { get; set; }
public InputViewModel(UserControl owner) : this()
public ControllerInputViewModel(UserControl owner) : this()
{
if (Program.PreviewerDetached)
{
@@ -245,6 +244,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false;
@@ -255,7 +255,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
public InputViewModel()
public ControllerInputViewModel()
{
PlayerIndexes = new ObservableCollection<PlayerModel>();
Controllers = new ObservableCollection<ControllerModel>();
@@ -282,12 +282,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (Config is StandardKeyboardInputConfig keyboardInputConfig)
{
ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig));
Configuration = new InputConfiguration<Key, ConfigStickInputId>(keyboardInputConfig);
}
if (Config is StandardControllerInputConfig controllerInputConfig)
{
ConfigViewModel = new ControllerInputViewModel(this, new ControllerInputConfig(controllerInputConfig));
Configuration = new InputConfiguration<ConfigGamepadInputId, ConfigStickInputId>(controllerInputConfig);
}
}
@@ -323,6 +323,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
public async void ShowMotionConfig()
{
await MotionInputView.Show(this);
}
public async void ShowRumbleConfig()
{
await RumbleInputView.Show(this);
}
private void LoadInputDriver()
{
if (_device < 0)
@@ -730,7 +740,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
return;
}
if (ConfigViewModel == null)
if (Configuration == null)
{
return;
}
@@ -741,37 +751,35 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
return;
}
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
}
else if (IsController)
{
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
}
else if (IsController)
{
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
}
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
}
}
@@ -822,18 +830,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (device.Type == DeviceType.Keyboard)
{
var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
var inputConfig = Configuration as InputConfiguration<Key, ConfigStickInputId>;
inputConfig.Id = device.Id;
}
else
{
var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
var inputConfig = Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>;
inputConfig.Id = device.Id.Split(" ")[0];
}
var config = !IsController
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
? (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig()
: (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
config.ControllerType = Controllers[_controller].Type;
config.PlayerIndex = _playerId;
@@ -864,13 +872,12 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void NotifyChanges()
{
OnPropertyChanged(nameof(ConfigViewModel));
OnPropertyChanged(nameof(Configuration));
OnPropertyChanged(nameof(IsController));
OnPropertyChanged(nameof(ShowSettings));
OnPropertyChanged(nameof(IsKeyboard));
OnPropertyChanged(nameof(IsRight));
OnPropertyChanged(nameof(IsLeft));
NotifyChangesEvent?.Invoke();
}
public void Dispose()

View File

@@ -126,7 +126,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem);
@@ -232,7 +233,8 @@ namespace Ryujinx.Ava.UI.ViewModels
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);

View File

@@ -1,84 +0,0 @@
using Avalonia.Svg.Skia;
using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class ControllerInputViewModel : BaseModel
{
private ControllerInputConfig _config;
public ControllerInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
{
get => _isLeft;
set
{
_isLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
private bool _isRight;
public bool IsRight
{
get => _isRight;
set
{
_isRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public InputViewModel parentModel;
public ControllerInputViewModel(InputViewModel model, ControllerInputConfig config)
{
parentModel = model;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;
}
public async void ShowMotionConfig()
{
await MotionInputView.Show(this);
}
public async void ShowRumbleConfig()
{
await RumbleInputView.Show(this);
}
public void OnParentModelChanged()
{
IsLeft = parentModel.IsLeft;
IsRight = parentModel.IsRight;
Image = parentModel.Image;
}
}
}

View File

@@ -1,73 +0,0 @@
using Avalonia.Svg.Skia;
using Ryujinx.Ava.UI.Models.Input;
namespace Ryujinx.Ava.UI.ViewModels.Input
{
public class KeyboardInputViewModel : BaseModel
{
private KeyboardInputConfig _config;
public KeyboardInputConfig Config
{
get => _config;
set
{
_config = value;
OnPropertyChanged();
}
}
private bool _isLeft;
public bool IsLeft
{
get => _isLeft;
set
{
_isLeft = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
private bool _isRight;
public bool IsRight
{
get => _isRight;
set
{
_isRight = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSides));
}
}
public bool HasSides => IsLeft ^ IsRight;
private SvgImage _image;
public SvgImage Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public InputViewModel parentModel;
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config)
{
parentModel = model;
model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged();
Config = config;
}
public void OnParentModelChanged()
{
IsLeft = parentModel.IsLeft;
IsRight = parentModel.IsRight;
Image = parentModel.Image;
}
}
}

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.Ava.UI.ViewModels.Input
namespace Ryujinx.Ava.UI.ViewModels
{
public class MotionInputViewModel : BaseModel
{

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.Ava.UI.ViewModels.Input
namespace Ryujinx.Ava.UI.ViewModels
{
public class RumbleInputViewModel : BaseModel
{

View File

@@ -170,7 +170,9 @@ namespace Ryujinx.Ava.UI.ViewModels
try
{
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
var pfs = new PartitionFileSystem();
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{

View File

@@ -1,11 +1,13 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
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:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
@@ -13,7 +15,6 @@
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.ControllerInputView"
x:DataType="viewModels:ControllerInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
@@ -33,10 +34,191 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<StackPanel
Margin="0 0 0 5"
Orientation="Vertical"
Spacing="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Player Selection -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsPlayer}" />
<ComboBox
Grid.Column="1"
Name="PlayerIndexBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
ItemsSource="{Binding PlayerIndexes}"
SelectedIndex="{Binding PlayerId}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!-- Profile Selection -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" />
<ui:FAComboBox
Grid.Column="1"
IsEditable="True"
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedIndex="0"
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
Command="{ReflectionBinding LoadProfile}">
<ui:SymbolIcon
Symbol="Upload"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="3"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
Command="{ReflectionBinding SaveProfile}">
<ui:SymbolIcon
Symbol="Save"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="4"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
Command="{ReflectionBinding RemoveProfile}">
<ui:SymbolIcon
Symbol="Delete"
FontSize="15"
Height="20" />
</Button>
</Grid>
</Grid>
<Separator />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Input Device -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsInputDevice}" />
<ComboBox
Grid.Column="1"
Name="DeviceBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}"
SelectedIndex="{Binding Device}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
Command="{ReflectionBinding LoadDevices}">
<ui:SymbolIcon
Symbol="Refresh"
FontSize="15"
Height="20"/>
</Button>
</Grid>
<!-- Controller Type -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsControllerType}" />
<ComboBox
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Controllers}"
SelectedIndex="{Binding Controller}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="models:ControllerModel">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</StackPanel>
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450">
MinHeight="450"
IsVisible="{Binding ShowSettings}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
@@ -75,9 +257,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZl">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonZl, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonZl, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -91,9 +273,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonL">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonL, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonL, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -107,9 +289,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonMinus">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonMinus, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonMinus, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -129,8 +311,100 @@
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Keyboard -->
<StackPanel
IsVisible="{Binding !IsController}"
Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.LeftStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
<!-- Left Joystick Controller -->
<StackPanel Orientation="Vertical">
<StackPanel
IsVisible="{Binding IsController}"
Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Orientation="Horizontal">
@@ -141,9 +415,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickButton">
<ToggleButton>
<TextBlock
Text="{Binding Config.LeftStickButton, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.LeftControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -158,22 +432,22 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" />
<ToggleButton Name="LeftJoystick" Tag="stick">
<ToggleButton Tag="stick">
<TextBlock
Text="{Binding Config.LeftJoystick, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.LeftJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<Separator
Margin="0,8,0,8"
Height="1" />
<CheckBox IsChecked="{Binding Config.LeftInvertStickX}">
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox>
<CheckBox IsChecked="{Binding Config.LeftInvertStickY}">
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox>
<CheckBox IsChecked="{Binding Config.LeftRotate90}">
<CheckBox IsChecked="{ReflectionBinding Configuration.LeftRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox>
<Separator
@@ -194,11 +468,11 @@
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{Binding Config.DeadzoneLeft, Mode=TwoWay}" />
Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{Binding Config.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
Text="{ReflectionBinding Configuration.DeadzoneLeft, StringFormat=\{0:0.00\}}" />
</StackPanel>
<TextBlock
HorizontalAlignment="Center"
@@ -214,11 +488,11 @@
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{Binding Config.RangeLeft, Mode=TwoWay}" />
Value="{ReflectionBinding Configuration.RangeLeft, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{Binding Config.RangeLeft, StringFormat=\{0:0.00\}}" />
Text="{ReflectionBinding Configuration.RangeLeft, StringFormat=\{0:0.00\}}" />
</StackPanel>
</StackPanel>
</StackPanel>
@@ -251,9 +525,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" />
<ToggleButton Name="DpadUp">
<ToggleButton>
<TextBlock
Text="{Binding Config.DpadUp, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.DpadUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -268,9 +542,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" />
<ToggleButton Name="DpadDown">
<ToggleButton>
<TextBlock
Text="{Binding Config.DpadDown, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.DpadDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -285,9 +559,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" />
<ToggleButton Name="DpadLeft">
<ToggleButton>
<TextBlock
Text="{Binding Config.DpadLeft, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.DpadLeft, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -302,9 +576,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" />
<ToggleButton Name="DpadRight">
<ToggleButton>
<TextBlock
Text="{Binding Config.DpadRight, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.DpadRight, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -317,13 +591,6 @@
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
@@ -345,89 +612,92 @@
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{Binding Config.TriggerThreshold, Mode=TwoWay}" />
Value="{ReflectionBinding Configuration.TriggerThreshold, Mode=TwoWay}" />
<TextBlock
Width="25"
Text="{Binding Config.TriggerThreshold, StringFormat=\{0:0.00\}}" />
Text="{ReflectionBinding Configuration.TriggerThreshold, StringFormat=\{0:0.00\}}" />
</StackPanel>
<StackPanel
Orientation="Vertical"
IsVisible="{Binding HasSides}">
<StackPanel
Margin="0,4,0,0"
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
Text="{ReflectionBinding Configuration.LeftButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSr">
<TextBlock
Text="{Binding Config.LeftButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
Text="{ReflectionBinding Configuration.LeftButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSl">
<TextBlock
Text="{Binding Config.LeftButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
Text="{ReflectionBinding Configuration.RightButtonSr, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSr">
<TextBlock
Text="{Binding Config.RightButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding !IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
Text="{ReflectionBinding Configuration.RightButtonSl, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSl">
<TextBlock
Text="{Binding Config.RightButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
<!-- Controller Picture -->
<Image
Margin="0,10,0,0"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<!-- Motion + Rumble -->
<StackPanel
Margin="0,10,0,0"
@@ -439,7 +709,8 @@
BorderThickness="1"
CornerRadius="5"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch">
HorizontalAlignment="Stretch"
IsVisible="{Binding IsController}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@@ -449,7 +720,7 @@
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}">
IsChecked="{ReflectionBinding Configuration.EnableMotion, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsMotion}" />
</CheckBox>
<Button
@@ -465,6 +736,7 @@
BorderThickness="1"
CornerRadius="5"
HorizontalAlignment="Stretch"
IsVisible="{Binding IsController}"
Margin="0,-1,0,0">
<Grid>
<Grid.ColumnDefinitions>
@@ -475,7 +747,7 @@
Margin="10"
MinWidth="0"
Grid.Column="0"
IsChecked="{Binding Config.EnableRumble, Mode=TwoWay}">
IsChecked="{ReflectionBinding Configuration.EnableRumble, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsRumble}" />
</CheckBox>
<Button
@@ -521,9 +793,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZr">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonZr, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonZr, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -539,9 +811,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonR">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonR, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonR, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -557,15 +829,15 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonPlus">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonPlus, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonPlus, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Right Buttons -->
<!-- Right Joystick -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
@@ -592,9 +864,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}"
TextAlignment="Center" />
<ToggleButton Name="ButtonA">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonA, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonA, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -609,9 +881,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}"
TextAlignment="Center" />
<ToggleButton Name="ButtonB">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonB, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonB, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -626,9 +898,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}"
TextAlignment="Center" />
<ToggleButton Name="ButtonX">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonX, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonX, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -643,9 +915,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}"
TextAlignment="Center" />
<ToggleButton Name="ButtonY">
<ToggleButton>
<TextBlock
Text="{Binding Config.ButtonY, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.ButtonY, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -665,8 +937,100 @@
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Keyboard -->
<StackPanel
IsVisible="{Binding !IsController}"
Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightKeyboardStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickLeft, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton>
<TextBlock
Text="{ReflectionBinding Configuration.RightStickRight, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
<!-- Right Joystick Controller -->
<StackPanel Orientation="Vertical">
<StackPanel
IsVisible="{Binding IsController}"
Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Orientation="Horizontal">
@@ -677,9 +1041,9 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="RightStickButton">
<ToggleButton>
<TextBlock
Text="{Binding Config.RightStickButton, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.RightControllerStickButton, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
@@ -695,20 +1059,20 @@
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}"
TextAlignment="Center" />
<ToggleButton Name="RightJoystick" Tag="stick">
<ToggleButton Tag="stick">
<TextBlock
Text="{Binding Config.RightJoystick, Converter={StaticResource Key}}"
Text="{ReflectionBinding Configuration.RightJoystick, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<Separator Margin="0,8,0,8" Height="1" />
<CheckBox IsChecked="{Binding Config.RightInvertStickX}">
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox>
<CheckBox IsChecked="{Binding Config.RightInvertStickY}">
<CheckBox IsChecked="{ReflectionBinding Configuration.RightInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox>
<CheckBox IsChecked="{Binding Config.RightRotate90}">
<CheckBox IsChecked="{ReflectionBinding Configuration.RightRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" />
</CheckBox>
<Separator Margin="0,8,0,8" Height="1" />
@@ -729,11 +1093,11 @@
Padding="0"
VerticalAlignment="Center"
Minimum="0"
Value="{Binding Config.DeadzoneRight, Mode=TwoWay}" />
Value="{ReflectionBinding Configuration.DeadzoneRight, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{Binding Config.DeadzoneRight, StringFormat=\{0:0.00\}}" />
Text="{ReflectionBinding Configuration.DeadzoneRight, StringFormat=\{0:0.00\}}" />
</StackPanel>
<TextBlock
HorizontalAlignment="Center"
@@ -749,11 +1113,11 @@
IsSnapToTickEnabled="True"
SmallChange="0.01"
Minimum="0"
Value="{Binding Config.RangeRight, Mode=TwoWay}" />
Value="{ReflectionBinding Configuration.RangeRight, Mode=TwoWay}" />
<TextBlock
VerticalAlignment="Center"
Width="25"
Text="{Binding Config.RangeRight, StringFormat=\{0:0.00\}}" />
Text="{ReflectionBinding Configuration.RangeRight, StringFormat=\{0:0.00\}}" />
</StackPanel>
</StackPanel>
</StackPanel>

View File

@@ -1,28 +1,35 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
using System;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class ControllerInputView : UserControl
{
private bool _dialogOpen;
private ButtonKeyAssigner _currentAssigner;
internal ControllerInputViewModel ViewModel { get; set; }
public ControllerInputView()
{
DataContext = ViewModel = new ControllerInputViewModel(this);
InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button and not CheckBox)
if (visual is ToggleButton button && visual is not CheckBox)
{
button.IsCheckedChanged += Button_IsCheckedChanged;
}
@@ -52,7 +59,7 @@ namespace Ryujinx.Ava.UI.Views.Input
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null && (bool)button.IsChecked)
if (_currentAssigner == null)
{
_currentAssigner = new ButtonKeyAssigner(button);
@@ -60,86 +67,14 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)(DataContext as ControllerInputViewModel).parentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.ButtonValue.HasValue)
if (e.IsAssigned)
{
var viewModel = (DataContext as ControllerInputViewModel);
var buttonValue = e.ButtonValue.Value;
viewModel.parentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsGamepadButtonInputId();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsGamepadButtonInputId();
break;
case "LeftJoystick":
viewModel.Config.LeftJoystick = buttonValue.AsGamepadStickId();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsGamepadButtonInputId();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsGamepadButtonInputId();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsGamepadButtonInputId();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsGamepadButtonInputId();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsGamepadButtonInputId();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsGamepadButtonInputId();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsGamepadButtonInputId();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsGamepadButtonInputId();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsGamepadButtonInputId();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsGamepadButtonInputId();
break;
case "RightJoystick":
viewModel.Config.RightJoystick = buttonValue.AsGamepadStickId();
break;
}
ViewModel.IsModified = true;
}
};
@@ -165,29 +100,82 @@ namespace Ryujinx.Ava.UI.Views.Input
}
}
private void MouseClick(object sender, PointerPressedEventArgs e)
public void SaveCurrentProfile()
{
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
ViewModel.Save();
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
assigner = new GamepadButtonAssigner((DataContext as ControllerInputViewModel).parentModel.SelectedGamepad, ((DataContext as ControllerInputViewModel).parentModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
var device = ViewModel.Devices[ViewModel.Device];
if (device.Type == DeviceType.Keyboard)
{
assigner = new KeyboardKeyAssigner((IKeyboard)ViewModel.SelectedGamepad);
}
else if (device.Type == DeviceType.Controller)
{
assigner = new GamepadButtonAssigner(ViewModel.SelectedGamepad, (ViewModel.Config as StandardControllerInputConfig).TriggerThreshold, forStick);
}
else
{
throw new Exception("Controller not supported");
}
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = false;
if (e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed)
{
shouldUnbind = true;
}
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.IsModified && !_dialogOpen)
{
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
ViewModel.Save();
}
_dialogOpen = false;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
var player = (PlayerModel)e.AddedItems[0];
ViewModel.PlayerId = player.Id;
}
}
}
public void Dispose()
{
base.OnDetachedFromVisualTree(e);
_currentAssigner?.Cancel();
_currentAssigner = null;
ViewModel.Dispose();
}
}
}

View File

@@ -1,225 +0,0 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="800"
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.InputView"
x:DataType="viewModels:InputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:InputViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="ToggleButton">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</UserControl.Styles>
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<StackPanel
Margin="0 0 0 5"
Orientation="Vertical"
Spacing="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Player Selection -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsPlayer}" />
<ComboBox
Grid.Column="1"
Name="PlayerIndexBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
ItemsSource="{Binding PlayerIndexes}"
SelectedIndex="{Binding PlayerId}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<!-- Profile Selection -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" />
<ui:FAComboBox
Grid.Column="1"
IsEditable="True"
Name="ProfileBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
SelectedIndex="0"
ItemsSource="{Binding ProfilesList}"
Text="{Binding ProfileName, Mode=TwoWay}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
Command="{Binding LoadProfile}">
<ui:SymbolIcon
Symbol="Upload"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="3"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
Command="{Binding SaveProfile}">
<ui:SymbolIcon
Symbol="Save"
FontSize="15"
Height="20" />
</Button>
<Button
Grid.Column="4"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
Command="{Binding RemoveProfile}">
<ui:SymbolIcon
Symbol="Delete"
FontSize="15"
Height="20" />
</Button>
</Grid>
</Grid>
<Separator />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Input Device -->
<Grid
Grid.Column="0"
Margin="2"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsInputDevice}" />
<ComboBox
Grid.Column="1"
Name="DeviceBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
ItemsSource="{Binding DeviceList}"
SelectedIndex="{Binding Device}" />
<Button
Grid.Column="2"
MinWidth="0"
Margin="5,0,0,0"
VerticalAlignment="Center"
Command="{Binding LoadDevices}">
<ui:SymbolIcon
Symbol="Refresh"
FontSize="15"
Height="20"/>
</Button>
</Grid>
<!-- Controller Type -->
<Grid
Grid.Column="2"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="5,0,10,0"
Width="90"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsControllerType}" />
<ComboBox
Grid.Column="1"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Controllers}"
SelectedIndex="{Binding Controller}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="models:ControllerModel">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Grid>
</StackPanel>
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
<ContentControl.DataTemplates>
<DataTemplate DataType="viewModels:ControllerInputViewModel">
<views:ControllerInputView />
</DataTemplate>
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
<views:KeyboardInputView />
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</StackPanel>
</UserControl>

View File

@@ -1,61 +0,0 @@
using Avalonia.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels.Input;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class InputView : UserControl
{
private bool _dialogOpen;
private InputViewModel ViewModel { get; set; }
public InputView()
{
DataContext = ViewModel = new InputViewModel(this);
InitializeComponent();
}
public void SaveCurrentProfile()
{
ViewModel.Save();
}
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.IsModified && !_dialogOpen)
{
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result == UserResult.Yes)
{
ViewModel.Save();
}
_dialogOpen = false;
ViewModel.IsModified = false;
if (e.AddedItems.Count > 0)
{
var player = (PlayerModel)e.AddedItems[0];
ViewModel.PlayerId = player.Id;
}
}
}
public void Dispose()
{
ViewModel.Dispose();
}
}
}

View File

@@ -1,675 +0,0 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
d:DesignHeight="800"
d:DesignWidth="800"
x:Class="Ryujinx.Ava.UI.Views.Input.KeyboardInputView"
x:DataType="viewModels:KeyboardInputViewModel"
x:CompileBindings="True"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:KeyboardInputViewModel />
</Design.DataContext>
<UserControl.Resources>
<helpers:KeyValueConverter x:Key="Key" />
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="ToggleButton">
<Setter Property="Width" Value="90" />
<Setter Property="Height" Value="27" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</UserControl.Styles>
<StackPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Vertical">
<!-- Button / JoyStick Settings -->
<Grid
Name="SettingButtons"
MinHeight="450">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Left Controls -->
<StackPanel
Orientation="Vertical"
Margin="0,0,5,0"
Grid.Column="0">
<!-- Left Triggers -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsLeft}"
MinHeight="90"
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel
Grid.Column="0"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZl">
<TextBlock
Text="{Binding Config.ButtonZl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" />
<ToggleButton Name="ButtonL">
<TextBlock
Text="{Binding Config.ButtonL, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="1"
Grid.Row="1"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonMinus">
<TextBlock
Text="{Binding Config.ButtonMinus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Left Joystick -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsLeft}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Keyboard -->
<StackPanel Orientation="Vertical">
<!-- Left Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickButton">
<TextBlock
Text="{Binding Config.LeftStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickUp">
<TextBlock
Text="{Binding Config.LeftStickUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickDown">
<TextBlock
Text="{Binding Config.LeftStickDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickLeft">
<TextBlock
Text="{Binding Config.LeftStickLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton Name="LeftStickRight">
<TextBlock
Text="{Binding Config.LeftStickRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Left DPad -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
VerticalAlignment="Top"
IsVisible="{Binding IsLeft}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPad}" />
<StackPanel Orientation="Vertical">
<!-- Left DPad Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" />
<ToggleButton Name="DpadUp">
<TextBlock
Text="{Binding Config.DpadUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" />
<ToggleButton Name="DpadDown">
<TextBlock
Text="{Binding Config.DpadDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" />
<ToggleButton Name="DpadLeft">
<TextBlock
Text="{Binding Config.DpadLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Left DPad Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" />
<ToggleButton Name="DpadRight">
<TextBlock
Text="{Binding Config.DpadRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Triggers & Side Buttons -->
<StackPanel
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Controller Picture -->
<Image
Margin="0,10"
MaxHeight="300"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="{Binding Image}" />
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
MinHeight="90"
IsVisible="{Binding HasSides}">
<StackPanel
Margin="8"
Orientation="Vertical">
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSr">
<TextBlock
Text="{Binding Config.LeftButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsLeft}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" />
<ToggleButton Name="LeftButtonSl">
<TextBlock
Text="{Binding Config.LeftButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSr">
<TextBlock
Text="{Binding Config.RightButtonSr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding IsRight}"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}"
TextAlignment="Center" />
<ToggleButton Name="RightButtonSl">
<TextBlock
Text="{Binding Config.RightButtonSl, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
<!-- Right Controls -->
<StackPanel
Orientation="Vertical"
Margin="5,0,0,0"
Grid.Column="2">
<!-- Right Triggers -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsRight}"
MinHeight="90"
CornerRadius="5">
<Grid
Margin="10"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel
Grid.Column="1"
Grid.Row="0"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonZr">
<TextBlock
Text="{Binding Config.ButtonZr, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="1"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" />
<ToggleButton Name="ButtonR">
<TextBlock
Text="{Binding Config.ButtonR, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel
Grid.Column="0"
Grid.Row="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Width="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" />
<ToggleButton Name="ButtonPlus">
<TextBlock
Text="{Binding Config.ButtonPlus, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</Grid>
</Border>
<!-- Right Buttons -->
<Border
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
IsVisible="{Binding IsRight}"
Margin="0,5,0,0"
CornerRadius="5">
<StackPanel
Margin="10"
Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtons}" />
<StackPanel
Orientation="Vertical">
<!-- Right Buttons A -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}"
TextAlignment="Center" />
<ToggleButton Name="ButtonA">
<TextBlock
Text="{Binding Config.ButtonA, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons B -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}"
TextAlignment="Center" />
<ToggleButton Name="ButtonB">
<TextBlock
Text="{Binding Config.ButtonB, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons X -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}"
TextAlignment="Center" />
<ToggleButton Name="ButtonX">
<TextBlock
Text="{Binding Config.ButtonX, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Buttons Y -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Width="120"
Margin="0,0,10,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}"
TextAlignment="Center" />
<ToggleButton Name="ButtonY">
<TextBlock
Text="{Binding Config.ButtonY, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- Right DPad -->
<Border
Padding="10"
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1"
CornerRadius="5"
IsVisible="{Binding IsRight}"
Margin="0,5,0,0">
<StackPanel Orientation="Vertical">
<TextBlock
Margin="0,0,0,10"
HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Keyboard -->
<StackPanel Orientation="Vertical">
<!-- Right Joystick Button -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}"
TextAlignment="Center" />
<ToggleButton Name="RightStickButton">
<TextBlock
Text="{Binding Config.RightStickButton, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Up -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}"
TextAlignment="Center" />
<ToggleButton Name="RightStickUp">
<TextBlock
Text="{Binding Config.RightStickUp, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Down -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}"
TextAlignment="Center" />
<ToggleButton Name="RightStickDown">
<TextBlock
Text="{Binding Config.RightStickDown, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Left -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" />
<ToggleButton Name="RightStickLeft">
<TextBlock
Text="{Binding Config.RightStickLeft, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<!-- Right Joystick Right -->
<StackPanel
Margin="0,0,0,4"
Orientation="Horizontal">
<TextBlock
Margin="0,0,10,0"
Width="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}"
TextAlignment="Center" />
<ToggleButton Name="RightStickRight">
<TextBlock
Text="{Binding Config.RightStickRight, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -1,210 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Input;
using Ryujinx.Input.Assigner;
namespace Ryujinx.Ava.UI.Views.Input
{
public partial class KeyboardInputView : UserControl
{
private ButtonKeyAssigner _currentAssigner;
public KeyboardInputView()
{
InitializeComponent();
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
{
if (visual is ToggleButton button and not CheckBox)
{
button.IsCheckedChanged += Button_IsCheckedChanged;
}
}
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver)
{
_currentAssigner.Cancel();
}
}
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
{
if ((bool)button.IsChecked)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
bool isStick = button.Tag != null && button.Tag.ToString() == "stick";
if (_currentAssigner == null && (bool)button.IsChecked)
{
_currentAssigner = new ButtonKeyAssigner(button);
this.Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)(DataContext as KeyboardInputViewModel).parentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) =>
{
if (e.ButtonValue.HasValue)
{
var viewModel = (DataContext as KeyboardInputViewModel);
var buttonValue = e.ButtonValue.Value;
viewModel.parentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsKey();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsKey();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsKey();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsKey();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsKey();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsKey();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsKey();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsKey();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsKey();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsKey();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsKey();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsKey();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsKey();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsKey();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsKey();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsKey();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsKey();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsKey();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsKey();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsKey();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsKey();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsKey();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsKey();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsKey();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsKey();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsKey();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsKey();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsKey();
break;
}
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
ToggleButton oldButton = _currentAssigner.ToggledButton;
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
else
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}
private void MouseClick(object sender, PointerPressedEventArgs e)
{
bool shouldUnbind = e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed;
_currentAssigner?.Cancel(shouldUnbind);
PointerPressed -= MouseClick;
}
private IButtonAssigner CreateButtonAssigner(bool forStick)
{
IButtonAssigner assigner;
assigner = new KeyboardKeyAssigner((IKeyboard)(DataContext as KeyboardInputViewModel).parentModel.SelectedGamepad);
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
}

View File

@@ -6,7 +6,7 @@
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
x:DataType="viewModels:MotionInputViewModel"

View File

@@ -1,7 +1,9 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
@@ -17,7 +19,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public MotionInputView(ControllerInputViewModel viewModel)
{
var config = viewModel.Config;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewModel = new MotionInputViewModel
{
@@ -49,7 +51,7 @@ namespace Ryujinx.Ava.UI.Views.Input
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewModel.Config;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewModel.Slot;
config.Sensitivity = content._viewModel.Sensitivity;
config.GyroDeadzone = content._viewModel.GyroDeadzone;

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
x:DataType="viewModels:RumbleInputViewModel"

View File

@@ -1,7 +1,9 @@
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration.Hid.Controller;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Input
@@ -17,7 +19,7 @@ namespace Ryujinx.Ava.UI.Views.Input
public RumbleInputView(ControllerInputViewModel viewModel)
{
var config = viewModel.Config;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
_viewModel = new RumbleInputViewModel
{
@@ -45,7 +47,7 @@ namespace Ryujinx.Ava.UI.Views.Input
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewModel.Config;
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewModel.StrongRumble;
config.WeakRumble = content._viewModel.WeakRumble;
};

View File

@@ -27,9 +27,9 @@
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<views:InputView
<views:ControllerInputView
Grid.Row="0"
Name="InputView" />
Name="ControllerSettings" />
<StackPanel
Orientation="Vertical"
Grid.Row="2">

View File

@@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
public void Dispose()
{
InputView.Dispose();
ControllerSettings.Dispose();
}
}
}

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Ava.UI.Windows
public void SaveSettings()
{
InputPage.InputView?.SaveCurrentProfile();
InputPage.ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
{

View File

@@ -3,5 +3,6 @@
public enum MultiplayerMode
{
Disabled,
LdnMitm,
}
}

View File

@@ -12,6 +12,13 @@ namespace Ryujinx.Common.SystemInfo
{
internal MacOSSystemInfo()
{
if (SysctlByName("kern.osversion", out string buildRevision) != 0)
{
buildRevision = "Unknown Build";
}
OsDescription = $"macOS {Environment.OSVersion.Version} ({buildRevision}) ({RuntimeInformation.OSArchitecture})";
string cpuName = GetCpuidCpuName();
if (cpuName == null && SysctlByName("machdep.cpu.brand_string", out cpuName) != 0)

View File

@@ -74,5 +74,10 @@ namespace Ryujinx.Common.Utilities
{
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
}
public static IPAddress ConvertUint(uint ipAddress)
{
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) });
}
}
}

View File

@@ -101,6 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool AlwaysFlushOnOverlap { get; private set; }
/// <summary>
/// Indicates that the texture was fully unmapped since the modified flag was set, and flushes should be ignored until it is modified again.
/// </summary>
public bool FlushStale { get; private set; }
/// <summary>
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
/// </summary>
@@ -149,6 +154,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public bool HadPoolOwner { get; private set; }
/// <summary>
/// Physical memory ranges where the texture data is located.
/// </summary>
public MultiRange Range { get; private set; }
@@ -1411,6 +1417,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
public void SignalModified()
{
FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
if (_modifiedStale || Group.HasCopyDependencies)
@@ -1431,6 +1438,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (bound)
{
FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
}
@@ -1695,12 +1703,17 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="unmapRange">The range of memory being unmapped</param>
public void Unmapped(MultiRange unmapRange)
{
if (unmapRange.Contains(Range))
{
// If this is a full unmap, prevent flushes until the texture is mapped again.
FlushStale = true;
}
ChangedMapping = true;
if (Group.Storage == this)
{
Group.Unmapped();
Group.ClearModified(unmapRange);
}
}

View File

@@ -107,8 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
// Any texture that has been unmapped at any point or is partially unmapped
// should update their pool references after the remap completes.
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
foreach (var texture in _partiallyMappedTextures)
{
texture.UpdatePoolMappings();
@@ -735,9 +733,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (overlap.IsView)
{
overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ?
TextureViewCompatibility.Incompatible :
TextureViewCompatibility.CopyOnly;
overlapCompatibility = TextureViewCompatibility.CopyOnly;
}
else
{
@@ -815,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias)
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
{
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
{

View File

@@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
// D32F and R32F texture have the same representation internally,
// however the R32F format is used to sample from depth textures.
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias))
if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias))
{
return TextureMatchQuality.FormatAlias;
}
@@ -239,14 +239,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{
return TextureMatchQuality.FormatAlias;
}
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
{
return TextureMatchQuality.FormatAlias;
}
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}
@@ -632,12 +626,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
{
return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch
bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias);
if (matchQuality == TextureMatchQuality.Perfect)
{
TextureMatchQuality.Perfect => TextureViewCompatibility.Full,
TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias,
_ => TextureViewCompatibility.Incompatible,
};
return TextureViewCompatibility.Full;
}
else if (matchQuality == TextureMatchQuality.FormatAlias)
{
return TextureViewCompatibility.FormatAlias;
}
else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format))
{
return TextureViewCompatibility.CopyOnly;
}
else
{
return TextureViewCompatibility.Incompatible;
}
}
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
@@ -666,6 +675,30 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible;
}
/// <summary>
/// Checks if it's valid to alias a color format as a depth format.
/// </summary>
/// <param name="lhsFormat">Source format to be checked</param>
/// <param name="rhsFormat">Target format to be checked</param>
/// <returns>True if it's valid to alias the formats</returns>
private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat)
{
return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) ||
(lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm);
}
/// <summary>
/// Checks if it's valid to alias a depth format as a color format.
/// </summary>
/// <param name="lhsFormat">Source format to be checked</param>
/// <param name="rhsFormat">Target format to be checked</param>
/// <returns>True if it's valid to alias the formats</returns>
private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat)
{
return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) ||
(lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm);
}
/// <summary>
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
/// using copy dependencies.

View File

@@ -1659,6 +1659,14 @@ namespace Ryujinx.Graphics.Gpu.Image
return;
}
// If size is zero, we have nothing to flush.
// If the flush is stale, we should ignore it because the texture was unmapped since the modified
// flag was set, and flushing it is not safe anymore as the GPU might no longer own the memory.
if (size == 0 || Storage.FlushStale)
{
return;
}
// There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions.

View File

@@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
return to;
}
private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
{
int dstWidth = width;
int dstHeight = height;
@@ -445,8 +445,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
return to;
}
private void EnsurePbo(TextureView view)

View File

@@ -140,6 +140,28 @@ namespace Ryujinx.Graphics.OpenGL.Image
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
}
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
for (int level = 0; level < levels; level++)
{
int srcWidth = Math.Max(1, Width >> level);
int srcHeight = Math.Max(1, Height >> level);
int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level));
int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level));
int minWidth = Math.Min(srcWidth, dstWidth);
int minHeight = Math.Min(srcHeight, dstHeight);
for (int layer = 0; layer < layers; layer++)
{
_renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight);
}
}
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
@@ -169,6 +191,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
{
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
{
int minWidth = Math.Min(Width, destinationView.Width);
int minHeight = Math.Min(Height, destinationView.Height);
_renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight);
}
else
{
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);

View File

@@ -766,7 +766,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset;
}
sourcesList.Add(Const((int)component));
if (!hasDepthCompare)
{
sourcesList.Add(Const((int)component));
}
Operand[] sources = sourcesList.ToArray();
Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)];

View File

@@ -211,6 +211,13 @@ namespace Ryujinx.Graphics.Vulkan
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
}
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
}
else
{
TextureCopy.Copy(
@@ -260,6 +267,10 @@ namespace Ryujinx.Graphics.Vulkan
{
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else
{
TextureCopy.Copy(

View File

@@ -198,7 +198,7 @@ namespace Ryujinx.HLE.FileSystem
{
using var ncaFile = new UniqueRef<IFile>();
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType != NcaContentType.Meta)
{
@@ -210,7 +210,7 @@ namespace Ryujinx.HLE.FileSystem
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
using var cnmtFile = new UniqueRef<IFile>();
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
@@ -220,7 +220,7 @@ namespace Ryujinx.HLE.FileSystem
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
AddAocItem(cnmt.TitleId, containerPath, $"{ncaId}.nca", true);
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
}
}
@@ -238,7 +238,8 @@ namespace Ryujinx.HLE.FileSystem
if (!mergedToContainer)
{
using FileStream fileStream = File.OpenRead(containerPath);
using PartitionFileSystem partitionFileSystem = new(fileStream.AsStorage());
using PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem);
}
@@ -259,17 +260,17 @@ namespace Ryujinx.HLE.FileSystem
{
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
using var ncaFile = new UniqueRef<IFile>();
PartitionFileSystem pfs;
switch (Path.GetExtension(aoc.ContainerPath))
{
case ".xci":
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
break;
case ".nsp":
pfs = new PartitionFileSystem(file.AsStorage());
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
var pfs = new PartitionFileSystem();
pfs.Initialize(file.AsStorage());
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
break;
default:
return false; // Print error?
@@ -606,11 +607,11 @@ namespace Ryujinx.HLE.FileSystem
if (filesystem.FileExists($"{path}/00"))
{
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode);
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure();
}
else
{
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode);
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure();
}
return file.Release();

View File

@@ -7,6 +7,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Sdmmc;
using LibHac.Spl;
using LibHac.Tools.Es;
using LibHac.Tools.Fs;
@@ -32,7 +33,7 @@ namespace Ryujinx.HLE.FileSystem
public KeySet KeySet { get; private set; }
public EmulatedGameCard GameCard { get; private set; }
public EmulatedSdCard SdCard { get; private set; }
public SdmmcApi SdCard { get; private set; }
public ModLoader ModLoader { get; private set; }
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
@@ -198,15 +199,15 @@ namespace Ryujinx.HLE.FileSystem
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
GameCard = fsServerObjects.GameCard;
SdCard = fsServerObjects.SdCard;
SdCard = fsServerObjects.Sdmmc;
SdCard.SetSdCardInsertionStatus(true);
SdCard.SetSdCardInserted(true);
var fsServerConfig = new FileSystemServerConfig
{
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators,
StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory,
RandomGenerator = randomGenerator,
};
@@ -263,7 +264,16 @@ namespace Ryujinx.HLE.FileSystem
if (result.IsSuccess())
{
Ticket ticket = new(ticketFile.Get.AsStream());
// When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle
// of the hashed portion (usually the first 0x200 bytes) of the file and end the read after
// the end of the hashed portion, so we read the ticket file using a single read.
byte[] ticketData = new byte[0x2C0];
result = ticketFile.Get.Read(out long bytesRead, 0, ticketData);
if (result.IsFailure() || bytesRead != ticketData.Length)
continue;
Ticket ticket = new(new MemoryStream(ticketData));
var titleKey = ticket.GetTitleKey(KeySet);
if (titleKey != null)

View File

@@ -101,7 +101,7 @@ namespace Ryujinx.HLE
/// <summary>
/// Control if the guest application should be told that there is a Internet connection available.
/// </summary>
internal readonly bool EnableInternetAccess;
public bool EnableInternetAccess { internal get; set; }
/// <summary>
/// Control LibHac's integrity check level.

View File

@@ -533,7 +533,9 @@ namespace Ryujinx.HLE.HOS
Logger.Info?.Print(LogClass.ModLoader, "Using replacement ExeFS partition");
exefs = new PartitionFileSystem(mods.ExefsContainers[0].Path.OpenRead().AsStorage());
var pfs = new PartitionFileSystem();
pfs.Initialize(mods.ExefsContainers[0].Path.OpenRead().AsStorage()).ThrowIfFailure();
exefs = pfs;
return true;
}

View File

@@ -26,7 +26,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
try
{
LocalStorage storage = new(pfsPath, FileAccess.Read, FileMode.Open);
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(new PartitionFileSystem(storage));
var pfs = new PartitionFileSystem();
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(pfs);
pfs.Initialize(storage).ThrowIfFailure();
ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet);
@@ -90,7 +92,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
try
{
PartitionFileSystem nsp = new(pfsFile.AsStorage());
PartitionFileSystem nsp = new();
nsp.Initialize(pfsFile.AsStorage()).ThrowIfFailure();
ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet);

View File

@@ -1,6 +1,7 @@
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using Path = LibHac.FsSrv.Sf.Path;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
@@ -202,6 +203,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
return (ResultCode)result.Value;
}
[CommandCmif(16)]
public ResultCode GetFileSystemAttribute(ServiceCtx context)
{
Result result = _fileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute);
context.ResponseData.Write(SpanHelpers.AsReadOnlyByteSpan(in attribute));
return (ResultCode)result.Value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)

View File

@@ -1380,7 +1380,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs
[CommandCmif(1016)]
public ResultCode FlushAccessLogOnSdCard(ServiceCtx context)
{
return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
// Logging the access log to the SD card isn't implemented, meaning this function will be a no-op since
// there's nothing to flush. Return success until it's implemented.
// return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
return ResultCode.Success;
}
[CommandCmif(1017)]

View File

@@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Ldn
{
static class LdnConst
{
public const int SsidLengthMax = 0x20;
public const int AdvertiseDataSizeMax = 0x180;
public const int UserNameBytesMax = 0x20;
public const int NodeCountMax = 8;
public const int StationCountMax = NodeCountMax - 1;
public const int PassphraseLengthMax = 0x40;
}
}

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
@@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Types
{
result[i].Reserved = new Array7<byte>();
if (i < 8)
if (i < LdnConst.NodeCountMax)
{
result[i].State = array[i].State;
array[i].State = NodeLatestUpdateFlags.None;

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types

View File

@@ -1,4 +1,4 @@
using Ryujinx.Common.Memory;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.Types

View File

@@ -1,7 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
@@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
_parent.NetworkClient.NetworkChange -= NetworkChanged;
}
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
{
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);

View File

@@ -1,12 +1,13 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
interface INetworkClient : IDisposable
{
bool NeedsRealId { get; }
event EventHandler<NetworkChangeEventArgs> NetworkChange;
void DisconnectNetwork();

View File

@@ -8,7 +8,7 @@ using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
@@ -395,7 +395,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
else
{
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1)
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
@@ -546,7 +546,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment?
NetworkConfig networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
if (networkConfig.IntentId.LocalCommunicationId == -1)
if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
@@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
if (!isLocalCommunicationIdValid)
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
{
return ResultCode.InvalidObject;
}
@@ -568,13 +568,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel);
securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
if (networkConfig.NodeCountMax <= 8)
if (networkConfig.NodeCountMax <= LdnConst.NodeCountMax)
{
if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0)
{
if (securityConfig.SecurityMode <= SecurityMode.Retail)
{
if (securityConfig.Passphrase.Length <= 0x40)
if (securityConfig.Passphrase.Length <= LdnConst.PassphraseLengthMax)
{
if (_state == NetworkState.AccessPoint)
{
@@ -678,7 +678,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
return _nifmResultCode;
}
if (bufferSize == 0 || bufferSize > 0x180)
if (bufferSize == 0 || bufferSize > LdnConst.AdvertiseDataSizeMax)
{
return ResultCode.InvalidArgument;
}
@@ -848,10 +848,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
context.Memory.Read(bufferPosition, networkInfoBytes);
networkInfo = MemoryMarshal.Cast<byte, NetworkInfo>(networkInfoBytes)[0];
networkInfo = MemoryMarshal.Read<NetworkInfo>(networkInfoBytes);
}
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1)
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
{
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
@@ -860,7 +860,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
}
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
if (!isLocalCommunicationIdValid)
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
{
return ResultCode.InvalidObject;
}
@@ -1061,10 +1061,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
MultiplayerMode mode = context.Device.Configuration.MultiplayerMode;
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initializing with multiplayer mode: {mode}");
switch (mode)
{
case MultiplayerMode.LdnMitm:
NetworkClient = new LdnMitmClient(context.Device.Configuration);
break;
case MultiplayerMode.Disabled:
NetworkClient = new DisabledLdnClient();
NetworkClient = new LdnDisabledClient();
break;
}

View File

@@ -1,12 +1,13 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
class DisabledLdnClient : INetworkClient
class LdnDisabledClient : INetworkClient
{
public bool NeedsRealId => true;
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
public NetworkError Connect(ConnectRequest request)

View File

@@ -0,0 +1,611 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
{
internal class LanDiscovery : IDisposable
{
private const int DefaultPort = 11452;
private const ushort CommonChannel = 6;
private const byte CommonLinkLevel = 3;
private const byte CommonNetworkType = 2;
private const int FailureTimeout = 4000;
private readonly LdnMitmClient _parent;
private readonly LanProtocol _protocol;
private bool _initialized;
private readonly Ssid _fakeSsid;
private ILdnTcpSocket _tcp;
private LdnProxyUdpServer _udp, _udp2;
private readonly List<LdnProxyTcpSession> _stations = new();
private readonly object _lock = new();
private readonly AutoResetEvent _apConnected = new(false);
internal readonly IPAddress LocalAddr;
internal readonly IPAddress LocalBroadcastAddr;
internal NetworkInfo NetworkInfo;
public bool IsHost => _tcp is LdnProxyTcpServer;
private readonly Random _random = new();
// NOTE: Credit to https://stackoverflow.com/a/39338188
private static IPAddress GetBroadcastAddress(IPAddress address, IPAddress mask)
{
uint ipAddress = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
uint ipMaskV4 = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
uint broadCastIpAddress = ipAddress | ~ipMaskV4;
return new IPAddress(BitConverter.GetBytes(broadCastIpAddress));
}
private static NetworkInfo GetEmptyNetworkInfo()
{
NetworkInfo networkInfo = new()
{
NetworkId = new NetworkId
{
SessionId = new Array16<byte>(),
},
Common = new CommonNetworkInfo
{
MacAddress = new Array6<byte>(),
Ssid = new Ssid
{
Name = new Array33<byte>(),
},
},
Ldn = new LdnNetworkInfo
{
NodeCountMax = LdnConst.NodeCountMax,
SecurityParameter = new Array16<byte>(),
Nodes = new Array8<NodeInfo>(),
AdvertiseData = new Array384<byte>(),
Reserved4 = new Array140<byte>(),
},
};
for (int i = 0; i < LdnConst.NodeCountMax; i++)
{
networkInfo.Ldn.Nodes[i] = new NodeInfo
{
MacAddress = new Array6<byte>(),
UserName = new Array33<byte>(),
Reserved2 = new Array16<byte>(),
};
}
return networkInfo;
}
public LanDiscovery(LdnMitmClient parent, IPAddress ipAddress, IPAddress ipv4Mask)
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initialize LanDiscovery using IP: {ipAddress}");
_parent = parent;
LocalAddr = ipAddress;
LocalBroadcastAddr = GetBroadcastAddress(ipAddress, ipv4Mask);
_fakeSsid = new Ssid
{
Length = LdnConst.SsidLengthMax,
};
_random.NextBytes(_fakeSsid.Name.AsSpan()[..32]);
_protocol = new LanProtocol(this);
_protocol.Accept += OnConnect;
_protocol.SyncNetwork += OnSyncNetwork;
_protocol.DisconnectStation += DisconnectStation;
NetworkInfo = GetEmptyNetworkInfo();
ResetStations();
if (!InitUdp())
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Initialize: InitUdp failed.");
return;
}
_initialized = true;
}
protected void OnSyncNetwork(NetworkInfo info)
{
bool updated = false;
lock (_lock)
{
if (!NetworkInfo.Equals(info))
{
NetworkInfo = info;
updated = true;
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"Host IP: {NetworkHelpers.ConvertUint(info.Ldn.Nodes[0].Ipv4Address)}");
}
}
if (updated)
{
_parent.InvokeNetworkChange(info, true);
}
_apConnected.Set();
}
protected void OnConnect(LdnProxyTcpSession station)
{
lock (_lock)
{
station.NodeId = LocateEmptyNode();
if (_stations.Count > LdnConst.StationCountMax || station.NodeId == -1)
{
station.Disconnect();
station.Dispose();
return;
}
_stations.Add(station);
UpdateNodes();
}
}
public void DisconnectStation(LdnProxyTcpSession station)
{
if (!station.IsDisposed)
{
if (station.IsConnected)
{
station.Disconnect();
}
station.Dispose();
}
lock (_lock)
{
if (_stations.Remove(station))
{
NetworkInfo.Ldn.Nodes[station.NodeId] = new NodeInfo()
{
MacAddress = new Array6<byte>(),
UserName = new Array33<byte>(),
Reserved2 = new Array16<byte>(),
};
UpdateNodes();
}
}
}
public bool SetAdvertiseData(byte[] data)
{
if (data.Length > LdnConst.AdvertiseDataSizeMax)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "AdvertiseData exceeds size limit.");
return false;
}
data.CopyTo(NetworkInfo.Ldn.AdvertiseData.AsSpan());
NetworkInfo.Ldn.AdvertiseDataSize = (ushort)data.Length;
// NOTE: Otherwise this results in SessionKeepFailed or MasterDisconnected
lock (_lock)
{
if (NetworkInfo.Ldn.Nodes[0].IsConnected == 1)
{
UpdateNodes(true);
}
}
return true;
}
public void InitNetworkInfo()
{
lock (_lock)
{
NetworkInfo.Common.MacAddress = GetFakeMac();
NetworkInfo.Common.Channel = CommonChannel;
NetworkInfo.Common.LinkLevel = CommonLinkLevel;
NetworkInfo.Common.NetworkType = CommonNetworkType;
NetworkInfo.Common.Ssid = _fakeSsid;
NetworkInfo.Ldn.Nodes = new Array8<NodeInfo>();
for (int i = 0; i < LdnConst.NodeCountMax; i++)
{
NetworkInfo.Ldn.Nodes[i].NodeId = (byte)i;
NetworkInfo.Ldn.Nodes[i].IsConnected = 0;
}
}
}
protected Array6<byte> GetFakeMac(IPAddress address = null)
{
address ??= LocalAddr;
byte[] ip = address.GetAddressBytes();
var macAddress = new Array6<byte>();
new byte[] { 0x02, 0x00, ip[0], ip[1], ip[2], ip[3] }.CopyTo(macAddress.AsSpan());
return macAddress;
}
public bool InitTcp(bool listening, IPAddress address = null, int port = DefaultPort)
{
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LanDiscovery InitTcp: IP: {address}, listening: {listening}");
if (_tcp != null)
{
_tcp.DisconnectAndStop();
_tcp.Dispose();
_tcp = null;
}
ILdnTcpSocket tcpSocket;
if (listening)
{
try
{
address ??= LocalAddr;
tcpSocket = new LdnProxyTcpServer(_protocol, address, port);
}
catch (Exception ex)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpServer: {ex}");
return false;
}
if (!tcpSocket.Start())
{
return false;
}
}
else
{
if (address == null)
{
return false;
}
try
{
tcpSocket = new LdnProxyTcpClient(_protocol, address, port);
}
catch (Exception ex)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpClient: {ex}");
return false;
}
}
_tcp = tcpSocket;
return true;
}
public bool InitUdp()
{
_udp?.Stop();
_udp2?.Stop();
try
{
// NOTE: Linux won't receive any broadcast packets if the socket is not bound to the broadcast address.
// Windows only works if bound to localhost or the local address.
// See this discussion: https://stackoverflow.com/questions/13666789/receiving-udp-broadcast-packets-on-linux
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
_udp2 = new LdnProxyUdpServer(_protocol, LocalBroadcastAddr, DefaultPort);
}
_udp = new LdnProxyUdpServer(_protocol, LocalAddr, DefaultPort);
}
catch (Exception ex)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyUdpServer: {ex}");
return false;
}
return true;
}
public NetworkInfo[] Scan(ushort channel, ScanFilter filter)
{
_udp.ClearScanResults();
if (_protocol.SendBroadcast(_udp, LanPacketType.Scan, DefaultPort) < 0)
{
return Array.Empty<NetworkInfo>();
}
List<NetworkInfo> outNetworkInfo = new();
foreach (KeyValuePair<ulong, NetworkInfo> item in _udp.GetScanResults())
{
bool copy = true;
if (filter.Flag.HasFlag(ScanFilterFlag.LocalCommunicationId))
{
copy &= filter.NetworkId.IntentId.LocalCommunicationId == item.Value.NetworkId.IntentId.LocalCommunicationId;
}
if (filter.Flag.HasFlag(ScanFilterFlag.SessionId))
{
copy &= filter.NetworkId.SessionId.AsSpan().SequenceEqual(item.Value.NetworkId.SessionId.AsSpan());
}
if (filter.Flag.HasFlag(ScanFilterFlag.NetworkType))
{
copy &= filter.NetworkType == (NetworkType)item.Value.Common.NetworkType;
}
if (filter.Flag.HasFlag(ScanFilterFlag.Ssid))
{
Span<byte> gameSsid = item.Value.Common.Ssid.Name.AsSpan()[item.Value.Common.Ssid.Length..];
Span<byte> scanSsid = filter.Ssid.Name.AsSpan()[filter.Ssid.Length..];
copy &= gameSsid.SequenceEqual(scanSsid);
}
if (filter.Flag.HasFlag(ScanFilterFlag.SceneId))
{
copy &= filter.NetworkId.IntentId.SceneId == item.Value.NetworkId.IntentId.SceneId;
}
if (copy)
{
if (item.Value.Ldn.Nodes[0].UserName[0] != 0)
{
outNetworkInfo.Add(item.Value);
}
else
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Scan: Got empty Username. There might be a timing issue somewhere...");
}
}
}
return outNetworkInfo.ToArray();
}
protected void ResetStations()
{
lock (_lock)
{
foreach (LdnProxyTcpSession station in _stations)
{
station.Disconnect();
station.Dispose();
}
_stations.Clear();
}
}
private int LocateEmptyNode()
{
Array8<NodeInfo> nodes = NetworkInfo.Ldn.Nodes;
for (int i = 1; i < nodes.Length; i++)
{
if (nodes[i].IsConnected == 0)
{
return i;
}
}
return -1;
}
protected void UpdateNodes(bool forceUpdate = false)
{
int countConnected = 1;
foreach (LdnProxyTcpSession station in _stations.Where(station => station.IsConnected))
{
countConnected++;
station.OverrideInfo();
// NOTE: This is not part of the original implementation.
NetworkInfo.Ldn.Nodes[station.NodeId] = station.NodeInfo;
}
byte nodeCount = (byte)countConnected;
bool networkInfoChanged = forceUpdate || NetworkInfo.Ldn.NodeCount != nodeCount;
NetworkInfo.Ldn.NodeCount = nodeCount;
foreach (LdnProxyTcpSession station in _stations)
{
if (station.IsConnected)
{
if (_protocol.SendPacket(station, LanPacketType.SyncNetwork, SpanHelpers.AsSpan<NetworkInfo, byte>(ref NetworkInfo).ToArray()) < 0)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to send {LanPacketType.SyncNetwork} to station {station.NodeId}");
}
}
}
if (networkInfoChanged)
{
_parent.InvokeNetworkChange(NetworkInfo, true);
}
}
protected NodeInfo GetNodeInfo(NodeInfo node, UserConfig userConfig, ushort localCommunicationVersion)
{
uint ipAddress = NetworkHelpers.ConvertIpv4Address(LocalAddr);
node.MacAddress = GetFakeMac();
node.IsConnected = 1;
node.UserName = userConfig.UserName;
node.LocalCommunicationVersion = localCommunicationVersion;
node.Ipv4Address = ipAddress;
return node;
}
public bool CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig)
{
if (!InitTcp(true))
{
return false;
}
InitNetworkInfo();
NetworkInfo.Ldn.NodeCountMax = networkConfig.NodeCountMax;
NetworkInfo.Ldn.SecurityMode = (ushort)securityConfig.SecurityMode;
NetworkInfo.Common.Channel = networkConfig.Channel == 0 ? (ushort)6 : networkConfig.Channel;
NetworkInfo.NetworkId.SessionId = new Array16<byte>();
_random.NextBytes(NetworkInfo.NetworkId.SessionId.AsSpan());
NetworkInfo.NetworkId.IntentId = networkConfig.IntentId;
NetworkInfo.Ldn.Nodes[0] = GetNodeInfo(NetworkInfo.Ldn.Nodes[0], userConfig, networkConfig.LocalCommunicationVersion);
NetworkInfo.Ldn.Nodes[0].IsConnected = 1;
NetworkInfo.Ldn.NodeCount++;
_parent.InvokeNetworkChange(NetworkInfo, true);
return true;
}
public void DestroyNetwork()
{
if (_tcp != null)
{
try
{
_tcp.DisconnectAndStop();
}
finally
{
_tcp.Dispose();
_tcp = null;
}
}
ResetStations();
}
public NetworkError Connect(NetworkInfo networkInfo, UserConfig userConfig, uint localCommunicationVersion)
{
_apConnected.Reset();
if (networkInfo.Ldn.NodeCount == 0)
{
return NetworkError.Unknown;
}
IPAddress address = NetworkHelpers.ConvertUint(networkInfo.Ldn.Nodes[0].Ipv4Address);
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Connecting to host: {address}");
if (!InitTcp(false, address))
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Could not initialize TCPClient");
return NetworkError.ConnectNotFound;
}
if (!_tcp.Connect())
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Failed to connect.");
return NetworkError.ConnectFailure;
}
NodeInfo myNode = GetNodeInfo(new NodeInfo(), userConfig, (ushort)localCommunicationVersion);
if (_protocol.SendPacket(_tcp, LanPacketType.Connect, SpanHelpers.AsSpan<NodeInfo, byte>(ref myNode).ToArray()) < 0)
{
return NetworkError.Unknown;
}
return _apConnected.WaitOne(FailureTimeout) ? NetworkError.None : NetworkError.ConnectTimeout;
}
public void Dispose()
{
if (_initialized)
{
DisconnectAndStop();
ResetStations();
_initialized = false;
}
_protocol.Accept -= OnConnect;
_protocol.SyncNetwork -= OnSyncNetwork;
_protocol.DisconnectStation -= DisconnectStation;
}
public void DisconnectAndStop()
{
if (_udp != null)
{
try
{
_udp.Stop();
}
finally
{
_udp.Dispose();
_udp = null;
}
}
if (_udp2 != null)
{
try
{
_udp2.Stop();
}
finally
{
_udp2.Dispose();
_udp2 = null;
}
}
if (_tcp != null)
{
try
{
_tcp.DisconnectAndStop();
}
finally
{
_tcp.Dispose();
_tcp = null;
}
}
}
}
}

View File

@@ -0,0 +1,314 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
{
internal class LanProtocol
{
private const uint LanMagic = 0x11451400;
public const int BufferSize = 2048;
public const int TcpTxBufferSize = 0x800;
public const int TcpRxBufferSize = 0x1000;
public const int TxBufferSizeMax = 0x2000;
public const int RxBufferSizeMax = 0x2000;
private readonly int _headerSize = Marshal.SizeOf<LanPacketHeader>();
private readonly LanDiscovery _discovery;
public event Action<LdnProxyTcpSession> Accept;
public event Action<EndPoint, LanPacketType, byte[]> Scan;
public event Action<NetworkInfo> ScanResponse;
public event Action<NetworkInfo> SyncNetwork;
public event Action<NodeInfo, EndPoint> Connect;
public event Action<LdnProxyTcpSession> DisconnectStation;
public LanProtocol(LanDiscovery parent)
{
_discovery = parent;
}
public void InvokeAccept(LdnProxyTcpSession session)
{
Accept?.Invoke(session);
}
public void InvokeDisconnectStation(LdnProxyTcpSession session)
{
DisconnectStation?.Invoke(session);
}
private void DecodeAndHandle(LanPacketHeader header, byte[] data, EndPoint endPoint = null)
{
switch (header.Type)
{
case LanPacketType.Scan:
// UDP
if (_discovery.IsHost)
{
Scan?.Invoke(endPoint, LanPacketType.ScanResponse, SpanHelpers.AsSpan<NetworkInfo, byte>(ref _discovery.NetworkInfo).ToArray());
}
break;
case LanPacketType.ScanResponse:
// UDP
ScanResponse?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
break;
case LanPacketType.SyncNetwork:
// TCP
SyncNetwork?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
break;
case LanPacketType.Connect:
// TCP Session / Station
Connect?.Invoke(MemoryMarshal.Cast<byte, NodeInfo>(data)[0], endPoint);
break;
default:
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decode error: Unhandled type {header.Type}");
break;
}
}
public void Read(scoped ref byte[] buffer, scoped ref int bufferEnd, byte[] data, int offset, int size, EndPoint endPoint = null)
{
if (endPoint != null && _discovery.LocalAddr.Equals(((IPEndPoint)endPoint).Address))
{
return;
}
int index = 0;
while (index < size)
{
if (bufferEnd < _headerSize)
{
int copyable2 = Math.Min(size - index, Math.Min(size, _headerSize - bufferEnd));
Array.Copy(data, index + offset, buffer, bufferEnd, copyable2);
index += copyable2;
bufferEnd += copyable2;
}
if (bufferEnd >= _headerSize)
{
LanPacketHeader header = MemoryMarshal.Cast<byte, LanPacketHeader>(buffer)[0];
if (header.Magic != LanMagic)
{
bufferEnd = 0;
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"Invalid magic number in received packet. [magic: {header.Magic}] [EP: {endPoint}]");
return;
}
int totalSize = _headerSize + header.Length;
if (totalSize > BufferSize)
{
bufferEnd = 0;
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Max packet size {BufferSize} exceeded.");
return;
}
int copyable = Math.Min(size - index, Math.Min(size, totalSize - bufferEnd));
Array.Copy(data, index + offset, buffer, bufferEnd, copyable);
index += copyable;
bufferEnd += copyable;
if (totalSize == bufferEnd)
{
byte[] ldnData = new byte[totalSize - _headerSize];
Array.Copy(buffer, _headerSize, ldnData, 0, ldnData.Length);
if (header.Compressed == 1)
{
if (Decompress(ldnData, out byte[] decompressedLdnData) != 0)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error:\n {header}, {_headerSize}\n {ldnData}, {ldnData.Length}");
return;
}
if (decompressedLdnData.Length != header.DecompressLength)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})");
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{string.Join("", decompressedLdnData.Select(x => (int)x).ToArray())}'");
return;
}
ldnData = decompressedLdnData;
}
DecodeAndHandle(header, ldnData, endPoint);
bufferEnd = 0;
}
}
}
}
public int SendBroadcast(ILdnSocket s, LanPacketType type, int port)
{
return SendPacket(s, type, Array.Empty<byte>(), new IPEndPoint(_discovery.LocalBroadcastAddr, port));
}
public int SendPacket(ILdnSocket s, LanPacketType type, byte[] data, EndPoint endPoint = null)
{
byte[] buf = PreparePacket(type, data);
return s.SendPacketAsync(endPoint, buf) ? 0 : -1;
}
public int SendPacket(LdnProxyTcpSession s, LanPacketType type, byte[] data)
{
byte[] buf = PreparePacket(type, data);
return s.SendAsync(buf) ? 0 : -1;
}
private LanPacketHeader PrepareHeader(LanPacketHeader header, LanPacketType type)
{
header.Magic = LanMagic;
header.Type = type;
header.Compressed = 0;
header.Length = 0;
header.DecompressLength = 0;
header.Reserved = new Array2<byte>();
return header;
}
private byte[] PreparePacket(LanPacketType type, byte[] data)
{
LanPacketHeader header = PrepareHeader(new LanPacketHeader(), type);
header.Length = (ushort)data.Length;
byte[] buf;
if (data.Length > 0)
{
if (Compress(data, out byte[] compressed) == 0)
{
header.DecompressLength = header.Length;
header.Length = (ushort)compressed.Length;
header.Compressed = 1;
buf = new byte[compressed.Length + _headerSize];
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
compressed.CopyTo(buf, _headerSize);
}
else
{
buf = new byte[data.Length + _headerSize];
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Compressing packet data failed.");
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
data.CopyTo(buf, _headerSize);
}
}
else
{
buf = new byte[_headerSize];
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
}
return buf;
}
private int Compress(byte[] input, out byte[] output)
{
List<byte> outputList = new();
int i = 0;
int maxCount = 0xFF;
while (i < input.Length)
{
byte inputByte = input[i++];
int count = 0;
if (inputByte == 0)
{
while (i < input.Length && input[i] == 0 && count < maxCount)
{
count += 1;
i++;
}
}
if (inputByte == 0)
{
outputList.Add(0);
if (outputList.Count == BufferSize)
{
output = null;
return -1;
}
outputList.Add((byte)count);
}
else
{
outputList.Add(inputByte);
}
}
output = outputList.ToArray();
return i == input.Length ? 0 : -1;
}
private int Decompress(byte[] input, out byte[] output)
{
List<byte> outputList = new();
int i = 0;
while (i < input.Length && outputList.Count < BufferSize)
{
byte inputByte = input[i++];
outputList.Add(inputByte);
if (inputByte == 0)
{
if (i == input.Length)
{
output = null;
return -1;
}
int count = input[i++];
for (int j = 0; j < count; j++)
{
if (outputList.Count == BufferSize)
{
break;
}
outputList.Add(inputByte);
}
}
}
output = outputList.ToArray();
return i == input.Length ? 0 : -1;
}
}
}

View File

@@ -0,0 +1,104 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
using System.Net.NetworkInformation;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
{
/// <summary>
/// Client implementation for <a href="https://github.com/spacemeowx2/ldn_mitm">ldn_mitm</a>
/// </summary>
internal class LdnMitmClient : INetworkClient
{
public bool NeedsRealId => false;
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
private readonly LanDiscovery _lanDiscovery;
public LdnMitmClient(HLEConfiguration config)
{
UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2;
_lanDiscovery = new LanDiscovery(this, localIpInterface.Address, localIpInterface.IPv4Mask);
}
internal void InvokeNetworkChange(NetworkInfo info, bool connected, DisconnectReason reason = DisconnectReason.None)
{
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, connected: connected, disconnectReason: reason));
}
public NetworkError Connect(ConnectRequest request)
{
return _lanDiscovery.Connect(request.NetworkInfo, request.UserConfig, request.LocalCommunicationVersion);
}
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
{
// NOTE: This method is not implemented in ldn_mitm
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient ConnectPrivate");
return NetworkError.None;
}
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
{
return _lanDiscovery.CreateNetwork(request.SecurityConfig, request.UserConfig, request.NetworkConfig);
}
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
{
// NOTE: This method is not implemented in ldn_mitm
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient CreateNetworkPrivate");
return true;
}
public void DisconnectAndStop()
{
_lanDiscovery.DisconnectAndStop();
}
public void DisconnectNetwork()
{
_lanDiscovery.DestroyNetwork();
}
public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
{
// NOTE: This method is not implemented in ldn_mitm
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient Reject");
return ResultCode.Success;
}
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
{
return _lanDiscovery.Scan(channel, scanFilter);
}
public void SetAdvertiseData(byte[] data)
{
_lanDiscovery.SetAdvertiseData(data);
}
public void SetGameVersion(byte[] versionString)
{
// NOTE: This method is not implemented in ldn_mitm
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetGameVersion");
}
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
{
// NOTE: This method is not implemented in ldn_mitm
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetStationAcceptPolicy");
}
public void Dispose()
{
_lanDiscovery.Dispose();
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Net;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal interface ILdnSocket : IDisposable
{
bool SendPacketAsync(EndPoint endpoint, byte[] buffer);
bool Start();
bool Stop();
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal interface ILdnTcpSocket : ILdnSocket
{
bool Connect();
void DisconnectAndStop();
}
}

View File

@@ -0,0 +1,99 @@
using Ryujinx.Common.Logging;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal class LdnProxyTcpClient : NetCoreServer.TcpClient, ILdnTcpSocket
{
private readonly LanProtocol _protocol;
private byte[] _buffer;
private int _bufferEnd;
public LdnProxyTcpClient(LanProtocol protocol, IPAddress address, int port) : base(address, port)
{
_protocol = protocol;
_buffer = new byte[LanProtocol.BufferSize];
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
}
protected override void OnConnected()
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient connected!");
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size);
}
public void DisconnectAndStop()
{
DisconnectAsync();
while (IsConnected)
{
Thread.Yield();
}
}
public bool SendPacketAsync(EndPoint endPoint, byte[] data)
{
if (endPoint != null)
{
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTcpClient is sending a packet but endpoint is not null.");
}
if (IsConnecting && !IsConnected)
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPClient needs to connect before sending packets. Waiting...");
while (IsConnecting && !IsConnected)
{
Thread.Yield();
}
}
return SendAsync(data);
}
protected override void OnError(SocketError error)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPClient caught an error with code {error}");
}
protected override void Dispose(bool disposingManagedResources)
{
DisconnectAndStop();
base.Dispose(disposingManagedResources);
}
public override bool Connect()
{
// TODO: NetCoreServer has a Connect() method, but it currently leads to weird issues.
base.ConnectAsync();
while (IsConnecting)
{
Thread.Sleep(1);
}
return IsConnected;
}
public bool Start()
{
throw new InvalidOperationException("Start was called.");
}
public bool Stop()
{
throw new InvalidOperationException("Stop was called.");
}
}
}

View File

@@ -0,0 +1,54 @@
using NetCoreServer;
using Ryujinx.Common.Logging;
using System;
using System.Net;
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal class LdnProxyTcpServer : TcpServer, ILdnTcpSocket
{
private readonly LanProtocol _protocol;
public LdnProxyTcpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
{
_protocol = protocol;
OptionReuseAddress = true;
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer created a server for this address: {address}:{port}");
}
protected override TcpSession CreateSession()
{
return new LdnProxyTcpSession(this, _protocol);
}
protected override void OnError(SocketError error)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPServer caught an error with code {error}");
}
protected override void Dispose(bool disposingManagedResources)
{
Stop();
base.Dispose(disposingManagedResources);
}
public bool Connect()
{
throw new InvalidOperationException("Connect was called.");
}
public void DisconnectAndStop()
{
Stop();
}
public bool SendPacketAsync(EndPoint endpoint, byte[] buffer)
{
throw new InvalidOperationException("SendPacketAsync was called.");
}
}
}

View File

@@ -0,0 +1,83 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Net;
using System.Net.Sockets;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal class LdnProxyTcpSession : NetCoreServer.TcpSession
{
private readonly LanProtocol _protocol;
internal int NodeId;
internal NodeInfo NodeInfo;
private byte[] _buffer;
private int _bufferEnd;
public LdnProxyTcpSession(LdnProxyTcpServer server, LanProtocol protocol) : base(server)
{
_protocol = protocol;
_protocol.Connect += OnConnect;
_buffer = new byte[LanProtocol.BufferSize];
OptionSendBufferSize = LanProtocol.TcpTxBufferSize;
OptionReceiveBufferSize = LanProtocol.TcpRxBufferSize;
OptionSendBufferLimit = LanProtocol.TxBufferSizeMax;
OptionReceiveBufferLimit = LanProtocol.RxBufferSizeMax;
}
public void OverrideInfo()
{
NodeInfo.NodeId = (byte)NodeId;
NodeInfo.IsConnected = (byte)(IsConnected ? 1 : 0);
}
protected override void OnConnected()
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession connected!");
}
protected override void OnDisconnected()
{
Logger.Info?.PrintMsg(LogClass.ServiceLdn, "LdnProxyTCPSession disconnected!");
_protocol.InvokeDisconnectStation(this);
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, this.Socket.RemoteEndPoint);
}
protected override void OnError(SocketError error)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession caught an error with code {error}");
Dispose();
}
protected override void Dispose(bool disposingManagedResources)
{
_protocol.Connect -= OnConnect;
base.Dispose(disposingManagedResources);
}
private void OnConnect(NodeInfo info, EndPoint endPoint)
{
try
{
if (endPoint.Equals(this.Socket.RemoteEndPoint))
{
NodeInfo = info;
_protocol.InvokeAccept(this);
}
}
catch (System.ObjectDisposedException)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyTCPSession was disposed. [IP: {NodeInfo.Ipv4Address}]");
_protocol.InvokeDisconnectStation(this);
}
}
}
}

View File

@@ -0,0 +1,157 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
{
internal class LdnProxyUdpServer : NetCoreServer.UdpServer, ILdnSocket
{
private const long ScanFrequency = 1000;
private readonly LanProtocol _protocol;
private byte[] _buffer;
private int _bufferEnd;
private readonly object _scanLock = new();
private Dictionary<ulong, NetworkInfo> _scanResultsLast = new();
private Dictionary<ulong, NetworkInfo> _scanResults = new();
private readonly AutoResetEvent _scanResponse = new(false);
private long _lastScanTime;
public LdnProxyUdpServer(LanProtocol protocol, IPAddress address, int port) : base(address, port)
{
_protocol = protocol;
_protocol.Scan += HandleScan;
_protocol.ScanResponse += HandleScanResponse;
_buffer = new byte[LanProtocol.BufferSize];
OptionReuseAddress = true;
OptionReceiveBufferSize = LanProtocol.RxBufferSizeMax;
OptionSendBufferSize = LanProtocol.TxBufferSizeMax;
Start();
}
protected override Socket CreateSocket()
{
return new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
{
EnableBroadcast = true,
};
}
protected override void OnStarted()
{
ReceiveAsync();
}
protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size)
{
_protocol.Read(ref _buffer, ref _bufferEnd, buffer, (int)offset, (int)size, endpoint);
ReceiveAsync();
}
protected override void OnError(SocketError error)
{
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"LdnProxyUdpServer caught an error with code {error}");
}
protected override void Dispose(bool disposingManagedResources)
{
_protocol.Scan -= HandleScan;
_protocol.ScanResponse -= HandleScanResponse;
_scanResponse.Dispose();
base.Dispose(disposingManagedResources);
}
public bool SendPacketAsync(EndPoint endpoint, byte[] data)
{
return SendAsync(endpoint, data);
}
private void HandleScan(EndPoint endpoint, LanPacketType type, byte[] data)
{
_protocol.SendPacket(this, type, data, endpoint);
}
private void HandleScanResponse(NetworkInfo info)
{
Span<byte> mac = stackalloc byte[8];
info.Common.MacAddress.AsSpan().CopyTo(mac);
lock (_scanLock)
{
_scanResults[BitConverter.ToUInt64(mac)] = info;
_scanResponse.Set();
}
}
public void ClearScanResults()
{
// Rate limit scans.
long timeMs = Stopwatch.GetTimestamp() / (Stopwatch.Frequency / 1000);
long delay = ScanFrequency - (timeMs - _lastScanTime);
if (delay > 0)
{
Thread.Sleep((int)delay);
}
_lastScanTime = timeMs;
lock (_scanLock)
{
var newResults = _scanResultsLast;
newResults.Clear();
_scanResultsLast = _scanResults;
_scanResults = newResults;
_scanResponse.Reset();
}
}
public Dictionary<ulong, NetworkInfo> GetScanResults()
{
// NOTE: Try to minimize waiting time for scan results.
// After we receive the first response, wait a short time for follow-ups and return.
// Responses that were too late to catch will appear in the next scan.
// ldn_mitm does not do this, but this improves latency for games that expect it to be low (it is on console).
if (_scanResponse.WaitOne(1000))
{
// Wait a short while longer in case there are some other responses.
Thread.Sleep(33);
}
lock (_scanLock)
{
var results = new Dictionary<ulong, NetworkInfo>();
foreach (KeyValuePair<ulong, NetworkInfo> last in _scanResultsLast)
{
results[last.Key] = last.Value;
}
foreach (KeyValuePair<ulong, NetworkInfo> scan in _scanResults)
{
results[scan.Key] = scan.Value;
}
return results;
}
}
}
}

View File

@@ -0,0 +1,16 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types
{
[StructLayout(LayoutKind.Sequential, Size = 12)]
internal struct LanPacketHeader
{
public uint Magic;
public LanPacketType Type;
public byte Compressed;
public ushort Length;
public ushort DecompressLength;
public Array2<byte> Reserved;
}
}

View File

@@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types
{
internal enum LanPacketType : byte
{
Scan,
ScanResponse,
Connect,
SyncNetwork,
}
}

View File

@@ -1,7 +1,7 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
{
class NetworkChangeEventArgs : EventArgs
{

View File

@@ -1,7 +1,6 @@
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using System;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
@@ -22,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
_parent.NetworkClient.NetworkChange += NetworkChanged;
}
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
{
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);

View File

@@ -1,7 +1,7 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0xBC)]
struct ConnectPrivateRequest

View File

@@ -1,7 +1,7 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x4FC)]
struct ConnectRequest

View File

@@ -1,7 +1,7 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
/// <remarks>
/// Advertise data is appended separately (remaining data in the buffer).

View File

@@ -1,7 +1,7 @@
using Ryujinx.HLE.HOS.Services.Ldn.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
/// <remarks>
/// Advertise data is appended separately (remaining data in the buffer).

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
enum NetworkError : int
{

View File

@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x4)]
struct NetworkErrorMessage

View File

@@ -39,6 +39,8 @@ namespace Ryujinx.HLE.HOS.Services
private readonly KernelContext _context;
private KProcess _selfProcess;
private KThread _selfThread;
private KEvent _wakeEvent;
private int _wakeHandle = 0;
private readonly ReaderWriterLockSlim _handleLock = new();
private readonly Dictionary<int, IpcService> _sessions = new();
@@ -125,6 +127,8 @@ namespace Ryujinx.HLE.HOS.Services
_handleLock.ExitWriteLock();
}
}
_wakeEvent.WritableEvent.Signal();
}
private IpcService GetSessionObj(int serverSessionHandle)
@@ -187,6 +191,9 @@ namespace Ryujinx.HLE.HOS.Services
AddPort(serverPortHandle, SmObjectFactory);
}
_wakeEvent = new KEvent(_context);
Result result = _selfProcess.HandleTable.GenerateHandle(_wakeEvent.ReadableEvent, out _wakeHandle);
InitDone.Set();
ulong messagePtr = _selfThread.TlsAddress;
@@ -195,7 +202,6 @@ namespace Ryujinx.HLE.HOS.Services
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
int replyTargetHandle = 0;
while (true)
@@ -211,13 +217,15 @@ namespace Ryujinx.HLE.HOS.Services
portHandleCount = _ports.Count;
handleCount = portHandleCount + _sessions.Count;
handleCount = portHandleCount + _sessions.Count + 1;
handles = ArrayPool<int>.Shared.Rent(handleCount);
_ports.Keys.CopyTo(handles, 0);
handles[0] = _wakeHandle;
_sessions.Keys.CopyTo(handles, portHandleCount);
_ports.Keys.CopyTo(handles, 1);
_sessions.Keys.CopyTo(handles, portHandleCount + 1);
}
finally
{
@@ -227,8 +235,7 @@ namespace Ryujinx.HLE.HOS.Services
}
}
// We still need a timeout here to allow the service to pick up and listen new sessions...
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, -1);
_selfThread.HandlePostSyscall();
@@ -239,7 +246,7 @@ namespace Ryujinx.HLE.HOS.Services
replyTargetHandle = 0;
if (rc == Result.Success && signaledIndex >= portHandleCount)
if (rc == Result.Success && signaledIndex >= portHandleCount + 1)
{
// We got a IPC request, process it, pass to the appropriate service if needed.
int signaledHandle = handles[signaledIndex];
@@ -253,24 +260,32 @@ namespace Ryujinx.HLE.HOS.Services
{
if (rc == Result.Success)
{
// We got a new connection, accept the session to allow servicing future requests.
if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
if (signaledIndex > 0)
{
bool handleWriteLockTaken = false;
try
// We got a new connection, accept the session to allow servicing future requests.
if (_context.Syscall.AcceptSession(out int serverSessionHandle, handles[signaledIndex]) == Result.Success)
{
handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite);
IpcService obj = _ports[handles[signaledIndex]].Invoke();
_sessions.Add(serverSessionHandle, obj);
}
finally
{
if (handleWriteLockTaken)
bool handleWriteLockTaken = false;
try
{
_handleLock.ExitWriteLock();
handleWriteLockTaken = _handleLock.TryEnterWriteLock(Timeout.Infinite);
IpcService obj = _ports[handles[signaledIndex]].Invoke();
_sessions.Add(serverSessionHandle, obj);
}
finally
{
if (handleWriteLockTaken)
{
_handleLock.ExitWriteLock();
}
}
}
}
else
{
// The _wakeEvent signalled, which means we have a new session.
_wakeEvent.WritableEvent.Clear();
}
}
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
@@ -499,6 +514,8 @@ namespace Ryujinx.HLE.HOS.Services
if (Interlocked.Exchange(ref _isDisposed, 1) == 0)
{
_selfProcess.HandleTable.CloseHandle(_wakeHandle);
foreach (IpcService service in _sessions.Values)
{
(service as IDisposable)?.Dispose();

View File

@@ -20,7 +20,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage)
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
where TFormat : IPartitionFileSystemFormat
where THeader : unmanaged, IPartitionFileSystemHeader
where TEntry : unmanaged, IPartitionFileSystemEntry
{
errorMessage = null;
@@ -91,7 +95,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
if (File.Exists(updatePath))
{
PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage());
PartitionFileSystem updatePartitionFileSystem = new();
updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure();
device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);

View File

@@ -69,7 +69,8 @@ namespace Ryujinx.HLE.Loaders.Processes
public bool LoadNsp(string path)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
PartitionFileSystem partitionFileSystem = new(file.AsStorage());
PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);

View File

@@ -1,8 +1,8 @@
using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Ns;
@@ -33,7 +33,7 @@ namespace Ryujinx.HLE.Loaders.Processes
// TODO: Remove this workaround when ASLR is implemented.
private const ulong CodeStartOffset = 0x500000UL;
public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem)
public static LibHac.Result RegisterProgramMapInfo(Switch device, IFileSystem partitionFileSystem)
{
ulong applicationId = 0;
int programCount = 0;

View File

@@ -27,6 +27,7 @@
<PackageReference Include="SixLabors.ImageSharp" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="NetCoreServer" />
</ItemGroup>
<!-- Due to Concentus. -->

View File

@@ -59,16 +59,16 @@ namespace Ryujinx.Input.Assigner
return _gamepad == null || !_gamepad.IsConnected;
}
public ButtonValue? GetPressedButton()
public string GetPressedButton()
{
IEnumerable<GamepadButtonInputId> pressedButtons = _detector.GetPressedButtons();
if (pressedButtons.Any())
{
return !_forStick ? new(pressedButtons.First()) : new(((StickInputId)pressedButtons.First()));
return !_forStick ? pressedButtons.First().ToString() : ((StickInputId)pressedButtons.First()).ToString();
}
return null;
return "";
}
private void CollectButtonStats()

View File

@@ -31,6 +31,6 @@ namespace Ryujinx.Input.Assigner
/// Get the pressed button that was read in <see cref="ReadInput"/> by the button assigner.
/// </summary>
/// <returns>The pressed button that was read</returns>
ButtonValue? GetPressedButton();
string GetPressedButton();
}
}

View File

@@ -23,7 +23,7 @@ namespace Ryujinx.Input.Assigner
public bool HasAnyButtonPressed()
{
return GetPressedButton() is not null;
return GetPressedButton().Length != 0;
}
public bool ShouldCancel()
@@ -31,20 +31,20 @@ namespace Ryujinx.Input.Assigner
return _keyboardState.IsPressed(Key.Escape);
}
public ButtonValue? GetPressedButton()
public string GetPressedButton()
{
ButtonValue? keyPressed = null;
string keyPressed = "";
for (Key key = Key.Unknown; key < Key.Count; key++)
{
if (_keyboardState.IsPressed(key))
{
keyPressed = new(key);
keyPressed = key.ToString();
break;
}
}
return !ShouldCancel() ? keyPressed : null;
return !ShouldCancel() ? keyPressed : "";
}
}
}

View File

@@ -1,48 +0,0 @@
using System.Diagnostics;
namespace Ryujinx.Input
{
public enum ButtonValueType { Key, GamepadButtonInputId, StickId }
public readonly struct ButtonValue
{
private readonly ButtonValueType _type;
private readonly uint _rawValue;
public ButtonValue(Key key)
{
_type = ButtonValueType.Key;
_rawValue = (uint)key;
}
public ButtonValue(GamepadButtonInputId gamepad)
{
_type = ButtonValueType.GamepadButtonInputId;
_rawValue = (uint)gamepad;
}
public ButtonValue(StickInputId stick)
{
_type = ButtonValueType.StickId;
_rawValue = (uint)stick;
}
public Common.Configuration.Hid.Key AsKey()
{
Debug.Assert(_type == ButtonValueType.Key);
return (Common.Configuration.Hid.Key)_rawValue;
}
public Common.Configuration.Hid.Controller.GamepadInputId AsGamepadButtonInputId()
{
Debug.Assert(_type == ButtonValueType.GamepadButtonInputId);
return (Common.Configuration.Hid.Controller.GamepadInputId)_rawValue;
}
public Common.Configuration.Hid.Controller.StickInputId AsGamepadStickId()
{
Debug.Assert(_type == ButtonValueType.StickId);
return (Common.Configuration.Hid.Controller.StickInputId)_rawValue;
}
}
}

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Ui.App.Common
if (extension is ".nsp" or ".xci")
{
PartitionFileSystem pfs;
IFileSystem pfs;
if (extension == ".xci")
{
@@ -75,7 +75,9 @@ namespace Ryujinx.Ui.App.Common
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
}
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))

View File

@@ -174,7 +174,7 @@ namespace Ryujinx.Ui.App.Common
{
try
{
PartitionFileSystem pfs;
IFileSystem pfs;
bool isExeFs = false;
@@ -186,7 +186,9 @@ namespace Ryujinx.Ui.App.Common
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
bool hasMainNca = false;
@@ -500,7 +502,7 @@ namespace Ryujinx.Ui.App.Common
ApplicationCountUpdated?.Invoke(null, e);
}
private void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
private void GetControlFsAndTitleId(IFileSystem pfs, out IFileSystem controlFs, out string titleId)
{
(_, _, Nca controlNca) = GetGameData(_virtualFileSystem, pfs, 0);
@@ -563,7 +565,7 @@ namespace Ryujinx.Ui.App.Common
{
try
{
PartitionFileSystem pfs;
IFileSystem pfs;
bool isExeFs = false;
@@ -575,7 +577,9 @@ namespace Ryujinx.Ui.App.Common
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
{
@@ -827,7 +831,7 @@ namespace Ryujinx.Ui.App.Common
return false;
}
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, IFileSystem pfs, int programIndex)
{
Nca mainNca = null;
Nca patchNca = null;
@@ -931,7 +935,8 @@ namespace Ryujinx.Ui.App.Common
if (File.Exists(updatePath))
{
FileStream file = new(updatePath, FileMode.Open, FileAccess.Read);
PartitionFileSystem nsp = new(file.AsStorage());
PartitionFileSystem nsp = new();
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
}

View File

@@ -571,6 +571,7 @@ namespace Ryujinx.Ui.Common.Configuration
{
LanInterfaceId = new ReactiveObject<string>();
Mode = new ReactiveObject<MultiplayerMode>();
Mode.Event += static (_, e) => LogValueChange(e, nameof(MultiplayerMode));
}
}

View File

@@ -1121,6 +1121,14 @@ namespace Ryujinx.Ui
Graphics.Gpu.GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
}
public void UpdateInternetAccess()
{
if (_gameLoaded)
{
_emulationContext.Configuration.EnableInternetAccess = ConfigurationState.Instance.System.EnableInternetAccess.Value;
}
}
public static void SaveConfig()
{
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);

View File

@@ -211,7 +211,7 @@ namespace Ryujinx.Ui.Widgets
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
{
PartitionFileSystem pfs;
IFileSystem pfs;
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
{
@@ -221,7 +221,9 @@ namespace Ryujinx.Ui.Widgets
}
else
{
pfs = new PartitionFileSystem(file.AsStorage());
var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
}
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))

View File

@@ -893,7 +893,7 @@ namespace Ryujinx.Ui.Windows
}
}
string pressedButton = assigner.GetPressedButton().ToString();
string pressedButton = assigner.GetPressedButton();
Application.Invoke(delegate
{

View File

@@ -88,7 +88,8 @@ namespace Ryujinx.Ui.Windows
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
PartitionFileSystem pfs = new(containerFile.AsStorage());
PartitionFileSystem pfs = new();
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(pfs);
@@ -153,7 +154,8 @@ namespace Ryujinx.Ui.Windows
using FileStream containerFile = File.OpenRead(containerPath);
PartitionFileSystem pfs = new(containerFile.AsStorage());
PartitionFileSystem pfs = new();
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
bool containsDlc = false;
_virtualFileSystem.ImportTickets(pfs);

View File

@@ -671,6 +671,8 @@ namespace Ryujinx.Ui.Windows
}
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
_parent.UpdateInternetAccess();
MainWindow.UpdateGraphicsConfig();
ThemeHelper.ApplyTheme();
}

View File

@@ -2993,6 +2993,7 @@
<property name="active-id">Disabled</property>
<items>
<item id="Disabled" translatable="yes">Disabled</item>
<item id="LdnMitm" translatable="yes">ldn_mitm</item>
</items>
</object>
<packing>
@@ -3064,7 +3065,7 @@
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Network Interface:</property>
</object>
@@ -3079,7 +3080,7 @@
<object class="GtkComboBoxText" id="_multiLanSelect">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN features</property>
<property name="tooltip-text" translatable="yes">The network interface used for LAN/LDN features</property>
<property name="active-id">0</property>
<items>
<item id="0" translatable="yes">Default</item>

View File

@@ -90,7 +90,8 @@ namespace Ryujinx.Ui.Windows
{
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
PartitionFileSystem nsp = new(file.AsStorage());
PartitionFileSystem nsp = new();
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
try
{