Compare commits

..

29 Commits

Author SHA1 Message Date
IvonWei
300a120a5c Merge 9356b68f26 into ad3e80b383 2025-01-21 09:19:24 +08:00
madwind
9356b68f26 Add a '*' to label the virtual controller. 2025-01-13 08:41:32 +08:00
IvonWei
14aafebaa6 Merge branch 'Ryubing:master' into master 2025-01-13 08:33:30 +08:00
Evan Husted
4518666a04 Merge branch 'master' into master 2025-01-10 21:39:41 -06:00
madwind
4399edaa9f Fix typo: SQL_JOYBATTERYUPDATED => SDL_JOYBATTERYUPDATED 2025-01-02 11:50:20 +08:00
Evan Husted
4e77bcb55a Merge branch 'master' into master 2025-01-01 21:19:02 -06:00
IvonWei
3cbd7dc1a1 Merge branch 'Ryubing:master' into master 2024-12-31 19:21:10 +08:00
IvonWei
536f792558 Merge branch 'Ryubing:master' into master 2024-12-30 22:04:08 +08:00
madwind
7a451ab160 fix GetMappedStateSnapshot 2024-12-30 22:01:21 +08:00
IvonWei
99c7c3fb14 Merge branch 'Ryubing:master' into master 2024-12-29 18:37:03 +08:00
Evan Husted
09e7b660f4 Merge branch 'master' into master 2024-12-29 03:42:20 -06:00
madwind
69dfd8c60e fix right Stick 2024-12-29 02:11:31 +08:00
madwind
8e50dd9fa6 fix right JoyCon stick 2024-12-29 01:49:25 +08:00
madwind
68c03051ad For the JoyCon controller, wrap SDL2Gamepad as SDL2JoyCon to use a suitable layout and correct the motion sensing and joystick orientation. 2024-12-29 00:56:03 +08:00
Evan Husted
a837294b11 Merge branch 'master' into master 2024-12-28 06:01:06 -06:00
IvonWei
f1c0cc8076 Merge branch 'Ryubing:master' into master 2024-12-28 09:09:10 +08:00
madwind
6dec7ff8ba fix motionData 2024-12-28 09:07:22 +08:00
madwind
20fdbff964 clean log 2024-12-26 14:47:40 +08:00
IvonWei
e426680cb0 Merge branch 'GreemDev:master' into master 2024-12-26 11:58:50 +08:00
madwind
7863e97cb0 invoke OnGamepadConnected and OnGamepadDisconnected 2024-12-26 11:58:00 +08:00
madwind
c4dea0ee28 add SQL_JOYBATTERYUPDATED , OnJoyBatteryUpdated 2024-12-26 11:54:52 +08:00
IvonWei
e0b6a01e9d Merge branch 'GreemDev:master' into master 2024-12-25 17:00:53 +08:00
madwind
e509ffa716 delay 2000ms before ShowPowerLevel 2024-12-25 16:57:36 +08:00
IvonWei
714c68b548 Merge branch 'GreemDev:master' into master 2024-12-25 10:41:20 +08:00
madwind
fec197d9ec log powerLevel 2024-12-25 10:39:07 +08:00
IvonWei
a4b2feef79 Merge branch 'GreemDev:master' into master 2024-12-23 22:13:47 +08:00
IvonWei
ad7d9d1ce0 Update NpadController.cs
add ?
2024-12-23 18:55:49 +08:00
IvonWei
86f9544910 Update NpadController.cs
back to Debug
2024-12-23 18:54:11 +08:00
madwind
e9ecbd44fc Add a virtual controller to merge Joy-Cons. 2024-12-23 17:57:55 +08:00
34 changed files with 851 additions and 317 deletions

View File

@@ -202,7 +202,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@@ -183,7 +183,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

View File

@@ -2681,7 +2681,7 @@
01005EA01C0FC000,"SONIC X SHADOW GENERATIONS",crash,ingame,2025-01-07 04:20:45
010064F00C212000,"Soul Axiom Rebooted",nvdec;slow,ingame,2020-09-04 12:41:01
0100F2100F0B2000,"Soul Searching",,playable,2020-07-09 18:39:07
01008F2005154000,"South Park™: The Fractured but Whole™ - Standard Edition",slow;online-broken;vulkan-backend-bug;gpu,ingame,2025-01-21 17:35:10
01008F2005154000,"South Park™: The Fractured but Whole™ - Standard Edition",slow;online-broken,playable,2024-07-08 17:47:28
0100B9F00C162000,"Space Blaze",,playable,2020-08-30 16:18:05
010005500E81E000,"Space Cows",UE4;crash,menus,2020-06-15 11:33:20
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
1 title_id game_name labels status last_updated
2681 01005EA01C0FC000 SONIC X SHADOW GENERATIONS crash ingame 2025-01-07 04:20:45
2682 010064F00C212000 Soul Axiom Rebooted nvdec;slow ingame 2020-09-04 12:41:01
2683 0100F2100F0B2000 Soul Searching playable 2020-07-09 18:39:07
2684 01008F2005154000 South Park™: The Fractured but Whole™ - Standard Edition slow;online-broken;vulkan-backend-bug;gpu slow;online-broken ingame playable 2025-01-21 17:35:10 2024-07-08 17:47:28
2685 0100B9F00C162000 Space Blaze playable 2020-08-30 16:18:05
2686 010005500E81E000 Space Cows UE4;crash menus 2020-06-15 11:33:20
2687 0100707011722000 Space Elite Force playable 2020-11-27 15:21:05

View File

@@ -14,7 +14,6 @@ namespace Ryujinx.Common
{
switch (currentBackend)
{
case GraphicsBackend.Metal when !OperatingSystem.IsMacOS():
case GraphicsBackend.OpenGl when OperatingSystem.IsMacOS():
return GraphicsBackend.Vulkan;
case GraphicsBackend.Vulkan or GraphicsBackend.OpenGl or GraphicsBackend.Metal:
@@ -29,28 +28,18 @@ namespace Ryujinx.Common
public static readonly string[] GreatMetalTitles =
[
"010076f0049a2000", // Bayonetta
"01006f8002326000", // Animal Crossings: New Horizons
"01009bf0072d4000", // Captain Toad: Treasure Tracker
"0100a5c00d162000", // Cuphead
"010023800d64a000", // Deltarune
"01003a30012c0000", // LEGO City Undercover
"010028600EBDA000", // Mario 3D World
"0100152000022000", // Mario Kart 8 Deluxe
"010075a016a3a000", // Persona 4 Arena Ultimax
"0100187003A36000", // Pokémon: Let's Go, Eevee!
"01005CA01580E000", // Persona 5
"0100187003A36000", // Pokémon: Let's Go, Evoli!
"010003f003a34000", // Pokémon: Let's Go, Pikachu!
"01008C0016544000", // Sea of Stars
"01006A800016E000", // Smash Ultimate
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
// These ones have small issues, but those happen on Vulkan as well:
"01006f8002326000", // Animal Crossings: New Horizons
"01009bf0072d4000", // Captain Toad: Treasure Tracker
"01009510001ca000", // Fast RMX
"01005CA01580E000", // Persona 5 Royale
"0100000000010000", // Super Mario Odyssey
//Isaac claims it has a issue in level 2, but I am not able to replicate it on my M3. More testing would be appreciated:
"010015100b514000", // Super Mario Bros. Wonder
"0100000000010000", // Super Mario Odyessy
];
public static string GetDiscordGameAsset(string titleId)
@@ -58,87 +47,72 @@ namespace Ryujinx.Common
public static readonly string[] DiscordGameAssetKeys =
[
//All games are in Alphabetical order by Game name.
//Dragon Quest Franchise
"010008900705c000", // Dragon Quest Builders
"010042000a986000", // Dragon Quest Builders 2
//Fire Emblem Franchise
"0100a6301214e000", // Fire Emblem Engage
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
"010055d009f78000", // Fire Emblem: Three Houses
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
"0100a6301214e000", // Fire Emblem Engage
"0100f15003e64000", // Fire Emblem Warriors
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
//Kirby Franchise
"01004d300c5ae000", // Kirby and the Forgotten Land
"0100a8e016236000", // Kirby's Dream Buffet
"0100227010460000", // Kirby Fighters 2
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
"01007e3006dda000", // Kirby Star Allies
"01004d300c5ae000", // Kirby and the Forgotten Land
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
"01003fb00c5a8000", // Super Kirby Clash
//The Zelda Franchise
"01000b900d8b0000", // Cadence of Hyrule
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"0100227010460000", // Kirby Fighters 2
"0100a8e016236000", // Kirby's Dream Buffet
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
"01000b900d8b0000", // Cadence of Hyrule
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
//Luigi Franchise
"010048701995e000", // Luigi's Mansion 2 HD
"0100dca0064a6000", // Luigi's Mansion 3
//Metroid Franchise
"010093801237c000", // Metroid Dread
"010012101468c000", // Metroid Prime Remastered
//Monster Hunter Franchise
"0100770008dd8000", // Monster Hunter Generations Ultimate
"0100b04011742000", // Monster Hunter Rise
//Mario Franchise
"0100000000010000", // SUPER MARIO ODYSSEY
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"01009b90006dc000", // Super Mario Maker 2
"010049900f546000", // Super Mario 3D All-Stars
"010049900F546001", // ^ 64
"010049900F546002", // ^ Sunshine
"010049900F546003", // ^ Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010015100b514000", // Super Mario Bros. Wonder
"0100152000022000", // Mario Kart 8 Deluxe
"010036b0034e4000", // Super Mario Party
"01006fe013472000", // Mario Party Superstars
"0100965017338000", // Super Mario Party Jamboree
"01006d0017f7a000", // Mario & Luigi: Brothership
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"010067300059a000", // Mario + Rabbids: Kingdom Battle
"0100317013770000", // Mario + Rabbids: Sparks of Hope
"0100c9c00e25c000", // Mario Golf: Super Rush
"0100152000022000", // Mario Kart 8 Deluxe
"01006fe013472000", // Mario Party Superstars
"010019401051c000", // Mario Strikers: Battle League
"0100bde00862a000", // Mario Tennis Aces
"0100b99019412000", // Mario vs. Donkey Kong
"010049900f546000", // Super Mario 3D All-Stars
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010049900F546001", // Super Mario 64
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"010015100b514000", // Super Mario Bros. Wonder
"010049900F546003", // Super Mario Galaxy
"01009b90006dc000", // Super Mario Maker 2
"0100000000010000", // SUPER MARIO ODYSSEY
"010036b0034e4000", // Super Mario Party
"0100965017338000", // Super Mario Party Jamboree
"0100bc0018138000", // Super Mario RPG
"010049900F546002", // Super Mario Sunshine
"0100a3900c3e2000", // Paper Mario: The Origami King
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"0100bc0018138000", // Super Mario RPG
"0100bde00862a000", // Mario Tennis Aces
"0100c9c00e25c000", // Mario Golf: Super Rush
"010019401051c000", // Mario Strikers: Battle League
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"0100b99019412000", // Mario vs. Donkey Kong
//Pikmin Franchise
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin 4
//The Pokémon Franchise
"0100f4300bf2c000", // New Pokémon Snap
"0100000011d90000", // Pokémon Brilliant Diamond
"01001f5010dfa000", // Pokémon Legends: Arceus
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100187003a36000", // Pokémon: Let's Go Eevee!
"01003d200baa2000", // Pokémon Mystery Dungeon - Rescue Team DX
"0100a3d008c5c000", // Pokémon Scarlet
"01008db008c2c000", // Pokémon Shield
@@ -146,29 +120,24 @@ namespace Ryujinx.Common
"0100abf008968000", // Pokémon Sword
"01008f6008c5e000", // Pokémon Violet
"0100b3f000be2000", // Pokkén Tournament DX
"0100187003a36000", // Pokémon: Let's Go Eevee!
"010003f003a34000", // Pokémon: Let's Go Pikachu!
//Splatoon Franchise
"01003bc0000a0000", // Splatoon 2 (US)
"0100f8f0000a2000", // Splatoon 2 (EU)
"01003c700009c000", // Splatoon 2 (JP)
"01003bc0000a0000", // Splatoon 2 (US)
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
//NSO Membership games
"010040600c5ce000", // Tetris 99
"0100277011f1a000", // Super Mario Bros. 35
"0100ad9012510000", // PAC-MAN 99
"0100ccf019c8c000", // F-ZERO 99
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100d870045b6000", // NES - Nintendo Switch Online
"0100ad9012510000", // PAC-MAN 99
"010040600c5ce000", // Tetris 99
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100277011f1a000", // Super Mario Bros. 35
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
//Misc Nintendo 1st party games
"01000320000cc000", // 1-2 Switch
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
"01006f8002326000", // Animal Crossing: New Horizons
@@ -183,32 +152,27 @@ namespace Ryujinx.Common
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
//Bayonetta Franchise
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
//Persona Franchise
"0100dcd01525a000", // Persona 3 Portable
"010075a016a3a000", // Persona 4 Arena Ultimax
"010062b01525c000", // Persona 4 Golden
"010075a016a3a000", // Persona 4 Arena Ultimax
"01005ca01580e000", // Persona 5 Royal
"0100801011c3e000", // Persona 5 Strikers
"010087701b092000", // Persona 5 Tactica
//Sonic Franchise
"01004ad014bf0000", // Sonic Frontiers
"01009aa000faa000", // Sonic Mania
"01004ad014bf0000", // Sonic Frontiers
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
"01005ea01c0fc001", // ^
//Xenoblade Franchise
"0100ff500e34a000", // Xenoblade Chronicles - Definitive Edition
"0100e95004038000", // Xenoblade Chronicles 2
"010074f013262000", // Xenoblade Chronicles 3
//Misc Games
"010056e00853a000", // A Hat in Time
"0100fd1014726000", // Baldurs Gate: Dark Alliance
"0100dbf01000a000", // Burnout Paradise Remastered
@@ -221,6 +185,8 @@ namespace Ryujinx.Common
"01003620068ea000", // Hand of Fate 2
"010085500130a000", // Lego City: Undercover
"010073c01af34000", // LEGO Horizon Adventures
"0100770008dd8000", // Monster Hunter Generations Ultimate
"0100b04011742000", // Monster Hunter Rise
"0100853015e86000", // No Man's Sky
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2

View File

@@ -118,7 +118,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
private static string GetDiskCachePath()
{
return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId.ToLower(), "cache", "shader")
? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader")
: null;
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Logging;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Generic;
@@ -36,6 +37,7 @@ namespace Ryujinx.Input.SDL2
SDL2Driver.Instance.Initialize();
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
// Add already connected gamepads
int numJoysticks = SDL_NumJoysticks();
@@ -83,19 +85,30 @@ namespace Ryujinx.Input.SDL2
private void HandleJoyStickDisconnected(int joystickInstanceId)
{
bool joyConPairDisconnected = false;
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
return;
lock (_lock)
{
_gamepadsIds.Remove(id);
if (!SDL2JoyConPair.IsCombinable(_gamepadsIds))
{
_gamepadsIds.Remove(SDL2JoyConPair.Id);
joyConPairDisconnected = true;
}
}
OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected)
{
OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id);
}
}
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
{
bool joyConPairConnected = false;
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
{
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
@@ -120,13 +133,29 @@ namespace Ryujinx.Input.SDL2
_gamepadsIds.Insert(joystickDeviceId, id);
else
_gamepadsIds.Add(id);
if (SDL2JoyConPair.IsCombinable(_gamepadsIds))
{
_gamepadsIds.Remove(SDL2JoyConPair.Id);
_gamepadsIds.Add(SDL2JoyConPair.Id);
joyConPairConnected = true;
}
}
OnGamepadConnected?.Invoke(id);
if (joyConPairConnected)
{
OnGamepadConnected?.Invoke(SDL2JoyConPair.Id);
}
}
}
}
private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel)
{
Logger.Info?.Print(LogClass.Hid,
$"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}");
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
@@ -157,6 +186,14 @@ namespace Ryujinx.Input.SDL2
public IGamepad GetGamepad(string id)
{
if (id == SDL2JoyConPair.Id)
{
lock (_lock)
{
return SDL2JoyConPair.GetGamepad(_gamepadsIds);
}
}
int joystickIndex = GetJoystickIndexByGamepadId(id);
if (joystickIndex == -1)
@@ -165,12 +202,16 @@ namespace Ryujinx.Input.SDL2
}
nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
if (gamepadHandle == nint.Zero)
{
return null;
}
if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix))
{
return new SDL2JoyCon(gamepadHandle, id);
}
return new SDL2Gamepad(gamepadHandle, id);
}
}

View File

@@ -0,0 +1,409 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using static SDL2.SDL;
namespace Ryujinx.Input.SDL2
{
internal class SDL2JoyCon : IGamepad
{
private bool HasConfiguration => _configuration != null;
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private StandardControllerInputConfig _configuration;
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _leftButtonsDriverMapping = new()
{
{ GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK },
{GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
{GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
{GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
{GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
{GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
{GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2},
{GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4},
{GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
};
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _rightButtonsDriverMapping = new()
{
{GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK},
{GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B},
{GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y},
{GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A},
{GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X},
{GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START},
{GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1},
{GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3},
{GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}
};
private readonly Dictionary<GamepadButtonInputId, SDL_GameControllerButton> _buttonsDriverMapping;
private readonly Lock _userMappingLock = new();
private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly StickInputId[] _stickUserMapping = new StickInputId[(int)StickInputId.Count]
{
StickInputId.Unbound, StickInputId.Left, StickInputId.Right,
};
public GamepadFeaturesFlag Features { get; }
private nint _gamepadHandle;
private enum JoyConType
{
Left, Right
}
public const string Prefix = "Nintendo Switch Joy-Con";
public const string LeftName = "Nintendo Switch Joy-Con (L)";
public const string RightName = "Nintendo Switch Joy-Con (R)";
private readonly JoyConType _joyConType;
public SDL2JoyCon(nint gamepadHandle, string driverId)
{
_gamepadHandle = gamepadHandle;
_buttonsUserMapping = new List<ButtonMappingEntry>(10);
Name = SDL_GameControllerName(_gamepadHandle);
Id = driverId;
Features = GetFeaturesFlag();
// Enable motion tracking
if (Features.HasFlag(GamepadFeaturesFlag.Motion))
{
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL,
SDL_bool.SDL_TRUE) != 0)
{
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
}
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO,
SDL_bool.SDL_TRUE) != 0)
{
Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
}
}
switch (Name)
{
case LeftName:
{
_buttonsDriverMapping = _leftButtonsDriverMapping;
_joyConType = JoyConType.Left;
break;
}
case RightName:
{
_buttonsDriverMapping = _rightButtonsDriverMapping;
_joyConType = JoyConType.Right;
break;
}
}
}
private GamepadFeaturesFlag GetFeaturesFlag()
{
GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE &&
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE)
{
result |= GamepadFeaturesFlag.Motion;
}
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100);
if (error == 0)
{
result |= GamepadFeaturesFlag.Rumble;
}
return result;
}
public string Id { get; }
public string Name { get; }
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE;
protected virtual void Dispose(bool disposing)
{
if (disposing && _gamepadHandle != nint.Zero)
{
SDL_GameControllerClose(_gamepadHandle);
_gamepadHandle = nint.Zero;
}
}
public void Dispose()
{
Dispose(true);
}
public void SetTriggerThreshold(float triggerThreshold)
{
}
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble))
return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (durationMs == uint.MaxValue)
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) !=
0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
else if (durationMs > SDL_HAPTIC_INFINITY)
{
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
}
else
{
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
public Vector3 GetMotionData(MotionInputId inputId)
{
SDL_SensorType sensorType = inputId switch
{
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
_ => SDL_SensorType.SDL_SENSOR_INVALID
};
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
return Vector3.Zero;
const int ElementCount = 3;
unsafe
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount);
if (result != 0)
return Vector3.Zero;
Vector3 value = _joyConType switch
{
JoyConType.Left => new Vector3(-values[2], values[1], values[0]),
JoyConType.Right => new Vector3(values[2], values[1], -values[0])
};
return inputId switch
{
MotionInputId.Gyroscope => RadToDegree(value),
MotionInputId.Accelerometer => GsToMs2(value),
_ => value
};
}
}
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
public void SetConfiguration(InputConfig configuration)
{
lock (_userMappingLock)
{
_configuration = (StandardControllerInputConfig)configuration;
_buttonsUserMapping.Clear();
// First update sticks
_stickUserMapping[(int)StickInputId.Left] = (StickInputId)_configuration.LeftJoyconStick.Joystick;
_stickUserMapping[(int)StickInputId.Right] = (StickInputId)_configuration.RightJoyconStick.Joystick;
switch (_joyConType)
{
case JoyConType.Left:
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (GamepadButtonInputId)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (GamepadButtonInputId)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (GamepadButtonInputId)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (GamepadButtonInputId)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (GamepadButtonInputId)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (GamepadButtonInputId)_configuration.LeftJoycon.ButtonSl));
break;
case JoyConType.Right:
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (GamepadButtonInputId)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (GamepadButtonInputId)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (GamepadButtonInputId)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (GamepadButtonInputId)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (GamepadButtonInputId)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (GamepadButtonInputId)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (GamepadButtonInputId)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (GamepadButtonInputId)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (GamepadButtonInputId)_configuration.RightJoycon.ButtonSl));
break;
default:
throw new ArgumentOutOfRangeException();
}
SetTriggerThreshold(_configuration.TriggerThreshold);
}
}
public GamepadStateSnapshot GetStateSnapshot()
{
return IGamepad.GetStateSnapshot(this);
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
GamepadStateSnapshot rawState = GetStateSnapshot();
GamepadStateSnapshot result = default;
lock (_userMappingLock)
{
if (_buttonsUserMapping.Count == 0)
return rawState;
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{
if (!entry.IsValid)
continue;
// Do not touch state of button already pressed
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
}
(float leftStickX, float leftStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Left]);
(float rightStickX, float rightStickY) = rawState.GetStick(_stickUserMapping[(int)StickInputId.Right]);
result.SetStick(StickInputId.Left, leftStickX, leftStickY);
result.SetStick(StickInputId.Right, rightStickX, rightStickY);
}
return result;
}
private static float ConvertRawStickValue(short value)
{
const float ConvertRate = 1.0f / (short.MaxValue + 0.5f);
return value * ConvertRate;
}
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId>
GetLogicalJoyStickConfig(StickInputId inputId)
{
switch (inputId)
{
case StickInputId.Left:
if (_configuration.RightJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Left)
return _configuration.RightJoyconStick;
else
return _configuration.LeftJoyconStick;
case StickInputId.Right:
if (_configuration.LeftJoyconStick.Joystick ==
Common.Configuration.Hid.Controller.StickInputId.Right)
return _configuration.LeftJoyconStick;
else
return _configuration.RightJoyconStick;
}
return null;
}
public (float, float) GetStick(StickInputId inputId)
{
if (inputId == StickInputId.Unbound)
return (0.0f, 0.0f);
if (inputId == StickInputId.Left && _joyConType == JoyConType.Right || inputId == StickInputId.Right && _joyConType == JoyConType.Left)
{
return (0.0f, 0.0f);
}
(short stickX, short stickY) = GetStickXY();
float resultX = ConvertRawStickValue(stickX);
float resultY = -ConvertRawStickValue(stickY);
if (HasConfiguration)
{
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
if (joyconStickConfig != null)
{
if (joyconStickConfig.InvertStickX)
resultX = -resultX;
if (joyconStickConfig.InvertStickY)
resultY = -resultY;
if (joyconStickConfig.Rotate90CW)
{
float temp = resultX;
resultX = resultY;
resultY = -temp;
}
}
}
return inputId switch
{
StickInputId.Left when _joyConType == JoyConType.Left => (resultY, -resultX),
StickInputId.Right when _joyConType == JoyConType.Right => (-resultY, resultX),
_ => (0.0f, 0.0f)
};
}
private (short, short) GetStickXY()
{
return (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY));
}
public bool IsPressed(GamepadButtonInputId inputId)
{
if (!_buttonsDriverMapping.TryGetValue(inputId, out var button))
{
return false;
}
return SDL_GameControllerGetButton(_gamepadHandle, button) == 1;
}
}
}

View File

@@ -0,0 +1,142 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using static SDL2.SDL;
namespace Ryujinx.Input.SDL2
{
internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad
{
private StandardControllerInputConfig _configuration;
private readonly StickInputId[] _stickUserMapping =
[
StickInputId.Unbound,
StickInputId.Left,
StickInputId.Right
];
public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) |
(right?.Features ?? GamepadFeaturesFlag.None);
public const string Id = "JoyConPair";
string IGamepad.Id => Id;
public string Name => "* Nintendo Switch Joy-Con (L/R)";
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
public void Dispose()
{
left?.Dispose();
right?.Dispose();
}
public GamepadStateSnapshot GetMappedStateSnapshot()
{
return GetStateSnapshot();
}
public Vector3 GetMotionData(MotionInputId inputId)
{
return inputId switch
{
MotionInputId.Accelerometer or
MotionInputId.Gyroscope => left.GetMotionData(inputId),
MotionInputId.SecondAccelerometer => right.GetMotionData(MotionInputId.Accelerometer),
MotionInputId.SecondGyroscope => right.GetMotionData(MotionInputId.Gyroscope),
_ => Vector3.Zero
};
}
public GamepadStateSnapshot GetStateSnapshot()
{
return IGamepad.GetStateSnapshot(this);
}
public (float, float) GetStick(StickInputId inputId)
{
return inputId switch
{
StickInputId.Left => left.GetStick(StickInputId.Left),
StickInputId.Right => right.GetStick(StickInputId.Right),
_ => (0, 0)
};
}
public bool IsPressed(GamepadButtonInputId inputId)
{
return left.IsPressed(inputId) || right.IsPressed(inputId);
}
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{
if (lowFrequency != 0)
{
right.Rumble(lowFrequency, lowFrequency, durationMs);
}
if (highFrequency != 0)
{
left.Rumble(highFrequency, highFrequency, durationMs);
}
if (lowFrequency == 0 && highFrequency == 0)
{
left.Rumble(0, 0, durationMs);
right.Rumble(0, 0, durationMs);
}
}
public void SetConfiguration(InputConfig configuration)
{
left.SetConfiguration(configuration);
right.SetConfiguration(configuration);
}
public void SetTriggerThreshold(float triggerThreshold)
{
left.SetTriggerThreshold(triggerThreshold);
right.SetTriggerThreshold(triggerThreshold);
}
public static bool IsCombinable(List<string> gamepadsIds)
{
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
return leftIndex >= 0 && rightIndex >= 0;
}
private static (int leftIndex, int rightIndex) DetectJoyConPair(List<string> gamepadsIds)
{
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id)
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList();
int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName);
int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName);
return (leftIndex, rightIndex);
}
public static IGamepad GetGamepad(List<string> gamepadsIds)
{
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
if (leftIndex == -1 || rightIndex == -1)
{
return null;
}
nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex);
nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex);
if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero)
{
return null;
}
return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]),
new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex]));
}
}
}

View File

@@ -266,6 +266,7 @@ namespace Ryujinx.Input.HLE
if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
{
_leftMotionInput = new MotionInput();
_rightMotionInput = new MotionInput();
}
else
{
@@ -298,7 +299,20 @@ namespace Ryujinx.Input.HLE
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
_rightMotionInput = _leftMotionInput;
if (gamepad.Id== "JoyConPair")
{
Vector3 rightAccelerometer = gamepad.GetMotionData(MotionInputId.SecondAccelerometer);
Vector3 rightGyroscope = gamepad.GetMotionData(MotionInputId.SecondGyroscope);
rightAccelerometer = new Vector3(rightAccelerometer.X, -rightAccelerometer.Z, rightAccelerometer.Y);
rightGyroscope = new Vector3(rightGyroscope.X, -rightGyroscope.Z, rightGyroscope.Y);
_rightMotionInput.Update(rightAccelerometer, rightGyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
}
else
{
_rightMotionInput = _leftMotionInput;
}
}
}
}
@@ -333,6 +347,7 @@ namespace Ryujinx.Input.HLE
// Reset states
State = default;
_leftMotionInput = null;
_rightMotionInput = null;
}
}

View File

@@ -21,5 +21,17 @@ namespace Ryujinx.Input
/// </summary>
/// <remarks>Values are in degrees</remarks>
Gyroscope,
/// <summary>
/// Second accelerometer.
/// </summary>
/// <remarks>Values are in m/s^2</remarks>
SecondAccelerometer,
/// <summary>
/// Second gyroscope.
/// </summary>
/// <remarks>Values are in degrees</remarks>
SecondGyroscope
}
}

View File

@@ -25,14 +25,17 @@ namespace Ryujinx.SDL2.Common
public static Action<Action> MainThreadDispatcher { get; set; }
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK |
SDL_INIT_AUDIO | SDL_INIT_VIDEO;
private bool _isRunning;
private uint _refereceCount;
private Thread _worker;
private const uint SDL_JOYBATTERYUPDATED = 1543;
public event Action<int, int> OnJoyStickConnected;
public event Action<int> OnJoystickDisconnected;
public event Action<int, SDL_JoystickPowerLevel> OnJoyBatteryUpdated;
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers;
@@ -78,12 +81,14 @@ namespace Ryujinx.SDL2.Common
// First ensure that we only enable joystick events (for connected/disconnected).
if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE)
{
Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events.");
Logger.Error?.PrintMsg(LogClass.Application,
"Couldn't change the state of game controller events.");
}
if (SDL_JoystickEventState(SDL_ENABLE) < 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}");
Logger.Error?.PrintMsg(LogClass.Application,
$"Failed to enable joystick event polling: {SDL_GetError()}");
}
// Disable all joysticks information, we don't need them no need to flood the event queue for that.
@@ -143,7 +148,12 @@ namespace Ryujinx.SDL2.Common
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
}
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED)
{
OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code);
}
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN
or SDL_EventType.SDL_MOUSEBUTTONUP)
{
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
{

View File

@@ -674,7 +674,7 @@ namespace Ryujinx.Ava
public async Task<bool> LoadGuestApplication(BlitStruct<ApplicationControlProperty>? customNacpData = null)
{
InitEmulatedSwitch();
InitializeSwitchInstance();
MainWindow.UpdateGraphicsConfig();
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -757,8 +757,6 @@ namespace Ryujinx.Ava
{
romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs");
}
Logger.Notice.Print(LogClass.Application, $"Loading unpacked content archive from '{ApplicationPath}'.");
if (romFsFiles.Length > 0)
{
@@ -785,8 +783,6 @@ namespace Ryujinx.Ava
}
else if (File.Exists(ApplicationPath))
{
Logger.Notice.Print(LogClass.Application, $"Loading content archive from '{ApplicationPath}'.");
switch (Path.GetExtension(ApplicationPath).ToLowerInvariant())
{
case ".xci":
@@ -889,7 +885,7 @@ namespace Ryujinx.Ava
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
}
private void InitEmulatedSwitch()
private void InitializeSwitchInstance()
{
// Initialize KeySet.
VirtualFileSystem.ReloadKeySet();
@@ -1044,7 +1040,7 @@ namespace Ryujinx.Ava
_viewModel.WindowState = WindowState.FullScreen;
}
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
if (_viewModel.WindowState is WindowState.FullScreen)
{
_viewModel.ShowMenuAndStatusBar = false;
}

View File

@@ -1,13 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="MenuItem.withCheckbox Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="MenuItem.withCheckbox ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</Styles>

View File

@@ -572,31 +572,6 @@
"zh_TW": "使用全螢幕模式啟動遊戲"
}
},
{
"ID": "MenuBarOptionsStartGamesWithoutUI",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Start Games with UI Hidden",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "MenuBarOptionsStopEmulation",
"Translations": {

View File

@@ -1,6 +1,6 @@
using Avalonia.Controls.Notifications;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Gommon;
using LibHac;
using LibHac.Account;
using LibHac.Common;
@@ -15,7 +15,6 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
@@ -419,27 +418,35 @@ namespace Ryujinx.Ava.Common
public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName)
{
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
});
if (!result.HasValue) return;
ExtractAoc(result.Value.Path.LocalPath, updateFilePath, updateName);
if (result.Count == 0)
{
return;
}
ExtractAoc(result[0].Path.LocalPath, updateFilePath, updateName);
}
public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
{
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle],
AllowMultiple = false,
});
if (!result.HasValue) return;
if (result.Count == 0)
{
return;
}
ExtractSection(result.Value.Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
ExtractSection(result[0].Path.LocalPath, ncaSectionType, titleFilePath, titleName, programIndex);
}
public static (Result? result, bool canceled) CopyDirectory(FileSystemClient fs, string sourcePath, string destPath, CancellationToken token)

View File

@@ -233,13 +233,11 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $".NET Runtime: {RuntimeInformation.FrameworkDescription}");
SystemInfo.Gather().Print();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {
Logger.GetEnabledLevels()
.FormatCollection(
x => x.ToString(),
separator: ", ",
emptyCollectionFallback: "<None>")
}");
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogLevels.Length is 0
? "<None>"
: enabledLogLevels.JoinToString(", "))}");
Logger.Notice.Print(LogClass.Application,
AppDataManager.Mode == AppDataManager.LaunchMode.Custom

View File

@@ -123,13 +123,12 @@
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
<AvaloniaResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\locales.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Styles\CheckboxMenuItemStyle.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
@@ -151,7 +150,6 @@
</EmbeddedResource>
<EmbeddedResource Include="Assets\locales.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />

View File

@@ -16,6 +16,5 @@
<Application.Styles>
<sty:FluentAvaloniaTheme PreferUserAccentColor="True" PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml" />
<StyleInclude Source="/Assets/Styles/CheckboxMenuItemStyle.axaml"/>
</Application.Styles>
</Application>

View File

@@ -251,7 +251,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString.ToLower(), "cache", "shader");
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "shader");
if (!Directory.Exists(shaderCacheDir))
{

View File

@@ -488,19 +488,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool StartGamesWithoutUI
{
get => ConfigurationState.Instance.UI.StartNoUI;
set
{
ConfigurationState.Instance.UI.StartNoUI.Value = value;
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
OnPropertyChanged();
}
}
public bool ShowConsole
{
get => ConfigurationState.Instance.UI.ShowConsole;
@@ -637,7 +624,6 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize],
ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath],
ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite],
ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel],
_ => string.Empty,
};
}
@@ -708,7 +694,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public IHostUIHandler UiHandler { get; internal set; }
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByTitleId => SortMode == ApplicationSort.TitleId;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
@@ -741,7 +726,6 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationSort.FileSize => CreateComparer(IsAscending, app => app.FileSize),
ApplicationSort.Path => CreateComparer(IsAscending, app => app.Path),
ApplicationSort.Favorite => CreateComparer(IsAscending, app => new AppListFavoriteComparable(app)),
ApplicationSort.TitleId => CreateComparer(IsAscending, app => app.Id),
_ => null,
#pragma warning restore IDE0055
};
@@ -1211,11 +1195,6 @@ namespace Ryujinx.Ava.UI.ViewModels
StartGamesInFullscreen = !StartGamesInFullscreen;
}
public void ToggleStartGamesWithoutUI()
{
StartGamesWithoutUI = !StartGamesWithoutUI;
}
public void ToggleShowConsole()
{
ShowConsole = !ShowConsole;
@@ -1662,7 +1641,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task OpenAmiiboWindow()
{
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId) && IsGameRunning)
if (!IsAmiiboRequested)
return;
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
@@ -1680,7 +1662,10 @@ namespace Ryujinx.Ava.UI.ViewModels
}
public async Task OpenBinFile()
{
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
if (!IsAmiiboRequested)
return;
if (AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{

View File

@@ -81,16 +81,25 @@
<MenuItem
Command="{Binding ToggleFullscreen}"
Header="{ext:Locale MenuBarOptionsToggleFullscreen}"
Classes="withCheckbox"
Padding="0"
Icon="{ext:Icon fa-solid fa-expand}"
InputGesture="F11">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesInFullscreen}"
Header="{ext:Locale MenuBarOptionsStartGamesInFullscreen}"
Classes="withCheckbox">
Header="{ext:Locale MenuBarOptionsStartGamesInFullscreen}">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
@@ -98,26 +107,23 @@
IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesWithoutUI}"
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}"
Classes="withCheckbox">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding StartGamesWithoutUI, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Padding="0"
IsVisible="{Binding ShowConsoleVisible}"
Command="{Binding ToggleShowConsole}"
Header="{ext:Locale MenuBarOptionsShowConsole}"
Classes="withCheckbox">
Header="{ext:Locale MenuBarOptionsShowConsole}">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
@@ -125,14 +131,35 @@
IsChecked="{Binding ShowConsole, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<Separator/>
<MenuItem
Name="ChangeLanguageMenuItem"
Padding="0"
Header="{ext:Locale MenuBarOptionsChangeLanguage}"
Icon="{ext:Icon fa-solid fa-language}"
Classes="withCheckbox">
Icon="{ext:Icon fa-solid fa-language}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Name="ToggleFileTypesMenuItem"
@@ -144,8 +171,18 @@
Padding="0"
Header="{ext:Locale MenuBarOptionsSettings}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale OpenSettingsTooltip}"
Classes="withCheckbox">
ToolTip.Tip="{ext:Locale OpenSettingsTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
<MenuItem
Command="{Binding ManageProfiles}"
@@ -153,8 +190,18 @@
Header="{ext:Locale MenuBarOptionsManageUserProfiles}"
Icon="{ext:Icon mdi-account}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{ext:Locale OpenProfileManagerTooltip}"
Classes="withCheckbox">
ToolTip.Tip="{ext:Locale OpenProfileManagerTooltip}">
<MenuItem.Styles>
<Style Selector="Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</MenuItem.Styles>
</MenuItem>
</MenuItem>
<MenuItem

View File

@@ -105,12 +105,6 @@
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{ext:Locale DlcManagerTableHeadingTitleIdLabel}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitleId, Mode=OneTime}"
Tag="TitleId" />
<RadioButton
Checked="Sort_Checked"
Content="{ext:Locale GameListHeaderDeveloper}"

View File

@@ -29,7 +29,7 @@
<TextBlock
Foreground="{DynamicResource SecondaryTextColor}"
TextDecorations="Underline"
Text="Highly specific hacks &amp; tricks to alleviate performance issues, crashing, or freezing. Will cause issues." />
Text="Game-specific hacks &amp; tricks to alleviate performance issues or crashing. Will cause issues." />
<StackPanel
Margin="0,10,0,0"
Orientation="Horizontal"

View File

@@ -114,7 +114,8 @@
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddGameDirTooltip}">
ToolTip.Tip="{ext:Locale AddGameDirTooltip}"
Click="AddGameDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
@@ -167,7 +168,8 @@
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddAutoloadDirTooltip}">
ToolTip.Tip="{ext:Locale AddAutoloadDirTooltip}"
Click="AddAutoloadDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>

View File

@@ -1,17 +1,12 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
using Gommon;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Settings
{
@@ -23,39 +18,31 @@ namespace Ryujinx.Ava.UI.Views.Settings
{
InitializeComponent();
ShowTitleBarBox.IsVisible = OperatingSystem.IsWindows();
AddGameDirButton.Command =
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
AddAutoloadDirButton.Command =
Commands.Create(() => AddDirButton(AutoloadDirPathBox, ViewModel.AutoloadDirectories, false));
}
private async Task AddDirButton(TextBox addDirBox, AvaloniaList<string> directories, bool isGameList)
private async void AddGameDirButton_OnClick(object sender, RoutedEventArgs e)
{
string path = addDirBox.Text;
string path = GameDirPathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !directories.Contains(path))
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{
directories.Add(path);
addDirBox.Clear();
if (isGameList)
ViewModel.GameDirectoryChanged = true;
else
ViewModel.AutoloadDirectoryChanged = true;
ViewModel.GameDirectories.Add(path);
ViewModel.GameDirectoryChanged = true;
}
else
{
Optional<IStorageFolder> folder = await RyujinxApp.MainWindow.ViewModel.StorageProvider.OpenSingleFolderPickerAsync();
if (folder.HasValue)
if (this.GetVisualRoot() is Window window)
{
directories.Add(folder.Value.Path.LocalPath);
if (isGameList)
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
if (result.Count > 0)
{
ViewModel.GameDirectories.Add(result[0].Path.LocalPath);
ViewModel.GameDirectoryChanged = true;
else
ViewModel.AutoloadDirectoryChanged = true;
}
}
}
}
@@ -76,6 +63,33 @@ namespace Ryujinx.Ava.UI.Views.Settings
}
}
private async void AddAutoloadDirButton_OnClick(object sender, RoutedEventArgs e)
{
string path = AutoloadDirPathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.AutoloadDirectories.Contains(path))
{
ViewModel.AutoloadDirectories.Add(path);
ViewModel.AutoloadDirectoryChanged = true;
}
else
{
if (this.GetVisualRoot() is Window window)
{
var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
if (result.Count > 0)
{
ViewModel.AutoloadDirectories.Add(result[0].Path.LocalPath);
ViewModel.AutoloadDirectoryChanged = true;
}
}
}
}
private void RemoveAutoloadDirButton_OnClick(object sender, RoutedEventArgs e)
{
int oldIndex = AutoloadDirsList.SelectedIndex;

View File

@@ -691,7 +691,6 @@ namespace Ryujinx.Ava.UI.Windows
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
autoloadDirs.ForEach(dir => Logger.Info?.Print(LogClass.Application, $"Auto loading DLC & updates from: {dir}"));
if (autoloadDirs.Count > 0)
{
var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);

View File

@@ -36,7 +36,7 @@ namespace Ryujinx.Ava.UI.Windows
SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty,
Content = new XCITrimmerWindow(RyujinxApp.MainWindow.ViewModel),
Title = LocaleManager.Instance[LocaleKeys.XCITrimmerWindowTitle]
Title = string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerWindowTitle]),
};
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());

View File

@@ -87,8 +87,6 @@ namespace Ryujinx.Ava
return;
}
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
// Get latest version number from GitHub API
try
@@ -142,8 +140,6 @@ namespace Ryujinx.Ava
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion));
}
}
Logger.Info?.Print(LogClass.Application, "Up to date.");
_running = false;
@@ -218,8 +214,6 @@ namespace Ryujinx.Ava
? $"Canary {currentVersion} -> Canary {newVersion}"
: $"{currentVersion} -> {newVersion}";
Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString}");
RequestUserToUpdate:
// Show a message asking the user if they want to update
UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog(

View File

@@ -840,6 +840,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
try
{
// Remove any downloadable content which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title DLCs");
var dlcToRemove = _downloadableContents.Items
.Where(dlc => !File.Exists(dlc.Dlc.ContainerPath))
.ToList();
@@ -851,6 +852,8 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
foreach (string appDir in appDirs)
{
Logger.Notice.Print(LogClass.Application, $"Auto loading DLC from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested)
{
return newDlcLoaded;
@@ -953,6 +956,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
var titleIdsToRefresh = new HashSet<ulong>();
// Remove any updates which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title Updates");
var updatesToRemove = _titleUpdates.Items
.Where(it => !File.Exists(it.TitleUpdate.Path))
.ToList();
@@ -967,6 +971,8 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
foreach (string appDir in appDirs)
{
Logger.Notice.Print(LogClass.Application, $"Auto loading updates from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested)
{
return numUpdatesLoaded;

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 60;
public const int CurrentVersion = 59;
/// <summary>
/// Version of the configuration file format
@@ -351,11 +351,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public bool StartFullscreen { get; set; }
/// <summary>
/// Start games with UI hidden
/// </summary>
public bool StartNoUI { get; set; }
/// <summary>
/// Show console window
/// </summary>

View File

@@ -127,7 +127,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
@@ -415,8 +414,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
// This was accidentally enabled by default when it was PRed. That is not what we want,
// so as a compromise users who want to use it will simply need to re-enable it once after updating.
cff.IgnoreApplet = false;
}),
(60, static cff => cff.StartNoUI = false)
})
);
}
}

View File

@@ -152,11 +152,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary>
public ReactiveObject<bool> StartFullscreen { get; private set; }
/// <summary>
/// Start games with UI hidden
/// </summary>
public ReactiveObject<bool> StartNoUI { get; private set; }
/// <summary>
/// Hide / Show Console Window
/// </summary>
@@ -197,7 +192,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
WindowStartup = new WindowStartupSettings();
BaseStyle = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>();
StartNoUI = new ReactiveObject<bool>();
GameListViewMode = new ReactiveObject<int>();
ShowNames = new ReactiveObject<bool>();
GridSize = new ReactiveObject<int>();

View File

@@ -125,7 +125,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
ApplicationSort = UI.ApplicationSort,
IsAscendingOrder = UI.IsAscendingOrder,
StartFullscreen = UI.StartFullscreen,
StartNoUI = UI.StartNoUI,
ShowConsole = UI.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
@@ -234,7 +233,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.ApplicationSort.Value = 0;
UI.IsAscendingOrder.Value = true;
UI.StartFullscreen.Value = false;
UI.StartNoUI.Value = false;
UI.ShowConsole.Value = true;
UI.WindowStartup.WindowSizeWidth.Value = 1280;
UI.WindowStartup.WindowSizeHeight.Value = 760;

View File

@@ -1,47 +0,0 @@
using Avalonia.Platform.Storage;
using Gommon;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Utilities
{
public static class StorageProviderExtensions
{
public static async Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false))
.Then(folders => folders.FindFirst());
public static async Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false))
.Then(files => files.FindFirst());
public static async Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(this IStorageProvider storageProvider, FolderPickerOpenOptions openOptions = null) =>
await storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true))
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
public static async Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(this IStorageProvider storageProvider, FilePickerOpenOptions openOptions = null) =>
await storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true))
.Then(files => files.Count > 0 ? Optional.Of(files) : default);
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
{
if (openOptions is null)
return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
private static FolderPickerOpenOptions FixOpenOptions(this FolderPickerOpenOptions openOptions, bool allowMultiple)
{
if (openOptions is null)
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple;
return openOptions;
}
}
}