[Pad] Track independent standard, special and remote port handles
Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com>
This commit is contained in:
parent
d3f584a2f1
commit
0e55fa924c
|
|
@ -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<PadOpenKey, int> PadHandleByKey = new();
|
||||
private static readonly Dictionary<int, PadOpenKey> 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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue