diff --git a/include/services/ir_user.hpp b/include/services/ir_user.hpp index 2c7b1559..186d9717 100644 --- a/include/services/ir_user.hpp +++ b/include/services/ir_user.hpp @@ -11,6 +11,10 @@ class Kernel; class IRUserService { + enum class DeviceID : u8 { + CirclePadPro = 1, + }; + Handle handle = KernelHandles::IR_USER; Memory& mem; Kernel& kernel; @@ -20,10 +24,35 @@ class IRUserService { void disconnect(u32 messagePointer); void finalizeIrnop(u32 messagePointer); void getConnectionStatusEvent(u32 messagePointer); + void getReceiveEvent(u32 messagePointer); void initializeIrnopShared(u32 messagePointer); void requireConnection(u32 messagePointer); + void sendIrnop(u32 messagePointer); - std::optional connectionStatusEvent = std::nullopt; + using IREvent = std::optional; + + IREvent connectionStatusEvent = std::nullopt; + IREvent receiveEvent = std::nullopt; + + std::optional sharedMemory = std::nullopt; + bool connectedDevice = false; + + // Header of the IR shared memory containing various bits of info + // https://www.3dbrew.org/wiki/IRUSER_Shared_Memory + struct SharedMemoryStatus { + u32 latestReceiveError; + u32 latestSharedError; + + u8 connectionStatus; + u8 connectionAttemptStatus; + u8 connectionRole; + u8 machineID; + u8 isConnected; + u8 networkID; + u8 isInitialized; // https://github.com/citra-emu/citra/blob/c10ffda91feb3476a861c47fb38641c1007b9d33/src/core/hle/service/ir/ir_user.cpp#L41 + u8 unk1; + }; + static_assert(sizeof(SharedMemoryStatus) == 16); public: IRUserService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} diff --git a/src/core/services/ir_user.cpp b/src/core/services/ir_user.cpp index a4c98a82..47a2299a 100644 --- a/src/core/services/ir_user.cpp +++ b/src/core/services/ir_user.cpp @@ -1,5 +1,7 @@ #include "services/ir_user.hpp" +#include + #include "ipc.hpp" #include "kernel.hpp" @@ -8,21 +10,30 @@ namespace IRUserCommands { FinalizeIrnop = 0x00020000, RequireConnection = 0x00060040, Disconnect = 0x00090000, + GetReceiveEvent = 0x000A0000, GetConnectionStatusEvent = 0x000C0000, + SendIrnop = 0x000D0042, InitializeIrnopShared = 0x00180182 }; } -void IRUserService::reset() { connectionStatusEvent = std::nullopt; } +void IRUserService::reset() { + connectionStatusEvent = std::nullopt; + receiveEvent = std::nullopt; + sharedMemory = std::nullopt; + connectedDevice = false; +} void IRUserService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { case IRUserCommands::Disconnect: disconnect(messagePointer); break; case IRUserCommands::FinalizeIrnop: finalizeIrnop(messagePointer); break; + case IRUserCommands::GetReceiveEvent: getReceiveEvent(messagePointer); break; case IRUserCommands::GetConnectionStatusEvent: getConnectionStatusEvent(messagePointer); break; case IRUserCommands::InitializeIrnopShared: initializeIrnopShared(messagePointer); break; case IRUserCommands::RequireConnection: requireConnection(messagePointer); break; + case IRUserCommands::SendIrnop: sendIrnop(messagePointer); break; default: Helpers::panic("ir:USER service requested. Command: %08X\n", command); } } @@ -40,6 +51,19 @@ void IRUserService::initializeIrnopShared(u32 messagePointer) { log("IR:USER: InitializeIrnopShared (shared mem size = %08X, sharedMemHandle = %X) (stubbed)\n", sharedMemSize, sharedMemHandle); Helpers::warn("Game is initializing IR:USER. If it explodes, this is probably why"); + KernelObject* object = kernel.getObject(sharedMemHandle, KernelObjectType::MemoryBlock); + if (object == nullptr) { + Helpers::panic("IR::InitializeIrnopShared: Shared memory object does not exist"); + } + + MemoryBlock* memoryBlock = object->getData(); + sharedMemory = *memoryBlock; + + // Set the initialized byte in shared mem to 1 + mem.write8(memoryBlock->addr + offsetof(SharedMemoryStatus, isInitialized), 1); + mem.write64(memoryBlock->addr + 0x10, 0); // Initialize the receive buffer info to all 0s + mem.write64(memoryBlock->addr + 0x18, 0); + mem.write32(messagePointer, IPC::responseHeader(0x18, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -47,6 +71,13 @@ void IRUserService::initializeIrnopShared(u32 messagePointer) { void IRUserService::finalizeIrnop(u32 messagePointer) { log("IR:USER: FinalizeIrnop\n"); + if (connectedDevice) { + connectedDevice = false; + // This should also disconnect CirclePad Pro? + } + + sharedMemory = std::nullopt; + // This should disconnect any connected device de-initialize the shared memory mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0)); mem.write32(messagePointer + 4, Result::Success); @@ -58,6 +89,7 @@ void IRUserService::getConnectionStatusEvent(u32 messagePointer) { if (!connectionStatusEvent.has_value()) { connectionStatusEvent = kernel.makeEvent(ResetType::OneShot); } + //kernel.signalEvent(connectionStatusEvent.value()); // ?????????????? mem.write32(messagePointer, IPC::responseHeader(0xC, 1, 2)); mem.write32(messagePointer + 4, Result::Success); @@ -65,16 +97,72 @@ void IRUserService::getConnectionStatusEvent(u32 messagePointer) { mem.write32(messagePointer + 12, connectionStatusEvent.value()); } +void IRUserService::getReceiveEvent(u32 messagePointer) { + log("IR:USER: GetReceiveEvent\n"); + + if (!receiveEvent.has_value()) { + receiveEvent = kernel.makeEvent(ResetType::OneShot); + } + + mem.write32(messagePointer, IPC::responseHeader(0xA, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 0x40000000); + // TOOD: Descriptor here + mem.write32(messagePointer + 12, receiveEvent.value()); +} + void IRUserService::requireConnection(u32 messagePointer) { const u8 deviceID = mem.read8(messagePointer + 4); log("IR:USER: RequireConnection (device: %d)\n", deviceID); + // Reference: https://github.com/citra-emu/citra/blob/c10ffda91feb3476a861c47fb38641c1007b9d33/src/core/hle/service/ir/ir_user.cpp#L306 + if (sharedMemory.has_value()) { + u32 sharedMemAddress = sharedMemory.value().addr; + + if (deviceID == u8(DeviceID::CirclePadPro)) { + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionStatus), 2); // Citra uses 2 here but only 1 works?? + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionRole), 2); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, isConnected), 1); + + connectedDevice = true; + if (connectionStatusEvent.has_value()) { + kernel.signalEvent(connectionStatusEvent.value()); + } + } else { + log("IR:USER: Unknown device %d\n", deviceID); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionStatus), 1); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionAttemptStatus), 2); + } + } + mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } +void IRUserService::sendIrnop(u32 messagePointer) { + Helpers::panic("IR:USER: SendIrnop\n"); + + mem.write32(messagePointer + 4, Result::Success); +} + void IRUserService::disconnect(u32 messagePointer) { log("IR:USER: Disconnect\n"); + + if (sharedMemory.has_value()) { + u32 sharedMemAddress = sharedMemory.value().addr; + + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, connectionStatus), 0); + mem.write8(sharedMemAddress + offsetof(SharedMemoryStatus, isConnected), 0); + } + + // If there's a connected device, disconnect it and trigger the status event + if (connectedDevice) { + connectedDevice = false; + if (connectionStatusEvent.has_value()) { + kernel.signalEvent(connectionStatusEvent.value()); + } + } + mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file