mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-02 01:39:01 +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.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.HLE;
|
||||
using System;
|
||||
@@ -11,6 +12,8 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
|
||||
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;
|
||||
|
||||
namespace LibKenjinx
|
||||
@@ -268,7 +271,12 @@ namespace LibKenjinx
|
||||
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();
|
||||
}
|
||||
@@ -487,9 +495,14 @@ namespace LibKenjinx
|
||||
//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()
|
||||
|
||||
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
/// Enable Rumble
|
||||
/// </summary>
|
||||
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 = "")
|
||||
{
|
||||
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
|
||||
.Where(r => r.EndsWith(ext))
|
||||
(Assembly assembly, string resourcePath) = ResolveManifestPath(path);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.Translation.PTC;
|
||||
using Ryujinx.Cpu.Signal;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Memory;
|
||||
@@ -127,7 +128,7 @@ namespace Ryujinx.Cpu.Nce
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||
public IDiskCacheLoadState LoadDiskCache(PtcCacheInfo cacheInfo, bool enabled)
|
||||
{
|
||||
return new DummyDiskCacheLoadState();
|
||||
}
|
||||
|
||||
@@ -1773,7 +1773,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
||||
|
||||
if (IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||
if (context.Definitions.Stage.IsVtg() && IoMap.IsPerVertexBuiltIn(ioVariable))
|
||||
{
|
||||
perVertexBuiltIn = ioVariable;
|
||||
ioVariable = IoVariable.Position;
|
||||
|
||||
@@ -26,12 +26,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
||||
{
|
||||
MemoryArrange.MemoryArrange4GiB or
|
||||
MemoryArrange.MemoryArrange4GiBSystemDev or
|
||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3152 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiBAppletDev => 3236 * MiB,
|
||||
MemoryArrange.MemoryArrange4GiBAppletDev => 2048 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiB => 4783 * MiB,
|
||||
MemoryArrange.MemoryArrange8GiB => 6831 * MiB,
|
||||
MemoryArrange.MemoryArrange10GiB => 8879 * MiB,
|
||||
MemoryArrange.MemoryArrange12GiB => 10927 * MiB,
|
||||
MemoryArrange.MemoryArrange6GiB => 4867 * MiB,
|
||||
MemoryArrange.MemoryArrange8GiB => 6915 * MiB,
|
||||
MemoryArrange.MemoryArrange10GiB => 8963 * MiB,
|
||||
MemoryArrange.MemoryArrange12GiB => 11011 * MiB,
|
||||
_ => 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>> _arbiterThreads;
|
||||
private readonly ByDynamicPriority _byDynamicPriority;
|
||||
|
||||
public KAddressArbiter(KernelContext context)
|
||||
{
|
||||
@@ -23,7 +22,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
_condVarThreads = [];
|
||||
_arbiterThreads = [];
|
||||
_byDynamicPriority = new ByDynamicPriority();
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
@@ -332,14 +325,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
@@ -424,14 +412,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
|
||||
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);
|
||||
}
|
||||
else
|
||||
@@ -627,12 +610,28 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||
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;
|
||||
Dictionary<string, ulong> processExes = tamperInfo.BuildIds.Zip(tamperInfo.CodeAddresses, (k, v) => new { k, v })
|
||||
.ToDictionary(x => x.k[..Math.Min(Cheat.CheatIdSize, x.k.Length)], x => x.v);
|
||||
Dictionary<string, ulong> processExes = new();
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -71,7 +71,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
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
|
||||
public ResultCode TrySelectUserWithoutInteraction(ServiceCtx context)
|
||||
{
|
||||
|
||||
+15
@@ -21,6 +21,21 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
|
||||
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)]
|
||||
// CreateStorage(u64) -> object<nn::am::service::IStorage>
|
||||
public ResultCode CreateStorage(ServiceCtx context)
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
|
||||
private readonly bool[] _supportedPlayers;
|
||||
private VibrationValue _neutralVibrationValue = new()
|
||||
{
|
||||
AmplitudeLow = 0f,
|
||||
AmplitudeLow = 0.01f,
|
||||
FrequencyLow = 160f,
|
||||
AmplitudeHigh = 0f,
|
||||
FrequencyHigh = 320f,
|
||||
|
||||
@@ -24,7 +24,16 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
@@ -12,140 +13,276 @@ namespace Ryujinx.Input.SDL3
|
||||
{
|
||||
private readonly SDL_hid_device* _hidHandle;
|
||||
|
||||
private byte[] _buffer;
|
||||
private static ushort _vendor;
|
||||
private static ushort _product;
|
||||
|
||||
private int _globalCount;
|
||||
private ulong _lastWriteTicks;
|
||||
|
||||
private NpadHdRumble(SDL_hid_device* hidHandle)
|
||||
{
|
||||
_hidHandle = hidHandle;
|
||||
InitializeDevice();
|
||||
}
|
||||
|
||||
public static NpadHdRumble Create(SDL_Gamepad* gamepadHandle)
|
||||
{
|
||||
ushort vendor = SDL_GetGamepadVendor(gamepadHandle);
|
||||
if (vendor != 0x057e)
|
||||
_vendor = SDL_GetGamepadVendor(gamepadHandle);
|
||||
if (!Enum.IsDefined(typeof(HDRumbleSupportedVendor), _vendor))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ushort product = SDL_GetGamepadProduct(gamepadHandle);
|
||||
if (product != 0x2006 && product != 0x2007 && product != 0x2009 && product != 0x200e)
|
||||
_product = SDL_GetGamepadProduct(gamepadHandle);
|
||||
if (!Enum.IsDefined(typeof(HDRumbleSupportedProduct), _product))
|
||||
{
|
||||
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
|
||||
private void WriteHdRumble(
|
||||
int encLeftLowFreq, int encLeftLowAmp,
|
||||
int encLeftHighFreq, int encLeftHighAmp,
|
||||
int encRightLowFreq, int encRightLowAmp,
|
||||
int encRightHighFreq, int encRightHighAmp)
|
||||
private bool WriteNintendoHdRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
byte[] buf = new byte[10];
|
||||
|
||||
buf[0] = 0x10;
|
||||
buf[1] = (byte)((++_globalCount) & 0xF);
|
||||
|
||||
buf[2] = (byte)(encLeftHighFreq & 0xFF);
|
||||
buf[3] = (byte)(encLeftHighAmp + ((encLeftHighFreq >> 8) & 0xFF));
|
||||
buf[4] = (byte)(encLeftLowFreq + ((encLeftLowAmp >> 8) & 0xFF));
|
||||
buf[5] = (byte)(encLeftLowAmp & 0xFF);
|
||||
|
||||
buf[6] = (byte)(encRightHighFreq & 0xFF);
|
||||
buf[7] = (byte)(encRightHighAmp + ((encRightHighFreq >> 8) & 0xFF));
|
||||
buf[8] = (byte)(encRightLowFreq + ((encRightLowAmp >> 8) & 0xFF));
|
||||
buf[9] = (byte)(encRightLowAmp & 0xFF);
|
||||
|
||||
int leftLowAmp = EncodeLowAmp(left.AmplitudeLow);
|
||||
int leftLowFreq = EncodeLowFreq(left.FrequencyLow) + (leftLowAmp >> 8);
|
||||
int leftHighFreq = EncodeHighFreq(left.FrequencyHigh);
|
||||
int leftHighAmp = EncodeHighAmp(left.AmplitudeHigh) + (leftHighFreq >> 8);
|
||||
|
||||
int rightLowAmp = EncodeLowAmp(right.AmplitudeLow);
|
||||
int rightLowFreq = EncodeLowFreq(right.FrequencyLow) + (rightLowAmp >> 8);
|
||||
int rightHighFreq = EncodeHighFreq(right.FrequencyHigh);
|
||||
int rightHighAmp = EncodeHighAmp(right.AmplitudeHigh) + (rightHighFreq >> 8);
|
||||
|
||||
_buffer[0] = 0x10;
|
||||
_buffer[1] = (byte)((_globalCount++) & 0xF);
|
||||
|
||||
// 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)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
float lf = Math.Clamp(lowFreq, 40.875885f, 626.286133f);
|
||||
return (int)Math.Round(32 * Math.Log2(lf * 0.1f)) - 0x40;
|
||||
return (int)Math.Clamp(32 * Math.Log2(lowFreq * 0.1f) - 0x40, 81.75177f, 1252.572266f);
|
||||
}
|
||||
|
||||
private static int EncodeHighFreq(float highFreq)
|
||||
{
|
||||
float hf = Math.Clamp(highFreq, 81.75177f, 1252.572266f);
|
||||
return ((int)Math.Round(32 * Math.Log2(hf * 0.1f)) - 0x60) * 4;
|
||||
return (int)Math.Clamp(32 * Math.Log2(highFreq * 0.1f) - 0x60, 81.75177f, 1252.572266f);
|
||||
}
|
||||
|
||||
private static int EncodeLowAmp(float rawAmp)
|
||||
{
|
||||
int encodedAmp = 0;
|
||||
double encodedAmp = 0;
|
||||
|
||||
if (rawAmp is > 0 and < 0.012f)
|
||||
{
|
||||
encodedAmp = 1;
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f));
|
||||
}
|
||||
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||
|
||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f));
|
||||
}
|
||||
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||
|
||||
else if (rawAmp is >= 0.225f and <= 1f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f));
|
||||
}
|
||||
|
||||
return (int)Math.Floor(encodedAmp / 2.0) + 64;
|
||||
encodedAmp = 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.Round(encodedAmp);
|
||||
}
|
||||
|
||||
private static int EncodeHighAmp(float rawAmp)
|
||||
{
|
||||
int encodedAmp = 0;
|
||||
double encodedAmp = 0;
|
||||
|
||||
if (rawAmp is > 0 and < 0.012f)
|
||||
{
|
||||
encodedAmp = 1;
|
||||
}
|
||||
|
||||
else if (rawAmp is >= 0.012f and < 0.112f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(4 * Math.Log2(rawAmp * 110f));
|
||||
}
|
||||
encodedAmp = 4 * Math.Log2(rawAmp * 110f);
|
||||
|
||||
else if (rawAmp is >= 0.112f and < 0.225f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(16 * Math.Log2(rawAmp * 17f));
|
||||
}
|
||||
encodedAmp = 16 * Math.Log2(rawAmp * 17f);
|
||||
|
||||
else if (rawAmp is >= 0.225f and <= 1f)
|
||||
{
|
||||
encodedAmp = (int)Math.Round(32 * Math.Log2(rawAmp * 8.7f));
|
||||
}
|
||||
|
||||
return encodedAmp * 2;
|
||||
encodedAmp = 32 * Math.Log2(rawAmp * 8.7f);
|
||||
|
||||
encodedAmp = Math.Round(encodedAmp / 2.0);
|
||||
encodedAmp = Math.Clamp(encodedAmp, 0.0, 100.2867);
|
||||
return (int)encodedAmp;
|
||||
}
|
||||
|
||||
public bool HdRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
WriteHdRumble(EncodeLowFreq(left.FrequencyLow),
|
||||
EncodeLowAmp(left.AmplitudeLow),
|
||||
EncodeHighFreq(left.FrequencyHigh),
|
||||
EncodeHighAmp(left.AmplitudeHigh),
|
||||
EncodeLowFreq(right.FrequencyLow),
|
||||
EncodeLowAmp(right.AmplitudeLow),
|
||||
EncodeHighFreq(right.FrequencyHigh),
|
||||
EncodeHighAmp(right.AmplitudeHigh));
|
||||
return true;
|
||||
if(_product is (ushort) HDRumbleSupportedProduct.ProController
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconLeft
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconRight
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconPair
|
||||
or (ushort) HDRumbleSupportedProduct.JoyconGrip)
|
||||
{
|
||||
return WriteNintendoHdRumble(left, right);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
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;
|
||||
}
|
||||
|
||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
public bool Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||
{
|
||||
if ((Features & GamepadFeaturesFlag.Rumble) == 0)
|
||||
return;
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||
@@ -199,6 +201,15 @@ namespace Ryujinx.Input.SDL3
|
||||
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
||||
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)
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
public void SetTriggerThreshold(float triggerThreshold)
|
||||
{
|
||||
|
||||
// No operations
|
||||
}
|
||||
|
||||
public bool HDRumble(VibrationValue left, VibrationValue right)
|
||||
@@ -171,10 +171,12 @@ namespace Ryujinx.Input.SDL3
|
||||
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)
|
||||
return;
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||
@@ -193,6 +195,15 @@ namespace Ryujinx.Input.SDL3
|
||||
if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
|
||||
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)
|
||||
|
||||
@@ -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.Linq;
|
||||
using System.Numerics;
|
||||
@@ -61,7 +64,14 @@ namespace Ryujinx.Input.SDL3
|
||||
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)
|
||||
{
|
||||
@@ -78,6 +88,15 @@ namespace Ryujinx.Input.SDL3
|
||||
left.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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using SDL;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
@@ -385,9 +386,14 @@ namespace Ryujinx.Input.SDL3
|
||||
// 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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
@@ -66,7 +67,12 @@ namespace Ryujinx.Input.SDL3
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -554,34 +554,38 @@ namespace Ryujinx.Input.HLE
|
||||
{
|
||||
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;
|
||||
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}");
|
||||
return;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <param name="left">The vibration data for the left side</param>
|
||||
/// <param name="right">The vibration data for the right side</param>
|
||||
bool HDRumble(VibrationValue left, VibrationValue right)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool HDRumble(VibrationValue left, VibrationValue right);
|
||||
|
||||
/// <summary>
|
||||
/// 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="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>
|
||||
void Rumble(float lowFrequency, float highFrequency, uint durationMs);
|
||||
bool Rumble(float lowFrequency, float highFrequency, uint durationMs);
|
||||
|
||||
/// <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>
|
||||
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
|
||||
GamepadStateSnapshot GetMappedStateSnapshot();
|
||||
|
||||
@@ -1323,6 +1323,7 @@ namespace Ryujinx.UI.Common.Configuration
|
||||
EnableRumble = false,
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
UseHDRumble = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +455,8 @@
|
||||
"ControllerSettingsRumble": "Rumble",
|
||||
"ControllerSettingsRumbleStrongMultiplier": "Strong 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}]",
|
||||
"DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?",
|
||||
"DialogConfirmationTitle": "Ryujinx - Confirmation",
|
||||
|
||||
@@ -222,6 +222,7 @@ namespace Ryujinx.Headless
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false,
|
||||
UseHDRumble = true
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
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;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
@@ -63,8 +64,13 @@ namespace Ryujinx.Ava.Input
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
startedDeferring = true;
|
||||
|
||||
ContentDialogButtonClickDeferral deferral = args.GetDeferral();
|
||||
Deferral deferral = args.GetDeferral();
|
||||
|
||||
sender.PrimaryButtonClick -= DeferClose;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
|
||||
public float WeakRumble { get; set; }
|
||||
public float StrongRumble { get; set; }
|
||||
public bool UseHDRumble { get; set; }
|
||||
|
||||
public string Id { get; set; }
|
||||
public ControllerType ControllerType { get; set; }
|
||||
@@ -482,6 +483,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
EnableRumble = controllerInput.Rumble.EnableRumble;
|
||||
WeakRumble = controllerInput.Rumble.WeakRumble;
|
||||
StrongRumble = controllerInput.Rumble.StrongRumble;
|
||||
UseHDRumble = controllerInput.Rumble.UseHDRumble;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -539,6 +541,7 @@ namespace Ryujinx.Ava.UI.Models.Input
|
||||
EnableRumble = EnableRumble,
|
||||
WeakRumble = WeakRumble,
|
||||
StrongRumble = StrongRumble,
|
||||
UseHDRumble = UseHDRumble,
|
||||
},
|
||||
Version = InputConfig.CurrentVersion,
|
||||
DeadzoneLeft = DeadzoneLeft,
|
||||
|
||||
@@ -685,6 +685,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
StrongRumble = 1f,
|
||||
WeakRumble = 1f,
|
||||
EnableRumble = false,
|
||||
UseHDRumble = false
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,5 +23,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableHDRumble;
|
||||
public bool EnableHDRumble
|
||||
{
|
||||
get => _enableHDRumble;
|
||||
set
|
||||
{
|
||||
_enableHDRumble = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,15 @@
|
||||
Margin="5,0"
|
||||
Text="{Binding WeakRumble, StringFormat=\{0:0.00\}}" />
|
||||
</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>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
StrongRumble = config.StrongRumble,
|
||||
WeakRumble = config.WeakRumble,
|
||||
EnableHDRumble = config.UseHDRumble
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
@@ -49,6 +50,7 @@ namespace Ryujinx.Ava.UI.Views.Input
|
||||
GamepadInputConfig config = viewModel.Config;
|
||||
config.StrongRumble = content._viewModel.StrongRumble;
|
||||
config.WeakRumble = content._viewModel.WeakRumble;
|
||||
config.UseHDRumble = content._viewModel.EnableHDRumble;
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
|
||||
Reference in New Issue
Block a user