mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-07 14:45:41 +12:00
Merge branch 'Sync-Objects' of https://github.com/wheremyfoodat/Virtual3DS into Sync-Objects
This commit is contained in:
commit
8cfb038226
49 changed files with 1256 additions and 138 deletions
|
@ -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
|
||||
|
|
|
@ -81,6 +81,7 @@ namespace PICAInternalRegs {
|
|||
CmdBufTrigger1 = 0x23D,
|
||||
|
||||
PrimitiveConfig = 0x25E,
|
||||
PrimitiveRestart = 0x25F,
|
||||
|
||||
// Vertex shader registers
|
||||
VertexShaderAttrNum = 0x242,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
9
include/ipc.hpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ namespace ConfigMem {
|
|||
NetworkState = 0x1FF81067,
|
||||
LedState3D = 0x1FF81084,
|
||||
BatteryState = 0x1FF81085,
|
||||
Unknown1086 = 0x1FF81086,
|
||||
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
|
|
|
@ -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)'
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -25,6 +25,7 @@ Handle Kernel::makeArbiter() {
|
|||
|
||||
// Result CreateAddressArbiter(Handle* arbiter)
|
||||
void Kernel::createAddressArbiter() {
|
||||
logSVC("CreateAddressArbiter\n");
|
||||
regs[0] = SVCResult::Success;
|
||||
regs[1] = makeArbiter();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
70
src/core/kernel/idle_thread.cpp
Normal file
70
src/core/kernel/idle_thread.cpp
Normal 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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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.
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "services/gsp_lcd.hpp"
|
||||
#include "ipc.hpp"
|
||||
|
||||
namespace LCDCommands {
|
||||
enum : u32 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
255
tests/ImmediateModeTriangles/Makefile
Normal file
255
tests/ImmediateModeTriangles/Makefile
Normal 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
|
||||
#---------------------------------------------------------------------------------------
|
121
tests/ImmediateModeTriangles/source/main.c
Normal file
121
tests/ImmediateModeTriangles/source/main.c
Normal 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;
|
||||
}
|
38
tests/ImmediateModeTriangles/source/vshader.v.pica
Normal file
38
tests/ImmediateModeTriangles/source/vshader.v.pica
Normal 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
|
2
third_party/result/include/result.hpp
vendored
2
third_party/result/include/result.hpp
vendored
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue