Merge branch 'Sync-Objects' of https://github.com/wheremyfoodat/Virtual3DS into Sync-Objects

This commit is contained in:
wheremyfoodat 2023-04-23 21:32:10 +03:00
commit 8cfb038226
49 changed files with 1256 additions and 138 deletions

View file

@ -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

View file

@ -81,6 +81,7 @@ namespace PICAInternalRegs {
CmdBufTrigger1 = 0x23D,
PrimitiveConfig = 0x25E,
PrimitiveRestart = 0x25F,
// Vertex shader registers
VertexShaderAttrNum = 0x242,

View file

@ -1,8 +1,9 @@
#pragma once
#include <cstdint>
// 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

View file

@ -18,6 +18,12 @@
#define _CRT_SECURE_NO_WARNINGS
#endif
#ifdef WIN32
#include <io.h> // For _chsize_s
#else
#include <unistd.h> // 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; }
};

9
include/ipc.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
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;
}
}

View file

@ -14,6 +14,7 @@ namespace ConfigMem {
NetworkState = 0x1FF81067,
LedState3D = 0x1FF81084,
BatteryState = 0x1FF81085,
Unknown1086 = 0x1FF81086,
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
};

View file

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cassert>
#include <limits>
#include <string>
#include <vector>
@ -19,7 +20,16 @@ class Kernel {
// The handle number for the next kernel object to be created
u32 handleCounter;
std::array<Thread, appResourceLimits.maxThreads> 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<Thread, appResourceLimits.maxThreads + 1> 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<KernelObject> objects;
std::vector<Handle> 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<Handle> 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

View file

@ -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<T*>(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<Event>()->waitlist;
case KernelObjectType::Mutex: return getData<Mutex>()->waitlist;
case KernelObjectType::Semaphore: return getData<Mutex>()->waitlist;
case KernelObjectType::Thread: return getData<Thread>()->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());
}
}
};

View file

@ -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<FCRAM_PAGE_COUNT> usedFCRAMPages;
std::optional<u32> findPaddr(u32 size);
u64 timeSince3DSEpoch();

View file

@ -39,7 +39,7 @@ class Renderer {
SurfaceCache<DepthBuffer, 10> depthBufferCache;
SurfaceCache<ColourBuffer, 10> colourBufferCache;
SurfaceCache<Texture, 16> textureCache;
SurfaceCache<Texture, 256> textureCache;
OpenGL::uvec2 fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'

View file

@ -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);

View file

@ -1,4 +1,6 @@
#pragma once
#include <array>
#include <optional>
#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<Handle>;
DSPEvent semaphoreEvent;
DSPEvent interrupt0;
DSPEvent interrupt1;
std::array<DSPEvent, pipeCount> 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);

View file

@ -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);

View file

@ -1,5 +1,6 @@
#pragma once
#include <cstring>
#include <optional>
#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<Handle> 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);

View file

@ -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);

View file

@ -1,23 +1,30 @@
#pragma once
#include <optional>
#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<Handle> 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);
};

View file

@ -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

View file

@ -25,6 +25,7 @@ Handle Kernel::makeArbiter() {
// Result CreateAddressArbiter(Handle* arbiter)
void Kernel::createAddressArbiter() {
logSVC("CreateAddressArbiter\n");
regs[0] = SVCResult::Success;
regs[1] = makeArbiter();
}

View file

@ -1,5 +1,7 @@
#include "kernel.hpp"
#include "cpu.hpp"
#include <bit>
#include <utility>
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>();
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<Event>();
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<Handle, KernelObject*>;
std::vector<WaitObject> 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");
}
}

View file

@ -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<FileSession>();
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);

View file

@ -0,0 +1,70 @@
#include <cstring>
#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();
}

View file

@ -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

View file

@ -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<Mutex>()->ownerThread = currentThreadIndex;
Mutex* moo = objects[ret].getData<Mutex>();
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<Event>();
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<Mutex>();
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<int> 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<Mutex>();
// 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<Event>()->fired;
case KernelObjectType::Mutex: {
Mutex* moo = object->getData<Mutex>(); // 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<Thread>()->status != ThreadStatus::Dead;
case KernelObjectType::Semaphore: // Wait if the semaphore count <= 0
return object->getData<Semaphore>()->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;
}
}

View file

@ -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);
}
}

View file

@ -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]);

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<u32>(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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<u32>(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);
}

View file

@ -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);
}

View file

@ -1,5 +1,6 @@
#include "services/frd.hpp"
#include <string>
#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);
}

View file

@ -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> 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> 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> 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<u32>(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<u32>(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.
}

View file

@ -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<u8>(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);
}

View file

@ -1,4 +1,5 @@
#include "services/gsp_lcd.hpp"
#include "ipc.hpp"
namespace LCDCommands {
enum : u32 {

View file

@ -1,4 +1,5 @@
#include "services/hid.hpp"
#include "ipc.hpp"
#include <bit>
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<u32, float>(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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -1,11 +1,12 @@
#include "services/service_manager.hpp"
#include <map>
#include "ipc.hpp"
#include "kernel.hpp"
ServiceManager::ServiceManager(std::array<u32, 16>& 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<std::string, Handle> 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);
}

View file

@ -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;
}

View file

@ -0,0 +1,255 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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):
# - <Project name>.png
# - icon.png
# - <libctru folder>/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
#---------------------------------------------------------------------------------------

View file

@ -0,0 +1,121 @@
#include <3ds.h>
#include <citro3d.h>
#include <string.h>
#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;
}

View file

@ -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

View file

@ -506,7 +506,7 @@ Ret map(const Rust::Result<T, E>& result, Func func) {
template<typename T, typename E, typename Func,
typename Ret =
Result<T,
Rust::Result<T,
typename details::ResultErrType<
typename details::result_of<Func>::type
>::type