From 09750e64c81fa2c6fbcebeb3e0059790c47c0eb3 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 4 Mar 2025 18:35:21 +0100 Subject: [PATCH] First commit --- README.md | 26 ++ includes/ArticBaseCommon.hpp | 80 ++++ includes/ArticBaseFunctions.hpp | 33 ++ includes/ArticBaseServer.hpp | 155 ++++++++ includes/Logger.hpp | 47 +++ sources/ArticBaseServer.cpp | 639 ++++++++++++++++++++++++++++++++ sources/Logger.cpp | 181 +++++++++ 7 files changed, 1161 insertions(+) create mode 100644 README.md create mode 100644 includes/ArticBaseCommon.hpp create mode 100644 includes/ArticBaseFunctions.hpp create mode 100644 includes/ArticBaseServer.hpp create mode 100644 includes/Logger.hpp create mode 100644 sources/ArticBaseServer.cpp create mode 100644 sources/Logger.cpp diff --git a/README.md b/README.md new file mode 100644 index 0000000..579c8b5 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Artic Protocol + +The inner prototol used by [Artic Base Server](https://github.com/PabloMK7/ArticBaseServer). Can be used as a submodule in projects implementing the artic protocol. + +## License +MIT License + +Copyright (c) 2024 PabloMK7 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/includes/ArticBaseCommon.hpp b/includes/ArticBaseCommon.hpp new file mode 100644 index 0000000..fc11e45 --- /dev/null +++ b/includes/ArticBaseCommon.hpp @@ -0,0 +1,80 @@ +#pragma once +#include <3ds/types.h> +#include + +namespace ArticBaseCommon { + enum class MethodState : int { + PARSING_INPUT = 0, + PARAMETER_TYPE_MISMATCH = 1, + PARAMETER_COUNT_MISMATCH = 2, + BIG_BUFFER_READ_FAIL = 3, + BIG_BUFFER_WRITE_FAIL = 4, + OUT_OF_MEMORY = 5, + + GENERATING_OUTPUT = 6, + UNEXPECTED_PARSING_INPUT = 7, + OUT_OF_MEMORY_OUTPUT = 8, + + INTERNAL_METHOD_ERROR = 9, + FINISHED = 10, + }; + enum class RequestParameterType : u16 { + IN_INTEGER_8 = 0, + IN_INTEGER_16 = 1, + IN_INTEGER_32 = 2, + IN_INTEGER_64 = 3, + IN_SMALL_BUFFER = 4, + IN_BIG_BUFFER = 5, + }; + struct RequestParameter { + RequestParameterType type; + union { + u16 parameterSize; + u16 bigBufferID; + }; + + char data[0x1C]; + }; + struct RequestPacket { + u32 requestID; + std::array method; + u32 parameterCount; + }; + static_assert(sizeof(RequestPacket) == 0x28); + + struct Buffer { + u32 bufferID; + u32 bufferSize; + + char data[]; + }; + + struct ResponseMethod { + enum class ArticResult : u32 { + SUCCESS = 0, + METHOD_NOT_FOUND = 1, + METHOD_ERROR = 2, + PROVIDE_INPUT = 3, + }; + ArticResult articResult{}; + union { + int methodResult{}; + int provideInputBufferID; + }; + int bufferSize{}; + u8 padding[0x10]{}; + }; + + struct DataPacket { + DataPacket() {} + u32 requestID{}; + + union { + char dataRaw[0x1C]{}; + ResponseMethod resp; + }; + }; + + static_assert(sizeof(DataPacket) == 0x20); +}; + diff --git a/includes/ArticBaseFunctions.hpp b/includes/ArticBaseFunctions.hpp new file mode 100644 index 0000000..1356407 --- /dev/null +++ b/includes/ArticBaseFunctions.hpp @@ -0,0 +1,33 @@ +#pragma once +#include "3ds.h" +#include "map" +#include "ArticBaseCommon.hpp" +#include "ArticBaseServer.hpp" +#include "memory.h" +#include "string" + +namespace ArticBaseFunctions { + // All method handlers should be defined here + extern std::map functionHandlers; + + // All setup functions should be defined here + extern std::vector setupFunctions; + + // All destruct functions should be defined here + extern std::vector destructFunctions; + + enum class HandleType { + FILE, + DIR, + ARCHIVE + }; + + // Controller_Start + namespace ArticController { + extern Thread thread; + extern bool thread_run; + extern int socket_fd; + extern volatile bool socket_ready; + void Handler(void* arg); + }; +}; \ No newline at end of file diff --git a/includes/ArticBaseServer.hpp b/includes/ArticBaseServer.hpp new file mode 100644 index 0000000..d11884f --- /dev/null +++ b/includes/ArticBaseServer.hpp @@ -0,0 +1,155 @@ +#pragma once +#include "Main.hpp" +#include "ArticBaseCommon.hpp" +#include "queue" +#include +#include "CTRPluginFramework/System/Mutex.hpp" + +class ArticBaseServer { +public: + ArticBaseServer(int socket_fd); + ~ArticBaseServer(); + + void Serve(); + void QueryStop(); + + static bool SetNonBlock(int sockFD, bool nonBlocking); + static bool Read(int& sockFD, void* buffer, size_t size); + static bool Write(int& sockFD, void* buffer, size_t size); + static size_t RecvFrom(int& sockFD, void* buffer, size_t size, void* addr, void* addr_size); + static size_t SendTo(int& sockFD, void* buffer, size_t size, void* addr, void* addr_size); +private: + void Stop(); + static constexpr size_t MAX_WORK_BUF_SIZE = 4 * 1024 * 1024 + 512 * 1024; // 4.5MB + static constexpr size_t MAX_PARAM_AMOUNT = 10; + + class RequestHandler { + public: + RequestHandler(ArticBaseServer* serv, int id); + ~RequestHandler(); + ArticBaseServer* server; + Thread thread; + static void HandleThread(void* arg); + void Serve(); + bool ready = false; + bool run = true; + int workBufferSize; + void* workBuffer; + int id; + int listen_fd = -1; + int accept_fd = -1; + }; + + class Request { + public: + ArticBaseCommon::RequestPacket reqPacket; + std::vector reqParameters; + }; + + static constexpr const char* VERSION = "2"; + + int socketFd; + bool run = true; + bool stopQueried = false; + + std::queue pendingRequests; + CTRPluginFramework::Mutex pendingRequestsMutex; + LightEvent newPendingRequest; + std::array requestHandlers; + +public: + class MethodInterface { + public: + using MethodState = ArticBaseCommon::MethodState; + + MethodInterface(Request& _req, void* _workBuffer, size_t _workBufferSize, int& _socketFD) : req(_req), workBuffer(_workBuffer, _workBufferSize), socketFD(_socketFD) {}; + + bool GetParameterS8(s8& out); + bool GetParameterS16(s16& out); + bool GetParameterS32(s32& out); + bool GetParameterS64(s64& out); + bool GetParameterBuffer(void*& outBuff, size_t& outSize); + + bool FinishInputParameters() { + if (state != MethodState::PARSING_INPUT) + return false; + if (currParameter != req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + state = MethodState::GENERATING_OUTPUT; + workBuffer.Clear(); + return true; + } + + ArticBaseCommon::Buffer* ReserveResultBuffer(u32 bufferID, size_t resultBuffSize); + ArticBaseCommon::Buffer* ResizeLastResultBuffer(ArticBaseCommon::Buffer* buffer, size_t newSize); + + void FinishGood(int returnValue); + void FinishInternalError(); + + int GetMethodReturnValue() { + return returnValue; + } + MethodState GetMethodState() { + return state; + } + + private: + class WorkBufferHandler + { + private: + void* workBuffer; + size_t workBufferSize; + size_t offset = 0; + public: + WorkBufferHandler(void* _workBuffer, size_t _workBufferSize) : workBuffer(_workBuffer), workBufferSize(_workBufferSize) {} + + void Clear() { + offset = 0; + } + + size_t Capacity() { + return workBufferSize; + } + + size_t Size() { + return offset; + } + + ArticBaseCommon::Buffer* Reserve(u32 bufferID, u32 bufferSize) { + if (offset + sizeof(ArticBaseCommon::Buffer) + bufferSize > workBufferSize) { + logger.Error("o=0x%08X, bs=0x%08X, wbs=0x%08X", offset, bufferSize, workBufferSize); + return nullptr; + } + ArticBaseCommon::Buffer* buf = (ArticBaseCommon::Buffer*)((uintptr_t)workBuffer + offset); + offset += sizeof(ArticBaseCommon::Buffer) + bufferSize; + buf->bufferID = bufferID; + buf->bufferSize = bufferSize; + return buf; + } + + enum class ResizeState { + GOOD, + INPUT_ERROR, + OUT_OF_MEMORY, + }; + ResizeState ResizeLast(ArticBaseCommon::Buffer* buffer, size_t newSize); + + std::pair GetRaw() { + return std::make_pair(workBuffer, Size()); + } + }; + + friend class RequestHandler; + std::pair GetOutputBuffer() { + return workBuffer.GetRaw(); + } + Request& req; + int& socketFD; + WorkBufferHandler workBuffer; + int currParameter = 0; + MethodState state = MethodState::PARSING_INPUT; + int returnValue = 0; + }; +}; \ No newline at end of file diff --git a/includes/Logger.hpp b/includes/Logger.hpp new file mode 100644 index 0000000..ca82c9c --- /dev/null +++ b/includes/Logger.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "3ds.h" +#include "string" +#include "queue" +#include "CTRPluginFramework/System/Mutex.hpp" +#include "CTRPluginFramework/System/Lock.hpp" + +class Logger { +public: + Logger(); + void Start(); + void End(); + ~Logger(); + void Raw(bool isTopScr, const char* fmt, ...); + void Info(const char* fmt, ...); + void Debug(const char* fmt, ...); + void Warning(const char* fmt, ...); + void Error(const char* fmt, ...); + void Traffic(const char* fmt, ...); + + void Wait(); + + bool debug_enable = false; +private: + struct PendingLog { + enum class Type : u8 { + RAW, + DEBUG, + INFO, + WARNING, + ERROR, + TRAFFIC, + }; + Type type; + bool isTopScr = true; + std::string string; + }; + static void LoggerThread(void* arg); + void Handler(); + Thread thread; + LightEvent event; + + std::queue pendingLogs; + CTRPluginFramework::Mutex pendingLogsMutex; + + bool run = true; +}; \ No newline at end of file diff --git a/sources/ArticBaseServer.cpp b/sources/ArticBaseServer.cpp new file mode 100644 index 0000000..7d95325 --- /dev/null +++ b/sources/ArticBaseServer.cpp @@ -0,0 +1,639 @@ +#include "ArticBaseServer.hpp" +#include "ArticBaseFunctions.hpp" +#include "CTRPluginFramework/System/Lock.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +extern int transferedBytes; + +ArticBaseServer::ArticBaseServer(int sock_fd) { + socketFd = sock_fd; + + LightEvent_Init(&newPendingRequest, ResetType::RESET_ONESHOT); + + for (int i = 0; i < requestHandlers.size(); i++) { + requestHandlers[i] = new RequestHandler(this, i); + } +} + +ArticBaseServer::~ArticBaseServer() { + for (int i = 0; i < requestHandlers.size(); i++) { + delete requestHandlers[i]; + } + if (socketFd >= 0) { + int fd = socketFd; + socketFd = -1; + shutdown(fd, SHUT_RDWR); + close(fd); + } +} + +void ArticBaseServer::Serve() { + ArticBaseCommon::RequestPacket req; + std::array params; + int retryCount = 0; + while (run && !stopQueried) { + if (!Read(socketFd, &req, sizeof(req))) { + if (stopQueried) { + break; + } + if (run) { + logger.Error("Server: Error reading from socket"); + svcSleepThread(1000000000); + if (++retryCount == 3) { + break; + } + } + continue; + } + + std::array methodArray = {0}; + memcpy(methodArray.data(), req.method.data(), req.method.size()); + // Process special method now, delegate otherwise + if (methodArray[0] == '$') { + logger.Debug("Server: Processing %s (rID %d)", methodArray.data(), req.requestID); + ArticBaseCommon::DataPacket resp{}; + resp.requestID = req.requestID; + std::string_view method(methodArray.data()); + if (method == "$PING") { + // Do nothing + } else if (method == "$VERSION") { + strcpy(resp.dataRaw, VERSION); + } else if (method == "$PORTS") { + snprintf(resp.dataRaw, sizeof(resp.dataRaw), "%d,%d,%d,%d", SERVER_PORT + 1, SERVER_PORT + 2, SERVER_PORT + 3, SERVER_PORT + 4); + } else if (method == "$MAXSIZE") { + snprintf(resp.dataRaw, sizeof(resp.dataRaw), "%d", ArticBaseServer::MAX_WORK_BUF_SIZE); + } else if (method == "$MAXPARAM") { + snprintf(resp.dataRaw, sizeof(resp.dataRaw), "%d", ArticBaseServer::MAX_PARAM_AMOUNT); + } else if (method == "$READY") { + bool ready = true; + for (int i = 0; i < requestHandlers.size(); i++) { + if (!requestHandlers[i]->ready) { + ready = false; + } + } + snprintf(resp.dataRaw, sizeof(resp.dataRaw), "%d", ready ? 1 : 0); + } else if (method == "$STOP") { + stopQueried = true; + } else { + logger.Error("Server: Method not found: %s", methodArray.data()); + } + if (!Write(socketFd, &resp, sizeof(resp))) { + if (run) logger.Error("Server: Error writing to socket"); + continue; + } + } else { + u32 readParams = 0; + if (req.parameterCount) { + if (req.parameterCount <= MAX_PARAM_AMOUNT) { + if (!Read(socketFd, params.data(), req.parameterCount * sizeof(ArticBaseCommon::RequestParameter))) { + if (run) logger.Error("Server: Error reading from socket"); + continue; + } + readParams = req.parameterCount; + } else { + logger.Error("Server: Too many parameters in request"); + readParams = 0; + } + } + + { + CTRPluginFramework::Lock l(pendingRequestsMutex); + pendingRequests.push(Request{.reqPacket = req, .reqParameters = std::vector(params.begin(), params.begin() + readParams)}); + } + LightEvent_Signal(&newPendingRequest); + } + } + Stop(); +} + +void ArticBaseServer::QueryStop() { + stopQueried = true; + if (socketFd >= 0) { + int fd = socketFd; + socketFd = -1; + shutdown(fd, SHUT_RDWR); + close(fd); + } +} + +bool ArticBaseServer::SetNonBlock(int sockFD, bool nonBlocking) { + int flags = fcntl(sockFD, F_GETFL, 0); + if (flags < 0) { + return false; + } + if (nonBlocking) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + int res = fcntl(sockFD, F_SETFL, flags); + if (res < 0) { + return false; + } + return true; +} + + +void ArticBaseServer::Stop() { + if (!run) + return; + run = false; + if (socketFd >= 0) { + int fd = socketFd; + socketFd = -1; + shutdown(fd, SHUT_RDWR); + close(fd); + } + for (int i = 0; i < requestHandlers.size(); i++) { + RequestHandler& handler = *requestHandlers[i]; + handler.run = false; + if (handler.accept_fd >= 0) { + int fd = handler.accept_fd; + handler.accept_fd = -1; + shutdown(fd, SHUT_RDWR); + close(fd); + } + if (handler.listen_fd >= 0) { + int fd = handler.listen_fd; + handler.listen_fd = -1; + shutdown(fd, SHUT_RDWR); + close(fd); + } + } + while(true) { + bool allStopped = true; + for (int i = 0; i < requestHandlers.size(); i++) { + allStopped = allStopped && threadGetExitCode(requestHandlers[i]->thread) == 1; + } + if (allStopped) + break; + svcSleepThread(1000000); + LightEvent_Signal(&newPendingRequest); + } +} + +bool ArticBaseServer::Read(int& sockFD, void* buffer, size_t size) { + size_t read_bytes = 0; + while (read_bytes != size) { + int new_read = recv(sockFD, (void*)((uintptr_t)buffer + read_bytes), size - read_bytes, 0); + if (new_read < 0) { + if (errno == EWOULDBLOCK) { + svcSleepThread(1000000); + continue; + } + read_bytes = 0; + break; + } + transferedBytes += new_read; + read_bytes += new_read; + } + return read_bytes == size; +} + +bool ArticBaseServer::Write(int& sockFD, void* buffer, size_t size) { + size_t write_bytes = 0; + while (write_bytes != size) + { + int new_written = send(sockFD, (void*)((uintptr_t)buffer + write_bytes), size - write_bytes, 0); + if (new_written < 0) { + if (errno == EWOULDBLOCK) { + svcSleepThread(1000000); + continue; + } + write_bytes = 0; + break; + } + transferedBytes += new_written; + write_bytes += new_written; + } + return write_bytes == size; +} + +size_t ArticBaseServer::RecvFrom(int& sockFD, void* buffer, size_t size, void* addr, void* addr_size) { + while (true) { + int new_read = recvfrom(sockFD, buffer, size, 0, (sockaddr*)addr, (socklen_t*)addr_size); + if (new_read < 0) { + if (errno == EWOULDBLOCK) { + svcSleepThread(1000000); + continue; + } + return 0; + } + transferedBytes += new_read; + return new_read; + } +} + +size_t ArticBaseServer::SendTo(int& sockFD, void* buffer, size_t size, void* addr, void* addr_size) { + socklen_t addr_len = *(socklen_t*)addr_size; + while (true) { + int new_written = sendto(sockFD, buffer, size, 0, (sockaddr*)addr, addr_len); + if (new_written < 0) { + if (errno == EWOULDBLOCK) { + svcSleepThread(1000000); + continue; + } + return 0; + } + transferedBytes += new_written; + return new_written; + } +} + +ArticBaseServer::RequestHandler::RequestHandler(ArticBaseServer* serv, int id) { + server = serv; + this->id = id; + + workBufferSize = ArticBaseServer::MAX_WORK_BUF_SIZE; + workBuffer = linearAlloc(workBufferSize); + + if (workBuffer == nullptr) { + logger.Error("Worker %d: Failed to allocate work buffer", id); + } + + s32 prio = 0; + svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + thread = threadCreate(RequestHandler::HandleThread, this, 0x1000, prio - 1, -2, false); +} + +ArticBaseServer::RequestHandler::~RequestHandler() { + threadJoin(thread, U64_MAX); + threadFree(thread); + + linearFree(workBuffer); +} + +void ArticBaseServer::RequestHandler::HandleThread(void* arg) { + RequestHandler* own = (RequestHandler*)arg; + own->Serve(); + if (own->accept_fd >= 0) { + int fd = own->accept_fd; + shutdown(fd, SHUT_RDWR); + close(fd); + } + logger.Debug("Worker %d: Exited", own->id); + threadExit(1); +} + +void ArticBaseServer::RequestHandler::Serve() { + if (!workBuffer) { + return; + } + + struct sockaddr_in servaddr = {0}; + int res; + listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) { + logger.Error("Worker %d: Cannot create socket", id); + return; + } + + if (!ArticBaseServer::SetNonBlock(listen_fd, true)) { + logger.Error("Worker %d: Cannot set non-block", id); + close(listen_fd); + listen_fd = -1; + return; + } + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htonl(INADDR_ANY); + servaddr.sin_port = htons(SERVER_PORT + id + 1); + res = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)); + if (res < 0) { + logger.Error("Worker %d: Failed to bind() to port %d", id, SERVER_PORT + id + 1); + close(listen_fd); + listen_fd = -1; + return; + } + + res = listen(listen_fd, 1); + if (res < 0) { + if (run) { + logger.Error("Worker %d: Failed to listen()", id); + } + close(listen_fd); + listen_fd = -1; + return; + } + + struct in_addr host_id; + host_id.s_addr = gethostid(); + logger.Debug("Worker %d: Listening on: %s:%d", id, inet_ntoa(host_id), SERVER_PORT + id + 1); + + struct sockaddr_in peeraddr = {0}; + socklen_t peeraddr_len = sizeof(peeraddr); + ready = true; + while (true) { + accept_fd = accept(listen_fd, (struct sockaddr *) &peeraddr, &peeraddr_len); + if (accept_fd < 0 || peeraddr_len == 0) { + if (errno == EWOULDBLOCK && run) { + svcSleepThread(10000000); + continue; + } + if (run) { + logger.Error("Worker %d: Failed to accept()", id); + } + close(listen_fd); + listen_fd = -1; + return; + } + break; + } + close(listen_fd); + listen_fd = -1; + + logger.Debug("Worker %d: Accepted %s:%d", id, inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); + + if (!ArticBaseServer::SetNonBlock(accept_fd, true)) { + logger.Error("Worker %d: Cannot set non-block", id); + shutdown(accept_fd, SHUT_RDWR); + close(accept_fd); + accept_fd = -1; + return; + } + + while (true) { + LightEvent_Wait(&server->newPendingRequest); + if (!run) { + break; + } + Request req; + while (run) { + { + CTRPluginFramework::Lock l(server->pendingRequestsMutex); + if (!server->pendingRequests.size()) + break; + req = server->pendingRequests.front(); + server->pendingRequests.pop(); + } + + ArticBaseCommon::DataPacket respPacket{}; + respPacket.requestID = req.reqPacket.requestID; + + std::array methodArray = {0}; + memcpy(methodArray.data(), req.reqPacket.method.data(), req.reqPacket.method.size()); + auto it = ArticBaseFunctions::functionHandlers.find(std::string(methodArray.data())); + if (it == ArticBaseFunctions::functionHandlers.end()) { + respPacket.resp.articResult = ArticBaseCommon::ResponseMethod::ArticResult::METHOD_NOT_FOUND; + logger.Error("Worker %d: Method not found: %s", id, methodArray.data()); + if (!Write(accept_fd, &respPacket, sizeof(respPacket))) { + if (run) + logger.Error("Worker %d: Error writing to socket", id); + return; + } + continue; + } + + logger.Debug("Worker %d: Processing %s (rID %d)", id, methodArray.data(), req.reqPacket.requestID); + MethodInterface mi(req, workBuffer, workBufferSize, accept_fd); + it->second(mi); + + ArticBaseCommon::MethodState mState = mi.GetMethodState(); + if (mState != ArticBaseCommon::MethodState::FINISHED) { + respPacket.resp.articResult = ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR; + logger.Error("Worker %d: %s method error: %d", id, methodArray.data(), mState); + respPacket.resp.methodResult = (int)mState; + if (!Write(accept_fd, &respPacket, sizeof(respPacket))) { + if (run) + logger.Error("Worker %d: Error writing to socket", id); + return; + } + } else { + respPacket.resp.articResult = ArticBaseCommon::ResponseMethod::ArticResult::SUCCESS; + respPacket.resp.methodResult = mi.GetMethodReturnValue(); + auto outBuffer = mi.GetOutputBuffer(); + respPacket.resp.bufferSize = outBuffer.second; + if (!Write(accept_fd, &respPacket, sizeof(respPacket))) { + if (run) + logger.Error("Worker %d: Error writing to socket", id); + return; + } + if (outBuffer.second) + if (!Write(accept_fd, outBuffer.first, outBuffer.second)) { + if (run) + logger.Error("Worker %d: Error writing to socket", id); + return; + } + } + } + } +} + +bool ArticBaseServer::MethodInterface::GetParameterS8(s8& out) { + if (state != MethodState::PARSING_INPUT) + return false; + + if (currParameter >= req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + + ArticBaseCommon::RequestParameter& currParam = req.reqParameters[currParameter++]; + + if (currParam.type == ArticBaseCommon::RequestParameterType::IN_INTEGER_8) { + out = *(s8*)currParam.data; + return true; + } + + state = MethodState::PARAMETER_TYPE_MISMATCH; + return false; +} + +bool ArticBaseServer::MethodInterface::GetParameterS16(s16& out) { + if (state != MethodState::PARSING_INPUT) + return false; + + if (currParameter >= req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + + ArticBaseCommon::RequestParameter& currParam = req.reqParameters[currParameter++]; + + if (currParam.type == ArticBaseCommon::RequestParameterType::IN_INTEGER_16) { + out = *(s16*)currParam.data; + return true; + } + + state = MethodState::PARAMETER_TYPE_MISMATCH; + return false; +} + +bool ArticBaseServer::MethodInterface::GetParameterS32(s32& out) { + if (state != MethodState::PARSING_INPUT) + return false; + + if (currParameter >= req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + + ArticBaseCommon::RequestParameter& currParam = req.reqParameters[currParameter++]; + + if (currParam.type == ArticBaseCommon::RequestParameterType::IN_INTEGER_32) { + out = *(s32*)currParam.data; + return true; + } + + state = MethodState::PARAMETER_TYPE_MISMATCH; + return false; +} + +bool ArticBaseServer::MethodInterface::GetParameterS64(s64& out) { + if (state != MethodState::PARSING_INPUT) + return false; + + if (currParameter >= req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + + ArticBaseCommon::RequestParameter& currParam = req.reqParameters[currParameter++]; + + if (currParam.type == ArticBaseCommon::RequestParameterType::IN_INTEGER_64) { + out = *(s64*)currParam.data; + return true; + } + + state = MethodState::PARAMETER_TYPE_MISMATCH; + return false; +} + +bool ArticBaseServer::MethodInterface::GetParameterBuffer(void*& outBuff, size_t& outSize) { + if (state != MethodState::PARSING_INPUT) + return false; + + if (currParameter >= req.reqPacket.parameterCount) { + state = MethodState::PARAMETER_COUNT_MISMATCH; + return false; + } + + ArticBaseCommon::RequestParameter& currParam = req.reqParameters[currParameter++]; + + if (currParam.type == ArticBaseCommon::RequestParameterType::IN_SMALL_BUFFER) { + outBuff = currParam.data; + outSize = currParam.parameterSize; + return true; + } else if (currParam.type == ArticBaseCommon::RequestParameterType::IN_BIG_BUFFER) { + // Perform request for buffer + size_t bufferSize = *(size_t*)currParam.data; + ArticBaseCommon::Buffer* buf = workBuffer.Reserve(currParam.bigBufferID, bufferSize); + if (buf == nullptr) { + state = MethodState::OUT_OF_MEMORY; + return false; + } + + ArticBaseCommon::DataPacket dataPacket{}; + dataPacket.requestID = req.reqPacket.requestID; + dataPacket.resp.articResult = ArticBaseCommon::ResponseMethod::ArticResult::PROVIDE_INPUT; + dataPacket.resp.provideInputBufferID = currParam.bigBufferID; + dataPacket.resp.bufferSize = (int)bufferSize; + + if (!Write(socketFD, &dataPacket, sizeof(dataPacket))) { + state = MethodState::BIG_BUFFER_WRITE_FAIL; + return false; + } + + if (!Read(socketFD, &dataPacket, sizeof(dataPacket))) { + state = MethodState::BIG_BUFFER_READ_FAIL; + return false; + } + + if (dataPacket.requestID != req.reqPacket.requestID || + dataPacket.resp.articResult != ArticBaseCommon::ResponseMethod::ArticResult::PROVIDE_INPUT || + dataPacket.resp.provideInputBufferID != currParam.bigBufferID || + dataPacket.resp.bufferSize != (int)bufferSize) { + + state = MethodState::BIG_BUFFER_READ_FAIL; + return false; + } + + if (!Read(socketFD, buf->data, buf->bufferSize)) { + state = MethodState::BIG_BUFFER_READ_FAIL; + return false; + } + outBuff = buf->data; + outSize = buf->bufferSize; + return true; + } + state = MethodState::PARAMETER_TYPE_MISMATCH; + return false; +} + +ArticBaseCommon::Buffer* ArticBaseServer::MethodInterface::ReserveResultBuffer(u32 bufferID, size_t resultBuffSize) { + if (state != MethodState::GENERATING_OUTPUT) { + if (state == MethodState::PARSING_INPUT) { + state = MethodState::UNEXPECTED_PARSING_INPUT; + } + return nullptr; + } + + ArticBaseCommon::Buffer* buf = workBuffer.Reserve(bufferID, resultBuffSize); + if (buf == nullptr) { + state = MethodState::OUT_OF_MEMORY_OUTPUT; + return nullptr; + } + return buf; +} + +ArticBaseCommon::Buffer* ArticBaseServer::MethodInterface::ResizeLastResultBuffer(ArticBaseCommon::Buffer* buffer, size_t newSize) { + if (state != MethodState::GENERATING_OUTPUT) { + if (state == MethodState::PARSING_INPUT) { + state = MethodState::UNEXPECTED_PARSING_INPUT; + } + return nullptr; + } + WorkBufferHandler::ResizeState resizeSate = workBuffer.ResizeLast(buffer, newSize); + if (resizeSate != WorkBufferHandler::ResizeState::GOOD) { + if (resizeSate == WorkBufferHandler::ResizeState::OUT_OF_MEMORY) + state = MethodState::OUT_OF_MEMORY_OUTPUT; + else + state = MethodState::INTERNAL_METHOD_ERROR; + return nullptr; + } + return buffer; +} + +void ArticBaseServer::MethodInterface::FinishGood(int returnValue) { + if (state == MethodState::GENERATING_OUTPUT) + state = MethodState::FINISHED; + if (state == MethodState::PARSING_INPUT) + state = MethodState::UNEXPECTED_PARSING_INPUT; + this->returnValue = returnValue; +} +void ArticBaseServer::MethodInterface::FinishInternalError() { + if (state == MethodState::GENERATING_OUTPUT || state == MethodState::PARSING_INPUT) + state = MethodState::INTERNAL_METHOD_ERROR; + return; +} + +ArticBaseServer::MethodInterface::WorkBufferHandler::ResizeState ArticBaseServer::MethodInterface::WorkBufferHandler::ResizeLast(ArticBaseCommon::Buffer* buffer, size_t newSize) { + if (buffer->bufferSize == newSize) + return ResizeState::GOOD; + if ((uintptr_t)workBuffer + offset != (uintptr_t)buffer + buffer->bufferSize + sizeof(ArticBaseCommon::Buffer)) + return ResizeState::INPUT_ERROR; + int sizeDiff = newSize - buffer->bufferSize; + if (newSize == 0) { + // Remove the buffer completely + sizeDiff -= sizeof(ArticBaseCommon::Buffer); + } + if (offset + sizeDiff > workBufferSize) + return ResizeState::OUT_OF_MEMORY; + offset += sizeDiff; + if (newSize != 0) + buffer->bufferSize = newSize; + return ResizeState::GOOD; +} \ No newline at end of file diff --git a/sources/Logger.cpp b/sources/Logger.cpp new file mode 100644 index 0000000..b515ac0 --- /dev/null +++ b/sources/Logger.cpp @@ -0,0 +1,181 @@ +#include "Logger.hpp" +#include +#include +#include +#include "3ds.h" + +Logger::Logger() {} + +void Logger::Start() { + LightEvent_Init(&event, ResetType::RESET_ONESHOT); + + s32 prio = 0; + svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + + thread = threadCreate(LoggerThread, this, 0x300, prio + 1, -2, false); +} + +void Logger::End() { + run = false; + LightEvent_Signal(&event); + threadJoin(thread, U64_MAX); +} + +Logger::~Logger() { + if (run) End(); + threadFree(thread); +} + +extern PrintConsole topScreenConsole, bottomScreenConsole; + +void Logger::Raw(bool isTopScr, const char* fmt, ...) { + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::RAW, .isTopScr = isTopScr, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Info(const char* fmt, ...) { + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::INFO, .isTopScr = true, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Debug(const char* fmt, ...) { + if (!debug_enable) return; + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::DEBUG, .isTopScr = true, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Warning(const char* fmt, ...) { + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::WARNING, .isTopScr = true, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Error(const char* fmt, ...) { + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::ERROR, .isTopScr = true, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Traffic(const char* fmt, ...) { + va_list valist; + char buffer[256]; + va_start(valist, fmt); + int ret = vsnprintf(buffer, 255, fmt, valist); + va_end(valist); + if (ret >= 0) buffer[ret] = '\0'; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + pendingLogs.push(PendingLog{.type = PendingLog::Type::TRAFFIC, .isTopScr = false, .string{buffer}}); + } + LightEvent_Signal(&event); +} + +void Logger::Handler() { + bool currentIsTop = true; + int back; + while (true) { + LightEvent_Wait(&event); + if (!run) { + break; + } + while (run) { + PendingLog log; + { + CTRPluginFramework::Lock l(pendingLogsMutex); + if (!pendingLogs.size()) + break; + log = pendingLogs.front(); + pendingLogs.pop(); + } + if (log.isTopScr && !currentIsTop) { + currentIsTop = true; + consoleSelect(&topScreenConsole); + } + if (!log.isTopScr && currentIsTop) { + currentIsTop = false; + consoleSelect(&bottomScreenConsole); + } + switch (log.type) + { + case PendingLog::Type::RAW: + back = bottomScreenConsole.cursorY; + bottomScreenConsole.cursorY = 0; + printf("%s\n", log.string.c_str()); + bottomScreenConsole.cursorY = back; + break; + case PendingLog::Type::DEBUG: + printf("[D] %s\n", log.string.c_str()); + break; + case PendingLog::Type::INFO: + printf("[I] %s\n", log.string.c_str()); + break; + case PendingLog::Type::WARNING: + topScreenConsole.fg = 19; + printf("[W] %s\n", log.string.c_str()); + topScreenConsole.fg = 0; + break; + case PendingLog::Type::ERROR: + topScreenConsole.fg = 17; + printf("[E] %s\n", log.string.c_str()); + topScreenConsole.fg = 0; + break; + case PendingLog::Type::TRAFFIC: + back = bottomScreenConsole.cursorY; + bottomScreenConsole.cursorY = 25; + printf(log.string.c_str()); + bottomScreenConsole.cursorY = back; + break; + default: + break; + } + } + } +} + +void Logger::LoggerThread(void* arg) { + Logger* l = (Logger*)arg; + l->Handler(); +} \ No newline at end of file