commit 06f836c0d408c21ebea8b8c6948e5679fdc1b753 Author: niansa Date: Mon Apr 3 22:06:29 2023 +0200 Initial commit 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; +}