Compare commits

..
Author SHA1 Message Date
Shyanne 6fc8816deb Merge branch 'master' into implement-IsSixAxisSensorAtRest 2025-11-25 22:00:55 -05:00
Shyanne 0e92352e72 HLE: Implement IHidServer IsSixAxisSensorAtRest
Fixes the actually insane amount of log spam in games that check for this, such as Luigi's Mansion 3.

Values in HidDevices.NpadDevices.isAtRest may need to be tuned to a better range for resting detection. I originally set them to 0, and my controller rests at definitely NOT 0.

Will need to be revisited when implementing functionality for the global SixAxisActive bool, IHidServer.StartSixAxisTracking, and IHidServer.StopSixAxisTracking.
2025-11-25 21:31:34 -05:00
5 changed files with 90 additions and 98 deletions
-16
View File
@@ -20,21 +20,5 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(res != -1);
}
}
// "dumpable" attribute of the calling process
private const int PR_SET_DUMPABLE = 4;
[DllImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
if (OperatingSystem.IsLinux())
{
int dumpableInt = dumpable ? 1 : 0;
int result = prctl(PR_SET_DUMPABLE, dumpableInt);
Debug.Assert(result == 0);
}
}
}
}
+26 -46
View File
@@ -483,29 +483,10 @@ namespace Ryujinx.HLE.FileSystem
{
if (Directory.Exists(keysSource))
{
string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray();
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
foreach (string filePath in keyPaths)
foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
{
try
{
VerifyKeysFile(filePath);
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
if (File.Exists(destPath))
File.Delete(destPath);
File.Copy(filePath, destPath, true);
VerifyKeysFile(filePath);
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
}
return;
@@ -520,25 +501,13 @@ namespace Ryujinx.HLE.FileSystem
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
try
if (info.Extension is ".keys")
{
VerifyKeysFile(keysSource);
}
catch
{
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
}
else
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
}
string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
@@ -1016,8 +985,8 @@ namespace Ryujinx.HLE.FileSystem
public static void VerifyKeysFile(string filePath)
{
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$";
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
if (File.Exists(filePath))
{
@@ -1025,13 +994,24 @@ namespace Ryujinx.HLE.FileSystem
string fileName = Path.GetFileName(filePath);
string[] lines = File.ReadAllLines(filePath);
bool verified = fileName switch
bool verified;
switch (fileName)
{
"prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern),
"title.keys" => VerifyKeys(lines, titlePattern),
_ => throw new FormatException(
$"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.")
};
case "prod.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "title.keys":
verified = VerifyKeys(lines, titlePattern);
break;
case "console.keys":
verified = VerifyKeys(lines, genericPattern);
break;
case "dev.keys":
verified = VerifyKeys(lines, genericPattern);
break;
default:
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
}
if (!verified)
{
@@ -56,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical;
SixAxisActive = false;
}
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -580,6 +581,23 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight;
}
public bool isAtRest(int playerNumber)
{
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
float acceleration = Math.Abs(storage.Acceleration.X)
+ Math.Abs(storage.Acceleration.Y)
+ Math.Abs(storage.Acceleration.Z);
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ Math.Abs(storage.AngularVelocity.Y)
+ Math.Abs(storage.AngularVelocity.Z);
return ((acceleration <= 1.5F) && (angularVelocity <= 1.1F));
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{
+40 -7
View File
@@ -602,19 +602,52 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandCmif(82)]
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
// 4 byte struct w/ 4-byte alignment
// 0x0 0x4 TypeValue
// 0x0 0x1 NpadStyleIndex
// 0x1 0x1 PlayerNumber
// 0x2 0x1 DeviceIdx
// uint typeValue = (uint) sixAxisSensorHandle;
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff;
int playerNumber = (sixAxisSensorHandle << 8) & 0xff;
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff;
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
// 32bit sign extension padding
// if = 0, + offset, else - offset
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
bool isAtRest = true;
bool isAtRest;
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
// We currently do not support stopping or starting SixAxisTracking.
// It is just always tracking, the bool is unused.
// See Ryujinx.HLE.HOS.Services.Hid.NpadDevices
// common cases: if
// controller is keyboard (keyboards don't have gyroscopes, silly goose!)
// controller has no gyroscope
// SixAxisActive == false
// SixAxisTracking == false
// then isAtRest = true
// else check gyroscopic activity
// check gyroscopic activity
isAtRest = context.Device.Hid.Npads.isAtRest(playerNumber);
context.ResponseData.Write(isAtRest);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
return ResultCode.Success;
}
@@ -629,7 +662,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
return ResultCode.Success;
}
+6 -29
View File
@@ -17,7 +17,6 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL3.Common;
@@ -47,7 +46,7 @@ namespace Ryujinx.Ava
public static int Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows())
{
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
@@ -56,11 +55,8 @@ namespace Ryujinx.Ava
return 0;
}
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) ||
Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86))
if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") ||
Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)"))
{
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning);
return 0;
@@ -77,23 +73,11 @@ namespace Ryujinx.Ava
}
}
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
// This is undesirable and causes very odd behavior during development (the process stops responding,
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
if (!coreDumpArg)
{
OsUtils.SetCoreDumpable(false);
}
PreviewerDetached = true;
if (noGuiArg)
if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
{
HeadlessRyujinx.Entrypoint(args);
HeadlessRyujinx.Entrypoint(args[1..]);
return 0;
}
@@ -128,14 +112,6 @@ namespace Ryujinx.Ava
: [Win32RenderingMode.Software]
});
private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument)
{
List<string> argList = [.. args];
bool found = argList.Remove(targetArgument);
args = argList.ToArray();
return found;
}
private static void Initialize(string[] args)
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
@@ -201,6 +177,7 @@ namespace Ryujinx.Ava
}
}
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))