First commit

This commit is contained in:
PabloMK7 2025-03-04 18:35:21 +01:00
commit 09750e64c8
7 changed files with 1161 additions and 0 deletions

26
README.md Normal file
View File

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

View File

@ -0,0 +1,80 @@
#pragma once
#include <3ds/types.h>
#include <array>
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<char, 0x20> 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);
};

View File

@ -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<std::string, void(*)(ArticBaseServer::MethodInterface& out)> functionHandlers;
// All setup functions should be defined here
extern std::vector<bool(*)()> setupFunctions;
// All destruct functions should be defined here
extern std::vector<bool(*)()> 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);
};
};

View File

@ -0,0 +1,155 @@
#pragma once
#include "Main.hpp"
#include "ArticBaseCommon.hpp"
#include "queue"
#include <tuple>
#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<ArticBaseCommon::RequestParameter> reqParameters;
};
static constexpr const char* VERSION = "2";
int socketFd;
bool run = true;
bool stopQueried = false;
std::queue<Request> pendingRequests;
CTRPluginFramework::Mutex pendingRequestsMutex;
LightEvent newPendingRequest;
std::array<RequestHandler*, 4> 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<void*, size_t> GetRaw() {
return std::make_pair(workBuffer, Size());
}
};
friend class RequestHandler;
std::pair<void*, size_t> GetOutputBuffer() {
return workBuffer.GetRaw();
}
Request& req;
int& socketFD;
WorkBufferHandler workBuffer;
int currParameter = 0;
MethodState state = MethodState::PARSING_INPUT;
int returnValue = 0;
};
};

47
includes/Logger.hpp Normal file
View File

@ -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<PendingLog> pendingLogs;
CTRPluginFramework::Mutex pendingLogsMutex;
bool run = true;
};

639
sources/ArticBaseServer.cpp Normal file
View File

@ -0,0 +1,639 @@
#include "ArticBaseServer.hpp"
#include "ArticBaseFunctions.hpp"
#include "CTRPluginFramework/System/Lock.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <errno.h>
#include <stdarg.h>
#include <unistd.h>
#include <string_view>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
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<ArticBaseCommon::RequestParameter, MAX_PARAM_AMOUNT> 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<char, sizeof(req.method) + 1> 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<ArticBaseCommon::RequestParameter>(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<char, sizeof(req.reqPacket.method) + 1> 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;
}

181
sources/Logger.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "Logger.hpp"
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#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();
}