Add Artic Controller support

This commit is contained in:
PabloMK7 2024-07-16 22:53:13 +02:00
parent be877e7dfb
commit fea0a62d06
12 changed files with 414 additions and 23 deletions

View File

@ -1,5 +1,5 @@
VERSION_MAJOR := 1
VERSION_MINOR := 1
VERSION_MINOR := 2
VERSION_REVISION := 0
all:

View File

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

View File

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

View File

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

View File

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

View File

@ -13,15 +13,16 @@ public:
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);
@ -45,7 +46,7 @@ private:
std::vector<ArticBaseCommon::RequestParameter> reqParameters;
};
static constexpr const char* VERSION = "1";
static constexpr const char* VERSION = "2";
int socketFd;
bool run = true;

View File

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

View File

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

View File

@ -221,6 +221,37 @@ bool ArticBaseServer::Write(int& sockFD, void* buffer, size_t size) {
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;

View File

@ -141,7 +141,10 @@ void Logger::Handler() {
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());

View File

@ -0,0 +1,26 @@
#include "hidExtension.hpp"
#include <string.h>
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/srv.h>
#include <3ds/synchronization.h>
#include <3ds/services/fs.h>
#include <3ds/ipc.h>
#include <3ds/env.h>
extern Handle hidHandle;
Result HIDUSER_GetGyroscopeCalibrateParam(GyroscopeCalibrateParam* calibrateParam) {
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = IPC_MakeHeader(0x16,0,0);
Result ret = 0;
if(R_FAILED(ret = svcSendSyncRequest(hidHandle))) return ret;
if (R_SUCCEEDED(cmdbuf[1])) {
memcpy(calibrateParam, &cmdbuf[2], sizeof(GyroscopeCalibrateParam));
}
return cmdbuf[1];
}

View File

@ -30,6 +30,12 @@ static bool should_run = true;
static int listen_fd = -1;
static int accept_fd = -1;
static ArticBaseServer* articBase = nullptr;
static bool wasControllerMode = false;
static bool everControllerMode = false;
static bool reloadBottomText = false;
int transferedBytes = 0;
bool isControllerMode = false;
extern "C" {
#include "csvc.h"
@ -159,13 +165,15 @@ void Start(void* arg) {
for (auto it = ArticBaseFunctions::destructFunctions.begin(); it != ArticBaseFunctions::destructFunctions.end(); it++) {
(*it)();
}
isControllerMode = false;
everControllerMode = false;
}
socExit();
free(SOC_buffer);
}
PrintConsole topScreenConsole, bottomScreenConsole;
int transferedBytes = 0;
void Main() {
logger.Start();
@ -195,13 +203,39 @@ void Main() {
{
CTRPluginFramework::BCLIM((void*)__data_logo_bin, __data_logo_bin_size).Render(CTRPluginFramework::Rect<int>((320 - 128) / 2, (240 - 128) / 2, 128, 128));
}
logger.Raw(false, "\n ArticBase v%d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
logger.Raw(false, " Press A to enter sleep.");
logger.Raw(false, " Press X to show debug log.");
logger.Raw(false, " Press Y to restart server.");
logger.Raw(false, " Press START to exit.");
auto print_bottom_info = [](bool controller_mode) {
if (controller_mode) {
logger.Raw(false, "\n ArticBase v%d.%d.%d\n\n"
" Artic Controller enabled \n"
" \n"
" - X + START + SELECT: Exit \n"
" Artic Controller mode \n"
" "
, VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
} else {
if (everControllerMode) {
logger.Raw(false, "\n ArticBase v%d.%d.%d\n\n"
" - A: Enter sleep \n"
" - X: Show debug log \n"
" - Y: Restart server \n"
" - START: Exit \n"
" - SELECT: Enter Artic controller mode "
, VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
} else {
logger.Raw(false, "\n ArticBase v%d.%d.%d\n\n"
" - A: Enter sleep \n"
" - X: Show debug log \n"
" - Y: Restart server \n"
" - START: Exit \n"
" "
, VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
}
}
};
print_bottom_info(false);
logger.Raw(true, "");
bool setupCorrect = true;
@ -225,29 +259,44 @@ void Main() {
while (aptMainLoop())
{
//Scan all the inputs. This should be done once for each frame
if (isControllerMode != wasControllerMode || reloadBottomText) {
reloadBottomText = false;
everControllerMode |= isControllerMode;
if (isControllerMode) {
logger.Info("Server: Controller mode enabled");
} else {
logger.Info("Server: Controller mode disabled");
}
print_bottom_info(isControllerMode);
wasControllerMode = isControllerMode;
}
hidScanInput();
//hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame)
u32 kDown = hidKeysDown();
u32 kHeld = hidKeysHeld();
if ((kDown != 0) && sleeping) {
if (((kDown & ~KEY_SELECT) != 0) && sleeping && !isControllerMode) {
sleeping = false;
GSPLCD_PowerOnBacklight(3);
continue;
}
if ((kDown & KEY_A) && !sleeping) {
if ((kDown & KEY_A) && !sleeping && !isControllerMode) {
sleeping = true;
GSPLCD_PowerOffBacklight(3);
}
if (kDown & KEY_START) {
if ((kDown & KEY_START) && !isControllerMode) {
logger.Info("Server: Exiting");
break;
}
if (kDown & KEY_Y) {
if ((kDown & KEY_SELECT) && !isControllerMode && everControllerMode) {
isControllerMode = true;
}
if ((kDown & KEY_Y) && !isControllerMode) {
if (articBase) {
logger.Info("Server: Restarting");
articBase->QueryStop();
@ -256,7 +305,7 @@ void Main() {
}
}
if (kDown & KEY_X) {
if ((kDown & KEY_X) && !isControllerMode) {
if (logger.debug_enable) {
logger.Info("Server: Debug log disabled");
logger.debug_enable = false;
@ -266,6 +315,10 @@ void Main() {
}
}
if (((kHeld & (KEY_X | KEY_START | KEY_SELECT)) == (KEY_X | KEY_START | KEY_SELECT)) && isControllerMode) {
isControllerMode = false;
}
if (clock.HasTimePassed(CTRPluginFramework::Seconds(1)))
{
if (articBase) {