Compare commits

..

2 Commits

Author SHA1 Message Date
Frog Business
e7ca6e8d76 Merge 6d78e71fc7 into 744d813b87 2025-02-15 01:53:36 -06:00
Barış Hamil
6d78e71fc7 Handheld Gyro 2025-02-01 22:26:05 +03:00
28 changed files with 301 additions and 263 deletions

View File

@@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Alimer.Bindings.SDL" Version="3.7.1" />
<PackageVersion Include="Avalonia" Version="11.0.13" /> <PackageVersion Include="Avalonia" Version="11.0.13" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.13" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.13" />
<PackageVersion Include="Avalonia.Desktop" 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 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}" 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 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}" 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}.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.ActiveCfg = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

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

View File

@@ -9,5 +9,6 @@ namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
Invalid, Invalid,
GamepadDriver, GamepadDriver,
CemuHook, 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 namespace Ryujinx.Input.HLE
{ {
public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver) public class InputManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IHandheld handheld)
: IDisposable : IDisposable
{ {
public IGamepadDriver KeyboardDriver { get; } = keyboardDriver; public IGamepadDriver KeyboardDriver { get; } = keyboardDriver;
public IGamepadDriver GamepadDriver { get; } = gamepadDriver; public IGamepadDriver GamepadDriver { get; } = gamepadDriver;
public IGamepadDriver MouseDriver { get; private set; } public IGamepadDriver MouseDriver { get; private set; }
public IHandheld Handheld { get; } = handheld;
public void SetMouseDriver(IGamepadDriver mouseDriver) public void SetMouseDriver(IGamepadDriver mouseDriver)
{ {
@@ -18,7 +19,7 @@ namespace Ryujinx.Input.HLE
public NpadManager CreateNpadManager() public NpadManager CreateNpadManager()
{ {
return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver); return new NpadManager(KeyboardDriver, GamepadDriver, MouseDriver, Handheld);
} }
public TouchScreenManager CreateTouchScreenManager() public TouchScreenManager CreateTouchScreenManager()
@@ -38,6 +39,7 @@ namespace Ryujinx.Input.HLE
KeyboardDriver?.Dispose(); KeyboardDriver?.Dispose();
GamepadDriver?.Dispose(); GamepadDriver?.Dispose();
MouseDriver?.Dispose(); MouseDriver?.Dispose();
Handheld?.Dispose();
} }
} }

View File

@@ -218,12 +218,14 @@ namespace Ryujinx.Input.HLE
public string Id { get; private set; } public string Id { get; private set; }
private readonly CemuHookClient _cemuHookClient; private readonly CemuHookClient _cemuHookClient;
private readonly IHandheld _handheld;
public NpadController(CemuHookClient cemuHookClient) public NpadController(CemuHookClient cemuHookClient, IHandheld handheld)
{ {
State = default; State = default;
Id = null; Id = null;
_cemuHookClient = cemuHookClient; _cemuHookClient = cemuHookClient;
_handheld = handheld;
} }
public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config) public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config)
@@ -287,6 +289,18 @@ namespace Ryujinx.Input.HLE
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion) 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 (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
{ {
if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion)) if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))

View File

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

@@ -447,31 +447,6 @@
"zh_TW": "開啟 Ryujinx 資料夾" "zh_TW": "開啟 Ryujinx 資料夾"
} }
}, },
{
"ID": "MenuBarFileOpenScreenshotsFolder",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Open Screenshots Folder",
"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": "MenuBarFileOpenLogsFolder", "ID": "MenuBarFileOpenLogsFolder",
"Translations": { "Translations": {
@@ -8197,6 +8172,31 @@
"zh_TW": "使用與 CemuHook 相容的體感" "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", "ID": "ControllerSettingsMotionControllerSlot",
"Translations": { "Translations": {
@@ -17222,31 +17222,6 @@
"zh_TW": "開啟 Ryujinx 檔案系統資料夾" "zh_TW": "開啟 Ryujinx 檔案系統資料夾"
} }
}, },
{
"ID": "OpenScreenshotFolderTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Open Ryujinx screenshots folder",
"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": "OpenRyujinxLogsTooltip", "ID": "OpenRyujinxLogsTooltip",
"Translations": { "Translations": {
@@ -24123,4 +24098,4 @@
} }
} }
] ]
} }

View File

@@ -56,7 +56,6 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update; ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue); TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
HorizonStatic.PlayReport += HandlePlayReport; HorizonStatic.PlayReport += HandlePlayReport;
PlayReports.Initialize();
} }
private static void Update(object sender, ReactiveEventArgs<bool> evnt) private static void Update(object sender, ReactiveEventArgs<bool> evnt)

View File

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

View File

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

View File

@@ -119,23 +119,17 @@
TextWrapping="Wrap" > TextWrapping="Wrap" >
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5" ToolTip.Tip="{Binding DynamicRichPresenceDescription}"> <StackPanel Orientation="Horizontal" Spacing="5">
<ui:SymbolIcon <ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
Foreground="ForestGreen"
Symbol="Checkmark"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock <TextBlock
Foreground="ForestGreen" Foreground="ForestGreen"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
Text="{ext:Locale GameInfoRpcDynamic}" Text="{ext:Locale GameInfoRpcDynamic}"
TextAlignment="Start" TextAlignment="Start"
TextWrapping="Wrap"> TextWrapping="Wrap" >
</TextBlock> </TextBlock>
<ui:SymbolIcon <ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
Foreground="Red"
Symbol="Cancel"
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock <TextBlock
Foreground="Red" Foreground="Red"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

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

View File

@@ -1,7 +1,6 @@
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.PlayReport;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
@@ -11,11 +10,6 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData; public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
public string DynamicRichPresenceDescription =>
AppData.HasDynamicRichPresenceSupport
? AppData.RichPresenceSpec.Value.Description
: GameSpec.DefaultDescription;
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version); public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer); public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension); public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);

View File

@@ -18,6 +18,34 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
[ObservableProperty] private double _gyroDeadzone; [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

@@ -1347,25 +1347,6 @@ namespace Ryujinx.Ava.UI.ViewModels
OpenHelper.OpenFolder(AppDataManager.BaseDirPath); OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
} }
public void OpenScreenshotsFolder()
{
string screenshotsDir = Path.Combine(AppDataManager.BaseDirPath, "screenshots");
try
{
if (!Directory.Exists(screenshotsDir))
Directory.CreateDirectory(screenshotsDir);
}
catch (Exception ex)
{
Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot");
return;
}
OpenHelper.OpenFolder(screenshotsDir);
}
public void OpenLogsFolder() public void OpenLogsFolder()
{ {
string logPath = AppDataManager.GetOrCreateLogsDir(); string logPath = AppDataManager.GetOrCreateLogsDir();

View File

@@ -61,6 +61,17 @@
Margin="5, 0" Margin="5, 0"
Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" /> Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
</StackPanel> </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 <Separator
Height="1" Height="1"
Margin="0,5" /> Margin="0,5" />

View File

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

View File

@@ -66,10 +66,6 @@
Command="{Binding OpenRyujinxFolder}" Command="{Binding OpenRyujinxFolder}"
Header="{ext:Locale MenuBarFileOpenEmuFolder}" Header="{ext:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" /> ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{Binding OpenScreenshotsFolder}"
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
ToolTip.Tip="{ext:Locale OpenScreenshotFolderTooltip}"/>
<MenuItem <MenuItem
Command="{Binding OpenLogsFolder}" Command="{Binding OpenLogsFolder}"
Header="{ext:Locale MenuBarFileOpenLogsFolder}" Header="{ext:Locale MenuBarFileOpenLogsFolder}"

View File

@@ -31,6 +31,7 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2; using Ryujinx.Input.SDL2;
using Ryujinx.Input.SDL3;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -109,7 +110,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached) 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.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
this.ScalingChanged += OnScalingChanged; this.ScalingChanged += OnScalingChanged;

View File

@@ -10,7 +10,6 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Compat; using Ryujinx.Ava.Utilities.Compat;
using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.HLE.Loaders.Processes.Extensions;
@@ -36,14 +35,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
{ {
_id = value; _id = value;
Compatibility = CompatibilityCsv.Find(value); Compatibility = CompatibilityCsv.Find(Id);
RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec)
? gameSpec
: default(Optional<GameSpec>);
} }
} }
public Optional<GameSpec> RichPresenceSpec { get; set; }
public string Developer { get; set; } = "Unknown"; public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0"; public string Version { get; set; } = "0";
public int PlayerCount { get; set; } public int PlayerCount { get; set; }
@@ -52,7 +46,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0; public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString); public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue; public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
public TimeSpan TimePlayed { get; set; } public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; } public DateTime? LastPlayed { get; set; }

View File

@@ -1,6 +1,5 @@
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -20,11 +19,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs); public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId));
public bool TryGetSpec(string titleId, out GameSpec gameSpec)
=> (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null;
/// <summary> /// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary> /// </summary>
@@ -33,12 +27,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform) public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
{ {
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _)) Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
return AddSpec(transform(GameSpec.Create(titleId))); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
Logger.Notice.PrintMsg(LogClass.Application, return AddSpec(transform(GameSpec.Create(titleId)));
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
return this;
} }
/// <summary> /// <summary>
@@ -49,12 +41,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns> /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(string titleId, Action<GameSpec> transform) public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
{ {
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _)) Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
return AddSpec(GameSpec.Create(titleId).Apply(transform)); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
Logger.Notice.PrintMsg(LogClass.Application, return AddSpec(GameSpec.Create(titleId).Apply(transform));
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
return this;
} }
/// <summary> /// <summary>
@@ -67,19 +57,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
Func<GameSpec, GameSpec> transform) Func<GameSpec, GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x))) Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
return AddSpec(transform(GameSpec.Create(tids))); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
Logger.Notice.PrintMsg(LogClass.Application, return AddSpec(transform(GameSpec.Create(tids)));
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
tids.FormatCollection(
x => x,
separator: ", ",
prefix: "[",
suffix: "]"
)
}'");
return this;
} }
/// <summary> /// <summary>
@@ -91,21 +72,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform) public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
{ {
string[] tids = titleIds.ToArray(); string[] tids = titleIds.ToArray();
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x))) Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
return AddSpec(GameSpec.Create(tids).Apply(transform)); $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
Logger.Notice.PrintMsg(LogClass.Application, return AddSpec(GameSpec.Create(tids).Apply(transform));
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
tids.FormatCollection(
x => x,
separator: ", ",
prefix: "[",
suffix: "]"
)
}'");
return this;
} }
/// <summary> /// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec. /// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
/// </summary> /// </summary>
@@ -133,13 +105,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{ {
if (!playReport.ReportData.IsDictionary) if (!playReport.ReportData.IsDictionary)
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
if (!TryGetSpec(runningGameId, out GameSpec spec)) if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled; return FormattedValue.Unhandled;
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
{ {
if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value)) if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
continue; continue;
return value; return value;

View File

@@ -1,5 +1,4 @@
using Gommon; using Gommon;
using Humanizer;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
@@ -19,9 +18,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport
< -201d => "Exploring the Depths", < -201d => "Exploring the Depths",
_ => "Roaming Hyrule" _ => "Roaming Hyrule"
}; };
private static FormattedValue SkywardSwordHD_Rupees(SingleValue value)
=> "rupee".ToQuantity(value.Matched.IntValue);
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value) private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode"; => value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";

View File

@@ -1,97 +1,67 @@
using System; namespace Ryujinx.Ava.Utilities.PlayReport
namespace Ryujinx.Ava.Utilities.PlayReport
{ {
public static partial class PlayReports public static partial class PlayReports
{ {
public static void Initialize() public static Analyzer Analyzer { get; } = new Analyzer()
{ .AddSpec(
// init lazy value "01007ef00011e000",
_ = Analyzer; spec => spec
} .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
public static Analyzer Analyzer => _analyzerLazy.Value; .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
)
private static readonly Lazy<Analyzer> _analyzerLazy = new(() => .AddSpec(
new Analyzer() "0100f2c0115b6000",
.AddSpec( spec => spec
"01007ef00011e000", .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
spec => spec .AddSpec(
.WithDescription("based on being in Master Mode.") "0100000000010000",
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) spec =>
// reset to normal status when switching between normal & master mode in title screen spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) )
) .AddSpec(
.AddSpec( "010075000ecbe000",
"0100f2c0115b6000", spec =>
spec => spec spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") )
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) .AddSpec(
.AddSpec( "010028600ebda000",
"01002da013484000", spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
spec => spec )
.WithDescription("based on how many Rupees you have.") .AddSpec( // Global & China IDs
.AddValueFormatter("rupees", SkywardSwordHD_Rupees)) ["0100152000022000", "010075100e8ec000"],
.AddSpec( spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
"0100000000010000", )
spec => spec .AddSpec(
.WithDescription("based on if you're playing with Assist Mode.") ["0100a3d008c5c000", "01008f6008c5e000"],
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) spec => spec
) .AddValueFormatter("area_no", PokemonSVArea)
.AddSpec( .AddValueFormatter("team_circle", PokemonSVUnionCircle)
"010075000ecbe000", )
spec => spec .AddSpec(
.WithDescription("based on if you're playing with Assist Mode.") "01006a800016e000",
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) spec => spec
) .AddSparseMultiValueFormatter(
.AddSpec( [
"010028600ebda000", // Metadata to figure out what PlayReport we have.
spec => spec "match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") "adv_slot",
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) // List of Fighters
) "player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
.AddSpec( // Global & China IDs "player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",
["0100152000022000", "010075100e8ec000"], // List of rankings/placements
spec => spec "player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank",
.WithDescription("based on what modes you're selecting in the menu & whether or not you're in a race.") "player_6_rank", "player_7_rank", "player_8_rank"
.AddValueFormatter("To", MarioKart8Deluxe_Mode) ],
) SuperSmashBrosUltimate_Mode
.AddSpec( )
["0100a3d008c5c000", "01008f6008c5e000"], )
spec => spec .AddSpec(
.WithDescription("based on what area of Paldea you're exploring.") [
.AddValueFormatter("area_no", PokemonSVArea) "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
.AddValueFormatter("team_circle", PokemonSVUnionCircle) "010012f017576000", "0100c62011050000", "0100b3c014bda000"],
) spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
.AddSpec( );
"01006a800016e000",
spec => spec
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
.AddSparseMultiValueFormatter(
[
// Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot",
// List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",
// List of rankings/placements
"player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank",
"player_6_rank", "player_7_rank", "player_8_rank"
],
SuperSmashBrosUltimate_Mode
)
)
.AddSpec(
[
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
],
spec => spec
.WithDescription("based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
)
);
private static string Playing(string game) => $"Playing {game}"; private static string Playing(string game) => $"Playing {game}";
} }

View File

@@ -23,20 +23,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public required string[] TitleIds { get; init; } public required string[] TitleIds { get; init; }
public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game.";
private string _valueDescription;
public string Description => _valueDescription ?? DefaultDescription;
public GameSpec WithDescription(string description)
{
_valueDescription = description != null
? $"Formats the details on your Discord presence {description}"
: null;
return this;
}
public List<FormatterSpecBase> ValueFormatters { get; } = []; public List<FormatterSpecBase> ValueFormatters { get; } = [];
@@ -211,7 +197,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public string[] ReportKeys { get; init; } public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; } public Delegate Formatter { get; init; }
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue) out FormattedValue formattedValue)
{ {
formattedValue = default; formattedValue = default;