// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; using System.Buffers.Binary; using System.Diagnostics; namespace SharpEmu.Libs.Pad; public static class PadExports { private const int OrbisPadErrorInvalidHandle = unchecked((int)0x80920003); private const int OrbisPadErrorNotInitialized = unchecked((int)0x80920005); private const int OrbisPadErrorDeviceNotConnected = unchecked((int)0x80920007); private const int OrbisPadErrorDeviceNoHandle = unchecked((int)0x80920008); private const int PrimaryUserId = 1; private const int StandardPortType = 0; private const int PrimaryPadHandle = 1; private const int ControllerInformationSize = 0x1C; private const int PadDataSize = 0x78; private static bool _initialized; [SysAbiExport( Nid = "hv1luiJrqQM", ExportName = "scePadInit", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadInit(CpuContext ctx) { _initialized = true; return SetReturn(ctx, 0); } [SysAbiExport( Nid = "xk0AcarP3V4", ExportName = "scePadOpen", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadOpen(CpuContext ctx) { 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 SetReturn(ctx, OrbisPadErrorNotInitialized); } if (userId == -1) { return SetReturn(ctx, OrbisPadErrorDeviceNoHandle); } if (userId != PrimaryUserId || type != StandardPortType || index != 0 || parameterAddress != 0) { return SetReturn(ctx, OrbisPadErrorDeviceNotConnected); } return SetReturn(ctx, PrimaryPadHandle); } [SysAbiExport( Nid = "clVvL4ZDntw", ExportName = "scePadSetMotionSensorState", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadSetMotionSensorState(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); return handle == PrimaryPadHandle ? SetReturn(ctx, 0) : SetReturn(ctx, OrbisPadErrorInvalidHandle); } [SysAbiExport( Nid = "gjP9-KQzoUk", ExportName = "scePadGetControllerInformation", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadGetControllerInformation(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var informationAddress = ctx[CpuRegister.Rsi]; if (handle != PrimaryPadHandle) { return SetReturn(ctx, OrbisPadErrorInvalidHandle); } if (informationAddress == 0) { return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } Span information = stackalloc byte[ControllerInformationSize]; BinaryPrimitives.WriteSingleLittleEndian(information[0x00..], 44.86f); BinaryPrimitives.WriteUInt16LittleEndian(information[0x04..], 1920); BinaryPrimitives.WriteUInt16LittleEndian(information[0x06..], 943); information[0x08] = 30; information[0x09] = 30; information[0x0A] = StandardPortType; information[0x0B] = 1; information[0x0C] = 1; BinaryPrimitives.WriteInt32LittleEndian(information[0x10..], 0); return ctx.Memory.TryWrite(informationAddress, information) ? SetReturn(ctx, 0) : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } [SysAbiExport( Nid = "YndgXqQVV7c", ExportName = "scePadReadState", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadReadState(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var dataAddress = ctx[CpuRegister.Rsi]; if (handle != PrimaryPadHandle) { return SetReturn(ctx, OrbisPadErrorInvalidHandle); } if (dataAddress == 0) { return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } return WriteNeutralPadData(ctx, dataAddress) ? SetReturn(ctx, 0) : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } [SysAbiExport( Nid = "q1cHNfGycLI", ExportName = "scePadRead", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libScePad")] public static int PadRead(CpuContext ctx) { var handle = unchecked((int)ctx[CpuRegister.Rdi]); var dataAddress = ctx[CpuRegister.Rsi]; var count = unchecked((int)ctx[CpuRegister.Rdx]); if (handle != PrimaryPadHandle) { return SetReturn(ctx, OrbisPadErrorInvalidHandle); } if (dataAddress == 0 || count < 1 || count > 64) { return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } return WriteNeutralPadData(ctx, dataAddress) ? SetReturn(ctx, 1) : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } private static bool WriteNeutralPadData(CpuContext ctx, ulong dataAddress) { Span data = stackalloc byte[PadDataSize]; data.Clear(); data[0x04] = 128; data[0x05] = 128; data[0x06] = 128; data[0x07] = 128; BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f); data[0x4C] = 1; var timestampTicks = Stopwatch.GetTimestamp(); var timestampMicroseconds = ((ulong)(timestampTicks / Stopwatch.Frequency) * 1_000_000UL) + ((ulong)(timestampTicks % Stopwatch.Frequency) * 1_000_000UL / (ulong)Stopwatch.Frequency); BinaryPrimitives.WriteUInt64LittleEndian( data[0x50..], timestampMicroseconds); data[0x68] = 1; return ctx.Memory.TryWrite(dataAddress, data); } private static int SetReturn(CpuContext ctx, int result) { ctx[CpuRegister.Rax] = unchecked((ulong)result); return result; } }