1
0
Fork 0
mirror of https://gitlab.com/niansa/llama_nds.git synced 2025-03-06 20:53:28 +01:00

Initial commit

This commit is contained in:
niansa 2023-04-03 22:06:29 +02:00
commit 06f836c0d4
12 changed files with 779 additions and 0 deletions

74
.gitignore vendored Normal file
View file

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

30
CMakeLists.txt Normal file
View file

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

113
Client.cpp Normal file
View file

@ -0,0 +1,113 @@
#include "Client.hpp"
#include "Socket.hpp"
#include "Sender.hpp"
#include "Receiver.hpp"
#include <cerrno>
#ifndef PLATFORM_WINDOWS
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#else
# include <ws2tcpip.h>
#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<SocketConnection<Sender::Simple, Receiver::Simple>>(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<unsigned long *>(addrInfo->h_addr_list[0]);
if (connect(*connection, reinterpret_cast<sockaddr *>(&sain), sizeof(sain)) != 0) [[unlikely]] {
throw Exception("Connection has been declined");
}
# endif
}
void Client::ask(std::string_view prompt, const std::function<void (unsigned progress)>& on_progress, const std::function<void (std::string_view token)>& 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);
}
}

45
Client.hpp Normal file
View file

@ -0,0 +1,45 @@
#ifndef CLIENT_HPP
#define CLIENT_HPP
#include "Runtime.hpp"
#include "Socket.hpp"
#include "Sender.hpp"
#include "Receiver.hpp"
#include <string>
#include <string_view>
#include <stdexcept>
#include <memory>
#include <functional>
#ifndef PLATFORM_WINDOWS
# include <netdb.h>
#else
# include <ws2tcpip.h>
#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<SocketConnection<Sender::Simple, Receiver::Simple>> 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<void (unsigned progress)>& on_progress, const std::function<void (std::string_view token)>& on_token);
};
#endif // CLIENT_HPP

43
Receiver.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "Receiver.hpp"
#include <string_view>
#include <array>
#ifndef PLATFORM_WINDOWS
# include <sys/select.h>
# include <sys/socket.h>
#else
# include <ws2tcpip.h>
#endif
std::string Receiver::Simple::read(size_t amount) {
// Create buffer
std::string fres;
fres.resize(amount);
// Read into buffer
read(reinterpret_cast<std::byte*>(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;
}

30
Receiver.hpp Normal file
View file

@ -0,0 +1,30 @@
#ifndef _RECEIVER_HPP
#define _RECEIVER_HPP
#include "Runtime.hpp"
#include <string>
#include <cstddef>
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<typename T>
auto readObject(T& o) {
return read(reinterpret_cast<std::byte *>(&o), sizeof(o));
}
};
}
#endif

47
Runtime.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "Runtime.hpp"
#ifdef PLATFORM_3DS
#include <string>
#include <exception>
#include <cerrno>
#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

235
Runtime.hpp Normal file
View file

@ -0,0 +1,235 @@
#ifndef _RUNTIME_HPP
#define _RUNTIME_HPP
#ifdef PLATFORM_3DS
#include <iostream>
#include <string>
#include <string_view>
#include <thread>
#include <chrono>
#include <exception>
#include <stdexcept>
#include <cstring>
#include <malloc.h>
#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 <iostream>
#include <string>
#include <string_view>
#include <thread>
#include <chrono>
#include <exception>
#include <stdexcept>
#include <malloc.h>
#include <nds.h>
#include <dswifi9.h>
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 <iostream>
#include <stdexcept>
#include <mutex>
#include <cstring>
#include <csignal>
#include <cerrno>
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 <iostream>
#include <mutex>
#include <stdexcept>
#include <winsock2.h>
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

24
Sender.cpp Normal file
View file

@ -0,0 +1,24 @@
#include "Runtime.hpp"
#include "Sender.hpp"
#include <string_view>
#ifndef PLATFORM_WINDOWS
# include <sys/socket.h>
# include <sys/select.h>
#else
# include <ws2tcpip.h>
# include <winsock2.h>
#endif
void Sender::Simple::write(std::string_view str, bool moreData) {
this->write(reinterpret_cast<const std::byte*>(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<const char*>(data), size, MSG_FLAGS_OR_ZERO(MSG_WAITALL | MSG_NOSIGNAL | (int(moreData)*MSG_MORE)));
}

25
Sender.hpp Normal file
View file

@ -0,0 +1,25 @@
#ifndef _SENDER_HPP
#define _SENDER_HPP
#include <string_view>
#include <vector>
#include <cstddef>
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<typename T>
auto writeObject(const T& o, bool moreData = false) {
return write(reinterpret_cast<const std::byte *>(&o), sizeof(o), moreData);
}
};
}
#endif

68
Socket.hpp Normal file
View file

@ -0,0 +1,68 @@
#ifndef _SOCKET_HPP
#define _SOCKET_HPP
#include <memory>
#include <unistd.h>
#if defined(PLATFORM_WINDOWS)
# include <ws2tcpip.h>
#elif defined(PLATFORM_WII)
#include <network.h>
#else
# include <sys/socket.h>
# include <sys/select.h>
#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 SenderT, class ReceiverT>
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

45
main.cpp Normal file
View file

@ -0,0 +1,45 @@
#include "Runtime.hpp"
#include "Client.hpp"
#include <iostream>
#include <string>
#include <string_view>
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;
}