mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-02 19:18:52 +02:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
280c427c55 | ||
|
|
25b4892981 | ||
|
|
d16d67f7c4 | ||
|
|
e7cb0632c5 | ||
|
|
4ea8c0b280 | ||
|
|
aef309800c | ||
|
|
a008cd20f1 | ||
|
|
ec6ce46357 | ||
|
|
a7a3a17b48 | ||
|
|
94cafdd1f9 | ||
|
|
1b57022694 | ||
|
|
2bb07a4364 | ||
|
|
5e67569a6e | ||
|
|
97f0336aa2 |
@@ -2,6 +2,7 @@ using Ryujinx.Common.Configuration;
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using System;
|
using System;
|
||||||
@@ -11,6 +12,8 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||||
|
using ControllerType = Ryujinx.Common.Configuration.Hid.ControllerType;
|
||||||
|
using PlayerIndex = Ryujinx.Common.Configuration.Hid.PlayerIndex;
|
||||||
using StickInputId = Ryujinx.Input.StickInputId;
|
using StickInputId = Ryujinx.Input.StickInputId;
|
||||||
|
|
||||||
namespace LibKenjinx
|
namespace LibKenjinx
|
||||||
@@ -268,7 +271,12 @@ namespace LibKenjinx
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@@ -487,9 +495,14 @@ namespace LibKenjinx
|
|||||||
//throw new System.NotImplementedException();
|
//throw new System.NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
{
|
{
|
||||||
//throw new System.NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GamepadStateSnapshot GetMappedStateSnapshot()
|
public GamepadStateSnapshot GetMappedStateSnapshot()
|
||||||
|
|||||||
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
|
|||||||
/// Enable Rumble
|
/// Enable Rumble
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableRumble { get; set; }
|
public bool EnableRumble { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable HD Rumble support
|
||||||
|
/// </summary
|
||||||
|
public bool UseHDRumble { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,8 +126,13 @@ namespace Ryujinx.Common
|
|||||||
|
|
||||||
public static string[] GetAllAvailableResources(string path, string ext = "")
|
public static string[] GetAllAvailableResources(string path, string ext = "")
|
||||||
{
|
{
|
||||||
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
|
(Assembly assembly, string resourcePath) = ResolveManifestPath(path);
|
||||||
.Where(r => r.EndsWith(ext))
|
|
||||||
|
string manifestPath = assembly.GetName().Name + "." + resourcePath.Replace('/', '.');
|
||||||
|
|
||||||
|
return assembly.GetManifestResourceNames()
|
||||||
|
.Where(r => r.StartsWith(manifestPath + ".", StringComparison.Ordinal))
|
||||||
|
.Where(r => r.EndsWith(ext, StringComparison.Ordinal))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
|
using ARMeilleure.Translation.PTC;
|
||||||
using Ryujinx.Cpu.Signal;
|
using Ryujinx.Cpu.Signal;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
@@ -127,7 +128,7 @@ namespace Ryujinx.Cpu.Nce
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||||
{
|
{
|
||||||
return new DummyDiskCacheLoadState();
|
return new DummyDiskCacheLoadState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1773,7 +1773,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
{
|
{
|
||||||
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
||||||
|
|
||||||
if (IoMap.IsPerVertexBuiltIn(ioVariable))
|
if (context.Definitions.Stage.IsVtg() && IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||||
{
|
{
|
||||||
perVertexBuiltIn = ioVariable;
|
perVertexBuiltIn = ioVariable;
|
||||||
ioVariable = IoVariable.Position;
|
ioVariable = IoVariable.Position;
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
{
|
{
|
||||||
MemoryArrange.MemoryArrange4GiB or
|
MemoryArrange.MemoryArrange4GiB or
|
||||||
MemoryArrange.MemoryArrange4GiBSystemDev or
|
MemoryArrange.MemoryArrange4GiBSystemDev or
|
||||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3152 * MiB,
|
MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
|
||||||
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
||||||
MemoryArrange.MemoryArrange6GiB => 4783 * MiB,
|
MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
|
||||||
MemoryArrange.MemoryArrange8GiB => 6831 * MiB,
|
MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
|
||||||
MemoryArrange.MemoryArrange10GiB => 8879 * MiB,
|
MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
|
||||||
MemoryArrange.MemoryArrange12GiB => 10927 * MiB,
|
MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
|
||||||
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
|
_ => throw new ArgumentException($"Invalid memory arrange \"{arrange}\"."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
|
private readonly Dictionary<ulong, List<KThread>> _condVarThreads;
|
||||||
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
|
private readonly Dictionary<ulong, List<KThread>> _arbiterThreads;
|
||||||
private readonly ByDynamicPriority _byDynamicPriority;
|
|
||||||
|
|
||||||
public KAddressArbiter(KernelContext context)
|
public KAddressArbiter(KernelContext context)
|
||||||
{
|
{
|
||||||
@@ -23,7 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
_condVarThreads = [];
|
_condVarThreads = [];
|
||||||
_arbiterThreads = [];
|
_arbiterThreads = [];
|
||||||
_byDynamicPriority = new ByDynamicPriority();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
|
||||||
@@ -142,14 +140,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
|
if (_condVarThreads.TryGetValue(condVarAddress, out List<KThread> threads))
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||||
|
|
||||||
|
|
||||||
if (threads.Count > 0)
|
|
||||||
{
|
|
||||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
|
||||||
if (i < 0) i = ~i;
|
|
||||||
}
|
|
||||||
|
|
||||||
threads.Insert(i, currentThread);
|
threads.Insert(i, currentThread);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -332,14 +325,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||||
|
|
||||||
|
|
||||||
if (threads.Count > 0)
|
|
||||||
{
|
|
||||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
|
||||||
if (i < 0) i = ~i;
|
|
||||||
}
|
|
||||||
|
|
||||||
threads.Insert(i, currentThread);
|
threads.Insert(i, currentThread);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -424,14 +412,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
if (_arbiterThreads.TryGetValue(address, out List<KThread> threads))
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = FindDynamicPriorityFifoInsertionIndex(threads, currentThread);
|
||||||
|
|
||||||
|
|
||||||
if (threads.Count > 0)
|
|
||||||
{
|
|
||||||
i = threads.BinarySearch(currentThread, _byDynamicPriority);
|
|
||||||
if (i < 0) i = ~i;
|
|
||||||
}
|
|
||||||
|
|
||||||
threads.Insert(i, currentThread);
|
threads.Insert(i, currentThread);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -627,12 +610,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
return validCount;
|
return validCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ByDynamicPriority : IComparer<KThread>
|
private static int FindDynamicPriorityFifoInsertionIndex(List<KThread> threads, KThread currentThread)
|
||||||
{
|
{
|
||||||
public int Compare(KThread x, KThread y)
|
int low = 0;
|
||||||
|
int high = threads.Count;
|
||||||
|
|
||||||
|
// Lower numeric values represent higher priorities. Use upper-bound insertion
|
||||||
|
// to preserve FIFO order among waiters with the same dynamic priority.
|
||||||
|
while (low < high)
|
||||||
{
|
{
|
||||||
return x!.DynamicPriority.CompareTo(y!.DynamicPriority);
|
int middle = low + ((high - low) >> 1);
|
||||||
|
|
||||||
|
if (threads[middle].DynamicPriority <= currentThread.DynamicPriority)
|
||||||
|
{
|
||||||
|
low = middle + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
high = middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return low;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -749,8 +749,28 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Cheat> cheats = mods.Cheats;
|
List<Cheat> cheats = mods.Cheats;
|
||||||
Dictionary<string, ulong> processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
|
Dictionary<string, ulong> processExes = new();
|
||||||
.ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v);
|
|
||||||
|
foreach ((string buildId, ulong codeAddress) in tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses))
|
||||||
|
{
|
||||||
|
string normalizedBuildId = buildId[..Math.Min(Cheat.CheatIdSize, buildId.Length)];
|
||||||
|
|
||||||
|
if (processExes.TryGetValue(normalizedBuildId, out ulong existingAddress))
|
||||||
|
{
|
||||||
|
if (existingAddress != codeAddress)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(
|
||||||
|
LogClass.ModLoader,
|
||||||
|
$"Duplicate BuildId prefix '{normalizedBuildId}' has different code addresses. " +
|
||||||
|
$"Existing: 0x{existingAddress:X}, duplicate: 0x{codeAddress:X}. " +
|
||||||
|
$"Keeping the first one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
processExes.Add(normalizedBuildId, codeAddress);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Cheat cheat in cheats)
|
foreach (Cheat cheat in cheats)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
|
return _applicationServiceServer.IsUserRegistrationRequestPermitted(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
[CommandCmif(51)]
|
[CommandCmif(51)] // 1.0.0 – 18.1.0
|
||||||
|
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
||||||
|
public ResultCode TrySelectUserWithoutInteractionDeprecated(ServiceCtx context)
|
||||||
|
{
|
||||||
|
return _applicationServiceServer.TrySelectUserWithoutInteraction(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(52)] // 19.0.0+
|
||||||
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
// TrySelectUserWithoutInteraction(bool) -> nn::account::Uid
|
||||||
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
||||||
{
|
{
|
||||||
|
|||||||
+15
@@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(3)] // 20.0.0+
|
||||||
|
// CreateLibraryAppletEx(u32, u32, u64) -> object<nn::am::service::ILibraryAppletAccessor>
|
||||||
|
public ResultCode CreateLibraryAppletEx(ServiceCtx context)
|
||||||
|
{
|
||||||
|
AppletId appletId = (AppletId)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadInt32(); // libraryAppletMode
|
||||||
|
|
||||||
|
_ = context.RequestData.ReadUInt64(); // threadId
|
||||||
|
|
||||||
|
MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(10)]
|
[CommandCmif(10)]
|
||||||
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
||||||
public ResultCode CreateStorage(ServiceCtx context)
|
public ResultCode CreateStorage(ServiceCtx context)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
|||||||
private readonly bool[] _supportedPlayers;
|
private readonly bool[] _supportedPlayers;
|
||||||
private VibrationValue _neutralVibrationValue = new()
|
private VibrationValue _neutralVibrationValue = new()
|
||||||
{
|
{
|
||||||
AmplitudeLow = 0f,
|
AmplitudeLow = 0.01f,
|
||||||
FrequencyLow = 160f,
|
FrequencyLow = 160f,
|
||||||
AmplitudeHigh = 0f,
|
AmplitudeHigh = 0f,
|
||||||
FrequencyHigh = 320f,
|
FrequencyHigh = 320f,
|
||||||
|
|||||||
@@ -24,7 +24,16 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
private ulong _latestPid;
|
private ulong _latestPid;
|
||||||
|
|
||||||
public ProcessResult ActiveApplication => _processesByPid[_latestPid];
|
public ProcessResult ActiveApplication
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_latestPid != 0 && _processesByPid.TryGetValue(_latestPid, out ProcessResult process))
|
||||||
|
return process;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ProcessLoader(Switch device)
|
public ProcessLoader(Switch device)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using SDL;
|
using SDL;
|
||||||
using static SDL.SDL3;
|
using static SDL.SDL3;
|
||||||
@@ -12,140 +13,276 @@ namespace Ryujinx.Input.SDL3
|
|||||||
{
|
{
|
||||||
private readonly SDL_hid_device* _hidHandle;
|
private readonly SDL_hid_device* _hidHandle;
|
||||||
|
|
||||||
|
private byte[] _buffer;
|
||||||
|
private static ushort _vendor;
|
||||||
|
private static ushort _product;
|
||||||
|
|
||||||
private int _globalCount;
|
private int _globalCount;
|
||||||
|
private ulong _lastWriteTicks;
|
||||||
|
|
||||||
private NpadHdRumble(SDL_hid_device* hidHandle)
|
private NpadHdRumble(SDL_hid_device* hidHandle)
|
||||||
{
|
{
|
||||||
_hidHandle = hidHandle;
|
_hidHandle = hidHandle;
|
||||||
|
InitializeDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle)
|
public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle)
|
||||||
{
|
{
|
||||||
ushort vendor = SDL_GetGamepadVendor(gamepadHandle);
|
_vendor = SDL_GetGamepadVendor(gamepadHandle);
|
||||||
if (vendor != 0x057e)
|
if (!Enum.IsDefined(typeof(HDRumbleSupportedVendor), _vendor))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort product = SDL_GetGamepadProduct(gamepadHandle);
|
_product = SDL_GetGamepadProduct(gamepadHandle);
|
||||||
if (product != 0x2006 && product != 0x2007 && product != 0x2009 && product != 0x200e)
|
if (!Enum.IsDefined(typeof(HDRumbleSupportedProduct), _product))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NpadHdRumble(SDL_hid_open(vendor, product, 0));
|
int serialNumber = 0;
|
||||||
|
string? serial = SDL_GetGamepadSerial(gamepadHandle);
|
||||||
|
if (serial is not null)
|
||||||
|
{
|
||||||
|
int.TryParse(serial, out serialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NpadHdRumble(SDL_hid_open(_vendor, _product, serialNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some of the code was translated from https://github.com/MIZUSHIKI/JoyShockLibrary-plus-HDRumble
|
// Some of the code was translated from https://github.com/MIZUSHIKI/JoyShockLibrary-plus-HDRumble
|
||||||
private void WriteHdRumble(
|
private bool WriteNintendoHdRumble(VibrationValue left, VibrationValue right)
|
||||||
int encLeftLowFreq, int encLeftLowAmp,
|
|
||||||
int encLeftHighFreq, int encLeftHighAmp,
|
|
||||||
int encRightLowFreq, int encRightLowAmp,
|
|
||||||
int encRightHighFreq, int encRightHighAmp)
|
|
||||||
{
|
{
|
||||||
byte[] buf = new byte[10];
|
int leftLowAmp = EncodeLowAmp(left.AmplitudeLow);
|
||||||
|
int leftLowFreq = EncodeLowFreq(left.FrequencyLow) + (leftLowAmp >> 8);
|
||||||
buf[0] = 0x10;
|
int leftHighFreq = EncodeHighFreq(left.FrequencyHigh);
|
||||||
buf[1] = (byte)((++_globalCount) & 0xF);
|
int leftHighAmp = EncodeHighAmp(left.AmplitudeHigh) + (leftHighFreq >> 8);
|
||||||
|
|
||||||
buf[2] = (byte)(encLeftHighFreq & 0xFF);
|
int rightLowAmp = EncodeLowAmp(right.AmplitudeLow);
|
||||||
buf[3] = (byte)(encLeftHighAmp + ((encLeftHighFreq >> 8) & 0xFF));
|
int rightLowFreq = EncodeLowFreq(right.FrequencyLow) + (rightLowAmp >> 8);
|
||||||
buf[4] = (byte)(encLeftLowFreq + ((encLeftLowAmp >> 8) & 0xFF));
|
int rightHighFreq = EncodeHighFreq(right.FrequencyHigh);
|
||||||
buf[5] = (byte)(encLeftLowAmp & 0xFF);
|
int rightHighAmp = EncodeHighAmp(right.AmplitudeHigh) + (rightHighFreq >> 8);
|
||||||
|
|
||||||
buf[6] = (byte)(encRightHighFreq & 0xFF);
|
_buffer[0] = 0x10;
|
||||||
buf[7] = (byte)(encRightHighAmp + ((encRightHighFreq >> 8) & 0xFF));
|
_buffer[1] = (byte)((_globalCount++) & 0xF);
|
||||||
buf[8] = (byte)(encRightLowFreq + ((encRightLowAmp >> 8) & 0xFF));
|
|
||||||
buf[9] = (byte)(encRightLowAmp & 0xFF);
|
// Left LRA
|
||||||
|
_buffer[2] = (byte)(leftLowFreq & 0xFF);
|
||||||
|
_buffer[3] = (byte)(leftHighAmp & 0xFF);
|
||||||
|
_buffer[4] = (byte)(leftHighFreq & 0xFF);
|
||||||
|
_buffer[5] = (byte)(leftLowAmp & 0xFF);
|
||||||
|
|
||||||
|
// Right LRA
|
||||||
|
_buffer[6] = (byte)(rightLowFreq & 0xFF);
|
||||||
|
_buffer[7] = (byte)(rightHighAmp & 0xFF);
|
||||||
|
_buffer[8] = (byte)(rightHighFreq & 0xFF);
|
||||||
|
_buffer[9] = (byte)(rightLowAmp & 0xFF);
|
||||||
|
|
||||||
if (_globalCount > 0xF)
|
if (_globalCount > 0xF)
|
||||||
{
|
{
|
||||||
_globalCount = 0x0;
|
_globalCount = 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (byte* ptr = buf)
|
fixed (byte* ptr = _buffer)
|
||||||
{
|
{
|
||||||
SDL_hid_write(_hidHandle, ptr, (nuint)buf.Length);
|
if (SendHdRumble(ptr, (nuint)_buffer.Length) >= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||||
|
SDL_ClearError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static int EncodeLowFreq(float lowFreq)
|
private static int EncodeLowFreq(float lowFreq)
|
||||||
{
|
{
|
||||||
float lf = Math.Clamp(lowFreq, 40.875885f, 626.286133f);
|
return (int)Math.Clamp(32 * Math.Log2(lowFreq * 0.1f) - 0x40, 81.75177f, 1252.572266f);
|
||||||
return (int)Math.Round(32 * Math.Log2(lf * 0.1f)) - 0x40;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int EncodeHighFreq(float highFreq)
|
private static int EncodeHighFreq(float highFreq)
|
||||||
{
|
{
|
||||||
float hf = Math.Clamp(highFreq, 81.75177f, 1252.572266f);
|
return (int)Math.Clamp(32 * Math.Log2(highFreq * 0.1f) - 0x60, 81.75177f, 1252.572266f);
|
||||||
return ((int)Math.Round(32 * Math.Log2(hf * 0.1f)) - 0x60) * 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int EncodeLowAmp(float rawAmp)
|
private static int EncodeLowAmp(float rawAmp)
|
||||||
{
|
{
|
||||||
int encodedAmp = 0;
|
double encodedAmp = 0;
|
||||||
|
|
||||||
if (rawAmp is > 0 and < 0.012f)
|
if (rawAmp is > 0 and < 0.012f)
|
||||||
{
|
|
||||||
encodedAmp = 1;
|
encodedAmp = 1;
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||||
{
|
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||||
encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f));
|
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||||
{
|
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||||
encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f));
|
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.225f and <= 1f)
|
else if (rawAmp is >= 0.225f and <= 1f)
|
||||||
{
|
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
|
||||||
encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f));
|
|
||||||
}
|
encodedAmp = Math.Round((encodedAmp / 2.0) + 64.0);
|
||||||
|
encodedAmp = Math.Clamp(encodedAmp, 0.0, 100.2867);
|
||||||
return (int)Math.Floor(encodedAmp / 2.0) + 64;
|
return (int)Math.Round(encodedAmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int EncodeHighAmp(float rawAmp)
|
private static int EncodeHighAmp(float rawAmp)
|
||||||
{
|
{
|
||||||
int encodedAmp = 0;
|
double encodedAmp = 0;
|
||||||
|
|
||||||
if (rawAmp is > 0 and < 0.012f)
|
if (rawAmp is > 0 and < 0.012f)
|
||||||
{
|
|
||||||
encodedAmp = 1;
|
encodedAmp = 1;
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||||
{
|
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||||
encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f));
|
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||||
{
|
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||||
encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f));
|
|
||||||
}
|
|
||||||
else if (rawAmp is >= 0.225f and <= 1f)
|
else if (rawAmp is >= 0.225f and <= 1f)
|
||||||
{
|
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
|
||||||
encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f));
|
|
||||||
}
|
encodedAmp = Math.Round(encodedAmp / 2.0);
|
||||||
|
encodedAmp = Math.Clamp(encodedAmp, 0.0, 100.2867);
|
||||||
return encodedAmp * 2;
|
return (int)encodedAmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HdRumble(VibrationValue left, VibrationValue right)
|
public bool HdRumble(VibrationValue left, VibrationValue right)
|
||||||
{
|
{
|
||||||
WriteHdRumble(EncodeLowFreq(left.FrequencyLow),
|
if(_product is (ushort) HDRumbleSupportedProduct.ProController
|
||||||
EncodeLowAmp(left.AmplitudeLow),
|
or (ushort) HDRumbleSupportedProduct.JoyconLeft
|
||||||
EncodeHighFreq(left.FrequencyHigh),
|
or (ushort) HDRumbleSupportedProduct.JoyconRight
|
||||||
EncodeHighAmp(left.AmplitudeHigh),
|
or (ushort) HDRumbleSupportedProduct.JoyconPair
|
||||||
EncodeLowFreq(right.FrequencyLow),
|
or (ushort) HDRumbleSupportedProduct.JoyconGrip)
|
||||||
EncodeLowAmp(right.AmplitudeLow),
|
{
|
||||||
EncodeHighFreq(right.FrequencyHigh),
|
return WriteNintendoHdRumble(left, right);
|
||||||
EncodeHighAmp(right.AmplitudeHigh));
|
}
|
||||||
return true;
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SendHdRumble(byte* data, nuint length)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
ulong currentTicks = SDL_GetTicks();
|
||||||
|
|
||||||
|
// Ditch rumble if we haven't hit the poll-rate yet.
|
||||||
|
if ((currentTicks - _lastWriteTicks) <= GetPollRate())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = SDL_hid_write(_hidHandle, data, length);
|
||||||
|
if (result >= 0)
|
||||||
|
{
|
||||||
|
_lastWriteTicks = currentTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeDevice()
|
||||||
|
{
|
||||||
|
if (_vendor is (ushort)HDRumbleSupportedVendor.Nintendo)
|
||||||
|
{
|
||||||
|
_buffer = new byte[10];
|
||||||
|
byte[] init = new byte[64];
|
||||||
|
|
||||||
|
// Pro Controller and Charge Grip
|
||||||
|
if (_product
|
||||||
|
is (ushort)HDRumbleSupportedProduct.ProController
|
||||||
|
or (ushort)HDRumbleSupportedProduct.JoyconGrip)
|
||||||
|
{
|
||||||
|
SDL_LockJoysticks();
|
||||||
|
fixed (byte* ptr = init)
|
||||||
|
{
|
||||||
|
init[0] = 0x80;
|
||||||
|
init[1] = 0x05; // Allow bluetooth timeout TODO: use 0x04 to force USB only (toggle?)
|
||||||
|
SDL_hid_write(_hidHandle, ptr, 64);
|
||||||
|
}
|
||||||
|
SDL_UnlockJoysticks();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joycons
|
||||||
|
if (_product
|
||||||
|
is (ushort)HDRumbleSupportedProduct.JoyconLeft
|
||||||
|
or (ushort)HDRumbleSupportedProduct.JoyconRight
|
||||||
|
or (ushort)HDRumbleSupportedProduct.JoyconPair)
|
||||||
|
{
|
||||||
|
|
||||||
|
SDL_LockJoysticks();
|
||||||
|
fixed (byte* ptr = init)
|
||||||
|
{
|
||||||
|
// we could write data to the controller here (see above)
|
||||||
|
}
|
||||||
|
SDL_UnlockJoysticks();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetPollRate()
|
||||||
|
{
|
||||||
|
ulong pollRate = 0;
|
||||||
|
if (_vendor is (ushort)HDRumbleSupportedVendor.Nintendo)
|
||||||
|
{
|
||||||
|
// https://docs.handheldlegend.com/s/progcc-3/doc/lag-comparison-aAR1mV3JLX
|
||||||
|
pollRate = (ulong) 16.67;
|
||||||
|
if (_product is (ushort)HDRumbleSupportedProduct.ProController
|
||||||
|
&& SDL_hid_get_device_info(_hidHandle)->bus_type == SDL_hid_bus_type.SDL_HID_API_BUS_USB)
|
||||||
|
{
|
||||||
|
pollRate = (ulong) 8.33;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
SDL_hid_close(_hidHandle);
|
SDL_hid_close(_hidHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum HDRumbleSupportedVendor : ushort
|
||||||
|
{
|
||||||
|
Nintendo = 0x057e,
|
||||||
|
Valve = 0x28de,
|
||||||
|
Sony = 0x054c
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HDRumbleSupportedProduct : ushort
|
||||||
|
{
|
||||||
|
// TODO: Currently, HD Rumble only supports the Pro Controller and JoyCons.
|
||||||
|
// We need to initialize and report to each device differently.
|
||||||
|
|
||||||
|
// Nintendo Switch: 0x057e
|
||||||
|
JoyconLeft = 0x2006,
|
||||||
|
JoyconRight = 0x2007,
|
||||||
|
JoyconPair = 0x2008,
|
||||||
|
ProController = 0x2009,
|
||||||
|
JoyconGrip = 0x200e,
|
||||||
|
|
||||||
|
// Nintendo Switch 2: 0x057e
|
||||||
|
Joycon2Right = 0x2066,
|
||||||
|
Joycon2Left = 0x2067,
|
||||||
|
Joycon2Pair = 0x2068,
|
||||||
|
Switch2ProController = 0x2069,
|
||||||
|
GamecubeController = 0x2073,
|
||||||
|
|
||||||
|
// Valve Steam Family: 0x28de
|
||||||
|
// https://github.com/libsdl-org/SDL/issues/9148
|
||||||
|
SteamDeck = 0x11ff,
|
||||||
|
SteamDeckVirtualDevice = 0x1205,
|
||||||
|
SteamController = 0x1106,
|
||||||
|
|
||||||
|
// PlayStation Dualsense: 0x054c
|
||||||
|
Dualsense = 0x0ce6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,10 +177,12 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return _hdRumble?.HdRumble(left, right) ?? false;
|
return _hdRumble?.HdRumble(left, right) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
|
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
|
||||||
return;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||||
@@ -199,6 +201,15 @@ namespace Ryujinx.Input.SDL3
|
|||||||
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
||||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(SDL_GetError()))
|
||||||
|
{
|
||||||
|
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||||
|
SDL_ClearError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId)
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace Ryujinx.Input.SDL3
|
|||||||
|
|
||||||
public void SetTriggerThreshold(float triggerThreshold)
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
{
|
{
|
||||||
|
// No operations
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HDRumble(VibrationValue left, VibrationValue right)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
@@ -171,10 +171,12 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return _hdRumble?.HdRumble(left, right) ?? false;
|
return _hdRumble?.HdRumble(left, right) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
|
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
|
||||||
return;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||||
@@ -193,6 +195,15 @@ namespace Ryujinx.Input.SDL3
|
|||||||
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
||||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(SDL_GetError()))
|
||||||
|
{
|
||||||
|
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||||
|
SDL_ClearError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId)
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Gommon;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -61,7 +64,14 @@ namespace Ryujinx.Input.SDL3
|
|||||||
return left.IsPressed(inputId) || right.IsPressed(inputId);
|
return left.IsPressed(inputId) || right.IsPressed(inputId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
|
{
|
||||||
|
// return _hdRumble?.HdRumble(left, right) ?? false;
|
||||||
|
// TODO: Track rumble and motion on both controllers
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
if (lowFrequency != 0)
|
if (lowFrequency != 0)
|
||||||
{
|
{
|
||||||
@@ -78,6 +88,15 @@ namespace Ryujinx.Input.SDL3
|
|||||||
left.Rumble(0, 0, durationMs);
|
left.Rumble(0, 0, durationMs);
|
||||||
right.Rumble(0, 0, durationMs);
|
right.Rumble(0, 0, durationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!SDL_GetError().IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
Logger.Error?.PrintMsg(LogClass.Hid, SDL_GetError());
|
||||||
|
SDL_ClearError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetConfiguration(InputConfig configuration)
|
public void SetConfiguration(InputConfig configuration)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using SDL;
|
using SDL;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -385,9 +386,14 @@ namespace Ryujinx.Input.SDL3
|
|||||||
// No operations
|
// No operations
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
{
|
{
|
||||||
// No operations
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId)
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -66,7 +67,12 @@ namespace Ryujinx.Input.SDL3
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -554,34 +554,38 @@ namespace Ryujinx.Input.HLE
|
|||||||
{
|
{
|
||||||
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
|
if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
|
||||||
{
|
{
|
||||||
if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
|
if (_config is not StandardControllerInputConfig controllerConfig ||
|
||||||
|
!controllerConfig.Rumble.EnableRumble)
|
||||||
{
|
{
|
||||||
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
|
return;
|
||||||
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
|
|
||||||
|
|
||||||
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
|
|
||||||
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
|
|
||||||
|
|
||||||
leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
|
||||||
leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
|
||||||
rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
|
||||||
rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
|
||||||
|
|
||||||
if (_gamepad?.HDRumble(leftVibrationValue, rightVibrationValue) == false)
|
|
||||||
{
|
|
||||||
_gamepad.Rumble(low, high, uint.MaxValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
|
||||||
$"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
|
|
||||||
$"L.high.amp={leftVibrationValue.AmplitudeHigh}, " +
|
|
||||||
$"L.low.freq={leftVibrationValue.FrequencyLow}, " +
|
|
||||||
$"L.high.freq={leftVibrationValue.FrequencyHigh}, " +
|
|
||||||
$"R.low.amp={rightVibrationValue.AmplitudeLow}, " +
|
|
||||||
$"R.high.amp={rightVibrationValue.AmplitudeHigh} " +
|
|
||||||
$"R.low.freq={rightVibrationValue.FrequencyLow}, " +
|
|
||||||
$"R.high.freq={rightVibrationValue.FrequencyHigh}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VibrationValue leftVibrationValue = dualVibrationValue.Item1;
|
||||||
|
VibrationValue rightVibrationValue = dualVibrationValue.Item2;
|
||||||
|
|
||||||
|
float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15)));
|
||||||
|
float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85)));
|
||||||
|
|
||||||
|
leftVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
||||||
|
leftVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
||||||
|
rightVibrationValue.AmplitudeLow *= controllerConfig.Rumble.WeakRumble;
|
||||||
|
rightVibrationValue.AmplitudeHigh *= controllerConfig.Rumble.StrongRumble;
|
||||||
|
|
||||||
|
if (!controllerConfig.Rumble.UseHDRumble || !_gamepad.HDRumble(leftVibrationValue, rightVibrationValue))
|
||||||
|
{
|
||||||
|
_gamepad.Rumble(low, high, 0xFFFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
|
||||||
|
// Value=value/multiplier * multiplier (result)
|
||||||
|
$"L.low.amp={leftVibrationValue.AmplitudeLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.AmplitudeLow}), " +
|
||||||
|
$"L.high.amp={leftVibrationValue.AmplitudeHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({leftVibrationValue.AmplitudeHigh}), " +
|
||||||
|
$"L.low.freq={leftVibrationValue.FrequencyLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({leftVibrationValue.FrequencyLow}), " +
|
||||||
|
$"L.high.freq={leftVibrationValue.FrequencyHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({leftVibrationValue.FrequencyHigh}), " +
|
||||||
|
$"R.low.amp={rightVibrationValue.AmplitudeLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({rightVibrationValue.AmplitudeLow}), " +
|
||||||
|
$"R.high.amp={rightVibrationValue.AmplitudeHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.AmplitudeHigh}), " +
|
||||||
|
$"R.low.freq={rightVibrationValue.FrequencyLow / controllerConfig.Rumble.WeakRumble} * {controllerConfig.Rumble.WeakRumble} ({rightVibrationValue.FrequencyLow}), " +
|
||||||
|
$"R.high.freq={rightVibrationValue.FrequencyHigh / controllerConfig.Rumble.StrongRumble} * {controllerConfig.Rumble.StrongRumble} ({rightVibrationValue.FrequencyHigh})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,10 +71,7 @@ namespace Ryujinx.Input
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="left">The vibration data for the left side</param>
|
/// <param name="left">The vibration data for the left side</param>
|
||||||
/// <param name="right">The vibration data for the right side</param>
|
/// <param name="right">The vibration data for the right side</param>
|
||||||
bool HDRumble(VibrationValue left, VibrationValue right)
|
bool HDRumble(VibrationValue left, VibrationValue right);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts a rumble effect on the gamepad.
|
/// Starts a rumble effect on the gamepad.
|
||||||
@@ -82,10 +79,10 @@ namespace Ryujinx.Input
|
|||||||
/// <param name="lowFrequency">The intensity of the low frequency from 0.0f to 1.0f</param>
|
/// <param name="lowFrequency">The intensity of the low frequency from 0.0f to 1.0f</param>
|
||||||
/// <param name="highFrequency">The intensity of the high frequency from 0.0f to 1.0f</param>
|
/// <param name="highFrequency">The intensity of the high frequency from 0.0f to 1.0f</param>
|
||||||
/// <param name="durationMs">The duration of the rumble effect in milliseconds.</param>
|
/// <param name="durationMs">The duration of the rumble effect in milliseconds.</param>
|
||||||
void Rumble(float lowFrequency, float highFrequency, uint durationMs);
|
bool Rumble(float lowFrequency, float highFrequency, uint durationMs);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a snaphost of the state of the gamepad that is remapped with the informations from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
|
/// Get a snaphost of the state of the gamepad that is remapped with the information from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
|
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
|
||||||
GamepadStateSnapshot GetMappedStateSnapshot();
|
GamepadStateSnapshot GetMappedStateSnapshot();
|
||||||
|
|||||||
@@ -1323,6 +1323,7 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
EnableRumble = false,
|
EnableRumble = false,
|
||||||
StrongRumble = 1f,
|
StrongRumble = 1f,
|
||||||
WeakRumble = 1f,
|
WeakRumble = 1f,
|
||||||
|
UseHDRumble = false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -455,6 +455,8 @@
|
|||||||
"ControllerSettingsRumble": "Rumble",
|
"ControllerSettingsRumble": "Rumble",
|
||||||
"ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier",
|
"ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier",
|
||||||
"ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier",
|
"ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier",
|
||||||
|
"ControllerSettingsRumbleUseHDRumble": "Enable HD Rumble",
|
||||||
|
"HDRumbleTooltip": "EXPERIMENTAL.\n\nSends more data to the controller for better rumble.\n\nCurrently only supports first-party Nintendo Switch controllers.\n\nLeave OFF if unsure.",
|
||||||
"DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]",
|
"DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]",
|
||||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?",
|
"DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?",
|
||||||
"DialogConfirmationTitle": "Ryujinx - Confirmation",
|
"DialogConfirmationTitle": "Ryujinx - Confirmation",
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ namespace Ryujinx.Headless
|
|||||||
StrongRumble = 1f,
|
StrongRumble = 1f,
|
||||||
WeakRumble = 1f,
|
WeakRumble = 1f,
|
||||||
EnableRumble = false,
|
EnableRumble = false,
|
||||||
|
UseHDRumble = true
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -149,9 +150,20 @@ namespace Ryujinx.Ava.Input
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTriggerThreshold(float triggerThreshold) { }
|
public void SetTriggerThreshold(float triggerThreshold)
|
||||||
|
{
|
||||||
|
// No operations
|
||||||
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero;
|
public Vector3 GetMotionData(MotionInputId inputId) => Vector3.Zero;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -63,8 +64,13 @@ namespace Ryujinx.Ava.Input
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
startedDeferring = true;
|
startedDeferring = true;
|
||||||
|
|
||||||
ContentDialogButtonClickDeferral deferral = args.GetDeferral();
|
Deferral deferral = args.GetDeferral();
|
||||||
|
|
||||||
sender.PrimaryButtonClick -= DeferClose;
|
sender.PrimaryButtonClick -= DeferClose;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
|
|
||||||
public float WeakRumble { get; set; }
|
public float WeakRumble { get; set; }
|
||||||
public float StrongRumble { get; set; }
|
public float StrongRumble { get; set; }
|
||||||
|
public bool UseHDRumble { get; set; }
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public ControllerType ControllerType { get; set; }
|
public ControllerType ControllerType { get; set; }
|
||||||
@@ -482,6 +483,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
EnableRumble = controllerInput.Rumble.EnableRumble;
|
EnableRumble = controllerInput.Rumble.EnableRumble;
|
||||||
WeakRumble = controllerInput.Rumble.WeakRumble;
|
WeakRumble = controllerInput.Rumble.WeakRumble;
|
||||||
StrongRumble = controllerInput.Rumble.StrongRumble;
|
StrongRumble = controllerInput.Rumble.StrongRumble;
|
||||||
|
UseHDRumble = controllerInput.Rumble.UseHDRumble;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,6 +541,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
|||||||
EnableRumble = EnableRumble,
|
EnableRumble = EnableRumble,
|
||||||
WeakRumble = WeakRumble,
|
WeakRumble = WeakRumble,
|
||||||
StrongRumble = StrongRumble,
|
StrongRumble = StrongRumble,
|
||||||
|
UseHDRumble = UseHDRumble,
|
||||||
},
|
},
|
||||||
Version = InputConfig.CurrentVersion,
|
Version = InputConfig.CurrentVersion,
|
||||||
DeadzoneLeft = DeadzoneLeft,
|
DeadzoneLeft = DeadzoneLeft,
|
||||||
|
|||||||
@@ -685,6 +685,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
StrongRumble = 1f,
|
StrongRumble = 1f,
|
||||||
WeakRumble = 1f,
|
WeakRumble = 1f,
|
||||||
EnableRumble = false,
|
EnableRumble = false,
|
||||||
|
UseHDRumble = false
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
|||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _enableHDRumble;
|
||||||
|
public bool EnableHDRumble
|
||||||
|
{
|
||||||
|
get => _enableHDRumble;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_enableHDRumble = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,15 @@
|
|||||||
Margin="5,0"
|
Margin="5,0"
|
||||||
Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" />
|
Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<CheckBox
|
||||||
|
Margin="5"
|
||||||
|
IsChecked="{Binding EnableHDRumble}">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,3,0,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{locale:Locale ControllerSettingsRumbleUseHDRumble}"
|
||||||
|
ToolTip.Tip="{locale:Locale HDRumbleTooltip}" />
|
||||||
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
{
|
{
|
||||||
StrongRumble = config.StrongRumble,
|
StrongRumble = config.StrongRumble,
|
||||||
WeakRumble = config.WeakRumble,
|
WeakRumble = config.WeakRumble,
|
||||||
|
EnableHDRumble = config.UseHDRumble
|
||||||
};
|
};
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -49,6 +50,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
|||||||
GamepadInputConfig config = viewModel.Config;
|
GamepadInputConfig config = viewModel.Config;
|
||||||
config.StrongRumble = content._viewModel.StrongRumble;
|
config.StrongRumble = content._viewModel.StrongRumble;
|
||||||
config.WeakRumble = content._viewModel.WeakRumble;
|
config.WeakRumble = content._viewModel.WeakRumble;
|
||||||
|
config.UseHDRumble = content._viewModel.EnableHDRumble;
|
||||||
};
|
};
|
||||||
|
|
||||||
await contentDialog.ShowAsync();
|
await contentDialog.ShowAsync();
|
||||||
|
|||||||
Reference in New Issue
Block a user