Compare commits

...

8 Commits

Author SHA1 Message Date
Frog Business
492024fce8 Merge 6d78e71fc7 into 1c0813d09d 2025-02-09 02:35:42 -06:00
Evan Husted
1c0813d09d misc: chore: [ci skip] shorten lines in FormatterSpecBase.Format & consistently format them 2025-02-09 00:50:11 -06:00
Evan Husted
8bec09d7ff Merge remote-tracking branch 'origin/master' 2025-02-09 00:45:54 -06:00
Evan Husted
e4b4e94b56 misc: chore: cleanup Specs.cs 2025-02-09 00:45:46 -06:00
Evan Husted
764c9e9d4e misc: chore: unify GameSpec creation 2025-02-09 00:37:54 -06:00
Evan Husted
05e991db87 misc: chore: collapse all NSO emulator formatters into one method 2025-02-09 00:37:35 -06:00
shinyoyo
2cd876b1cb Update Zh-CN Simplified Chinese (#642) 2025-02-08 21:33:20 -06:00
Barış Hamil
6d78e71fc7 Handheld Gyro 2025-02-01 22:26:05 +03:00
22 changed files with 423 additions and 199 deletions

View File

@@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Alimer.Bindings.SDL" Version="3.7.1" />
<PackageVersion Include="Avalonia" Version="11.0.13" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.13" />

View File

@@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL3", "src\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj", "{3BF24278-547D-42C2-9D43-182B978F54DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
@@ -259,6 +261,10 @@ Global
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BF24278-547D-42C2-9D43-182B978F54DD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -56,6 +56,7 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
return motionBackendType switch
{
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMotionConfigController),
MotionInputBackendType.Handheld => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardMotionConfigController),
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, _serializerContext.CemuHookMotionConfigController),
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
};
@@ -66,6 +67,7 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
switch (value.MotionBackend)
{
case MotionInputBackendType.GamepadDriver:
case MotionInputBackendType.Handheld:
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, _serializerContext.StandardMotionConfigController);
break;
case MotionInputBackendType.CemuHook:

View File

@@ -9,5 +9,6 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
Invalid,
GamepadDriver,
CemuHook,
Handheld,
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<PackageReference Include="Alimer.Bindings.SDL" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
using SDL3;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using static SDL3.SDL3;
namespace Ryujinx.Input.SDL3
{
public unsafe class SDL3MotionDriver : IHandheld, IDisposable
{
private readonly Dictionary<SDL_SensorType, SDL_Sensor> sensors;
private bool _disposed;
public SDL3MotionDriver()
{
int result = SDL_Init(SDL_InitFlags.Sensor);
if (result < 0)
{
throw new InvalidOperationException($"SDL sensor initialization failed: {SDL_GetError()}");
}
sensors = SDL_GetSensors().ToArray().ToDictionary(SDL_GetSensorTypeForID, SDL_OpenSensor);
}
~SDL3MotionDriver()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing && sensors != null)
{
foreach (var sensor in sensors.Values)
{
if (sensor != IntPtr.Zero)
{
SDL_CloseSensor(sensor);
}
}
}
_disposed = true;
}
public Vector3 GetMotionData(MotionInputId inputType)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return inputType switch
{
MotionInputId.Gyroscope => GetSensorVector(SDL_SensorType.Gyro) * 180 / MathF.PI,
MotionInputId.Accelerometer => GetSensorVector(SDL_SensorType.Accel) / SDL_STANDARD_GRAVITY,
_ => Vector3.Zero
};
}
private Vector3 GetSensorVector(SDL_SensorType sensorType)
{
if (!sensors.TryGetValue(sensorType, out SDL_Sensor sensor))
{
return Vector3.Zero;
}
var data = stackalloc float[3];
if (SDL_GetSensorData(sensor, data, 3) < 0)
{
return Vector3.Zero;
}
return new Vector3(data[0], data[1], data[2]);
}
}
}

View File

@@ -2,12 +2,13 @@ using System;
namespace Ryujinx.Input.HLE
{
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver)
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IHandheld handheld)
: IDisposable
{
public IGamepadDriver KeyboardDriver { get; } = keyboardDriver;
public IGamepadDriver GamepadDriver { get; } = gamepadDriver;
public IGamepadDriver MouseDriver { get; private set; }
public IHandheld Handheld { get; } = handheld;
public void SetMouseDriver(IGamepadDriver mouseDriver)
{
@@ -18,7 +19,7 @@ namespace Ryujinx.Input.HLE
public NpadManager CreateNpadManager()
{
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver);
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver, Handheld);
}
public TouchScreenManager CreateTouchScreenManager()
@@ -38,6 +39,7 @@ namespace Ryujinx.Input.HLE
KeyboardDriver?.Dispose();
GamepadDriver?.Dispose();
MouseDriver?.Dispose();
Handheld?.Dispose();
}
}

View File

@@ -218,12 +218,14 @@ namespace Ryujinx.Input.HLE
public string Id { get; private set; }
private readonly CemuHookClient _cemuHookClient;
private readonly IHandheld _handheld;
public NpadController(CemuHookClient cemuHookClient)
public NpadController(CemuHookClient cemuHookClient, IHandheld handheld)
{
State = default;
Id = null;
_cemuHookClient = cemuHookClient;
_handheld = handheld;
}
public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config)
@@ -287,6 +289,18 @@ namespace Ryujinx.Input.HLE
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
{
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.Handheld)
{
Vector3 accelerometer = _handheld.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscope = _handheld.GetMotionData(MotionInputId.Gyroscope);
accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);
_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
_rightMotionInput = _leftMotionInput;
}
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
{
if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))

View File

@@ -31,6 +31,7 @@ namespace Ryujinx.Input.HLE
private readonly IGamepadDriver _keyboardDriver;
private readonly IGamepadDriver _gamepadDriver;
private readonly IGamepadDriver _mouseDriver;
private readonly IHandheld _handheld;
private bool _isDisposed;
private List<InputConfig> _inputConfig;
@@ -38,7 +39,7 @@ namespace Ryujinx.Input.HLE
private bool _enableMouse;
private Switch _device;
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver)
public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver, IHandheld handheld)
{
_controllers = new NpadController[MaxControllers];
_cemuHookClient = new CemuHookClient(this);
@@ -47,6 +48,7 @@ namespace Ryujinx.Input.HLE
_gamepadDriver = gamepadDriver;
_mouseDriver = mouseDriver;
_inputConfig = [];
_handheld = handheld;
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
@@ -139,7 +141,7 @@ namespace Ryujinx.Input.HLE
}
else
{
controller = new(_cemuHookClient);
controller = new(_cemuHookClient, _handheld);
}
bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);

View File

@@ -0,0 +1,10 @@
using System;
using System.Numerics;
namespace Ryujinx.Input
{
public interface IHandheld : IDisposable
{
Vector3 GetMotionData(MotionInputId gyroscope);
}
}

View File

@@ -1543,7 +1543,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "由 {0} 开发",
"zh_TW": ""
}
},
@@ -1843,7 +1843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "兼容性:",
"zh_TW": ""
}
},
@@ -1868,7 +1868,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "标题 ID:",
"zh_TW": ""
}
},
@@ -1893,7 +1893,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "服务的游戏: {0}",
"zh_TW": ""
}
},
@@ -1918,7 +1918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在线玩家: {0}",
"zh_TW": ""
}
},
@@ -2268,7 +2268,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "清理 PPTC 缓存",
"zh_TW": ""
}
},
@@ -2293,7 +2293,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "删除应用程序的所有 PPTC 缓存",
"zh_TW": ""
}
},
@@ -2768,7 +2768,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示兼容性项目",
"zh_TW": ""
}
},
@@ -2793,7 +2793,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在兼容性列表中显示选定的游戏,您通常可以通过帮助菜单访问。",
"zh_TW": ""
}
},
@@ -2818,7 +2818,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示游戏信息",
"zh_TW": ""
}
},
@@ -2843,7 +2843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示当前选定游戏的状态与详细信息。",
"zh_TW": ""
}
},
@@ -4493,7 +4493,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "与系统时间同步",
"zh_TW": ""
}
},
@@ -6143,7 +6143,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "重置设置",
"zh_TW": ""
}
},
@@ -6168,7 +6168,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "我要重置我的设置。",
"zh_TW": ""
}
},
@@ -7897,6 +7897,31 @@
"zh_TW": "使用與 CemuHook 相容的體感"
}
},
{
"ID": "ControllerSettingsMotionUseHandheldCompatibleMotion",
"Translations": {
"ar_SA": "استخدام الحركة المتوافقة مع Hendheld",
"de_DE": "Hendheld kompatible Bewegungssteuerung",
"el_GR": "Κίνηση συμβατή με Hendheld",
"en_US": "Use Hendheld compatible motion",
"es_ES": "Usar movimiento compatible con Hendheld",
"fr_FR": "Utiliser un capteur de mouvements Hendheld",
"he_IL": "השתמש בתנועת Hendheld תואמת ",
"it_IT": "Usa sensore compatibile con Hendheld",
"ja_JP": "Hendheld 互換モーションを使用",
"ko_KR": "Hendheld 호환 모션 사용",
"no_NO": "Bruk Hendheld kompatibel bevegelse",
"pl_PL": "Użyj ruchu zgodnego z Hendheld",
"pt_BR": "Usar sensor compatível com Hendheld",
"ru_RU": "Включить совместимость с Hendheld",
"sv_SE": "Använd Hendheld-kompatibel rörelse",
"th_TH": "ใช้การเคลื่อนไหวที่เข้ากันได้กับ Hendheld",
"tr_TR": "Hendheld uyumlu hareket kullan",
"uk_UA": "Використовувати рух, сумісний з Hendheld",
"zh_CN": "使用 Hendheld 兼容的体感协议",
"zh_TW": "使用與 Hendheld 相容的體感"
}
},
{
"ID": "ControllerSettingsMotionControllerSlot",
"Translations": {
@@ -8143,7 +8168,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "彩虹滚动速度",
"zh_TW": ""
}
},
@@ -13418,7 +13443,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "您正要清理 PPTC 数据:\n\n{0}\n\n您确实要继续吗?",
"zh_TW": ""
}
},
@@ -23568,7 +23593,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "启动和游戏时不会出现任何崩溃或任何类型的 GPU bug 且速度足够快可以在一般 PC 上尽情游玩。",
"zh_TW": ""
}
},
@@ -23593,7 +23618,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以成功启动并进入游戏但可能会遇到以下一种或多种问题: 崩溃、卡死、GPU bug、令人无法接受的音频,或者只是太慢。仍然可以继续进行游戏,但是可能无法达到预期。",
"zh_TW": ""
}
},
@@ -23618,7 +23643,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以启动并通过标题画面但是无法进入到主要的游戏流程。",
"zh_TW": ""
}
},
@@ -23643,7 +23668,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以启动但是无法通过标题画面。",
"zh_TW": ""
}
},
@@ -23668,7 +23693,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "无法启动或显示无任何动静。",
"zh_TW": ""
}
},
@@ -23718,7 +23743,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "Rich Presence 图像",
"zh_TW": ""
}
},
@@ -23743,9 +23768,9 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "动态 Rich Presence",
"zh_TW": ""
}
}
]
}
}

View File

@@ -23,6 +23,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Generic;
@@ -182,7 +183,7 @@ namespace Ryujinx.Headless
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver(), new SDL3MotionDriver());
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;

View File

@@ -76,6 +76,7 @@
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />

View File

@@ -10,6 +10,7 @@ namespace Ryujinx.Ava.UI.Models.Input
public partial class GamepadInputConfig : BaseModel
{
public bool EnableCemuHookMotion { get; set; }
public bool EnableHandheldMotion { get; set; }
public string DsuServerHost { get; set; }
public int DsuServerPort { get; set; }
public int Slot { get; set; }
@@ -162,7 +163,7 @@ namespace Ryujinx.Ava.UI.Models.Input
EnableMotion = controllerInput.Motion.EnableMotion;
GyroDeadzone = controllerInput.Motion.GyroDeadzone;
Sensitivity = controllerInput.Motion.Sensitivity;
EnableHandheldMotion = controllerInput.Motion.MotionBackend == MotionInputBackendType.Handheld;
if (controllerInput.Motion is CemuHookMotionConfigController cemuHook)
{
EnableCemuHookMotion = true;
@@ -285,7 +286,7 @@ namespace Ryujinx.Ava.UI.Models.Input
config.Motion = new StandardMotionConfigController
{
EnableMotion = EnableMotion,
MotionBackend = MotionInputBackendType.GamepadDriver,
MotionBackend = EnableHandheldMotion ? MotionInputBackendType.Handheld : MotionInputBackendType.GamepadDriver,
GyroDeadzone = GyroDeadzone,
Sensitivity = Sensitivity,
};

View File

@@ -18,6 +18,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private double _gyroDeadzone;
[ObservableProperty] private bool _enableCemuHookMotion;
private bool _enableCemuHookMotion;
public bool EnableCemuHookMotion
{
get => _enableCemuHookMotion;
set
{
if (value)
{
EnableHandheldMotion = false;
}
_enableCemuHookMotion = value;
OnPropertyChanged();
}
}
private bool _enableHandheldMotion;
public bool EnableHandheldMotion
{
get => _enableHandheldMotion;
set
{
if (value)
{
EnableCemuHookMotion = false;
}
_enableHandheldMotion = value;
OnPropertyChanged();
}
}
}
}

View File

@@ -61,6 +61,17 @@
Margin="5, 0"
Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
</StackPanel>
<Separator
Height="1"
Margin="0,5" />
<CheckBox
Margin="5"
IsChecked="{Binding EnableHandheldMotion}">
<TextBlock
Margin="0,3,0,0"
VerticalAlignment="Center"
Text="{ext:Locale ControllerSettingsMotionUseHandheldCompatibleMotion}" />
</CheckBox>
<Separator
Height="1"
Margin="0,5" />

View File

@@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Views.Input
Sensitivity = config.Sensitivity,
GyroDeadzone = config.GyroDeadzone,
EnableCemuHookMotion = config.EnableCemuHookMotion,
EnableHandheldMotion = config.EnableHandheldMotion,
};
InitializeComponent();
@@ -58,6 +59,7 @@ namespace Ryujinx.Ava.UI.Views.Input
config.DsuServerHost = content._viewModel.DsuServerHost;
config.DsuServerPort = content._viewModel.DsuServerPort;
config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion;
config.EnableHandheldMotion = content._viewModel.EnableHandheldMotion;
config.MirrorInput = content._viewModel.MirrorInput;
};

View File

@@ -29,6 +29,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.Input.SDL3;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -107,7 +108,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver(), new SDL3MotionDriver());
_ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
this.ScalingChanged += OnScalingChanged;

View File

@@ -31,8 +31,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
return this;
return AddSpec(transform(GameSpec.Create(titleId)));
}
/// <summary>
@@ -46,8 +45,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
return this;
return AddSpec(GameSpec.Create(titleId).Apply(transform));
}
/// <summary>
@@ -63,8 +61,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
return this;
return AddSpec(transform(GameSpec.Create(tids)));
}
/// <summary>
@@ -79,7 +76,17 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
return AddSpec(GameSpec.Create(tids).Apply(transform));
}
/// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
/// </summary>
/// <param name="spec">The <see cref="GameSpec"/> to add.</param>
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(GameSpec spec)
{
_specs.Add(spec);
return this;
}

View File

@@ -294,8 +294,67 @@ namespace Ryujinx.Ava.Utilities.PlayReport
};
}
private static FormattedValue N64_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
private static FormattedValue NsoEmulator_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
#region SEGA Genesis
"m_0054_e" => Playing("Alien Soldier"),
"m_3978_e" => Playing("Alien Storm"),
"m_5234_e" => Playing("ALISIA DRAGOON"),
"m_5003_e" => Playing("Streets of Rage 2"),
"m_4843_e" => Playing("Kid Chameleon"),
"m_2874_e" => Playing("Columns"),
"m_3167_e" => Playing("Comix Zone"),
"m_5007_e" => Playing("Contra: Hard Corps"),
"m_0865_e" => Playing("Ghouls 'n Ghosts"),
"m_0935_e" => Playing("Dynamite Headdy"),
"m_8314_e" => Playing("Earthworm Jim"),
"m_5012_e" => Playing("Ecco the Dolphin"),
"m_2207_e" => Playing("Flicky"),
"m_9432_e" => Playing("Golden Axe II"),
"m_5015_e" => Playing("Golden Axe"),
"m_5017_e" => Playing("Gunstar Heroes"),
"m_0732_e" => Playing("Altered Beast"),
"m_2245_e" or "m_2245_pd" or "m_2245_pf" => Playing("Landstalker"),
"m_1654_e" => Playing("Target Earth"),
"m_7050_e" => Playing("Light Crusader"),
"m_5027_e" => Playing("M.U.S.H.A."),
"m_5028_e" => Playing("Phantasy Star IV"),
"m_9155_e" => Playing("Pulseman"),
"m_5030_e" => Playing("Dr. Robotnik's Mean Bean Machine"),
"m_0098_e" => Playing("Crusader of Centy"),
"m_0098_k" => Playing("신창세기 라그나센티"),
"m_0098_pd" or "m_0098_pf" or "m_0098_ps" => Playing("Soleil"),
"m_5033_e" => Playing("Ristar"),
"m_1987_e" => Playing("MEGA MAN: THE WILY WARS"),
"m_2609_e" => Playing("WOLF OF THE BATTLEFIELD: MERCS"),
"m_3353_e" => Playing("Shining Force II"),
"m_5036_e" => Playing("Shining Force"),
"m_9866_e" => Playing("Sonic The Hedgehog Spinball"),
"m_5041_e" => Playing("Sonic The Hedgehog 2"),
"m_5523_e" => Playing("Space Harrier II"),
"m_0041_e" => Playing("STREET FIGHTER II' : SPECIAL CHAMPION EDITION"),
"m_5044_e" => Playing("STRIDER"),
"m_6353_e" => Playing("Super Fantasy Zone"),
"m_9569_e" => Playing("Beyond Oasis"),
"m_9569_k" => Playing("스토리 오브 도어"),
"m_9569_pd" or "m_9569_ps" => Playing("The Story of Thor"),
"m_9569_pf" => Playing("La Légende de Thor"),
"m_5049_e" => Playing("Shinobi III: Return of the Ninja Master"),
"m_6811_e" => Playing("The Revenge of Shinobi"),
"m_4372_e" => Playing("Thunder Force II"),
"m_1535_e" => Playing("ToeJam & Earl in Panic on Funkotron"),
"m_0432_e" => Playing("ToeJam & Earl"),
"m_5052_e" => Playing("Castlevania: BLOODLINES"),
"m_3626_e" => Playing("VectorMan"),
"m_7955_e" => Playing("Sword of Vermilion"),
"m_0394_e" => Playing("Virtua Fighter 2"),
"m_9417_e" => Playing("Zero Wing"),
#endregion
#region Nintendo 64
"n_1653_e" or "n_1653_p" => Playing("1080º ™ Snowboarding"),
"n_4868_e" or "n_4868_p" => Playing("Banjo Kazooie™"),
"n_1226_e" or "n_1226_p" => Playing("Banjo-Tooie™"),
@@ -331,11 +390,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"n_3036_e" or "n_3036_p" => Playing("Yoshi's Story™"),
"n_1407_e" or "n_1407_p" => Playing("The Legend of Zelda™: Majora's Mask™"),
"n_3038_e" or "n_3038_p" => Playing("The Legend of Zelda™: Ocarina of Time™"),
_ => FormattedValue.ForceReset
};
private static FormattedValue NES_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
#endregion
#region NES
"clv_p_naaae" => Playing("Super Mario Bros.™"),
"clv_p_naabe" => Playing("Super Mario Bros.™: The Lost Levels"),
"clv_p_naace" or "clv_p_naace_sp1" => Playing("Super Mario Bros.™ 3"),
@@ -415,11 +474,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"clv_p_najre" => Playing("COBRA TRIANGLE™"),
"clv_p_najse" => Playing("SNAKE RATTLE N ROLL™"),
"clv_p_najte" => Playing("SOLAR® JETMAN"),
_ => FormattedValue.ForceReset
};
private static FormattedValue SNES_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
#endregion
#region SNES
"s_2180_e" => Playing("BATTLETOADS™ DOUBLE DRAGON™"),
"s_2179_e" => Playing("BATTLETOADS™ IN BATTLEMANIACS"),
"s_2182_e" => Playing("BIG RUN"),
@@ -490,98 +549,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"s_2096_e" => Playing("Wrecking Crew™ '98"),
"s_2023_e" => Playing("Super Mario World™ 2: Yoshi's Island™"),
"s_2024_e" => Playing("The Legend of Zelda™: A Link to the Past™"),
_ => FormattedValue.ForceReset
};
private static FormattedValue Genesis_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
"m_0054_e" => Playing("Alien Soldier"),
"m_3978_e" => Playing("Alien Storm"),
"m_5234_e" => Playing("ALISIA DRAGOON"),
"m_5003_e" => Playing("Streets of Rage 2"),
"m_4843_e" => Playing("Kid Chameleon"),
"m_2874_e" => Playing("Columns"),
"m_3167_e" => Playing("Comix Zone"),
"m_5007_e" => Playing("Contra: Hard Corps"),
"m_0865_e" => Playing("Ghouls 'n Ghosts"),
"m_0935_e" => Playing("Dynamite Headdy"),
"m_8314_e" => Playing("Earthworm Jim"),
"m_5012_e" => Playing("Ecco the Dolphin"),
"m_2207_e" => Playing("Flicky"),
"m_9432_e" => Playing("Golden Axe II"),
"m_5015_e" => Playing("Golden Axe"),
"m_5017_e" => Playing("Gunstar Heroes"),
"m_0732_e" => Playing("Altered Beast"),
"m_2245_e" or "m_2245_pd" or "m_2245_pf" => Playing("Landstalker"),
"m_1654_e" => Playing("Target Earth"),
"m_7050_e" => Playing("Light Crusader"),
"m_5027_e" => Playing("M.U.S.H.A."),
"m_5028_e" => Playing("Phantasy Star IV"),
"m_9155_e" => Playing("Pulseman"),
"m_5030_e" => Playing("Dr. Robotnik's Mean Bean Machine"),
"m_0098_e" => Playing("Crusader of Centy"),
"m_0098_k" => Playing("신창세기 라그나센티"),
"m_0098_pd" or "m_0098_pf" or "m_0098_ps" => Playing("Soleil"),
"m_5033_e" => Playing("Ristar"),
"m_1987_e" => Playing("MEGA MAN: THE WILY WARS"),
"m_2609_e" => Playing("WOLF OF THE BATTLEFIELD: MERCS"),
"m_3353_e" => Playing("Shining Force II"),
"m_5036_e" => Playing("Shining Force"),
"m_9866_e" => Playing("Sonic The Hedgehog Spinball"),
"m_5041_e" => Playing("Sonic The Hedgehog 2"),
"m_5523_e" => Playing("Space Harrier II"),
"m_0041_e" => Playing("STREET FIGHTER II' : SPECIAL CHAMPION EDITION"),
"m_5044_e" => Playing("STRIDER"),
"m_6353_e" => Playing("Super Fantasy Zone"),
"m_9569_e" => Playing("Beyond Oasis"),
"m_9569_k" => Playing("스토리 오브 도어"),
"m_9569_pd" or "m_9569_ps" => Playing("The Story of Thor"),
"m_9569_pf" => Playing("La Légende de Thor"),
"m_5049_e" => Playing("Shinobi III: Return of the Ninja Master"),
"m_6811_e" => Playing("The Revenge of Shinobi"),
"m_4372_e" => Playing("Thunder Force II"),
"m_1535_e" => Playing("ToeJam & Earl in Panic on Funkotron"),
"m_0432_e" => Playing("ToeJam & Earl"),
"m_5052_e" => Playing("Castlevania: BLOODLINES"),
"m_3626_e" => Playing("VectorMan"),
"m_7955_e" => Playing("Sword of Vermilion"),
"m_0394_e" => Playing("Virtua Fighter 2"),
"m_9417_e" => Playing("Zero Wing"),
_ => FormattedValue.ForceReset
};
private static FormattedValue GBA_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
"a_9694_e" => Playing("Densetsu no Starfy 1"),
"a_5600_e" => Playing("Densetsu no Starfy 2"),
"a_7565_e" => Playing("Densetsu no Starfy 3"),
"a_6553_e" => Playing("F-ZERO CLIMAX"),
"a_7842_e" or "a_7842_p" => Playing("F-Zero™- GP Legend"),
"a_9283_e" => Playing("F-Zero™ Maximum Velocity"),
"a_3744_e" or "a_3744_x" or "a_3744_y" => Playing("Fire Emblem™"),
"a_8978_d" or "a_8978_e" or "a_8978_f" or "a_8978_i" or "a_8978_s" => Playing("Golden Sun™: The Lost Age"),
"a_3108_d" or "a_3108_e" or "a_3108_f" or "a_3108_i" or "a_3108_s" => Playing("Golden Sun™"),
"a_3654_e" or "a_3654_p" => Playing("Kirby™ & The Amazing Mirror"),
"a_7279_p" => Playing("Kuru Kuru Kururin™"),
"a_7311_e" or "a_7311_p" => Playing("Mario & Luigi™: Superstar Saga"),
"a_6845_e" => Playing("Mario Kart™: Super Circuit™"),
"a_4139_e" or "a_4139_p" => Playing("Metroid™ Fusion"),
"a_6834_e" or "a_6834_p" => Playing("Metroid™: Zero Mission"),
"a_8989_e" or "a_8989_p" => Playing("Pokémon™ Mystery Dungeon: Red Rescue Team"),
"a_9444_e" => Playing("Super Mario™ Advance"),
"a_9901_e" or "a_9901_p" => Playing("Super Mario™ Advance 4: Super Mario Bros.™ 3"),
"a_2939_e" => Playing("Super Mario World™: Super Mario Advance 2"),
"a_2939_p" => Playing("Super Mario World™: Super Mario Advance 2™"),
"a_1302_e" => Playing("WarioWare™, Inc.: Mega Microgame$!"),
"a_1302_p" => Playing("WarioWare™, Inc.: Minigame Mania."),
"a_6960_e" or "a_6960_p" => Playing("Yoshi's Island™: Super Mario™ Advance 3"),
"a_5190_e" or "a_5190_p" => Playing("The Legend of Zelda™: A Link to the Past™ Four Swords"),
"a_8665_e" or "a_8665_p" => Playing("The Legend of Zelda™: The Minish Cap"),
_ => FormattedValue.ForceReset
};
private static FormattedValue GB_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
#endregion
#region GameBoy
"c_7224_e" or "c_7224_p" => Playing("Alone in the Dark: The New Nightmare"),
"c_5022_e" => Playing("Blaster Master: Enemy Below"),
"c_3381_e" => Playing("Game & Watch™ Gallery 3"),
@@ -615,6 +587,39 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"d_5124_e" => Playing("Super Mario Land™ 2 - 6 Golden Coins™"),
"d_7970_e" => Playing("Super Mario Land™"),
"d_8484_e" => Playing("Tetris®"),
#endregion
#region GameBoy Advance
"a_9694_e" => Playing("Densetsu no Starfy 1"),
"a_5600_e" => Playing("Densetsu no Starfy 2"),
"a_7565_e" => Playing("Densetsu no Starfy 3"),
"a_6553_e" => Playing("F-ZERO CLIMAX"),
"a_7842_e" or "a_7842_p" => Playing("F-Zero™- GP Legend"),
"a_9283_e" => Playing("F-Zero™ Maximum Velocity"),
"a_3744_e" or "a_3744_x" or "a_3744_y" => Playing("Fire Emblem™"),
"a_8978_d" or "a_8978_e" or "a_8978_f" or "a_8978_i" or "a_8978_s" => Playing("Golden Sun™: The Lost Age"),
"a_3108_d" or "a_3108_e" or "a_3108_f" or "a_3108_i" or "a_3108_s" => Playing("Golden Sun™"),
"a_3654_e" or "a_3654_p" => Playing("Kirby™ & The Amazing Mirror"),
"a_7279_p" => Playing("Kuru Kuru Kururin™"),
"a_7311_e" or "a_7311_p" => Playing("Mario & Luigi™: Superstar Saga"),
"a_6845_e" => Playing("Mario Kart™: Super Circuit™"),
"a_4139_e" or "a_4139_p" => Playing("Metroid™ Fusion"),
"a_6834_e" or "a_6834_p" => Playing("Metroid™: Zero Mission"),
"a_8989_e" or "a_8989_p" => Playing("Pokémon™ Mystery Dungeon: Red Rescue Team"),
"a_9444_e" => Playing("Super Mario™ Advance"),
"a_9901_e" or "a_9901_p" => Playing("Super Mario™ Advance 4: Super Mario Bros.™ 3"),
"a_2939_e" => Playing("Super Mario World™: Super Mario Advance 2"),
"a_2939_p" => Playing("Super Mario World™: Super Mario Advance 2™"),
"a_1302_e" => Playing("WarioWare™, Inc.: Mega Microgame$!"),
"a_1302_p" => Playing("WarioWare™, Inc.: Minigame Mania."),
"a_6960_e" or "a_6960_p" => Playing("Yoshi's Island™: Super Mario™ Advance 3"),
"a_5190_e" or "a_5190_p" => Playing("The Legend of Zelda™: A Link to the Past™ Four Swords"),
"a_8665_e" or "a_8665_p" => Playing("The Legend of Zelda™: The Minish Cap"),
#endregion
_ => FormattedValue.ForceReset
};
}

View File

@@ -62,25 +62,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
)
)
.AddSpec(
"0100c9a00ece6000",
spec => spec.AddValueFormatter("launch_title_id", N64_LaunchedGame)
)
.AddSpec(
"01008d300c50c000",
spec => spec.AddValueFormatter("launch_title_id", SNES_LaunchedGame)
)
.AddSpec(
"0100d870045b6000",
spec => spec.AddValueFormatter("launch_title_id", NES_LaunchedGame)
).AddSpec(
"010012f017576000",
spec => spec.AddValueFormatter("launch_title_id", GBA_LaunchedGame)
).AddSpec(
"0100c62011050000",
spec => spec.AddValueFormatter("launch_title_id", GB_LaunchedGame)
).AddSpec(
"0100b3c014bda000",
spec => spec.AddValueFormatter("launch_title_id", Genesis_LaunchedGame)
[
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"],
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
);
private static string Playing(string game) => $"Playing {game}";

View File

@@ -14,8 +14,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// </summary>
public class GameSpec
{
public static GameSpec Create(string requiredTitleId, params IEnumerable<string> otherTitleIds)
=> new() { TitleIds = otherTitleIds.Prepend(requiredTitleId).ToArray() };
public static GameSpec Create(IEnumerable<string> titleIds)
=> new() { TitleIds = titleIds.ToArray() };
private int _lastPriority;
public required string[] TitleIds { get; init; }
public List<FormatterSpecBase> ValueFormatters { get; } = [];
@@ -28,8 +34,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(string reportKey, SingleValueFormatter valueFormatter)
=> AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
public GameSpec AddValueFormatter(
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
/// <summary>
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
@@ -39,15 +47,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(int priority, string reportKey,
SingleValueFormatter valueFormatter)
public GameSpec AddValueFormatter(
int priority,
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(new FormatterSpec
{
ValueFormatters.Add(new FormatterSpec
{
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
});
return this;
}
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
});
/// <summary>
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
@@ -56,8 +63,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
=> AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
public GameSpec AddMultiValueFormatter(
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
/// <summary>
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
@@ -67,15 +76,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
MultiValueFormatter valueFormatter)
public GameSpec AddMultiValueFormatter(
int priority,
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddValueFormatter(new MultiFormatterSpec
{
ValueFormatters.Add(new MultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
return this;
}
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
/// <summary>
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
@@ -87,8 +95,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
=> AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
public GameSpec AddSparseMultiValueFormatter(
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
/// <summary>
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
@@ -101,13 +111,18 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys,
SparseMultiValueFormatter valueFormatter)
public GameSpec AddSparseMultiValueFormatter(
int priority,
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddValueFormatter(new SparseMultiFormatterSpec
{
ValueFormatters.Add(new SparseMultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
private GameSpec AddValueFormatter<T>(T formatterSpec) where T : FormatterSpecBase
{
ValueFormatters.Add(formatterSpec);
return this;
}
}
@@ -174,16 +189,17 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return true;
}
}
public abstract class FormatterSpecBase
{
public abstract bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object data);
public int Priority { get; init; }
public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; }
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue)
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue)
{
formattedValue = default;
if (!GetData(playReport, out object data))
@@ -197,15 +213,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
switch (Formatter)
{
case SingleValueFormatter svf when data is MessagePackObject mpo:
formattedValue = svf(new SingleValue(mpo) { Application = appMeta, PlayReport = playReport });
case SingleValueFormatter svf when data is MessagePackObject match:
formattedValue = svf(
new SingleValue(match) { Application = appMeta, PlayReport = playReport }
);
return true;
case MultiValueFormatter mvf when data is List<MessagePackObject> messagePackObjects:
formattedValue = mvf(new MultiValue(messagePackObjects) { Application = appMeta, PlayReport = playReport });
case MultiValueFormatter mvf when data is List<MessagePackObject> matches:
formattedValue = mvf(
new MultiValue(matches) { Application = appMeta, PlayReport = playReport }
);
return true;
case SparseMultiValueFormatter smvf when
data is Dictionary<string, MessagePackObject> sparseMessagePackObjects:
formattedValue = smvf(new SparseMultiValue(sparseMessagePackObjects) { Application = appMeta, PlayReport = playReport });
case SparseMultiValueFormatter smvf when data is Dictionary<string, MessagePackObject> sparseMatches:
formattedValue = smvf(
new SparseMultiValue(sparseMatches) { Application = appMeta, PlayReport = playReport }
);
return true;
default:
throw new InvalidOperationException("Formatter delegate is not of a known type!");