From 0e55fa924cce62af3dcb0042bc04d582f50b4dd5 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:25:26 +0300 Subject: [PATCH] [Pad] Track independent standard, special and remote port handles Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com> --- src/SharpEmu.Libs/Pad/PadExports.cs | 134 ++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 37 deletions(-) diff --git a/src/SharpEmu.Libs/Pad/PadExports.cs b/src/SharpEmu.Libs/Pad/PadExports.cs index 48325bd..ed2976a 100644 --- a/src/SharpEmu.Libs/Pad/PadExports.cs +++ b/src/SharpEmu.Libs/Pad/PadExports.cs @@ -11,6 +11,7 @@ namespace SharpEmu.Libs.Pad; public static class PadExports { private const int OrbisPadErrorInvalidHandle = unchecked((int)0x80920003); + private const int OrbisPadErrorAlreadyOpened = unchecked((int)0x80920004); private const int OrbisPadErrorNotInitialized = unchecked((int)0x80920005); private const int OrbisPadErrorDeviceNotConnected = unchecked((int)0x80920007); private const int OrbisPadErrorDeviceNoHandle = unchecked((int)0x80920008); @@ -19,8 +20,10 @@ public static class PadExports // valid 0x10000000 user to scePadOpen and receive DEVICE_NOT_CONNECTED, // leaving every later keyboard/gamepad read on an invalid handle. private const int PrimaryUserId = 0x10000000; + private const int SystemUserId = 0xFF; private const int StandardPortType = 0; - private const int PrimaryPadHandle = 1; + private const int SpecialPortType = 2; + private const int RemotePortType = 16; private const int ControllerInformationSize = 0x1C; private const int PadDataSize = 0x78; private const float StandardGravity = 9.80665f; @@ -31,11 +34,32 @@ public static class PadExports // user slots and treats every valid handle as a connected controller, so accepting // 0 there reports phantom pads that repeat the primary pad's buttons. private static int _padOpened; + private static readonly object PadHandleGate = new(); + private static readonly Dictionary PadHandleByKey = new(); + private static readonly Dictionary PadKeyByHandle = new(); + private static int _nextPadHandle = 1; - private static bool IsPrimaryPadHandle(int handle) => - handle == PrimaryPadHandle || (handle == 0 && Volatile.Read(ref _padOpened) == 0); + private readonly record struct PadOpenKey(int UserId, int Type, int Index); - internal static void ResetOpenedPadForTests() => Volatile.Write(ref _padOpened, 0); + private static bool IsOpenPadHandle(int handle) + { + lock (PadHandleGate) + { + return PadKeyByHandle.ContainsKey(handle) || (handle == 0 && _padOpened == 0); + } + } + + internal static void ResetOpenedPadForTests() + { + lock (PadHandleGate) + { + PadHandleByKey.Clear(); + PadKeyByHandle.Clear(); + _nextPadHandle = 1; + _padOpened = 0; + _initialized = false; + } + } private static readonly long InputSampleIntervalTicks = Math.Max(1, Stopwatch.Frequency / 1000); [ThreadStatic] @@ -91,12 +115,6 @@ public static class PadExports LibraryName = "libScePad")] public static int PadOpenExt(CpuContext ctx) => PadOpenCore(ctx, extended: true); - // scePadGetHandle(userId, type, index): returns the handle of an already-open - // pad without opening a new one. Dead Cells calls it every frame to poll - // input; leaving it unresolved returned a garbage handle so the input path - // (and the game loop that drives it) misbehaved. Same validation as - // scePadOpen — the one primary pad — returning its handle or a not-connected - // error, never opening or logging. [SysAbiExport( Nid = "u1GRHp+oWoY", ExportName = "scePadGetHandle", @@ -112,22 +130,24 @@ public static class PadExports return ctx.SetReturn(OrbisPadErrorNotInitialized); } - if (userId != PrimaryUserId || type is not (0 or 1 or 2) || index != 0) + if (userId == -1) { - return ctx.SetReturn(OrbisPadErrorDeviceNotConnected); + return ctx.SetReturn(OrbisPadErrorDeviceNoHandle); } - return ctx.SetReturn(PrimaryPadHandle); + lock (PadHandleGate) + { + return PadHandleByKey.TryGetValue(new PadOpenKey(userId, type, index), out var handle) + ? ctx.SetReturn(handle) + : ctx.SetReturn(OrbisPadErrorDeviceNoHandle); + } } - // scePadOpen rejects a non-null 4th arg and non-standard ports; scePadOpenExt accepts a - // ScePadOpenExtParam* plus ports 1/2 (racing titles retry scePadOpenExt(type=2) forever if rejected). private static int PadOpenCore(CpuContext ctx, bool extended) { var userId = unchecked((int)ctx[CpuRegister.Rdi]); var type = unchecked((int)ctx[CpuRegister.Rsi]); var index = unchecked((int)ctx[CpuRegister.Rdx]); - var parameterAddress = ctx[CpuRegister.Rcx]; if (!_initialized) { return ctx.SetReturn(OrbisPadErrorNotInitialized); @@ -138,8 +158,7 @@ public static class PadExports return ctx.SetReturn(OrbisPadErrorDeviceNoHandle); } - var typeAccepted = extended ? type is 0 or 1 or 2 : type == StandardPortType; - if (userId != PrimaryUserId || !typeAccepted || index != 0 || (!extended && parameterAddress != 0)) + if (!IsSupportedOpenTuple(userId, type, index, extended)) { return ctx.SetReturn(OrbisPadErrorDeviceNotConnected); } @@ -153,8 +172,42 @@ public static class PadExports : "[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options. A DualSense or Xbox controller will be used automatically when plugged in."); } - Volatile.Write(ref _padOpened, 1); - return ctx.SetReturn(PrimaryPadHandle); + var key = new PadOpenKey(userId, type, index); + lock (PadHandleGate) + { + if (PadHandleByKey.ContainsKey(key)) + { + return ctx.SetReturn(OrbisPadErrorAlreadyOpened); + } + + var handle = _nextPadHandle++; + PadHandleByKey.Add(key, handle); + PadKeyByHandle.Add(handle, key); + _padOpened = 1; + return ctx.SetReturn(handle); + } + } + + private static bool IsSupportedOpenTuple(int userId, int type, int index, bool extended) + { + if (index != 0) + { + return false; + } + + if (userId == SystemUserId && type == RemotePortType) + { + return true; + } + + if (userId != PrimaryUserId) + { + return false; + } + + return extended + ? type is 0 or 1 or 2 + : type is StandardPortType or SpecialPortType; } [SysAbiExport( @@ -165,9 +218,16 @@ public static class PadExports public static int PadClose(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - return IsPrimaryPadHandle(handle) - ? ctx.SetReturn(0) - : ctx.SetReturn(OrbisPadErrorInvalidHandle); + lock (PadHandleGate) + { + if (!PadKeyByHandle.Remove(handle, out var key)) + { + return ctx.SetReturn(OrbisPadErrorInvalidHandle); + } + + PadHandleByKey.Remove(key); + return ctx.SetReturn(0); + } } [SysAbiExport( @@ -178,7 +238,7 @@ public static class PadExports public static int PadSetMotionSensorState(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -195,7 +255,7 @@ public static class PadExports public static int PadSetTiltCorrectionState(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - return IsPrimaryPadHandle(handle) + return IsOpenPadHandle(handle) ? ctx.SetReturn(0) : ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -208,7 +268,7 @@ public static class PadExports public static int PadSetAngularVelocityDeadbandState(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - return IsPrimaryPadHandle(handle) + return IsOpenPadHandle(handle) ? ctx.SetReturn(0) : ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -222,7 +282,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var informationAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -273,7 +333,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var informationAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -305,7 +365,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var dataAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -330,7 +390,7 @@ public static class PadExports var handle = unchecked((int)ctx[CpuRegister.Rdi]); var dataAddress = ctx[CpuRegister.Rsi]; var count = unchecked((int)ctx[CpuRegister.Rdx]); - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -364,7 +424,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var parameterAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -402,7 +462,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var stateAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -590,7 +650,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var parameterAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -620,7 +680,7 @@ public static class PadExports { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var parameterAddress = ctx[CpuRegister.Rsi]; - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -649,7 +709,7 @@ public static class PadExports public static int PadResetLightBar(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - if (!IsPrimaryPadHandle(handle)) + if (!IsOpenPadHandle(handle)) { return ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -666,7 +726,7 @@ public static class PadExports public static int PadResetOrientation(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - return IsPrimaryPadHandle(handle) + return IsOpenPadHandle(handle) ? ctx.SetReturn(0) : ctx.SetReturn(OrbisPadErrorInvalidHandle); } @@ -679,7 +739,7 @@ public static class PadExports public static int PadIsRemoteController(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); - return IsPrimaryPadHandle(handle) + return IsOpenPadHandle(handle) ? ctx.SetReturn(0) : ctx.SetReturn(OrbisPadErrorInvalidHandle); }