diff --git a/CMakeLists.txt b/CMakeLists.txt index eda9acaf..401b2494 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limi src/core/kernel/events.cpp src/core/kernel/threads.cpp src/core/kernel/address_arbiter.cpp src/core/kernel/error.cpp src/core/kernel/file_operations.cpp src/core/kernel/directory_operations.cpp + src/core/kernel/idle_thread.cpp ) set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services/apt.cpp src/core/services/hid.cpp src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp @@ -91,7 +92,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp include/fs/archive_ext_save_data.hpp include/services/shared_font.hpp include/fs/archive_ncch.hpp include/renderer_gl/textures.hpp include/colour.hpp include/services/y2r.hpp include/services/cam.hpp - include/services/ldr_ro.hpp + include/services/ldr_ro.hpp include/ipc.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/PICA/regs.hpp b/include/PICA/regs.hpp index 8815668f..8ef46e67 100644 --- a/include/PICA/regs.hpp +++ b/include/PICA/regs.hpp @@ -81,6 +81,7 @@ namespace PICAInternalRegs { CmdBufTrigger1 = 0x23D, PrimitiveConfig = 0x25E, + PrimitiveRestart = 0x25F, // Vertex shader registers VertexShaderAttrNum = 0x242, diff --git a/include/arm_defs.hpp b/include/arm_defs.hpp index 1d7a9bde..2f167e2f 100644 --- a/include/arm_defs.hpp +++ b/include/arm_defs.hpp @@ -1,8 +1,9 @@ #pragma once +#include // Status register definitions namespace CPSR { - enum : u32 { + enum : std::uint32_t { // Privilege modes UserMode = 16, FIQMode = 17, @@ -26,7 +27,7 @@ namespace CPSR { namespace FPSCR { // FPSCR Flags - enum : u32 { + enum : std::uint32_t { Sign = 1U << 31U, // Negative condition flag Zero = 1 << 30, // Zero condition flag Carry = 1 << 29, // Carry condition flag diff --git a/include/io_file.hpp b/include/io_file.hpp index 4054b7fb..41e2285c 100644 --- a/include/io_file.hpp +++ b/include/io_file.hpp @@ -18,6 +18,12 @@ #define _CRT_SECURE_NO_WARNINGS #endif +#ifdef WIN32 +#include // For _chsize_s +#else +#include // For ftruncate +#endif + class IOFile { FILE* handle = nullptr; static inline std::filesystem::path appData = ""; // Directory for holding app data. AppData on Windows @@ -112,5 +118,19 @@ public: appData = dir; } + // Sets the size of the file to "size" and returns whether it succeeded or not + bool setSize(std::uint64_t size) { + if (!isOpen()) return false; + bool success; + +#ifdef WIN32 + success = _chsize_s(_fileno(handle), size) == 0; +#else + success = ftruncate(fileno(handle), size) == 0; +#endif + fflush(handle); + return success; + } + static std::filesystem::path getAppData() { return IOFile::appData; } }; \ No newline at end of file diff --git a/include/ipc.hpp b/include/ipc.hpp new file mode 100644 index 00000000..67a8897e --- /dev/null +++ b/include/ipc.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace IPC { + constexpr std::uint32_t responseHeader(std::uint32_t commandID, std::uint32_t normalResponses, std::uint32_t translateResponses) { + // TODO: Maybe validate the response count stuff fits in 6 bits + return (commandID << 16) | (normalResponses << 6) | translateResponses; + } +} \ No newline at end of file diff --git a/include/kernel/config_mem.hpp b/include/kernel/config_mem.hpp index 6de9c6bc..dd4a3fd3 100644 --- a/include/kernel/config_mem.hpp +++ b/include/kernel/config_mem.hpp @@ -14,6 +14,7 @@ namespace ConfigMem { NetworkState = 0x1FF81067, LedState3D = 0x1FF81084, BatteryState = 0x1FF81085, + Unknown1086 = 0x1FF81086, HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here? }; diff --git a/include/kernel/kernel.hpp b/include/kernel/kernel.hpp index e46bb0a2..5e27e911 100644 --- a/include/kernel/kernel.hpp +++ b/include/kernel/kernel.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -19,7 +20,16 @@ class Kernel { // The handle number for the next kernel object to be created u32 handleCounter; - std::array threads; + // A list of our OS threads, the max number of which depends on the resource limit (hardcoded 32 per process on retail it seems). + // We have an extra thread for when no thread is capable of running. This thread is called the "idle thread" in our code + // This thread is set up in setupIdleThread and just yields in a loop to see if any other thread has woken up + std::array threads; + static constexpr int idleThreadIndex = appResourceLimits.maxThreads; + // Our waitlist system uses a bitfield of 64 bits to show which threads are waiting on an object. + // That means we can have a maximum of 63 threads + 1 idle thread. This assert should never trigger because the max thread # is 32 + // But we have it here for safety purposes + static_assert(appResourceLimits.maxThreads <= 63, "The waitlist system is built on the premise that <= 63 threads max can be active"); + std::vector objects; std::vector portHandles; @@ -52,6 +62,8 @@ public: Handle makeMutex(bool locked = false); // Needs to be public to be accessible to the APT/DSP services Handle makeSemaphore(u32 initialCount, u32 maximumCount); // Needs to be public to be accessible to the service manager port + // Signals an event, returns true on success or false if the event does not exist + bool signalEvent(Handle e); private: void signalArbiter(u32 waitingAddress, s32 threadCount); void sleepThread(s64 ns); @@ -63,6 +75,7 @@ private: void rescheduleThreads(); bool canThreadRun(const Thread& t); bool shouldWaitOnObject(KernelObject* object); + void releaseMutex(Mutex* moo); std::optional getPortHandle(const char* name); void deleteObjectData(KernelObject& object); @@ -71,7 +84,9 @@ private: s32 getCurrentResourceValue(const KernelObject* limit, u32 resourceName); u32 getMaxForResource(const KernelObject* limit, u32 resourceName); u32 getTLSPointer(); + void setupIdleThread(); + void acquireSyncObject(KernelObject* object, const Thread& thread); bool isWaitable(const KernelObject* object); // Functions for the err:f port @@ -90,11 +105,8 @@ private: // SVC implementations void arbitrateAddress(); - void clearEvent(); void createAddressArbiter(); - void createEvent(); void createMemoryBlock(); - void createMutex(); void createThread(); void controlMemory(); void duplicateHandle(); @@ -109,11 +121,14 @@ private: void getSystemTick(); void getThreadID(); void getThreadPriority(); - void releaseMutex(); void sendSyncRequest(); void setThreadPriority(); - void signalEvent(); + void svcClearEvent(); void svcCloseHandle(); + void svcCreateEvent(); + void svcCreateMutex(); + void svcReleaseMutex(); + void svcSignalEvent(); void svcSleepThread(); void connectToPort(); void outputDebugString(); @@ -127,6 +142,7 @@ private: void writeFile(u32 messagePointer, Handle file); void getFileSize(u32 messagePointer, Handle file); void openLinkFile(u32 messagePointer, Handle file); + void setFileSize(u32 messagePointer, Handle file); void setFilePriority(u32 messagePointer, Handle file); // Directory operations diff --git a/include/kernel/kernel_types.hpp b/include/kernel/kernel_types.hpp index 86f9ae07..302841a4 100644 --- a/include/kernel/kernel_types.hpp +++ b/include/kernel/kernel_types.hpp @@ -23,6 +23,12 @@ namespace SVCResult { BadThreadPriority = 0xE0E01BFD, PortNameTooLong = 0xE0E0181E, + + // Returned when a thread stops waiting due to timing out + Timeout = 0x9401BFE, + + // Returned when a thread releases a mutex it does not own + InvalidMutexRelease = 0xD8E0041F }; } @@ -72,10 +78,11 @@ struct Process { }; struct Event { + u64 waitlist; // A bitfield where each bit symbolizes if the thread with thread with the corresponding index is waiting on the event ResetType resetType = ResetType::OneShot; bool fired = false; - Event(ResetType resetType) : resetType(resetType) {} + Event(ResetType resetType) : resetType(resetType), waitlist(0) {} }; struct Port { @@ -101,7 +108,8 @@ enum class ThreadStatus { Ready, // Ready to run WaitArbiter, // Waiting on an address arbiter WaitSleep, // Waiting due to a SleepThread SVC - WaitSync1, // Waiting for AT LEAST one sync object in its wait list to be ready + WaitSync1, // Waiting for the single object in the wait list to be ready + WaitSyncAny, // Wait for one object of the many that might be in the wait list to be ready WaitSyncAll, // Waiting for ALL sync objects in its wait list to be ready WaitIPC, // Waiting for the reply from an IPC request Dormant, // Created but not yet made ready @@ -138,6 +146,9 @@ struct Thread { u32 cpsr; u32 fpscr; u32 tlsBase; // Base pointer for thread-local storage + + // A list of threads waiting for this thread to terminate. Yes, threads are sync objects too. + u64 threadsWaitingForTermination; }; static const char* kernelObjectTypeToString(KernelObjectType t) { @@ -161,17 +172,20 @@ static const char* kernelObjectTypeToString(KernelObjectType t) { } struct Mutex { + u64 waitlist; // Refer to the getWaitlist function below for documentation Handle ownerThread = 0; // Index of the thread that holds the mutex if it's locked + u32 lockCount; // Number of times this mutex has been locked by its daddy. 0 = not locked bool locked; - Mutex(bool lock = false) : locked(lock) {} + Mutex(bool lock = false) : locked(lock), waitlist(0), lockCount(lock ? 1 : 0) {} }; struct Semaphore { - u32 initialCount; - u32 maximumCount; + u64 waitlist; // Refer to the getWaitlist function below for documentation + s32 availableCount; + s32 maximumCount; - Semaphore(u32 initialCount, u32 maximumCount) : initialCount(initialCount), maximumCount(maximumCount) {} + Semaphore(s32 initialCount, s32 maximumCount) : availableCount(initialCount), maximumCount(maximumCount), waitlist(0) {} }; struct MemoryBlock { @@ -202,7 +216,26 @@ struct KernelObject { return static_cast(data); } - const char* getTypeName() { + const char* getTypeName() const { return kernelObjectTypeToString(type); } + + // Retrieves a reference to the waitlist for a specified object + // We return a reference because this function is only called in the kernel threading internals + // We want the kernel to be able to easily manage waitlists, by reading/parsing them or setting/clearing bits. + // As we mention in the definition of the "Event" struct, the format for wailists is very simple and made to be efficient. + // Each bit corresponds to a thread index and denotes whether the corresponding thread is waiting on this object + // For example if bit 0 of the wait list is set, then the thread with index 0 is waiting on our object + u64& getWaitlist() { + // This code is actually kinda trash but eh good enough + switch (type) { + case KernelObjectType::Event: return getData()->waitlist; + case KernelObjectType::Mutex: return getData()->waitlist; + case KernelObjectType::Semaphore: return getData()->waitlist; + case KernelObjectType::Thread: return getData()->threadsWaitingForTermination; + // This should be unreachable once we fully implement sync objects + default: [[unlikely]] + Helpers::panic("Called GetWaitList on kernel object without a waitlist (Type: %s)", getTypeName()); + } + } }; \ No newline at end of file diff --git a/include/memory.hpp b/include/memory.hpp index b15cac62..0e75f36c 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -111,6 +111,7 @@ class Memory { SharedMemoryBlock(0, 0x1000, KernelHandles::HIDSharedMemHandle) // HID shared memory }; +public: static constexpr u32 pageShift = 12; static constexpr u32 pageSize = 1 << pageShift; static constexpr u32 pageMask = pageSize - 1; @@ -125,6 +126,7 @@ class Memory { static constexpr u32 DSP_CODE_MEMORY_OFFSET = 0_KB; static constexpr u32 DSP_DATA_MEMORY_OFFSET = 256_KB; +private: std::bitset usedFCRAMPages; std::optional findPaddr(u32 size); u64 timeSince3DSEpoch(); diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index 88cb285c..ac11a813 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -39,7 +39,7 @@ class Renderer { SurfaceCache depthBufferCache; SurfaceCache colourBufferCache; - SurfaceCache textureCache; + SurfaceCache textureCache; OpenGL::uvec2 fbSize; // The size of the framebuffer (ie both the colour and depth buffer)' diff --git a/include/services/apt.hpp b/include/services/apt.hpp index de973dca..819738c6 100644 --- a/include/services/apt.hpp +++ b/include/services/apt.hpp @@ -34,6 +34,7 @@ class APTService { void enable(u32 messagePointer); void getSharedFont(u32 messagePointer); void getWirelessRebootInfo(u32 messagePointer); + void glanceParameter(u32 messagePointer); void initialize(u32 messagePointer); void inquireNotification(u32 messagePointer); void notifyToWait(u32 messagePointer); diff --git a/include/services/dsp.hpp b/include/services/dsp.hpp index 5ddf6e14..f3e4d51f 100644 --- a/include/services/dsp.hpp +++ b/include/services/dsp.hpp @@ -1,4 +1,6 @@ #pragma once +#include +#include #include "helpers.hpp" #include "logger.hpp" #include "memory.hpp" @@ -41,18 +43,38 @@ public: } }; +// Circular dependencies! +class Kernel; + class DSPService { Handle handle = KernelHandles::DSP; Memory& mem; + Kernel& kernel; MAKE_LOG_FUNCTION(log, dspServiceLogger) + // Number of DSP pipes + static constexpr size_t pipeCount = 8; DSPPipe audioPipe; + // DSP service event handles + using DSPEvent = std::optional; + + DSPEvent semaphoreEvent; + DSPEvent interrupt0; + DSPEvent interrupt1; + std::array pipeEvents; + + DSPEvent& getEventRef(u32 type, u32 pipe); + static constexpr size_t maxEventCount = 6; + + // Total number of DSP service events registered with registerInterruptEvents + size_t totalEventCount; + // Service functions void convertProcessAddressFromDspDram(u32 messagePointer); // Nice function name void flushDataCache(u32 messagePointer); void getHeadphoneStatus(u32 messagePointer); - void getSemaphoreHandle(u32 messagePointer); + void getSemaphoreEventHandle(u32 messagePointer); void invalidateDCache(u32 messagePointer); void loadComponent(u32 messagePointer); void readPipeIfPossible(u32 messagePointer); @@ -62,7 +84,7 @@ class DSPService { void writeProcessPipe(u32 messagePointer); public: - DSPService(Memory& mem) : mem(mem) {} + DSPService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); diff --git a/include/services/fs.hpp b/include/services/fs.hpp index 27944c13..67ea472c 100644 --- a/include/services/fs.hpp +++ b/include/services/fs.hpp @@ -39,6 +39,7 @@ class FSService { // Service commands void createFile(u32 messagePointer); void closeArchive(u32 messagePointer); + void controlArchive(u32 messagePointer); void deleteFile(u32 messagePointer); void formatSaveData(u32 messagePointer); void getFormatInfo(u32 messagePointer); diff --git a/include/services/gsp_gpu.hpp b/include/services/gsp_gpu.hpp index 19b63c08..cab6fbff 100644 --- a/include/services/gsp_gpu.hpp +++ b/include/services/gsp_gpu.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include "PICA/gpu.hpp" #include "helpers.hpp" #include "kernel_types.hpp" @@ -16,16 +17,21 @@ enum class GPUInterrupt : u8 { DMA = 6 }; +// More circular dependencies +class Kernel; + class GPUService { Handle handle = KernelHandles::GPU; Memory& mem; GPU& gpu; + Kernel& kernel; u32& currentPID; // Process ID of the current process u8* sharedMem; // Pointer to GSP shared memory // At any point in time only 1 process has privileges to use rendering functions // This is the PID of that process u32 privilegedProcess; + std::optional interruptEvent; MAKE_LOG_FUNCTION(log, gspGPULogger) void processCommandBuffer(); @@ -51,7 +57,8 @@ class GPUService { void flushCacheRegions(u32* cmd); public: - GPUService(Memory& mem, GPU& gpu, u32& currentPID) : mem(mem), gpu(gpu), currentPID(currentPID) {} + GPUService(Memory& mem, GPU& gpu, Kernel& kernel, u32& currentPID) : mem(mem), gpu(gpu), + kernel(kernel), currentPID(currentPID) {} void reset(); void handleSyncRequest(u32 messagePointer); void requestInterrupt(GPUInterrupt type); diff --git a/include/services/ptm.hpp b/include/services/ptm.hpp index 4e8e011c..5751fe28 100644 --- a/include/services/ptm.hpp +++ b/include/services/ptm.hpp @@ -10,6 +10,7 @@ class PTMService { MAKE_LOG_FUNCTION(log, ptmLogger) // Service commands + void configureNew3DSCPU(u32 messagePointer); void getStepHistory(u32 messagePointer); void getTotalStepCount(u32 messagePointer); diff --git a/include/services/y2r.hpp b/include/services/y2r.hpp index 4f15f592..6bd7e6f2 100644 --- a/include/services/y2r.hpp +++ b/include/services/y2r.hpp @@ -1,23 +1,30 @@ #pragma once +#include #include "helpers.hpp" #include "kernel_types.hpp" #include "logger.hpp" #include "memory.hpp" +// Circular dependencies go br +class Kernel; + class Y2RService { Handle handle = KernelHandles::Y2R; Memory& mem; + Kernel& kernel; MAKE_LOG_FUNCTION(log, y2rLogger) + std::optional transferEndEvent; bool transferEndInterruptEnabled; // Service commands void driverInitialize(u32 messagePointer); void pingProcess(u32 messagePointer); void setTransferEndInterrupt(u32 messagePointer); + void getTransferEndEvent(u32 messagePointer); public: - Y2RService(Memory& mem) : mem(mem) {} + Y2RService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {} void reset(); void handleSyncRequest(u32 messagePointer); }; \ No newline at end of file diff --git a/src/core/PICA/regs.cpp b/src/core/PICA/regs.cpp index 33b17a25..33cf570a 100644 --- a/src/core/PICA/regs.cpp +++ b/src/core/PICA/regs.cpp @@ -111,6 +111,14 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) { } break; + // Restart immediate mode primitive drawing + case PrimitiveRestart: + if (value & 1) { + immediateModeAttrIndex = 0; + immediateModeVertIndex = 0; + } + break; + case FixedAttribData0: case FixedAttribData1: case FixedAttribData2: fixedAttrBuff[fixedAttribCount++] = value; @@ -141,10 +149,35 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) { immediateModeAttrIndex = 0; immediateModeVertices[immediateModeVertIndex++] = v; + // Get primitive type + const u32 primConfig = regs[PICAInternalRegs::PrimitiveConfig]; + const u32 primType = (primConfig >> 8) & 3; + // If we've reached 3 verts, issue a draw call + // Handle rendering depending on the primitive type if (immediateModeVertIndex == 3) { renderer.drawVertices(OpenGL::Triangle, &immediateModeVertices[0], 3); - immediateModeVertIndex = 0; + + switch (primType) { + // Triangle or geometry primitive. Draw a triangle and discard all vertices + case 0: case 3: + immediateModeVertIndex = 0; + break; + + // Triangle strip. Draw triangle, discard first vertex and keep the last 2 + case 1: + immediateModeVertIndex = 2; + + immediateModeVertices[0] = immediateModeVertices[1]; + immediateModeVertices[1] = immediateModeVertices[2]; + break; + + // Triangle fan. Draw triangle, keep first vertex and last vertex, discard second vertex + case 2: + immediateModeVertIndex = 2; + immediateModeVertices[1] = immediateModeVertices[2]; + break; + } } } } else { // Writing to fixed attributes 13 and 14 probably does nothing, but we'll see diff --git a/src/core/kernel/address_arbiter.cpp b/src/core/kernel/address_arbiter.cpp index d8be5d66..daaab00e 100644 --- a/src/core/kernel/address_arbiter.cpp +++ b/src/core/kernel/address_arbiter.cpp @@ -25,6 +25,7 @@ Handle Kernel::makeArbiter() { // Result CreateAddressArbiter(Handle* arbiter) void Kernel::createAddressArbiter() { + logSVC("CreateAddressArbiter\n"); regs[0] = SVCResult::Success; regs[1] = makeArbiter(); } diff --git a/src/core/kernel/events.cpp b/src/core/kernel/events.cpp index df729f1d..7edb358c 100644 --- a/src/core/kernel/events.cpp +++ b/src/core/kernel/events.cpp @@ -1,5 +1,7 @@ #include "kernel.hpp" #include "cpu.hpp" +#include +#include const char* Kernel::resetTypeToString(u32 type) { switch (type) { @@ -16,8 +18,55 @@ Handle Kernel::makeEvent(ResetType resetType) { return ret; } +bool Kernel::signalEvent(Handle handle) { + KernelObject* object = getObject(handle, KernelObjectType::Event); + if (object == nullptr) [[unlikely]] { + Helpers::panic("Tried to signal non-existent event"); + return false; + } + + Event* event = object->getData(); + event->fired = true; + + // One shot events go back to being not fired once they are signaled + if (event->resetType == ResetType::Pulse) { + event->fired = false; + } + + // Wake up every single thread in the waitlist using a bit scanning algorithm + while (event->waitlist != 0) { + const uint index = std::countr_zero(event->waitlist); // Get one of the set bits to see which thread is waiting + event->waitlist ^= (1ull << index); // Remove thread from waitlist by toggling its bit + + // Get the thread we'll be signalling + Thread& t = threads[index]; + switch (t.status) { + case ThreadStatus::WaitSync1: + t.status = ThreadStatus::Ready; + break; + + case ThreadStatus::WaitSyncAny: + t.status = ThreadStatus::Ready; + // Get the index of the event in the object's waitlist, write it to r1 + for (size_t i = 0; i < t.waitList.size(); i++) { + if (t.waitList[i] == handle) { + t.gprs[1] = i; + break; + } + } + break; + + case ThreadStatus::WaitSyncAll: + Helpers::panic("SignalEvent: Thread on WaitSyncAll"); + break; + } + } + + return true; +} + // Result CreateEvent(Handle* event, ResetType resetType) -void Kernel::createEvent() { +void Kernel::svcCreateEvent() { const u32 outPointer = regs[0]; const u32 resetType = regs[1]; @@ -31,7 +80,7 @@ void Kernel::createEvent() { } // Result ClearEvent(Handle event) -void Kernel::clearEvent() { +void Kernel::svcClearEvent() { const Handle handle = regs[0]; const auto event = getObject(handle, KernelObjectType::Event); logSVC("ClearEvent(event handle = %X)\n", handle); @@ -47,38 +96,15 @@ void Kernel::clearEvent() { } // Result SignalEvent(Handle event) -void Kernel::signalEvent() { +void Kernel::svcSignalEvent() { const Handle handle = regs[0]; - const auto event = getObject(handle, KernelObjectType::Event); logSVC("SignalEvent(event handle = %X)\n", handle); - if (event == nullptr) [[unlikely]] { - logThread("Signalled non-existent event: %X\n", handle); + if (!signalEvent(handle)) { + Helpers::panic("Signalled non-existent event: %X\n", handle); + regs[0] = SVCResult::BadHandle; + } else { regs[0] = SVCResult::Success; - //regs[0] = SVCResult::BadHandle; - return; - } - - regs[0] = SVCResult::Success; - auto eventData = event->getData(); - eventData->fired = true; - - switch (eventData->resetType) { - case ResetType::OneShot: - for (int i = 0; i < threadCount; i++) { - Thread& t = threads[i]; - - if (t.status == ThreadStatus::WaitSync1 && t.waitList[0] == handle) { - t.status = ThreadStatus::Ready; - break; - } else if (t.status == ThreadStatus::WaitSyncAll) { - Helpers::panic("Trying to SignalEvent when a thread is waiting on multiple objects"); - } - } - break; - - default: - Helpers::panic("Signaled event of unimplemented type: %d", eventData->resetType); } } @@ -101,8 +127,15 @@ void Kernel::waitSynchronization1() { } if (!shouldWaitOnObject(object)) { + acquireSyncObject(object, threads[currentThreadIndex]); // Acquire the object since it's ready regs[0] = SVCResult::Success; } else { + // Timeout is 0, don't bother waiting, instantly timeout + if (ns == 0) { + regs[0] = SVCResult::Timeout; + return; + } + regs[0] = SVCResult::Success; auto& t = threads[currentThreadIndex]; @@ -111,6 +144,10 @@ void Kernel::waitSynchronization1() { t.sleepTick = cpu.getTicks(); t.waitingNanoseconds = ns; t.waitList[0] = handle; + + // Add the current thread to the object's wait list + object->getWaitlist() |= (1ull << currentThreadIndex); + switchToNextThread(); } } @@ -120,7 +157,7 @@ void Kernel::waitSynchronizationN() { // TODO: Are these arguments even correct? s32 ns1 = regs[0]; u32 handles = regs[1]; - u32 handleCount = regs[2]; + s32 handleCount = regs[2]; bool waitAll = regs[3] != 0; u32 ns2 = regs[4]; s32 outPointer = regs[5]; // "out" pointer - shows which object got bonked if we're waiting on multiple objects @@ -128,36 +165,77 @@ void Kernel::waitSynchronizationN() { logSVC("WaitSynchronizationN (handle pointer: %08X, count: %d, timeout = %lld)\n", handles, handleCount, ns); - if (waitAll && handleCount > 1) - Helpers::panic("Trying to wait on more than 1 object"); + if (handleCount < 0) + Helpers::panic("WaitSyncN: Invalid handle count"); - auto& t = threads[currentThreadIndex]; - t.waitList.resize(handleCount); - - for (uint i = 0; i < handleCount; i++) { + using WaitObject = std::pair; + std::vector waitObjects(handleCount); + + // We don't actually need to wait if waitAll == true unless one of the objects is not ready + bool allReady = true; // Default initialize to true, set to fault if one of the objects is not ready + + // Tracks whether at least one object is ready, + the index of the first ready object + // This is used when waitAll == false, because if one object is already available then we can skip the sleeping + bool oneObjectReady = false; + s32 firstReadyObjectIndex = 0; + + for (s32 i = 0; i < handleCount; i++) { Handle handle = mem.read32(handles); handles += sizeof(Handle); - t.waitList[i] = handle; - auto object = getObject(handle); + // Panic if one of the objects is not even an object if (object == nullptr) [[unlikely]] { Helpers::panic("WaitSynchronizationN: Bad object handle %X\n", handle); regs[0] = SVCResult::BadHandle; return; } + // Panic if one of the objects is not a valid sync object if (!isWaitable(object)) [[unlikely]] { - //Helpers::panic("Tried to wait on a non waitable object. Type: %s, handle: %X\n", object->getTypeName(), handle); + Helpers::panic("Tried to wait on a non waitable object in WaitSyncN. Type: %s, handle: %X\n", + object->getTypeName(), handle); } + + if (shouldWaitOnObject(object)) { + allReady = false; // Derp, not all objects are ready :( + } else { /// At least one object is ready to be acquired ahead of time. If it's the first one, write it down + if (!oneObjectReady) { + oneObjectReady = true; + firstReadyObjectIndex = i; + } + } + + waitObjects[i] = {handle, object}; } - regs[0] = SVCResult::Success; - regs[1] = waitAll ? handleCount - 1 : 0; // Index of the handle that triggered the exit. STUBBED - t.status = ThreadStatus::WaitSyncAll; - t.waitAll = waitAll; - t.outPointer = outPointer; - t.waitingNanoseconds = ns; - t.sleepTick = cpu.getTicks(); - switchToNextThread(); + auto& t = threads[currentThreadIndex]; + + // We only need to wait on one object. Easy...?! + if (!waitAll) { + // If there's ready objects, acquire the first one and return + if (oneObjectReady) { + regs[0] = SVCResult::Success; + regs[1] = firstReadyObjectIndex; // Return index of the acquired object + acquireSyncObject(waitObjects[firstReadyObjectIndex].second, t); // Acquire object + return; + } + + regs[0] = SVCResult::Success; // If the thread times out, this should be adjusted to SVCResult::Timeout + regs[1] = handleCount - 1; // When the thread exits, this will be adjusted to mirror which handle woke us up + t.waitList.resize(handleCount); + t.status = ThreadStatus::WaitSyncAny; + t.outPointer = outPointer; + t.waitingNanoseconds = ns; + t.sleepTick = cpu.getTicks(); + + for (s32 i = 0; i < handleCount; i++) { + t.waitList[i] = waitObjects[i].first; // Add object to this thread's waitlist + waitObjects[i].second->getWaitlist() |= (1ull << currentThreadIndex); // And add the thread to the object's waitlist + } + + switchToNextThread(); + } else { + Helpers::panic("WaitSynchronizatioN with waitAll"); + } } \ No newline at end of file diff --git a/src/core/kernel/file_operations.cpp b/src/core/kernel/file_operations.cpp index 62111283..84b77837 100644 --- a/src/core/kernel/file_operations.cpp +++ b/src/core/kernel/file_operations.cpp @@ -5,6 +5,7 @@ namespace FileOps { Read = 0x080200C2, Write = 0x08030102, GetSize = 0x08040000, + SetSize = 0x08050080, Close = 0x08080000, SetPriority = 0x080A0040, OpenLinkFile = 0x080C0000 @@ -25,6 +26,7 @@ void Kernel::handleFileOperation(u32 messagePointer, Handle file) { case FileOps::GetSize: getFileSize(messagePointer, file); break; case FileOps::OpenLinkFile: openLinkFile(messagePointer, file); break; case FileOps::Read: readFile(messagePointer, file); break; + case FileOps::SetSize: setFileSize(messagePointer, file); break; case FileOps::SetPriority: setFilePriority(messagePointer, file); break; case FileOps::Write: writeFile(messagePointer, file); break; default: Helpers::panic("Unknown file operation: %08X", cmd); @@ -132,6 +134,34 @@ void Kernel::writeFile(u32 messagePointer, Handle fileHandle) { } } +void Kernel::setFileSize(u32 messagePointer, Handle fileHandle) { + logFileIO("Setting size of file %X\n", fileHandle); + + const auto p = getObject(fileHandle, KernelObjectType::File); + if (p == nullptr) [[unlikely]] { + Helpers::panic("Called SetFileSize on non-existent file"); + } + + FileSession* file = p->getData(); + if (!file->isOpen) { + Helpers::panic("Tried to get size of closed file"); + } + + if (file->fd) { + const u64 newSize = mem.read64(messagePointer + 4); + IOFile f(file->fd); + bool success = f.setSize(newSize); + + if (success) { + mem.write32(messagePointer + 4, Result::Success); + } else { + Helpers::panic("FileOp::SetFileSize failed"); + } + } else { + Helpers::panic("Tried to set file size of file without file descriptor"); + } +} + void Kernel::getFileSize(u32 messagePointer, Handle fileHandle) { logFileIO("Getting size of file %X\n", fileHandle); diff --git a/src/core/kernel/idle_thread.cpp b/src/core/kernel/idle_thread.cpp new file mode 100644 index 00000000..5389fecc --- /dev/null +++ b/src/core/kernel/idle_thread.cpp @@ -0,0 +1,70 @@ +#include +#include "arm_defs.hpp" +#include "kernel.hpp" + +/* + This file sets up an idle thread that's meant to run when no other OS thread can run. + It simply idles and constantly yields to check if there's any other thread that can run + The code for our idle thread looks like this + +idle_thread_main: + mov r0, #4096 @ Loop counter + + .loop: + nop; nop; nop; nop @ NOP 4 times to waste some cycles + subs r0, #1 @ Decrement counter by 1, go back to looping if loop counter != 0 + bne .loop + + // Sleep for 0 seconds with the SleepThread SVC, which just yields execution + mov r0, #0 + mov r1, #0 + svc SleepThread + + b idle_thread_main +*/ + +static constexpr u8 idleThreadCode[] = { + 0x01, 0x0A, 0xA0, 0xE3, // mov r0, #4096 + 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, 0x00, 0xF0, 0x20, 0xE3, // nop (4 times) + 0x01, 0x00, 0x50, 0xE2, // subs r0, #1 + 0xF9, 0xFF, 0xFF, 0x1A, // bne loop + 0x00, 0x00, 0xA0, 0xE3, // mov r0, #0 + 0x00, 0x10, 0xA0, 0xE3, // mov r1, #0 + 0x0A, 0x00, 0x00, 0xEF, // svc SleepThread + 0xF4, 0xFF, 0xFF, 0xEA // b idle_thread_main +}; + +// Set up an idle thread to run when no thread is able to run +void Kernel::setupIdleThread() { + Thread& t = threads[idleThreadIndex]; + constexpr u32 codeAddress = 0xBFC00000; + + // Reserve some memory for the idle thread's code. We map this memory to vaddr BFC00000 which is not userland-accessible + // We only allocate 4KB (1 page) because our idle code is pretty small + const u32 fcramIndex = mem.allocateSysMemory(Memory::pageSize); + auto vaddr = mem.allocateMemory(codeAddress, fcramIndex, Memory::pageSize, true, true, false, true, false, true); + if (!vaddr.has_value() || vaddr.value() != codeAddress) { + Helpers::panic("Failed to setup idle thread"); + } + + // Copy idle thread code to the allocated FCRAM + std::memcpy(&mem.getFCRAM()[fcramIndex], idleThreadCode, sizeof(idleThreadCode)); + + t.entrypoint = codeAddress; + t.tlsBase = 0; + t.gprs[13] = 0; // Set SP & LR to 0 just in case. The idle thread should never access memory, but let's be safe + t.gprs[14] = 0; + t.gprs[15] = codeAddress; + t.cpsr = CPSR::UserMode; + t.fpscr = FPSCR::ThreadDefault; + + // Our idle thread should have as low of a priority as possible, because, well, it's an idle thread. + // We handle this by giving it a priority of 0xff, which is lower than is actually allowed for user threads + // (High priority value = low priority) + t.priority = 0xff; + t.status = ThreadStatus::Ready; + + // Add idle thread to the list of thread indices + threadIndices.push_back(idleThreadIndex); + sortThreads(); +} \ No newline at end of file diff --git a/src/core/kernel/kernel.cpp b/src/core/kernel/kernel.cpp index f22ab051..78c858c6 100644 --- a/src/core/kernel/kernel.cpp +++ b/src/core/kernel/kernel.cpp @@ -15,6 +15,7 @@ Kernel::Kernel(CPU& cpu, Memory& mem, GPU& gpu) t.index = i; t.tlsBase = VirtualAddrs::TLSBase + i * VirtualAddrs::TLSSize; t.status = ThreadStatus::Dead; + t.waitList.clear(); t.waitList.reserve(10); // Reserve some space for the wait list to avoid further memory allocs later // The state below isn't necessary to initialize but we do it anyways out of caution t.outPointer = 0; @@ -33,11 +34,11 @@ void Kernel::serviceSVC(u32 svc) { case 0x0A: svcSleepThread(); break; case 0x0B: getThreadPriority(); break; case 0x0C: setThreadPriority(); break; - case 0x13: createMutex(); break; - case 0x14: releaseMutex(); break; - case 0x17: createEvent(); break; - case 0x18: signalEvent(); break; - case 0x19: clearEvent(); break; + case 0x13: svcCreateMutex(); break; + case 0x14: svcReleaseMutex(); break; + case 0x17: svcCreateEvent(); break; + case 0x18: svcSignalEvent(); break; + case 0x19: svcClearEvent(); break; case 0x1E: createMemoryBlock(); break; case 0x1F: mapMemoryBlock(); break; case 0x21: createAddressArbiter(); break; @@ -111,6 +112,7 @@ void Kernel::reset() { for (auto& t : threads) { t.status = ThreadStatus::Dead; t.waitList.clear(); + t.threadsWaitingForTermination = 0; // No threads are waiting for this thread to terminate cause it's dead } for (auto& object : objects) { @@ -130,6 +132,7 @@ void Kernel::reset() { // which is thankfully not used. Maybe we should prevent this mainThread = makeThread(0, VirtualAddrs::StackTop, 0x30, -2, 0, ThreadStatus::Running); currentThreadIndex = 0; + setupIdleThread(); // Create some of the OS ports srvHandle = makePort("srv:"); // Service manager port diff --git a/src/core/kernel/threads.cpp b/src/core/kernel/threads.cpp index 3ff12af2..56bc60ca 100644 --- a/src/core/kernel/threads.cpp +++ b/src/core/kernel/threads.cpp @@ -47,12 +47,14 @@ void Kernel::sortThreads() { bool Kernel::canThreadRun(const Thread& t) { if (t.status == ThreadStatus::Ready) { return true; - } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 || t.status == ThreadStatus::WaitSyncAll) { + } else if (t.status == ThreadStatus::WaitSleep || t.status == ThreadStatus::WaitSync1 + || t.status == ThreadStatus::WaitSyncAny || t.status == ThreadStatus::WaitSyncAll) { const u64 elapsedTicks = cpu.getTicks() - t.sleepTick; constexpr double ticksPerSec = double(CPU::ticksPerSec); constexpr double nsPerTick = ticksPerSec / 1000000000.0; + // TODO: Set r0 to the correct error code on timeout for WaitSync{1/Any/All} const s64 elapsedNs = s64(double(elapsedTicks) * nsPerTick); return elapsedNs >= t.waitingNanoseconds; } @@ -83,6 +85,7 @@ void Kernel::switchToNextThread() { if (!newThreadIndex.has_value()) { log("Kernel tried to switch to the next thread but none found. Switching to random thread\n"); assert(aliveThreadCount != 0); + Helpers::panic("rpog"); int index; do { @@ -147,6 +150,7 @@ Handle Kernel::makeThread(u32 entrypoint, u32 initialSP, u32 priority, s32 id, u t.status = status; t.handle = ret; t.waitingAddress = 0; + t.threadsWaitingForTermination = 0; // Thread just spawned, no other threads waiting for it to terminate t.cpsr = CPSR::UserMode | (isThumb ? CPSR::Thumb : 0); t.fpscr = FPSCR::ThreadDefault; @@ -161,14 +165,28 @@ Handle Kernel::makeMutex(bool locked) { Handle ret = makeObject(KernelObjectType::Mutex); objects[ret].data = new Mutex(locked); - // If the mutex is initially locked, store the index of the thread that owns it + // If the mutex is initially locked, store the index of the thread that owns it and set lock count to 1 if (locked) { - objects[ret].getData()->ownerThread = currentThreadIndex; + Mutex* moo = objects[ret].getData(); + moo->ownerThread = currentThreadIndex; } return ret; } +void Kernel::releaseMutex(Mutex* moo) { + // TODO: Assert lockCount > 0 before release, maybe. The SVC should be safe at least. + moo->lockCount--; // Decrement lock count + + // If the lock count reached 0 then the thread no longer owns the mootex and it can be given to a new one + if (moo->lockCount == 0) { + moo->locked = false; + if (moo->waitlist != 0) { + Helpers::panic("Mutex got freed while it's got more threads waiting for it. Must make a new thread claim it."); + } + } +} + Handle Kernel::makeSemaphore(u32 initialCount, u32 maximumCount) { Handle ret = makeObject(KernelObjectType::Semaphore); objects[ret].data = new Semaphore(initialCount, maximumCount); @@ -184,14 +202,45 @@ void Kernel::sleepThreadOnArbiter(u32 waitingAddress) { switchToNextThread(); } +// Acquires an object that is **ready to be acquired** without waiting on it +void Kernel::acquireSyncObject(KernelObject* object, const Thread& thread) { + switch (object->type) { + case KernelObjectType::Event: { + Event* e = object->getData(); + if (e->resetType == ResetType::OneShot) { // One-shot events automatically get cleared after waking up a thread + e->fired = false; + } + break; + } + + case KernelObjectType::Mutex: { + Mutex* moo = object->getData(); + moo->locked = true; // Set locked to true, whether it's false or not because who cares + // Increment lock count by 1. If a thread acquires a mootex multiple times, it needs to release it until count == 0 + // For the mootex to be free. + moo->lockCount++; + moo->ownerThread = thread.index; + break; + } + + case KernelObjectType::Thread: + break; + + default: Helpers::panic("Acquiring unimplemented sync object %s", object->getTypeName()); + } +} + // Make a thread sleep for a certain amount of nanoseconds at minimum void Kernel::sleepThread(s64 ns) { if (ns < 0) { Helpers::panic("Sleeping a thread for a negative amount of ns"); } else if (ns == 0) { // Used when we want to force a thread switch - int curr = currentThreadIndex; - switchToNextThread(); // Mark thread as ready after switching, to avoid switching to the same thread - threads[curr].status = ThreadStatus::Ready; + std::optional newThreadIndex = getNextThread(); + // If there's no other thread waiting, don't bother yielding + if (newThreadIndex.has_value()) { + threads[currentThreadIndex].status = ThreadStatus::Ready; + switchThread(newThreadIndex.value()); + } } else { // If we're sleeping for > 0 ns Thread& t = threads[currentThreadIndex]; t.status = ThreadStatus::WaitSleep; @@ -226,7 +275,7 @@ void Kernel::createThread() { // void SleepThread(s64 nanoseconds) void Kernel::svcSleepThread() { const s64 ns = s64(u64(regs[0]) | (u64(regs[1]) << 32)); - logSVC("SleepThread(ns = %lld)\n", ns); + //logSVC("SleepThread(ns = %lld)\n", ns); regs[0] = SVCResult::Success; sleepThread(ns); @@ -310,10 +359,14 @@ void Kernel::exitThread() { t.status = ThreadStatus::Dead; aliveThreadCount--; + // Check if any threads are sleeping, waiting for this thread to terminate, and wake them up + if (t.threadsWaitingForTermination != 0) + Helpers::panic("TODO: Implement threads sleeping until another thread terminates"); + switchToNextThread(); } -void Kernel::createMutex() { +void Kernel::svcCreateMutex() { bool locked = regs[1] != 0; logSVC("CreateMutex (locked = %s)\n", locked ? "yes" : "no"); @@ -321,10 +374,25 @@ void Kernel::createMutex() { regs[1] = makeMutex(locked); } -void Kernel::releaseMutex() { +void Kernel::svcReleaseMutex() { const Handle handle = regs[0]; + logSVC("ReleaseMutex (handle = %x)\n", handle); - logSVC("ReleaseMutex (handle = %x) (STUBBED)\n", handle); + const auto object = getObject(handle, KernelObjectType::Mutex); + if (object == nullptr) [[unlikely]] { + Helpers::panic("Tried to release non-existent mutex"); + regs[0] = SVCResult::BadHandle; + return; + } + + Mutex* moo = object->getData(); + // A thread can't release a mutex it does not own + if (!moo->locked || moo->ownerThread != currentThreadIndex) { + regs[0] = SVCResult::InvalidMutexRelease; + return; + } + + releaseMutex(moo); regs[0] = SVCResult::Success; } @@ -344,8 +412,19 @@ bool Kernel::shouldWaitOnObject(KernelObject* object) { case KernelObjectType::Event: // We should wait on an event only if it has not been signalled return !object->getData()->fired; + case KernelObjectType::Mutex: { + Mutex* moo = object->getData(); // mooooooooooo + return moo->locked && moo->ownerThread != currentThreadIndex; // If the current thread owns the moo then no reason to wait + } + + case KernelObjectType::Thread: // Waiting on a thread waits until it's dead. If it's dead then no need to wait + return object->getData()->status != ThreadStatus::Dead; + + case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0 + return object->getData()->availableCount <= 0; + default: - logThread("Not sure whether to wait on object (type: %s)", object->getTypeName()); + Helpers::panic("Not sure whether to wait on object (type: %s)", object->getTypeName()); return true; } } \ No newline at end of file diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 6ed2e51a..6d64f3f8 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -76,6 +76,7 @@ u8 Memory::read8(u32 vaddr) { case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now case ConfigMem::NetworkState: return 2; // Report that we've got an internet connection case ConfigMem::HeadphonesConnectedMaybe: return 0; + case ConfigMem::Unknown1086: return 1; // It's unknown what this is but some games want it to be 1 default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr); } } diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 43d760e2..79e0ddd3 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -231,13 +231,6 @@ void Renderer::setupBlending() { GL_SRC_ALPHA_SATURATE, GL_ONE }; - // Temporarily here until we add constant color/alpha - const auto panicIfUnimplementedFunc = [](const u32 func) { - auto x = blendingFuncs[func]; - if (x == GL_CONSTANT_COLOR || x == GL_ONE_MINUS_CONSTANT_COLOR || x == GL_ALPHA || x == GL_ONE_MINUS_CONSTANT_ALPHA) [[unlikely]] - Helpers::panic("Unimplemented blending function!"); - }; - if (!blendingEnabled) { OpenGL::disableBlend(); } else { @@ -254,11 +247,12 @@ void Renderer::setupBlending() { const u32 alphaSourceFunc = (blendControl >> 24) & 0xf; const u32 alphaDestFunc = (blendControl >> 28) & 0xf; - // Panic if one of the blending funcs is unimplemented - panicIfUnimplementedFunc(rgbSourceFunc); - panicIfUnimplementedFunc(rgbDestFunc); - panicIfUnimplementedFunc(alphaSourceFunc); - panicIfUnimplementedFunc(alphaDestFunc); + const u32 constantColor = regs[PICAInternalRegs::BlendColour]; + const u32 r = constantColor & 0xff; + const u32 g = (constantColor >> 8) & 0xff; + const u32 b = (constantColor >> 16) & 0xff; + const u32 a = (constantColor >> 24) & 0xff; + OpenGL::setBlendColor(float(r) / 255.f, float(g) / 255.f, float(b) / 255.f, float(a) / 255.f); // Translate equations and funcs to their GL equivalents and set them glBlendEquationSeparate(blendingEquations[rgbEquation], blendingEquations[alphaEquation]); diff --git a/src/core/services/ac.cpp b/src/core/services/ac.cpp index cc1e3bd5..66452534 100644 --- a/src/core/services/ac.cpp +++ b/src/core/services/ac.cpp @@ -1,4 +1,5 @@ #include "services/ac.hpp" +#include "ipc.hpp" namespace ACCommands { enum : u32 { @@ -26,5 +27,6 @@ void ACService::setClientVersion(u32 messagePointer) { u32 version = mem.read32(messagePointer + 4); log("AC::SetClientVersion (version = %d)\n", version); + mem.write32(messagePointer, IPC::responseHeader(0x40, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/am.cpp b/src/core/services/am.cpp index 00abb6e2..9db92df5 100644 --- a/src/core/services/am.cpp +++ b/src/core/services/am.cpp @@ -1,4 +1,5 @@ #include "services/am.hpp" +#include "ipc.hpp" namespace AMCommands { enum : u32 { @@ -40,11 +41,14 @@ void AMService::listTitleInfo(u32 messagePointer) { pointer += 24; // = sizeof(TicketInfo) } + mem.write32(messagePointer, IPC::responseHeader(0x1007, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, ticketCount); } void AMService::getDLCTitleInfo(u32 messagePointer) { log("AM::GetDLCTitleInfo (stubbed to fail)\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x1005, 1, 4)); mem.write32(messagePointer + 4, -1); } \ No newline at end of file diff --git a/src/core/services/apt.cpp b/src/core/services/apt.cpp index c2ef0ba2..0ee0a16a 100644 --- a/src/core/services/apt.cpp +++ b/src/core/services/apt.cpp @@ -1,4 +1,5 @@ #include "services/apt.hpp" +#include "ipc.hpp" #include "kernel.hpp" namespace APTCommands { @@ -8,6 +9,7 @@ namespace APTCommands { Enable = 0x00030040, InquireNotification = 0x000B0040, ReceiveParameter = 0x000D0080, + GlanceParameter = 0x000E0080, ReplySleepQuery = 0x003E0080, NotifyToWait = 0x00430040, GetSharedFont = 0x00440000, @@ -25,7 +27,31 @@ namespace APTCommands { namespace Result { enum : u32 { Success = 0, - Failure = 0xFFFFFFFF + }; +} + +// https://www.3dbrew.org/wiki/NS_and_APT_Services#Command +namespace APTTransitions { + enum : u32 { + None = 0, + Wakeup = 1, + Request = 2, + Response = 3, + Exit = 4, + Message = 5, + HomeButtonSingle = 6, + HomeButtonDouble = 7, + DSPSleep = 8, + DSPWakeup = 9, + WakeupByExit = 10, + WakuepByPause = 11, + WakeupByCancel = 12, + WakeupByCancelAll = 13, + WakeupByPowerButton = 14, + WakeupToJumpHome = 15, + RequestForApplet = 16, + WakeupToLaunchApp = 17, + ProcessDed = 0x41 }; } @@ -52,6 +78,7 @@ void APTService::handleSyncRequest(u32 messagePointer) { case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break; case APTCommands::GetLockHandle: getLockHandle(messagePointer); break; case APTCommands::GetWirelessRebootInfo: getWirelessRebootInfo(messagePointer); break; + case APTCommands::GlanceParameter: glanceParameter(messagePointer); break; case APTCommands::NotifyToWait: notifyToWait(messagePointer); break; case APTCommands::ReceiveParameter: [[likely]] receiveParameter(messagePointer); break; case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break; @@ -70,11 +97,13 @@ void APTService::appletUtility(u32 messagePointer) { log("APT::AppletUtility(utility = %d, input size = %x, output size = %x, inputPointer = %08X)\n", utility, inputSize, outputSize, inputPointer); + mem.write32(messagePointer, IPC::responseHeader(0x4B, 2, 2)); mem.write32(messagePointer + 4, Result::Success); } void APTService::checkNew3DS(u32 messagePointer) { log("APT::CheckNew3DS\n"); + mem.write32(messagePointer, IPC::responseHeader(0x102, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, (model == ConsoleModel::New3DS) ? 1 : 0); // u8, Status (0 = Old 3DS, 1 = New 3DS) } @@ -82,20 +111,28 @@ void APTService::checkNew3DS(u32 messagePointer) { // TODO: Figure out the slight way this differs from APT::CheckNew3DS void APTService::checkNew3DSApp(u32 messagePointer) { log("APT::CheckNew3DSApp\n"); - checkNew3DS(messagePointer); + mem.write32(messagePointer, IPC::responseHeader(0x101, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); + mem.write8(messagePointer + 8, (model == ConsoleModel::New3DS) ? 1 : 0); // u8, Status (0 = Old 3DS, 1 = New 3DS) } void APTService::enable(u32 messagePointer) { log("APT::Enable\n"); + mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void APTService::initialize(u32 messagePointer) { log("APT::Initialize\n"); - notificationEvent = kernel.makeEvent(ResetType::OneShot); - resumeEvent = kernel.makeEvent(ResetType::OneShot); + if (!notificationEvent.has_value() || !resumeEvent.has_value()) { + notificationEvent = kernel.makeEvent(ResetType::OneShot); + resumeEvent = kernel.makeEvent(ResetType::OneShot); + kernel.signalEvent(resumeEvent.value()); // Seems to be signalled on startup + } + + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 3)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0x04000000); // Translation descriptor mem.write32(messagePointer + 12, notificationEvent.value()); // Notification Event Handle @@ -103,12 +140,10 @@ void APTService::initialize(u32 messagePointer) { } void APTService::inquireNotification(u32 messagePointer) { - log("APT::InquireNotification (STUBBED TO FAIL)\n"); + log("APT::InquireNotification (STUBBED TO RETURN NONE)\n"); - // Thanks to our silly WaitSynchronization hacks, sometimes games will switch to the APT thread without actually getting a notif - // After REing the APT code, I figured that making InquireNotification fail is one way of making games not crash when this happens - // We should fix this in the future, when the sync object implementation is less hacky. - mem.write32(messagePointer + 4, Result::Failure); + mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0)); + mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, static_cast(NotificationType::None)); } @@ -120,7 +155,8 @@ void APTService::getLockHandle(u32 messagePointer) { lockHandle = kernel.makeMutex(); } - mem.write32(messagePointer + 4, Result::Failure); // Result code + mem.write32(messagePointer, IPC::responseHeader(0x1, 3, 2)); + mem.write32(messagePointer + 4, Result::Success); // Result code mem.write32(messagePointer + 8, 0); // AppletAttr mem.write32(messagePointer + 12, 0); // APT State (bit0 = Power Button State, bit1 = Order To Close State) mem.write32(messagePointer + 16, 0); // Translation descriptor @@ -130,6 +166,7 @@ void APTService::getLockHandle(u32 messagePointer) { // This apparently does nothing on the original kernel either? void APTService::notifyToWait(u32 messagePointer) { log("APT::NotifyToWait\n"); + mem.write32(messagePointer, IPC::responseHeader(0x43, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -140,11 +177,30 @@ void APTService::receiveParameter(u32 messagePointer) { if (size > 0x1000) Helpers::panic("APT::ReceiveParameter with size > 0x1000"); - // TODO: Properly implement this. We currently stub it in the same way as 3dmoo + // TODO: Properly implement this. We currently stub somewhat like 3dmoo + mem.write32(messagePointer, IPC::responseHeader(0xD, 4, 4)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); // Sender App ID - mem.write32(messagePointer + 12, 1); // Signal type (1 = app just started, 0xB = returning to app, 0xC = exiting app) - mem.write32(messagePointer + 16, 0x10); + mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command + mem.write32(messagePointer + 16, 0); + mem.write32(messagePointer + 20, 0x10); + mem.write32(messagePointer + 24, 0); + mem.write32(messagePointer + 28, 0); +} + +void APTService::glanceParameter(u32 messagePointer) { + const u32 app = mem.read32(messagePointer + 4); + const u32 size = mem.read32(messagePointer + 8); + log("APT::GlanceParameter(app ID = %X, size = %04X) (STUBBED)\n", app, size); + + if (size > 0x1000) Helpers::panic("APT::GlanceParameter with size > 0x1000"); + + // TODO: Properly implement this. We currently stub it similar + mem.write32(messagePointer, IPC::responseHeader(0xE, 4, 4)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 8, 0); // Sender App ID + mem.write32(messagePointer + 12, APTTransitions::Wakeup); // Command + mem.write32(messagePointer + 16, 0); mem.write32(messagePointer + 20, 0); mem.write32(messagePointer + 24, 0); mem.write32(messagePointer + 28, 0); @@ -152,6 +208,7 @@ void APTService::receiveParameter(u32 messagePointer) { void APTService::replySleepQuery(u32 messagePointer) { log("APT::ReplySleepQuery (Stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x3E, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -163,6 +220,7 @@ void APTService::setApplicationCpuTimeLimit(u32 messagePointer) { if (percentage < 5 || percentage > 89 || fixed != 1) { Helpers::panic("Invalid parameters passed to APT::SetApplicationCpuTimeLimit"); } else { + mem.write32(messagePointer, IPC::responseHeader(0x4F, 1, 0)); mem.write32(messagePointer + 4, Result::Success); cpuTimeLimit = percentage; } @@ -170,6 +228,7 @@ void APTService::setApplicationCpuTimeLimit(u32 messagePointer) { void APTService::getApplicationCpuTimeLimit(u32 messagePointer) { log("APT::GetApplicationCpuTimeLimit\n"); + mem.write32(messagePointer, IPC::responseHeader(0x50, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, cpuTimeLimit); } @@ -178,8 +237,9 @@ void APTService::setScreencapPostPermission(u32 messagePointer) { u32 perm = mem.read32(messagePointer + 4); log("APT::SetScreencapPostPermission (perm = %d)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x55, 1, 0)); // Apparently only 1-3 are valid values, but I see 0 used in some games like Pokemon Rumble - mem.write32(messagePointer, Result::Success); + mem.write32(messagePointer + 4, Result::Success); screencapPostPermission = perm; } @@ -187,6 +247,7 @@ void APTService::getSharedFont(u32 messagePointer) { log("APT::GetSharedFont\n"); constexpr u32 fontVaddr = 0x18000000; + mem.write32(messagePointer, IPC::responseHeader(0x44, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, fontVaddr); mem.write32(messagePointer + 16, KernelHandles::FontSharedMemHandle); @@ -197,6 +258,7 @@ void APTService::getSharedFont(u32 messagePointer) { void APTService::theSmashBrosFunction(u32 messagePointer) { log("APT: Called the elusive Smash Bros function\n"); + mem.write32(messagePointer, IPC::responseHeader(0x103, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, (model == ConsoleModel::New3DS) ? 2 : 1); } @@ -208,6 +270,7 @@ void APTService::getWirelessRebootInfo(u32 messagePointer) { if (size > 0x10) Helpers::panic("APT::GetWirelessInfo with size > 0x10 bytes"); + mem.write32(messagePointer, IPC::responseHeader(0x45, 1, 2)); mem.write32(messagePointer + 4, Result::Success); for (u32 i = 0; i < size; i++) { mem.write8(messagePointer + 0x104 + i, 0); // Temporarily stub this until we add SetWirelessRebootInfo diff --git a/src/core/services/boss.cpp b/src/core/services/boss.cpp index 5c306cc6..f43aa5f3 100644 --- a/src/core/services/boss.cpp +++ b/src/core/services/boss.cpp @@ -1,4 +1,5 @@ #include "services/boss.hpp" +#include "ipc.hpp" namespace BOSSCommands { enum : u32 { @@ -40,22 +41,26 @@ void BOSSService::handleSyncRequest(u32 messagePointer) { void BOSSService::initializeSession(u32 messagePointer) { log("BOSS::InitializeSession (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void BOSSService::getOptoutFlag(u32 messagePointer) { log("BOSS::getOptoutFlag\n"); + mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, optoutFlag); } void BOSSService::getTaskIdList(u32 messagePointer) { log("BOSS::GetTaskIdList (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0xE, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void BOSSService::getStorageEntryInfo(u32 messagePointer) { log("BOSS::GetStorageEntryInfo (undocumented)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x30, 3, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); // u32, unknown meaning mem.write16(messagePointer + 12, 0); // s16, unknown meaning @@ -67,21 +72,25 @@ void BOSSService::receiveProperty(u32 messagePointer) { const u32 ptr = mem.read32(messagePointer + 16); log("BOSS::ReceiveProperty(stubbed) (id = %d, size = %08X, ptr = %08X)\n", id, size, ptr); + mem.write32(messagePointer, IPC::responseHeader(0x16, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); // Read size } void BOSSService::unregisterTask(u32 messagePointer) { log("BOSS::UnregisterTask (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x0C, 1, 2)); mem.write32(messagePointer + 4, Result::Success); } void BOSSService::registerStorageEntry(u32 messagePointer) { log("BOSS::RegisterStorageEntry (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x2F, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void BOSSService::unregisterStorage(u32 messagePointer) { log("BOSS::UnregisterStorage (stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/cam.cpp b/src/core/services/cam.cpp index fc6b27a1..db62aec3 100644 --- a/src/core/services/cam.cpp +++ b/src/core/services/cam.cpp @@ -1,4 +1,5 @@ #include "services/cam.hpp" +#include "ipc.hpp" namespace CAMCommands { enum : u32 { @@ -26,6 +27,7 @@ void CAMService::handleSyncRequest(u32 messagePointer) { void CAMService::driverInitialize(u32 messagePointer) { log("CAM::DriverInitialize\n"); + mem.write32(messagePointer, IPC::responseHeader(0x39, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -55,6 +57,7 @@ void CAMService::getMaxLines(u32 messagePointer) { } } + mem.write32(messagePointer, IPC::responseHeader(0xA, 2, 0)); mem.write32(messagePointer + 4, result); mem.write16(messagePointer + 8, lines); } diff --git a/src/core/services/cecd.cpp b/src/core/services/cecd.cpp index 8fa39d60..cc4bfffb 100644 --- a/src/core/services/cecd.cpp +++ b/src/core/services/cecd.cpp @@ -1,4 +1,5 @@ #include "services/cecd.hpp" +#include "ipc.hpp" namespace CECDCommands { enum : u32 { @@ -26,6 +27,8 @@ void CECDService::getEventHandle(u32 messagePointer) { log("CECD::GetEventHandle (stubbed)\n"); Helpers::panic("TODO: Actually implement CECD::GetEventHandle"); + mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2)); mem.write32(messagePointer + 4, Result::Success); + // TODO: Translation descriptor here? mem.write32(messagePointer + 12, 0x66666666); } \ No newline at end of file diff --git a/src/core/services/cfg.cpp b/src/core/services/cfg.cpp index 48496a78..f666efab 100644 --- a/src/core/services/cfg.cpp +++ b/src/core/services/cfg.cpp @@ -1,5 +1,6 @@ #include "services/cfg.hpp" #include "services/dsp.hpp" +#include "ipc.hpp" namespace CFGCommands { enum : u32 { @@ -101,12 +102,14 @@ void CFGService::getConfigInfoBlk2(u32 messagePointer) { Helpers::panic("Unhandled GetConfigInfoBlk2 configuration. Size = %d, block = %X", size, blockID); } + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 2)); mem.write32(messagePointer + 4, Result::Success); } void CFGService::secureInfoGetRegion(u32 messagePointer) { log("CFG::SecureInfoGetRegion\n"); + mem.write32(messagePointer, IPC::responseHeader(0x2, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, static_cast(Regions::USA)); // TODO: Detect the game region and report it } @@ -115,6 +118,7 @@ void CFGService::genUniqueConsoleHash(u32 messagePointer) { log("CFG::GenUniqueConsoleHash (semi-stubbed)\n"); const u32 salt = mem.read32(messagePointer + 4) & 0x000FFFFF; + mem.write32(messagePointer, IPC::responseHeader(0x3, 3, 0)); mem.write32(messagePointer + 4, Result::Success); // We need to implement hash generation & the SHA-256 digest properly later on. We have cryptopp so the hashing isn't too hard to do // Let's stub it for now @@ -128,6 +132,7 @@ void CFGService::getRegionCanadaUSA(u32 messagePointer) { log("CFG::GetRegionCanadaUSA\n"); const u8 ret = (country == CountryCodes::US || country == CountryCodes::CA) ? 1 : 0; + mem.write32(messagePointer, IPC::responseHeader(0x4, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, ret); } \ No newline at end of file diff --git a/src/core/services/dsp.cpp b/src/core/services/dsp.cpp index 212e8219..7309463f 100644 --- a/src/core/services/dsp.cpp +++ b/src/core/services/dsp.cpp @@ -1,4 +1,6 @@ #include "services/dsp.hpp" +#include "ipc.hpp" +#include "kernel.hpp" namespace DSPCommands { enum : u32 { @@ -10,7 +12,7 @@ namespace DSPCommands { FlushDataCache = 0x00130082, InvalidateDataCache = 0x00140082, RegisterInterruptEvents = 0x00150082, - GetSemaphoreHandle = 0x00160000, + GetSemaphoreEventHandle = 0x00160000, SetSemaphoreMask = 0x00170040, GetHeadphoneStatus = 0x001F0000 }; @@ -26,6 +28,15 @@ namespace Result { void DSPService::reset() { audioPipe.reset(); + totalEventCount = 0; + + semaphoreEvent = std::nullopt; + interrupt0 = std::nullopt; + interrupt1 = std::nullopt; + + for (DSPEvent& e : pipeEvents) { + e = std::nullopt; + } } void DSPService::handleSyncRequest(u32 messagePointer) { @@ -35,7 +46,7 @@ void DSPService::handleSyncRequest(u32 messagePointer) { case DSPCommands::FlushDataCache: flushDataCache(messagePointer); break; case DSPCommands::InvalidateDataCache: invalidateDCache(messagePointer); break; case DSPCommands::GetHeadphoneStatus: getHeadphoneStatus(messagePointer); break; - case DSPCommands::GetSemaphoreHandle: getSemaphoreHandle(messagePointer); break; + case DSPCommands::GetSemaphoreEventHandle: getSemaphoreEventHandle(messagePointer); break; case DSPCommands::LoadComponent: loadComponent(messagePointer); break; case DSPCommands::ReadPipeIfPossible: readPipeIfPossible(messagePointer); break; case DSPCommands::RegisterInterruptEvents: registerInterruptEvents(messagePointer); break; @@ -49,8 +60,9 @@ void DSPService::handleSyncRequest(u32 messagePointer) { void DSPService::convertProcessAddressFromDspDram(u32 messagePointer) { const u32 address = mem.read32(messagePointer + 4); log("DSP::ConvertProcessAddressFromDspDram (address = %08X)\n", address); - const u32 converted = (address << 1) + 0x1FF40000; + + mem.write32(messagePointer, IPC::responseHeader(0xC, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, converted); // Converted address } @@ -61,6 +73,7 @@ void DSPService::loadComponent(u32 messagePointer) { u32 dataMask = mem.read32(messagePointer + 12); log("DSP::LoadComponent (size = %08X, program mask = %X, data mask = %X\n", size, programMask, dataMask); + mem.write32(messagePointer, IPC::responseHeader(0x11, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 1); // Component loaded mem.write32(messagePointer + 12, (size << 4) | 0xA); @@ -89,37 +102,80 @@ void DSPService::readPipeIfPossible(u32 messagePointer) { mem.write16(buffer + i, pipe.readUnchecked()); } + mem.write32(messagePointer, IPC::responseHeader(0x10, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write16(messagePointer + 8, i); // Number of bytes read } -void DSPService::registerInterruptEvents(u32 messagePointer) { - u32 interrupt = mem.read32(messagePointer + 4); - u32 channel = mem.read32(messagePointer + 8); - u32 event = mem.read32(messagePointer + 16); +DSPService::DSPEvent& DSPService::getEventRef(u32 type, u32 pipe) { + switch (type) { + case 0: return interrupt0; + case 1: return interrupt1; - log("DSP::RegisterInterruptEvents (interrupt = %d, channel = %d, event = %d)\n", interrupt, channel, event); - mem.write32(messagePointer + 4, Result::Success); + case 2: + if (pipe >= pipeCount) + Helpers::panic("Tried to access the event of an invalid pipe"); + return pipeEvents[pipe]; + + default: + Helpers::panic("Unknown type for DSP::getEventRef"); + } +} + +void DSPService::registerInterruptEvents(u32 messagePointer) { + const u32 interrupt = mem.read32(messagePointer + 4); + const u32 channel = mem.read32(messagePointer + 8); + const u32 eventHandle = mem.read32(messagePointer + 16); + log("DSP::RegisterInterruptEvents (interrupt = %d, channel = %d, event = %d)\n", interrupt, channel, eventHandle); + + // The event handle being 0 means we're removing an event + if (eventHandle == 0) { + Helpers::panic("DSP::DSP::RegisterinterruptEvents Trying to remove a registered interrupt"); + } else { + const KernelObject* object = kernel.getObject(eventHandle, KernelObjectType::Event); + if (!object) { + Helpers::panic("DSP::DSP::RegisterInterruptEvents with invalid event handle"); + } + + if (totalEventCount >= maxEventCount) + Helpers::panic("DSP::RegisterInterruptEvents overflowed total number of allowed events"); + else { + getEventRef(interrupt, channel) = eventHandle; + mem.write32(messagePointer, IPC::responseHeader(0x15, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); + + totalEventCount++; + kernel.signalEvent(eventHandle); + } + } } void DSPService::getHeadphoneStatus(u32 messagePointer) { log("DSP::GetHeadphoneStatus\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1F, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, Result::HeadphonesInserted); // This should be toggleable for shits and giggles } -void DSPService::getSemaphoreHandle(u32 messagePointer) { - log("DSP::GetSemaphoreHandle\n"); +void DSPService::getSemaphoreEventHandle(u32 messagePointer) { + log("DSP::GetSemaphoreEventHandle\n"); + if (!semaphoreEvent.has_value()) { + semaphoreEvent = kernel.makeEvent(ResetType::OneShot); + } + + mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 2)); mem.write32(messagePointer + 4, Result::Success); - mem.write32(messagePointer + 12, 0xF9991234); // Semaphore handle (stubbed with random, obvious number) + // TODO: Translation descriptor here? + mem.write32(messagePointer + 12, semaphoreEvent.value()); // Semaphore event handle } void DSPService::setSemaphore(u32 messagePointer) { const u16 value = mem.read16(messagePointer + 4); log("DSP::SetSemaphore(value = %04X)\n", value); + mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -127,6 +183,7 @@ void DSPService::setSemaphoreMask(u32 messagePointer) { const u16 mask = mem.read16(messagePointer + 4); log("DSP::SetSemaphoreMask(mask = %04X)\n", mask); + mem.write32(messagePointer, IPC::responseHeader(0x17, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -136,6 +193,7 @@ void DSPService::writeProcessPipe(u32 messagePointer) { const u32 buffer = mem.read32(messagePointer + 16); log("DSP::writeProcessPipe (channel = %d, size = %X, buffer = %08X)\n", channel, size, buffer); + mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -145,6 +203,7 @@ void DSPService::flushDataCache(u32 messagePointer) { const Handle process = mem.read32(messagePointer + 16); log("DSP::FlushDataCache (addr = %08X, size = %08X, process = %X)\n", address, size, process); + mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -154,5 +213,6 @@ void DSPService::invalidateDCache(u32 messagePointer) { const Handle process = mem.read32(messagePointer + 16); log("DSP::InvalidateDataCache (addr = %08X, size = %08X, process = %X)\n", address, size, process); + mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/frd.cpp b/src/core/services/frd.cpp index 0207f0ae..4bb1b421 100644 --- a/src/core/services/frd.cpp +++ b/src/core/services/frd.cpp @@ -1,5 +1,6 @@ -#include "services/frd.hpp" #include +#include "services/frd.hpp" +#include "ipc.hpp" namespace FRDCommands { enum : u32 { @@ -43,6 +44,7 @@ void FRDService::attachToEventNotification(u32 messagePointer) { void FRDService::getMyFriendKey(u32 messagePointer) { log("FRD::GetMyFriendKey\n"); + mem.write32(messagePointer, IPC::responseHeader(0x5, 5, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); // Principal ID mem.write32(messagePointer + 12, 0); // Padding (?) @@ -56,6 +58,7 @@ void FRDService::getFriendKeyList(u32 messagePointer) { const u32 count = mem.read32(messagePointer + 8); // From what I understand this is a cap on the number of keys to receive? constexpr u32 friendCount = 0; // And this should be the number of friends whose keys were actually received? + mem.write32(messagePointer, IPC::responseHeader(0x11, 2, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, friendCount); @@ -74,6 +77,7 @@ void FRDService::getMyPresence(u32 messagePointer) { mem.write32(buffer + i, 0); } + mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 2)); mem.write32(messagePointer + 4, Result::Success); } @@ -97,10 +101,13 @@ void FRDService::setClientSDKVersion(u32 messagePointer) { u32 version = mem.read32(messagePointer + 4); log("FRD::SetClientSdkVersion (version = %d)\n", version); + mem.write32(messagePointer, IPC::responseHeader(0x32, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void FRDService::setNotificationMask(u32 messagePointer) { log("FRD::SetNotificationMask (Not documented)\n"); + + mem.write32(messagePointer, IPC::responseHeader(0x21, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/fs.cpp b/src/core/services/fs.cpp index 3642fa20..0f421b9d 100644 --- a/src/core/services/fs.cpp +++ b/src/core/services/fs.cpp @@ -1,6 +1,7 @@ #include "services/fs.hpp" #include "kernel/kernel.hpp" #include "io_file.hpp" +#include "ipc.hpp" #ifdef CreateFile // windows.h defines CreateFile & DeleteFile because of course it does. #undef CreateFile @@ -16,6 +17,7 @@ namespace FSCommands { CreateFile = 0x08080202, OpenDirectory = 0x080B0102, OpenArchive = 0x080C00C2, + ControlArchive = 0x080D0144, CloseArchive = 0x080E0080, IsSdmcDetected = 0x08170000, GetFormatInfo = 0x084500C2, @@ -151,6 +153,7 @@ void FSService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { case FSCommands::CreateFile: createFile(messagePointer); break; + case FSCommands::ControlArchive: controlArchive(messagePointer); break; case FSCommands::CloseArchive: closeArchive(messagePointer); break; case FSCommands::DeleteFile: deleteFile(messagePointer); break; case FSCommands::FormatSaveData: formatSaveData(messagePointer); break; @@ -170,6 +173,7 @@ void FSService::handleSyncRequest(u32 messagePointer) { void FSService::initialize(u32 messagePointer) { log("FS::Initialize\n"); + mem.write32(messagePointer, IPC::responseHeader(0x801, 1, 0)); mem.write32(messagePointer + 4, ResultCode::Success); } @@ -178,7 +182,8 @@ void FSService::initializeWithSdkVersion(u32 messagePointer) { const auto version = mem.read32(messagePointer + 4); log("FS::InitializeWithSDKVersion(version = %d)\n", version); - initialize(messagePointer); + mem.write32(messagePointer, IPC::responseHeader(0x861, 1, 0)); + mem.write32(messagePointer + 4, ResultCode::Success); } void FSService::closeArchive(u32 messagePointer) { @@ -186,6 +191,8 @@ void FSService::closeArchive(u32 messagePointer) { const auto object = kernel.getObject(handle, KernelObjectType::Archive); log("FSService::CloseArchive(handle = %X)\n", handle); + mem.write32(messagePointer, IPC::responseHeader(0x80E, 1, 0)); + if (object == nullptr) { log("FSService::CloseArchive: Tried to close invalid archive %X\n", handle); mem.write32(messagePointer + 4, ResultCode::Failure); @@ -205,6 +212,7 @@ void FSService::openArchive(u32 messagePointer) { log("FS::OpenArchive(archive ID = %d, archive path type = %d)\n", archiveID, archivePathType); std::optional handle = openArchiveHandle(archiveID, archivePath); + mem.write32(messagePointer, IPC::responseHeader(0x80C, 3, 0)); if (handle.has_value()) { mem.write32(messagePointer + 4, ResultCode::Success); mem.write64(messagePointer + 8, handle.value()); @@ -215,7 +223,7 @@ void FSService::openArchive(u32 messagePointer) { } void FSService::openFile(u32 messagePointer) { - const u32 archiveHandle = mem.read64(messagePointer + 8); + const Handle archiveHandle = mem.read64(messagePointer + 8); const u32 filePathType = mem.read32(messagePointer + 16); const u32 filePathSize = mem.read32(messagePointer + 20); const u32 openFlags = mem.read32(messagePointer + 24); @@ -238,6 +246,7 @@ void FSService::openFile(u32 messagePointer) { const FilePerms perms(openFlags); std::optional handle = openFileHandle(archive, filePath, archivePath, perms); + mem.write32(messagePointer, IPC::responseHeader(0x802, 1, 2)); if (!handle.has_value()) { printf("OpenFile failed\n"); mem.write32(messagePointer + 4, ResultCode::FileNotFound); @@ -266,6 +275,7 @@ void FSService::openDirectory(u32 messagePointer) { const auto dirPath = readPath(pathType, pathPointer, pathSize); auto dir = openDirectoryHandle(archive, dirPath); + mem.write32(messagePointer, IPC::responseHeader(0x80B, 1, 2)); if (dir.isOk()) { mem.write32(messagePointer + 4, ResultCode::Success); mem.write32(messagePointer + 12, dir.unwrap()); @@ -302,6 +312,7 @@ void FSService::openFileDirectly(u32 messagePointer) { } std::optional handle = openFileHandle(archive, filePath, archivePath, perms); + mem.write32(messagePointer, IPC::responseHeader(0x803, 1, 2)); if (!handle.has_value()) { Helpers::panic("OpenFileDirectly: Failed to open file with given path"); } else { @@ -331,11 +342,12 @@ void FSService::createFile(u32 messagePointer) { auto filePath = readPath(filePathType, filePathPointer, filePathSize); FSResult res = archive->createFile(filePath, size); + mem.write32(messagePointer, IPC::responseHeader(0x808, 1, 0)); mem.write32(messagePointer + 4, static_cast(res)); } void FSService::deleteFile(u32 messagePointer) { - const u32 archiveHandle = mem.read64(messagePointer + 8); + const Handle archiveHandle = mem.read64(messagePointer + 8); const u32 filePathType = mem.read32(messagePointer + 16); const u32 filePathSize = mem.read32(messagePointer + 20); const u32 filePathPointer = mem.read32(messagePointer + 28); @@ -352,6 +364,7 @@ void FSService::deleteFile(u32 messagePointer) { auto filePath = readPath(filePathType, filePathPointer, filePathSize); FSResult res = archive->deleteFile(filePath); + mem.write32(messagePointer, IPC::responseHeader(0x804, 1, 0)); mem.write32(messagePointer + 4, static_cast(res)); } @@ -370,6 +383,7 @@ void FSService::getFormatInfo(u32 messagePointer) { } ArchiveBase::FormatInfo info = archive->getFormatInfo(path); + mem.write32(messagePointer, IPC::responseHeader(0x845, 5, 0)); mem.write32(messagePointer + 4, ResultCode::Success); mem.write32(messagePointer + 8, info.size); mem.write32(messagePointer + 12, info.numOfDirectories); @@ -400,11 +414,42 @@ void FSService::formatSaveData(u32 messagePointer) { const bool duplicateData = mem.read8(messagePointer + 36) != 0; printf("Stubbed FS::FormatSaveData. File num: %d, directory num: %d\n", fileNum, directoryNum); + mem.write32(messagePointer, IPC::responseHeader(0x84C, 1, 0)); + mem.write32(messagePointer + 4, ResultCode::Success); +} + +void FSService::controlArchive(u32 messagePointer) { + const Handle archiveHandle = mem.read64(messagePointer + 4); + const u32 action = mem.read32(messagePointer + 12); + const u32 inputSize = mem.read32(messagePointer + 16); + const u32 outputSize = mem.read32(messagePointer + 20); + const u32 input = mem.read32(messagePointer + 28); + const u32 output = mem.read32(messagePointer + 36); + + log("FS::ControlArchive (action = %X, handle = %X)\n", action, archiveHandle); + + auto archiveObject = kernel.getObject(archiveHandle, KernelObjectType::Archive); + mem.write32(messagePointer, IPC::responseHeader(0x80D, 1, 0)); + if (archiveObject == nullptr) [[unlikely]] { + log("FS::ControlArchive: Invalid archive handle %d\n", archiveHandle); + mem.write32(messagePointer + 4, ResultCode::Failure); + return; + } + + switch (action) { + case 0: // Commit save data changes. Shouldn't need us to do anything + mem.write32(messagePointer + 4, ResultCode::Success); + break; + default: + Helpers::panic("Unimplemented action for ControlArchive (action = %X)\n", action); + break; + } } void FSService::getPriority(u32 messagePointer) { log("FS::GetPriority\n"); + mem.write32(messagePointer, IPC::responseHeader(0x863, 2, 0)); mem.write32(messagePointer + 4, ResultCode::Success); mem.write32(messagePointer + 8, priority); } @@ -412,13 +457,15 @@ void FSService::getPriority(u32 messagePointer) { void FSService::setPriority(u32 messagePointer) { const u32 value = mem.read32(messagePointer + 4); log("FS::SetPriority (priority = %d)\n", value); - + + mem.write32(messagePointer, IPC::responseHeader(0x862, 1, 0)); mem.write32(messagePointer + 4, ResultCode::Success); priority = value; } void FSService::isSdmcDetected(u32 messagePointer) { log("FS::IsSdmcDetected\n"); + mem.write32(messagePointer, IPC::responseHeader(0x817, 2, 0)); mem.write32(messagePointer + 4, ResultCode::Success); mem.write32(messagePointer + 8, 0); // Whether SD is detected. For now we emulate a 3DS without an SD. } \ No newline at end of file diff --git a/src/core/services/gsp_gpu.cpp b/src/core/services/gsp_gpu.cpp index b12adff1..44d6cfee 100644 --- a/src/core/services/gsp_gpu.cpp +++ b/src/core/services/gsp_gpu.cpp @@ -1,4 +1,6 @@ #include "services/gsp_gpu.hpp" +#include "ipc.hpp" +#include "kernel.hpp" // Commands used with SendSyncRequest targetted to the GSP::GPU service namespace ServiceCommands { @@ -37,6 +39,7 @@ namespace Result { void GPUService::reset() { privilegedProcess = 0xFFFFFFFF; // Set the privileged process to an invalid handle + interruptEvent = std::nullopt; sharedMem = nullptr; } @@ -72,6 +75,7 @@ void GPUService::acquireRight(u32 messagePointer) { privilegedProcess = pid; } + mem.write32(messagePointer, IPC::responseHeader(0x16, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -88,7 +92,15 @@ void GPUService::registerInterruptRelayQueue(u32 messagePointer) { const u32 eventHandle = mem.read32(messagePointer + 12); log("GSP::GPU::RegisterInterruptRelayQueue (flags = %X, event handle = %X)\n", flags, eventHandle); - mem.write32(messagePointer + 4, Result::SuccessRegisterIRQ); + const auto event = kernel.getObject(eventHandle, KernelObjectType::Event); + if (event == nullptr) { // Check if interrupt event is invalid + Helpers::panic("Invalid event passed to GSP::GPU::RegisterInterruptRelayQueue"); + } else { + interruptEvent = eventHandle; + } + + mem.write32(messagePointer, IPC::responseHeader(0x13, 2, 2)); + mem.write32(messagePointer + 4, Result::SuccessRegisterIRQ); // First init returns a unique result mem.write32(messagePointer + 8, 0); // TODO: GSP module thread index mem.write32(messagePointer + 12, 0); // Translation descriptor mem.write32(messagePointer + 16, KernelHandles::GSPSharedMemHandle); @@ -106,6 +118,11 @@ void GPUService::requestInterrupt(GPUInterrupt type) { sharedMem[2] = 0; // Set error code to 0 sharedMem[0xC + flagIndex] = static_cast(type); // Write interrupt type to queue + + // Signal interrupt event + if (interruptEvent.has_value()) { + kernel.signalEvent(interruptEvent.value()); + } } void GPUService::writeHwRegs(u32 messagePointer) { @@ -134,6 +151,8 @@ void GPUService::writeHwRegs(u32 messagePointer) { dataPointer += 4; ioAddr += 4; } + + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -176,6 +195,7 @@ void GPUService::writeHwRegsWithMask(u32 messagePointer) { ioAddr += 4; } + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -185,6 +205,7 @@ void GPUService::flushDataCache(u32 messagePointer) { u32 processHandle = handle = mem.read32(messagePointer + 16); log("GSP::GPU::FlushDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle); + mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -194,6 +215,7 @@ void GPUService::storeDataCache(u32 messagePointer) { u32 processHandle = handle = mem.read32(messagePointer + 16); log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle); + mem.write32(messagePointer, IPC::responseHeader(0x1F, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -205,23 +227,27 @@ void GPUService::setLCDForceBlack(u32 messagePointer) { printf("Filled both LCDs with black\n"); } + mem.write32(messagePointer, IPC::responseHeader(0xB, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void GPUService::triggerCmdReqQueue(u32 messagePointer) { processCommandBuffer(); + mem.write32(messagePointer, IPC::responseHeader(0xC, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } // Seems to be completely undocumented, probably not very important or useful void GPUService::setAxiConfigQoSMode(u32 messagePointer) { log("GSP::GPU::SetAxiConfigQoSMode\n"); + mem.write32(messagePointer, IPC::responseHeader(0x10, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } // Seems to also be completely undocumented void GPUService::setInternalPriorities(u32 messagePointer) { log("GSP::GPU::SetInternalPriorities\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1E, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } diff --git a/src/core/services/gsp_lcd.cpp b/src/core/services/gsp_lcd.cpp index 21764b31..ff438b56 100644 --- a/src/core/services/gsp_lcd.cpp +++ b/src/core/services/gsp_lcd.cpp @@ -1,4 +1,5 @@ #include "services/gsp_lcd.hpp" +#include "ipc.hpp" namespace LCDCommands { enum : u32 { diff --git a/src/core/services/hid.cpp b/src/core/services/hid.cpp index 27661954..e3056f70 100644 --- a/src/core/services/hid.cpp +++ b/src/core/services/hid.cpp @@ -1,4 +1,5 @@ #include "services/hid.hpp" +#include "ipc.hpp" #include namespace HIDCommands { @@ -38,20 +39,25 @@ void HIDService::handleSyncRequest(u32 messagePointer) { void HIDService::enableAccelerometer(u32 messagePointer) { log("HID::EnableAccelerometer\n"); - mem.write32(messagePointer + 4, Result::Success); accelerometerEnabled = true; + + mem.write32(messagePointer, IPC::responseHeader(0x11, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } void HIDService::enableGyroscopeLow(u32 messagePointer) { log("HID::EnableGyroscopeLow\n"); - mem.write32(messagePointer + 4, Result::Success); gyroEnabled = true; + + mem.write32(messagePointer, IPC::responseHeader(0x13, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } void HIDService::getGyroscopeLowCalibrateParam(u32 messagePointer) { log("HID::GetGyroscopeLowCalibrateParam\n"); constexpr s16 unit = 6700; // Approximately from Citra which took it from hardware + mem.write32(messagePointer, IPC::responseHeader(0x16, 6, 0)); mem.write32(messagePointer + 4, Result::Success); // Fill calibration data (for x/y/z depending on i) for (int i = 0; i < 3; i++) { @@ -67,12 +73,14 @@ void HIDService::getGyroscopeCoefficient(u32 messagePointer) { log("HID::GetGyroscopeLowRawToDpsCoefficient\n"); constexpr float gyroscopeCoeff = 14.375f; // Same as retail 3DS + mem.write32(messagePointer, IPC::responseHeader(0x15, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, std::bit_cast(gyroscopeCoeff)); } void HIDService::getIPCHandles(u32 messagePointer) { log("HID::GetIPCHandles\n"); + mem.write32(messagePointer, IPC::responseHeader(0xA, 1, 7)); mem.write32(messagePointer + 4, Result::Success); // Result code mem.write32(messagePointer + 8, 0x14000000); // Translation descriptor mem.write32(messagePointer + 12, KernelHandles::HIDSharedMemHandle); // Shared memory handle diff --git a/src/core/services/ldr_ro.cpp b/src/core/services/ldr_ro.cpp index 9d5d19a8..91ae041d 100644 --- a/src/core/services/ldr_ro.cpp +++ b/src/core/services/ldr_ro.cpp @@ -1,4 +1,5 @@ #include "services/ldr_ro.hpp" +#include "ipc.hpp" namespace LDRCommands { enum : u32 { @@ -31,6 +32,7 @@ void LDRService::initialize(u32 messagePointer) { const Handle process = mem.read32(messagePointer + 20); log("LDR_RO::Initialize (buffer = %08X, size = %08X, vaddr = %08X, process = %X)\n", crsPointer, size, mapVaddr, process); + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -40,5 +42,6 @@ void LDRService::loadCRR(u32 messagePointer) { const Handle process = mem.read32(messagePointer + 20); log("LDR_RO::LoadCRR (buffer = %08X, size = %08X, process = %X)\n", crrPointer, size, process); + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/mic.cpp b/src/core/services/mic.cpp index b69c6b7a..828fb1e4 100644 --- a/src/core/services/mic.cpp +++ b/src/core/services/mic.cpp @@ -1,4 +1,5 @@ #include "services/mic.hpp" +#include "ipc.hpp" namespace MICCommands { enum : u32 { @@ -41,11 +42,13 @@ void MICService::mapSharedMem(u32 messagePointer) { u32 handle = mem.read32(messagePointer + 12); log("MIC::MapSharedMem (size = %08X, handle = %X) (stubbed)\n", size, handle); + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void MICService::getGain(u32 messagePointer) { log("MIC::GetGain\n"); + mem.write32(messagePointer, IPC::responseHeader(0x9, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write8(messagePointer + 8, gain); } @@ -54,6 +57,7 @@ void MICService::setGain(u32 messagePointer) { gain = mem.read8(messagePointer + 4); log("MIC::SetGain (value = %d)\n", gain); + mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -62,6 +66,7 @@ void MICService::setPower(u32 messagePointer) { log("MIC::SetPower (value = %d)\n", val); micEnabled = val != 0; + mem.write32(messagePointer, IPC::responseHeader(0xA, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -70,6 +75,7 @@ void MICService::setClamp(u32 messagePointer) { log("MIC::SetClamp (value = %d)\n", val); shouldClamp = val != 0; + mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -84,5 +90,6 @@ void MICService::startSampling(u32 messagePointer) { encoding, sampleRate, offset, dataSize, loop ? "yes" : "no" ); + mem.write32(messagePointer, IPC::responseHeader(0x3, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/ndm.cpp b/src/core/services/ndm.cpp index d6c18afe..a9ea9140 100644 --- a/src/core/services/ndm.cpp +++ b/src/core/services/ndm.cpp @@ -1,4 +1,5 @@ #include "services/ndm.hpp" +#include "ipc.hpp" namespace NDMCommands { enum : u32 { @@ -32,25 +33,30 @@ void NDMService::handleSyncRequest(u32 messagePointer) { void NDMService::overrideDefaultDaemons(u32 messagePointer) { log("NDM::OverrideDefaultDaemons(stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x14, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void NDMService::resumeDaemons(u32 messagePointer) { log("NDM::resumeDaemons(stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x7, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void NDMService::suspendDaemons(u32 messagePointer) { log("NDM::SuspendDaemons(stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x6, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void NDMService::resumeScheduler(u32 messagePointer) { log("NDM::ResumeScheduler(stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } void NDMService::suspendScheduler(u32 messagePointer) { log("NDM::SuspendScheduler(stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x8, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/nim.cpp b/src/core/services/nim.cpp index a3c48fb4..df9737f6 100644 --- a/src/core/services/nim.cpp +++ b/src/core/services/nim.cpp @@ -1,4 +1,5 @@ #include "services/nim.hpp" +#include "ipc.hpp" namespace NIMCommands { enum : u32 { @@ -24,5 +25,6 @@ void NIMService::handleSyncRequest(u32 messagePointer) { void NIMService::initialize(u32 messagePointer) { log("NIM::Initialize\n"); + mem.write32(messagePointer, IPC::responseHeader(0x21, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/ptm.cpp b/src/core/services/ptm.cpp index 297f5455..fc2d9e93 100644 --- a/src/core/services/ptm.cpp +++ b/src/core/services/ptm.cpp @@ -1,9 +1,11 @@ #include "services/ptm.hpp" +#include "ipc.hpp" namespace PTMCommands { enum : u32 { GetStepHistory = 0x000B00C2, - GetTotalStepCount = 0x000C0000 + GetTotalStepCount = 0x000C0000, + ConfigureNew3DSCPU = 0x08180040 }; } @@ -18,7 +20,8 @@ void PTMService::reset() {} void PTMService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { - case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break; + case PTMCommands::ConfigureNew3DSCPU: configureNew3DSCPU(messagePointer); break; + case PTMCommands::GetStepHistory: getStepHistory(messagePointer); break; case PTMCommands::GetTotalStepCount: getTotalStepCount(messagePointer); break; default: Helpers::panic("PTM service requested. Command: %08X\n", command); } @@ -26,11 +29,19 @@ void PTMService::handleSyncRequest(u32 messagePointer) { void PTMService::getStepHistory(u32 messagePointer) { log("PTM::GetStepHistory [stubbed]\n"); + mem.write32(messagePointer, IPC::responseHeader(0xB, 1, 2)); mem.write32(messagePointer + 4, Result::Success); } void PTMService::getTotalStepCount(u32 messagePointer) { log("PTM::GetTotalStepCount\n"); + mem.write32(messagePointer, IPC::responseHeader(0xC, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 3); // We walk a lot +} + +void PTMService::configureNew3DSCPU(u32 messagePointer) { + log("PTM::ConfigureNew3DSCPU [stubbed]\n"); + mem.write32(messagePointer, IPC::responseHeader(0x818, 1, 0)); + mem.write32(messagePointer + 4, Result::Success); } \ No newline at end of file diff --git a/src/core/services/service_manager.cpp b/src/core/services/service_manager.cpp index 839ff82d..64b7be5d 100644 --- a/src/core/services/service_manager.cpp +++ b/src/core/services/service_manager.cpp @@ -1,11 +1,12 @@ #include "services/service_manager.hpp" #include +#include "ipc.hpp" #include "kernel.hpp" ServiceManager::ServiceManager(std::array& regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel) : regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), apt(mem, kernel), cam(mem), cecd(mem), cfg(mem), - dsp(mem), hid(mem), frd(mem), fs(mem, kernel), gsp_gpu(mem, gpu, currentPID), gsp_lcd(mem), ldr(mem), mic(mem), - nim(mem), ndm(mem), ptm(mem), y2r(mem) {} + dsp(mem, kernel), hid(mem), frd(mem), fs(mem, kernel), gsp_gpu(mem, gpu, kernel, currentPID), gsp_lcd(mem), ldr(mem), + mic(mem), nim(mem), ndm(mem), ptm(mem), y2r(mem, kernel) {} static constexpr int MAX_NOTIFICATION_COUNT = 16; @@ -79,6 +80,7 @@ void ServiceManager::handleSyncRequest(u32 messagePointer) { // https://www.3dbrew.org/wiki/SRV:RegisterClient void ServiceManager::registerClient(u32 messagePointer) { log("srv::registerClient (Stubbed)\n"); + mem.write32(messagePointer, IPC::responseHeader(0x1, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } @@ -102,7 +104,8 @@ static std::map serviceMap = { { "mic:u", KernelHandles::MIC }, { "ndm:u", KernelHandles::NDM }, { "nim:aoc", KernelHandles::NIM }, - { "ptm:u", KernelHandles::PTM }, + { "ptm:u", KernelHandles::PTM }, // TODO: ptm:u and ptm:sysm have very different command sets + { "ptm:sysm", KernelHandles::PTM }, { "y2r:u", KernelHandles::Y2R } }; @@ -121,6 +124,7 @@ void ServiceManager::getServiceHandle(u32 messagePointer) { else Helpers::panic("srv: GetServiceHandle with unknown service %s", service.c_str()); + mem.write32(messagePointer, IPC::responseHeader(0x5, 1, 2)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 12, handle); } @@ -133,6 +137,7 @@ void ServiceManager::enableNotification(u32 messagePointer) { notificationSemaphore = kernel.makeSemaphore(0, MAX_NOTIFICATION_COUNT); } + mem.write32(messagePointer, IPC::responseHeader(0x2, 1, 2)); mem.write32(messagePointer + 4, Result::Success); // Result code mem.write32(messagePointer + 8, 0); // Translation descriptor // Handle to semaphore signaled on process notification @@ -142,6 +147,7 @@ void ServiceManager::enableNotification(u32 messagePointer) { void ServiceManager::receiveNotification(u32 messagePointer) { log("srv::ReceiveNotification() (STUBBED)\n"); + mem.write32(messagePointer, IPC::responseHeader(0xB, 2, 0)); mem.write32(messagePointer + 4, Result::Success); // Result code mem.write32(messagePointer + 8, 0); // Notification ID } @@ -150,6 +156,7 @@ void ServiceManager::subscribe(u32 messagePointer) { u32 id = mem.read32(messagePointer + 4); log("srv::Subscribe (id = %d) (stubbed)\n", id); + mem.write32(messagePointer, IPC::responseHeader(0x9, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } diff --git a/src/core/services/y2r.cpp b/src/core/services/y2r.cpp index 1c932f03..3cbd7137 100644 --- a/src/core/services/y2r.cpp +++ b/src/core/services/y2r.cpp @@ -1,8 +1,11 @@ #include "services/y2r.hpp" +#include "ipc.hpp" +#include "kernel.hpp" namespace Y2RCommands { enum : u32 { SetTransferEndInterrupt = 0x000D0040, + GetTransferEndEvent = 0x000F0000, PingProcess = 0x002A0000, DriverInitialize = 0x002B0000 }; @@ -16,12 +19,14 @@ namespace Result { void Y2RService::reset() { transferEndInterruptEnabled = false; + transferEndEvent = std::nullopt; } void Y2RService::handleSyncRequest(u32 messagePointer) { const u32 command = mem.read32(messagePointer); switch (command) { case Y2RCommands::DriverInitialize: driverInitialize(messagePointer); break; + case Y2RCommands::GetTransferEndEvent: getTransferEndEvent(messagePointer); break; case Y2RCommands::PingProcess: pingProcess(messagePointer); break; case Y2RCommands::SetTransferEndInterrupt: setTransferEndInterrupt(messagePointer); break; default: Helpers::panic("Y2R service requested. Command: %08X\n", command); @@ -30,19 +35,32 @@ void Y2RService::handleSyncRequest(u32 messagePointer) { void Y2RService::pingProcess(u32 messagePointer) { log("Y2R::PingProcess\n"); + mem.write32(messagePointer, IPC::responseHeader(0x2A, 2, 0)); mem.write32(messagePointer + 4, Result::Success); mem.write32(messagePointer + 8, 0); // Connected number } void Y2RService::driverInitialize(u32 messagePointer) { log("Y2R::DriverInitialize\n"); + mem.write32(messagePointer, IPC::responseHeader(0x2B, 1, 0)); mem.write32(messagePointer + 4, Result::Success); } +void Y2RService::getTransferEndEvent(u32 messagePointer) { + log("Y2R::GetTransferEndEvent\n"); + if (!transferEndEvent.has_value()) + transferEndEvent = kernel.makeEvent(ResetType::OneShot); + + mem.write32(messagePointer, IPC::responseHeader(0xF, 1, 2)); + mem.write32(messagePointer + 4, Result::Success); + mem.write32(messagePointer + 12, transferEndEvent.value()); +} + void Y2RService::setTransferEndInterrupt(u32 messagePointer) { const bool enable = mem.read32(messagePointer + 4) != 0; log("Y2R::SetTransferEndInterrupt (enabled: %s)\n", enable ? "yes" : "no"); + mem.write32(messagePointer, IPC::responseHeader(0xD, 1, 0)); mem.write32(messagePointer + 4, Result::Success); transferEndInterruptEnabled = enable; } \ No newline at end of file diff --git a/tests/ImmediateModeTriangles/Makefile b/tests/ImmediateModeTriangles/Makefile new file mode 100644 index 00000000..46a94048 --- /dev/null +++ b/tests/ImmediateModeTriangles/Makefile @@ -0,0 +1,255 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# GRAPHICS is a list of directories containing graphics files +# GFXBUILD is the directory where converted graphics files will be placed +# If set to $(BUILD), it will statically link in the converted +# files as if they were data files. +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +GRAPHICS := gfx +GFXBUILD := $(BUILD) +#ROMFS := romfs +#GFXBUILD := $(ROMFS)/gfx + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -ffunction-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -D__3DS__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcitro3d -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +#--------------------------------------------------------------------------------- +ifeq ($(GFXBUILD),$(BUILD)) +#--------------------------------------------------------------------------------- +export T3XFILES := $(GFXFILES:.t3s=.t3x) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- +export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES)) +export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES)) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(addsuffix .o,$(T3XFILES)) + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ + $(addsuffix .h,$(subst .,_,$(BINFILES))) \ + $(GFXFILES:.t3s=.h) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +ifneq ($(ROMFS),) + export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) +endif + +.PHONY: all clean + +#--------------------------------------------------------------------------------- +all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +$(BUILD): + @mkdir -p $@ + +ifneq ($(GFXBUILD),$(BUILD)) +$(GFXBUILD): + @mkdir -p $@ +endif + +ifneq ($(DEPSDIR),$(BUILD)) +$(DEPSDIR): + @mkdir -p $@ +endif + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(GFXBUILD) + +#--------------------------------------------------------------------------------- +$(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).3dsx : $(OUTPUT).elf $(_3DSXDEPS) + +$(OFILES_SOURCES) : $(HFILES) + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +.PRECIOUS : %.t3x +#--------------------------------------------------------------------------------- +%.t3x.o %_t3x.h : %.t3x +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $*.shbin) + $(eval DEPSFILE := $(DEPSDIR)/$*.shbin.d) + echo "$(CURBIN).o: $< $1" > $(DEPSFILE) + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $*.shbin.o +endef + +%.shbin.o %_shbin.h : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o %_shbin.h : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o %_shbin.h : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)$(file))) + +#--------------------------------------------------------------------------------- +%.t3x %.h : %.t3s +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @tex3ds -i $< -H $*.h -d $*.d -o $*.t3x + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/tests/ImmediateModeTriangles/source/main.c b/tests/ImmediateModeTriangles/source/main.c new file mode 100644 index 00000000..4eda609f --- /dev/null +++ b/tests/ImmediateModeTriangles/source/main.c @@ -0,0 +1,121 @@ +#include <3ds.h> +#include +#include +#include "vshader_shbin.h" + +#define CLEAR_COLOR 0x68B0D8FF + +#define DISPLAY_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | \ + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +static DVLB_s* vshader_dvlb; +static shaderProgram_s program; +static int uLoc_projection; +static C3D_Mtx projection; + +static void sceneInit(void) +{ + // Load the vertex shader, create a shader program and bind it + vshader_dvlb = DVLB_ParseFile((u32*)vshader_shbin, vshader_shbin_size); + shaderProgramInit(&program); + shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]); + C3D_BindProgram(&program); + + // Get the location of the uniforms + uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection"); + + // Configure attributes for use with the vertex shader + // Attribute format and element count are ignored in immediate mode + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v1=color + + // Compute the projection matrix + Mtx_OrthoTilt(&projection, 0.0, 400.0, 0.0, 240.0, 0.0, 1.0, true); + + // Configure the first fragment shading substage to just pass through the vertex color + // See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, 0, 0); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); +} + +static void sceneRender(void) +{ + // Update the uniforms + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + + // Draw the triangle directly + C3D_ImmDrawBegin(GPU_TRIANGLES); + // Triangle 1 + C3D_ImmSendAttrib(200.0f, 200.0f, 0.5f, 0.0f); // v0=position + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); // v1=color + + C3D_ImmSendAttrib(100.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 1.0f, 0.0f, 1.0f); + + C3D_ImmSendAttrib(300.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 1.0f); + + // Triangle 2 + C3D_ImmSendAttrib(10.0f, 20.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); + + C3D_ImmSendAttrib(90.0f, 20.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); + + C3D_ImmSendAttrib(40.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); + C3D_ImmDrawEnd(); +} + +static void sceneExit(void) +{ + // Free the shader program + shaderProgramFree(&program); + DVLB_Free(vshader_dvlb); +} + +int main() +{ + // Initialize graphics + gfxInitDefault(); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + // Initialize the render target + C3D_RenderTarget* target = C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput(target, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); + + // Initialize the scene + sceneInit(); + + // Main loop + while (aptMainLoop()) + { + hidScanInput(); + + // Respond to user input + u32 kDown = hidKeysDown(); + if (kDown & KEY_START) + break; // break in order to return to hbmenu + + // Render the scene + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C3D_RenderTargetClear(target, C3D_CLEAR_ALL, CLEAR_COLOR, 0); + C3D_FrameDrawOn(target); + sceneRender(); + C3D_FrameEnd(0); + } + + // Deinitialize the scene + sceneExit(); + + // Deinitialize graphics + C3D_Fini(); + gfxExit(); + return 0; +} diff --git a/tests/ImmediateModeTriangles/source/vshader.v.pica b/tests/ImmediateModeTriangles/source/vshader.v.pica new file mode 100644 index 00000000..ca9498cb --- /dev/null +++ b/tests/ImmediateModeTriangles/source/vshader.v.pica @@ -0,0 +1,38 @@ +; Example PICA200 vertex shader + +; Uniforms +.fvec projection[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.1) +.constf myconst2(0.3, 0.0, 0.0, 0.0) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones + +; Outputs +.out outpos position +.out outclr color + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias inclr v1 + +.bool test + +.proc main + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, ones + + ; outpos = projectionMatrix * inpos + dp4 outpos.x, projection[0], r0 + dp4 outpos.y, projection[1], r0 + dp4 outpos.z, projection[2], r0 + dp4 outpos.w, projection[3], r0 + + ; outclr = inclr + mov outclr, inclr + + ; We're finished + end +.end diff --git a/third_party/result/include/result.hpp b/third_party/result/include/result.hpp index 214fe777..b75dee90 100644 --- a/third_party/result/include/result.hpp +++ b/third_party/result/include/result.hpp @@ -506,7 +506,7 @@ Ret map(const Rust::Result& result, Func func) { template::type >::type