From 06f836c0d408c21ebea8b8c6948e5679fdc1b753 Mon Sep 17 00:00:00 2001 From: niansa Date: Mon, 3 Apr 2023 22:06:29 +0200 Subject: [PATCH] Initial commit --- .gitignore | 74 ++++++++++++++++ CMakeLists.txt | 30 +++++++ Client.cpp | 113 ++++++++++++++++++++++++ Client.hpp | 45 ++++++++++ Receiver.cpp | 43 +++++++++ Receiver.hpp | 30 +++++++ Runtime.cpp | 47 ++++++++++ Runtime.hpp | 235 +++++++++++++++++++++++++++++++++++++++++++++++++ Sender.cpp | 24 +++++ Sender.hpp | 25 ++++++ Socket.hpp | 68 ++++++++++++++ main.cpp | 45 ++++++++++ 12 files changed, 779 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Client.cpp create mode 100644 Client.hpp create mode 100644 Receiver.cpp create mode 100644 Receiver.hpp create mode 100644 Runtime.cpp create mode 100644 Runtime.hpp create mode 100644 Sender.cpp create mode 100644 Sender.hpp create mode 100644 Socket.hpp create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0b530 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..298cbd9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.5) + +project(llama.any LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(llama + main.cpp + Client.hpp Client.cpp + Socket.hpp + Receiver.hpp Receiver.cpp + Sender.hpp Sender.cpp + Runtime.cpp Runtime.hpp +) + +target_compile_definitions(llama PUBLIC PLATFORM="${CMAKE_SYSTEM_NAME}") +if (CMAKE_SYSTEM_NAME STREQUAL Nintendo3DS) + target_compile_definitions(llama PUBLIC PLATFORM_3DS) +elseif (CMAKE_SYSTEM_NAME STREQUAL NintendoDS) + target_compile_definitions(llama PUBLIC PLATFORM_DS) + target_link_libraries(llama PUBLIC dswifi9 nds9) +elseif (CMAKE_SYSTEM_NAME STREQUAL Linux) + target_compile_definitions(llama PUBLIC PLATFORM_LINUX) +elseif (CMAKE_SYSTEM_NAME STREQUAL Windows) + target_compile_definitions(llama PUBLIC PLATFORM_WINDOWS) + target_link_libraries(llama PUBLIC Ws2_32) +else() + message(SEND_ERROR "${CMAKE_SYSTEM_NAME} is not a supported platform!") +endif() diff --git a/Client.cpp b/Client.cpp new file mode 100644 index 0000000..a32a97a --- /dev/null +++ b/Client.cpp @@ -0,0 +1,113 @@ +#include "Client.hpp" +#include "Socket.hpp" +#include "Sender.hpp" +#include "Receiver.hpp" + +#include +#ifndef PLATFORM_WINDOWS +# include +# include +# include +# include +# include +#else +# include +#endif + + + +void Client::fetchAddr(const std::string& addr, unsigned port) { +# ifdef HAS_ADDRINFO + // Set up hints + addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; //TODO: Care about IPv6 + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_CANONNAME; + + // Get port as C string + char portStr[7]; + sprintf(portStr, "%u", port); + + // Get addrinfo + auto error = getaddrinfo(addr.c_str(), portStr, &hints, &addrInfo); + auto bad = addrInfo == nullptr; +# else + addrInfo = gethostbyname(addr.c_str()); + auto error = errno; + auto bad = addrInfo == nullptr || addrInfo->h_addr_list[0] == nullptr; +# endif + + // Check for error + if (bad) { + throw Exception("DNS failed to look up hostname: "+std::string(strerror(error))+" ("+addr+')'); + } +} + +Client::Client(const std::string& addr, unsigned port) { + // Create socket + connection = std::make_unique>(Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); //TODO: Care about IPv6 + if (*connection < 0) [[unlikely]] { + throw Exception("Failed to create TCP socket"); + } + + // Fetch address + fetchAddr(addr, port); + +# ifdef HAS_ADDRINFO + // Connect to server + if (connect(*connection, addrInfo->ai_addr, addrInfo->ai_addrlen) != 0) [[unlikely]] { + throw Exception("Connection for HTTP::Request has been declined"); + } +# else + // Connect to server + struct sockaddr_in sain; + sain.sin_family = AF_INET; + sain.sin_port = htons(port); + sain.sin_addr.s_addr = *reinterpret_cast(addrInfo->h_addr_list[0]); + if (connect(*connection, reinterpret_cast(&sain), sizeof(sain)) != 0) [[unlikely]] { + throw Exception("Connection has been declined"); + } +# endif +} + +void Client::ask(std::string_view prompt, const std::function& on_progress, const std::function& on_token) { + std::string fres; + + // Send prompt length + uint8_t len = prompt.length(); + connection->writeObject(len, true); + + // Send prompt + connection->write(prompt); + + // Receive progress + for (;;) { + uint8_t progress; + + // Receive percentage + connection->readObject(progress); + + // Run on_progress callback + on_progress(progress); + + // Stop at 100% + if (progress == 100) break; + } + + // Receive response + for (;;) { + // Receive response length + connection->readObject(len); + + // End if zero + if (len == 0xFF) break; + + // Receive response + const auto token = connection->read(len); + + // Run on_token callback + on_token(token); + } +} diff --git a/Client.hpp b/Client.hpp new file mode 100644 index 0000000..1532a4f --- /dev/null +++ b/Client.hpp @@ -0,0 +1,45 @@ +#ifndef CLIENT_HPP +#define CLIENT_HPP +#include "Runtime.hpp" +#include "Socket.hpp" +#include "Sender.hpp" +#include "Receiver.hpp" + +#include +#include +#include +#include +#include +#ifndef PLATFORM_WINDOWS +# include +#else +# include +#endif + + +class Client +{ + struct Exception : public std::runtime_error { + using std::runtime_error::runtime_error; + }; + + int fd = -1; +# ifdef HAS_ADDRINFO + addrinfo +# else + bool sent = false; + hostent +# endif + *addrInfo; // Can't be null unless request has already been sent + + std::unique_ptr> connection; + + void fetchAddr(const std::string& addr, unsigned port); + +public: + Client(const std::string &addr, unsigned port); + + void ask(std::string_view prompt, const std::function& on_progress, const std::function& on_token); +}; + +#endif // CLIENT_HPP diff --git a/Receiver.cpp b/Receiver.cpp new file mode 100644 index 0000000..2ceb695 --- /dev/null +++ b/Receiver.cpp @@ -0,0 +1,43 @@ +#include "Receiver.hpp" + +#include +#include +#ifndef PLATFORM_WINDOWS +# include +# include +#else +# include +#endif + + + +std::string Receiver::Simple::read(size_t amount) { + // Create buffer + std::string fres; + fres.resize(amount); + + // Read into buffer + read(reinterpret_cast(fres.data()), fres.size()); + + // Return final buffer + return fres; +} +void Receiver::Simple::read(std::byte *buffer, size_t size) { + recv(fd, buffer, size, MSG_WAITALL); +} + +std::string Receiver::Simple::readSome(size_t max) { + // Create buffer + std::string fres; + fres.resize(max); + + // Receive data + ssize_t bytesRead; + if ((bytesRead = recv(fd, fres.data(), max, MSG_WAITALL)) < 0) [[unlikely]] { + return ""; + } + + // Resize and return final buffer + fres.resize(bytesRead); + return fres; +} diff --git a/Receiver.hpp b/Receiver.hpp new file mode 100644 index 0000000..85ab5dc --- /dev/null +++ b/Receiver.hpp @@ -0,0 +1,30 @@ +#ifndef _RECEIVER_HPP +#define _RECEIVER_HPP +#include "Runtime.hpp" + +#include +#include + + +namespace Receiver { +class Simple { +protected: + int fd; + +public: + Simple(int fd) : fd(fd) {} + + // Reads the exact amount of bytes given + std::string read(size_t amount); + void read(std::byte *buffer, size_t size); + // Reads at max. the amount of bytes given + std::string readSome(size_t max); + + // Reads an object of type T + template + auto readObject(T& o) { + return read(reinterpret_cast(&o), sizeof(o)); + } +}; +} +#endif diff --git a/Runtime.cpp b/Runtime.cpp new file mode 100644 index 0000000..ff6f100 --- /dev/null +++ b/Runtime.cpp @@ -0,0 +1,47 @@ +#include "Runtime.hpp" + +#ifdef PLATFORM_3DS +#include +#include + +#include +#include <3ds.h> + + + +void Runtime::customTerminate() noexcept { + // Get error message + std::string message; + try { + std::rethrow_exception(std::current_exception()); + } catch (const std::exception& e) { + message = e.what(); + } catch (...) { + message = "Unknown"; + } + // Display error + errorConf conf = { + .type = ERROR_TEXT_WORD_WRAP, + .errorCode = errno, + .upperScreenFlag = ERROR_NORMAL, + .useLanguage = CFG_LANGUAGE_EN, + .Text = {L'I', L'N', L'V', L'A', L'L', L'I', L'D', L'\0'}, + .homeButton = true, + .softwareReset = false, + .appJump = false, + .returnCode = ERROR_UNKNOWN, + .eulaVersion = 0 + }; + errorText(&conf, ("An exception was thrown but never handled:\n\n"+message).c_str()); + errorDisp(&conf); + // Exit + aptExit(); + socExit(); + gfxExit(); + exit(-errno); +} +#elif PLATFORM_DS +void Runtime::kbCallback(int key) { + if (key > 0) printf("%c", key); +} +#endif diff --git a/Runtime.hpp b/Runtime.hpp new file mode 100644 index 0000000..e53075f --- /dev/null +++ b/Runtime.hpp @@ -0,0 +1,235 @@ +#ifndef _RUNTIME_HPP +#define _RUNTIME_HPP + +#ifdef PLATFORM_3DS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <3ds.h> + + +class Runtime { + u32 *SOC_buffer = NULL; + constexpr static auto SOC_ALIGN = 0x1000, + SOC_BUFFERSIZE = 0x100000; + + [[noreturn]] static void customTerminate() noexcept; + +public: + Runtime() { + std::set_terminate(customTerminate); + gfxInitDefault(); + consoleInit(GFX_TOP, NULL); + aptInit(); + SOC_buffer = (u32*)memalign(SOC_ALIGN, SOC_BUFFERSIZE); + auto ret = socInit(SOC_buffer, SOC_BUFFERSIZE); + if (ret != 0) { + throw std::runtime_error("socInit() = "+std::to_string((unsigned int)ret)); + } + } + Runtime(Runtime&) = delete; + Runtime(const Runtime&) = delete; + Runtime(Runtime&&) = delete; + ~Runtime() { + aptSetHomeAllowed(false); + std::cout << std::flush; + std::cerr << std::flush; + std::clog << std::endl << "Runtime destroyed." << std::endl; + std::clog << "Press START to exit" << std::flush; + for (u32 kDown; !(hidKeysDown() & KEY_START) && cooperate(); hidScanInput()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + aptExit(); + socExit(); + gfxExit(); + } + + static inline + bool cooperate() noexcept { + return aptMainLoop(); + } + + static const char *readInput(const char *hint) { + static SwkbdState swkbd; + static char swkbd_buf[2048]; + // Read input + memset(swkbd_buf, 0, sizeof(swkbd_buf)); + swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 3, sizeof(swkbd_buf)); + swkbdSetHintText(&swkbd, hint); + swkbdInputText(&swkbd, swkbd_buf, sizeof(swkbd_buf)); + // Return input as string + return swkbd_buf; + } + + static void clearScreen() { + consoleClear(); + } +}; +#elif PLATFORM_DS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class Runtime { + static void kbCallback(int key); + + Keyboard *swkbd; + +public: + Runtime() { + // Initialize console + consoleDemoInit(); + + // Initialize WiFi + std::cout << "Connecting via WFC data" << std::endl; + if (!Wifi_InitDefault(WFC_CONNECT)) { + throw std::runtime_error("Failed to enable WiFi"); + } + + // Initialize keyboard + swkbd = keyboardDemoInit(); + swkbd->OnKeyPressed = kbCallback; + } + Runtime(Runtime&) = delete; + Runtime(const Runtime&) = delete; + Runtime(Runtime&&) = delete; + ~Runtime() {} + + static inline + bool cooperate() noexcept { + // The Nintendo DS does not support multitasking + return true; + } + + static const char *readInput(const char *hint) { + std::cout << hint << ": " << std::flush; + static std::string outstr; + std::getline(std::cin, outstr); + return outstr.c_str(); + } + + static void clearScreen() { + consoleClear(); + } +}; +#elif PLATFORM_LINUX +#include +#include +#include +#include +#include +#include + + + +class Runtime { + static inline bool stopping = false; + static inline void handler(int signo, siginfo_t *info, void *context) { + stopping = true; + } + +public: + Runtime() { + struct sigaction act = { 0 }; + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = handler; + for (int sig : {SIGTERM, SIGINT, SIGQUIT, SIGHUP}) { + if (sigaction(sig, &act, nullptr) < 0) { + throw std::runtime_error("sigaction() = "+std::string(strerror(errno))); + } + } + } + Runtime(Runtime&) = delete; + Runtime(const Runtime&) = delete; + Runtime(Runtime&&) = delete; + ~Runtime() { + std::cout << std::flush; + std::cerr << std::flush; + std::clog << std::endl << "Runtime destroyed." << std::endl; + } + + static inline bool cooperate() noexcept { + // Linux runs threads preemptively, no need to actually cooperate + return !stopping; + } + + static const char *readInput(const char *hint) { + static std::string content; + std::cout << hint << ": "; + std::getline(std::cin, content); + return content.c_str(); + } + + static void clearScreen() { + std::cout << "\033[H\033[2J\033[3J"; + } +}; +#elif PLATFORM_WINDOWS +#include +#include +#include +#include + + + +class Runtime { +public: + Runtime() { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + throw std::runtime_error("Failed to initialize WinSock"); + } + } + Runtime(Runtime&) = delete; + Runtime(const Runtime&) = delete; + Runtime(Runtime&&) = delete; + ~Runtime() { + std::cout << std::flush; + std::cerr << std::flush; + std::clog << std::endl << "Runtime destroyed." << std::endl; + WSACleanup(); + } + + static constexpr bool cooperate() noexcept { + // Windows runs threads preemptively, no need to cooperate. + // No signals to handle either, Windows doesn't support them. + return true; + } + + static const char *readInput(const char *hint) { + static std::string content; + std::cout << hint << ": "; + std::getline(std::cin, content); + return content.c_str(); + } +}; +#endif +#endif + +#if !(defined(PLATFORM_WINDOWS) || defined(PLATFORM_DS)) +# define MSG_FLAGS_OR_ZERO(...) __VA_ARGS__ +#else +# define MSG_FLAGS_OR_ZERO(...) 0 +#endif + +#ifdef PLATFORM_DS +# define IPPROTO_TCP 0 +#endif + +#ifndef PLATFORM_DS +# define HAS_ADDRINFO +#endif diff --git a/Sender.cpp b/Sender.cpp new file mode 100644 index 0000000..23545bc --- /dev/null +++ b/Sender.cpp @@ -0,0 +1,24 @@ +#include "Runtime.hpp" +#include "Sender.hpp" + +#include +#ifndef PLATFORM_WINDOWS +# include +# include +#else +# include +# include +#endif + + + +void Sender::Simple::write(std::string_view str, bool moreData) { + this->write(reinterpret_cast(str.data()), str.size(), moreData); +} + +void Sender::Simple::write(const std::byte *data, size_t size, bool moreData) { + std::string fres; + + // Write + send(fd, reinterpret_cast(data), size, MSG_FLAGS_OR_ZERO(MSG_WAITALL | MSG_NOSIGNAL | (int(moreData)*MSG_MORE))); +} diff --git a/Sender.hpp b/Sender.hpp new file mode 100644 index 0000000..e829647 --- /dev/null +++ b/Sender.hpp @@ -0,0 +1,25 @@ +#ifndef _SENDER_HPP +#define _SENDER_HPP +#include +#include +#include + + +namespace Sender { +class Simple { +protected: + int fd; + +public: + Simple(int fd) : fd(fd) {} + + void write(std::string_view, bool moreData = false); + void write(const std::byte *data, size_t, bool moreData = false); + + template + auto writeObject(const T& o, bool moreData = false) { + return write(reinterpret_cast(&o), sizeof(o), moreData); + } +}; +} +#endif diff --git a/Socket.hpp b/Socket.hpp new file mode 100644 index 0000000..c05e597 --- /dev/null +++ b/Socket.hpp @@ -0,0 +1,68 @@ +#ifndef _SOCKET_HPP +#define _SOCKET_HPP +#include +#include +#if defined(PLATFORM_WINDOWS) +# include +#elif defined(PLATFORM_WII) +#include +#else +# include +# include +#endif + + +class Socket { + int fd; + +protected: + void reset() { + fd = -1; + } + void set(int _fd) { + fd = _fd; + } + +public: + using Port = uint16_t; + + Socket() : fd(-1) {} + Socket(int domain, int type, int protocol) { + fd = socket(domain, type, protocol); + } + Socket(Socket&) = delete; + Socket(const Socket&) = delete; + Socket(Socket&& o) : fd(o.fd) { + o.fd = -1; + } + auto& operator =(Socket&& o) { + close(fd); + fd = o.fd; + o.fd = -1; + return *this; + } + ~Socket() { + close(fd); + } + + operator int() const { + return fd; + } + + int get() const { + return fd; + } +}; + + +template +class SocketConnection : public SenderT, public ReceiverT, public Socket { +public: + SocketConnection(Socket&& socket) + // Double-initialization seems to yield better assembly + : SenderT(socket), ReceiverT(socket), Socket(std::move(socket)) { + SenderT::fd = get(); + ReceiverT::fd = get(); + } +}; +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..052d5f4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,45 @@ +#include "Runtime.hpp" +#include "Client.hpp" + +#include +#include +#include + + + +void on_progress(float progress) { + std::cout << unsigned(progress) << '\r' << std::flush; +} + +int main() +{ + Runtime rt; + + // Print header + std::cout << "llama.any running on " PLATFORM ".\n" + "\n"; + + // Ask for server address + const std::string addr = rt.readInput("Server address"); + + // Create client + Client client(addr, 99181); + + // Connection loop + for (;; rt.cooperate()) { + // Clear screen + rt.clearScreen(); + + // Run inference + client.ask(rt.readInput("Prompt"), [&rt] (float progress) { + std::cout << unsigned(progress) << "%\r" << std::flush; + rt.cooperate(); + }, [&rt] (std::string_view token) { + std::cout << token << std::flush; + rt.cooperate(); + }); + std::cout << "\n"; + } + + return 0; +}