Skip to content

Commit

Permalink
Add Artic Controller support
Browse files Browse the repository at this point in the history
  • Loading branch information
PabloMK7 committed Jul 16, 2024
1 parent be877e7 commit fea0a62
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VERSION_MAJOR := 1
VERSION_MINOR := 1
VERSION_MINOR := 2
VERSION_REVISION := 0

all:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Artic Base Server is a Luma3DS 3GX plugin that allows using the Artic Base proto
### Features
- Play games from your console without having to dump them, with purchased updates and DLC.
- Sync the savedata/extdata of the broadcasted game during the play session.
- Use the console as the input device by enabling the Artic Controller feature on the emulator.
- Load shared ext data and NCCH archives from your console.
- Remove the need to dump AES keys, as the decryption is done by the console's OS.

Expand All @@ -39,7 +40,6 @@ NOTE: A recent version of Luma3DS (v13.1.1 or newer) is requires to use Artic Ba
This section lists features that Artic Base Server cannot currently provide. Some of these features may be added in the future.

### Things that might be implemented
- Use the console as a controller itself.
- Broadcasting homebrew applications

### Things that will never be implemented
Expand Down
2 changes: 1 addition & 1 deletion app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ LIBRARY_DIRS := $(PORTLIBS) $(CTRULIB) $(DEVKITPRO)/libcwav $(DEVKITPRO)/libncsn
LIBRARIES := ctru

VERSION_MAJOR := 1
VERSION_MINOR := 1
VERSION_MINOR := 2
VERSION_MICRO := 0

BUILD_FLAGS := -march=armv6k -mtune=mpcore -mfloat-abi=hard
Expand Down
2 changes: 1 addition & 1 deletion plugin/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ INCLUDES := includes
SOURCES := sources sources/CTRPluginFramework

VERSION_MAJOR := 1
VERSION_MINOR := 1
VERSION_MINOR := 2
VERSION_REVISION := 0
SERVER_PORT := 5543

Expand Down
9 changes: 9 additions & 0 deletions plugin/includes/ArticBaseFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ namespace ArticBaseFunctions {
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);
};
};
11 changes: 6 additions & 5 deletions plugin/includes/ArticBaseServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ class ArticBaseServer {
void Serve();
void QueryStop();

static bool SetNonBlock(int sockFD, bool blocking);
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;

static bool Read(int& sockFD, void* buffer, size_t size);
static bool Write(int& sockFD, void* buffer, size_t size);

class RequestHandler {
public:
RequestHandler(ArticBaseServer* serv, int id);
Expand All @@ -45,7 +46,7 @@ class ArticBaseServer {
std::vector<ArticBaseCommon::RequestParameter> reqParameters;
};

static constexpr const char* VERSION = "1";
static constexpr const char* VERSION = "2";

int socketFd;
bool run = true;
Expand Down
12 changes: 12 additions & 0 deletions plugin/includes/hidExtension.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once
#include "3ds.h"

struct GyroscopeCalibrateParam {
struct {
s16 zero_point;
s16 positive_unit_point;
s16 negative_unit_point;
} x, y, z;
};

Result HIDUSER_GetGyroscopeCalibrateParam(GyroscopeCalibrateParam* calibrateParam);
258 changes: 257 additions & 1 deletion plugin/sources/ArticBaseFunctions.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "ArticBaseFunctions.hpp"
#include "Main.hpp"
#include "amExtension.hpp"
#include "fsExtension.hpp"
#include "hidExtension.hpp"
#include "CTRPluginFramework/CTRPluginFramework.hpp"
#include "CTRPluginFramework/Clock.hpp"

extern bool isControllerMode;

namespace ArticBaseFunctions {

Expand Down Expand Up @@ -1547,9 +1557,224 @@ namespace ArticBaseFunctions {
mi.FinishGood(res);
}

void ArticController::Handler(void* arg) {
using namespace ArticController;

struct ControllerPacket {
u32 id;
u32 pad;
circlePosition c_pad;
touchPosition touch;
circlePosition c_stick;
accelVector accel;
angularRate gyro;
} packet;
u32 current_id = 0;
static_assert(sizeof(packet) == 0x20, "ControllerPacket invalid size");

constexpr int port = SERVER_PORT + 10;
struct sockaddr_in addr = {0};
socklen_t addr_size = static_cast<socklen_t>(sizeof(addr));
int res, failedCount = 0;

socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_fd < 0) {
logger.Error("ArticController: Cannot create socket");
socket_ready = true;
threadExit(1);
return;
}

if (!ArticBaseServer::SetNonBlock(socket_fd, true)) {
logger.Error("ArticController: Cannot set non-block");
close(socket_fd);
socket_fd = -1;
socket_ready = true;
threadExit(1);
return;
}

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
res = bind(socket_fd, (struct sockaddr *)&addr, addr_size);
if (res < 0) {
logger.Error("ArticController: Failed to bind() to port %d", port);
close(socket_fd);
socket_fd = -1;
socket_ready = true;
threadExit(1);
return;
}

socket_ready = true;

u8 a;
size_t transfered = ArticBaseServer::RecvFrom(socket_fd, &a, sizeof(a), &addr, &addr_size);
if (transfered > 0) {
logger.Debug("ArticController: Started");
} else {
logger.Error("ArticController: Error reading from socket");
close(socket_fd);
socket_fd = -1;
threadExit(1);
return;
}

constexpr CTRPluginFramework::Time INTERVAL = CTRPluginFramework::Milliseconds(2);
// hid scan input is done on the main thread, no need to do it here.
while (thread_run) {
CTRPluginFramework::Clock clock;

if (isControllerMode) {
packet.id = current_id++;
packet.pad = hidKeysHeld();
hidCircleRead(&packet.c_pad);
hidTouchRead(&packet.touch);
irrstCstickRead(&packet.c_stick);
hidAccelRead(&packet.accel);
hidGyroRead(&packet.gyro);

if (ArticBaseServer::SendTo(socket_fd, &packet, sizeof(packet), &addr, &addr_size) <= 0) {
if (failedCount++ >= 1000) {
logger.Error("ArticController: Error writing to socket");
break;
}
} else {
failedCount = 0;
}
}

CTRPluginFramework::Time elapsed = clock.GetElapsedTime();
if (elapsed < INTERVAL) {
svcSleepThread((INTERVAL - elapsed).AsMicroseconds() * 1000);
}
}

close(socket_fd);
socket_fd = -1;
threadExit(1);
}

static bool stopController(void);
void Controller_Start(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) good = mi.FinishInputParameters();

if (!good) return;

constexpr int port = SERVER_PORT + 10;
ArticBaseCommon::Buffer* conf_buf = mi.ReserveResultBuffer(0, sizeof(int));
if (!conf_buf) {
return;
}
*(int*)conf_buf->data = port;

stopController();

s32 prio = 0;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
ArticController::socket_ready = false;
ArticController::thread_run = true;
ArticController::thread = threadCreate(ArticController::Handler, nullptr, 0x800, prio + 1, -2, false);
logger.Debug("ArticController: Starting...");
isControllerMode = true;

while (!ArticController::socket_ready) {
svcSleepThread(1000000);
}
ArticController::socket_ready = false;

mi.FinishGood(0);
}

void HIDUSER_EnableAccelerometer_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

Result res = HIDUSER_EnableAccelerometer();

mi.FinishGood(res);
}

void HIDUSER_DisableAccelerometer_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

Result res = HIDUSER_DisableAccelerometer();

mi.FinishGood(res);
}

void HIDUSER_EnableGyroscope_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

Result res = HIDUSER_EnableGyroscope();

mi.FinishGood(res);
}

void HIDUSER_DisableGyroscope_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

Result res = HIDUSER_DisableGyroscope();

mi.FinishGood(res);
}

void HIDUSER_GetGyroscopeRawToDpsCoefficient_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

float coef;
Result res = HIDUSER_GetGyroscopeRawToDpsCoefficient(&coef);
if (R_FAILED(res)) {
mi.FinishGood(res);
return;
}

ArticBaseCommon::Buffer* coef_buf = mi.ReserveResultBuffer(0, sizeof(float));
if (!coef_buf) {
return;
}
*(float*)coef_buf->data = coef;

mi.FinishGood(res);
}


void HIDUSER_GetGyroscopeCalibrateParam_(ArticBaseServer::MethodInterface& mi) {
bool good = true;

if (good) mi.FinishInputParameters();

GyroscopeCalibrateParam param = { 0 };
Result res = HIDUSER_GetGyroscopeCalibrateParam(&param);
if (R_FAILED(res)) {
mi.FinishGood(res);
return;
}

ArticBaseCommon::Buffer* coef_buf = mi.ReserveResultBuffer(0, sizeof(param));
if (!coef_buf) {
return;
}
*(GyroscopeCalibrateParam*)coef_buf->data = param;

mi.FinishGood(res);
}

template<std::size_t N>
constexpr auto& METHOD_NAME(char const (&s)[N]) {
static_assert(N < sizeof(ArticBaseCommon::RequestPacket::method), "String exceeds 10 bytes!");
static_assert(N < sizeof(ArticBaseCommon::RequestPacket::method), "String exceeds 32 bytes!");
return s;
}

Expand Down Expand Up @@ -1607,6 +1832,15 @@ namespace ArticBaseFunctions {
{METHOD_NAME("AMAPP_ListDataTitleTicketInfos"), AMAPP_ListDataTitleTicketInfos_},
{METHOD_NAME("AMAPP_GetPatchTitleInfos"), AMAPP_GetPatchTitleInfos_},
{METHOD_NAME("CFGU_GetConfigInfoBlk2"), CFGU_GetConfigInfoBlk2_},
{METHOD_NAME("HIDUSER_EnableAccelerometer"), HIDUSER_EnableAccelerometer_},
{METHOD_NAME("HIDUSER_DisableAccelerometer"), HIDUSER_DisableAccelerometer_},
{METHOD_NAME("HIDUSER_EnableGyroscope"), HIDUSER_EnableGyroscope_},
{METHOD_NAME("HIDUSER_DisableGyroscope"), HIDUSER_DisableGyroscope_},
{METHOD_NAME("HIDUSER_GetGyroRawToDpsCoef"), HIDUSER_GetGyroscopeRawToDpsCoefficient_},
{METHOD_NAME("HIDUSER_GetGyroCalibrateParam"), HIDUSER_GetGyroscopeCalibrateParam_},

// UDP Streams
{METHOD_NAME("#ArticController"), Controller_Start},
};

bool obtainExheader() {
Expand Down Expand Up @@ -1654,11 +1888,33 @@ namespace ArticBaseFunctions {
return true;
}

static bool stopController(void) {
if (ArticController::thread_run) {
logger.Debug("ArticController: Stopping...");
ArticController::thread_run = false;
if (ArticController::socket_fd != -1) {
close(ArticController::socket_fd);
ArticController::socket_fd = -1;
}
threadJoin(ArticController::thread, U64_MAX);
threadFree(ArticController::thread);
ArticController::thread = nullptr;
logger.Debug("ArticController: Stopped...");
}
return true;
}

std::vector<bool(*)()> setupFunctions {
obtainExheader,
};

std::vector<bool(*)()> destructFunctions {
closeHandles,
stopController,
};

Thread ArticController::thread = nullptr;
bool ArticController::thread_run = false;
int ArticController::socket_fd = -1;
volatile bool ArticController::socket_ready = false;
}
Loading

0 comments on commit fea0a62

Please sign in to comment.