From 46cb0b529a16a7eae90bc332b7b42eb9c48a5cdd Mon Sep 17 00:00:00 2001 From: Trumeet Date: Thu, 5 Jan 2023 22:48:31 -0800 Subject: feat(applets): add applets --- client/applets/applet.c | 151 ++++++++++++++++++++++++++++++++++++ client/applets/applet.h | 20 +++++ client/applets/here.c | 92 ++++++++++++++++++++++ client/applets/meson.build | 21 +++++ client/applets/subprojects/libacron | 1 + 5 files changed, 285 insertions(+) create mode 100644 client/applets/applet.c create mode 100644 client/applets/applet.h create mode 100644 client/applets/here.c create mode 100644 client/applets/meson.build create mode 120000 client/applets/subprojects/libacron diff --git a/client/applets/applet.c b/client/applets/applet.c new file mode 100644 index 0000000..3f02e60 --- /dev/null +++ b/client/applets/applet.c @@ -0,0 +1,151 @@ +#include "applet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int fd = 0; +void *connection = NULL; + +const char *get_world(enum ac_world world) { + switch (world) { + case overworld: return "overworld"; + case nether: return "nether"; + case end: return "end"; + default: return "unknown"; + } +} + +static void cleanup(void) { + if (connection) ac_disconnect(connection, false); + if (fd) close(fd); + ac_free(); +} + +static void intr(int signum) {} + +static int on_send(const void *sock, + const void *buffer, + const size_t len) { + size_t pos; + const uint8_t *ptr = buffer; + int retval; + for (pos = 0U; pos < len; pos += retval) { + size_t n = len - pos; + retval = (int) send(fd, &ptr[pos], (int) n, 0); + if (retval < 0) { + const int e = errno; + perror("Cannot write to socket"); + return e; + } + } + return 0; +} + +/* Move init to a separate function from main() so we do not need to keep those stack variables all the time. */ +static void init(void) { + const char *id = getenv("ID"); + const char *token = getenv("TOKEN"); + if (!id) errx(64, "Required envvar: ID"); + if (!token) errx(64, "Required envvar: TOKEN"); + + atexit(cleanup); + int r; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0) err(errno, "Cannot create socket"); + struct in_addr ad = { + .s_addr = 127 | 0 << 8 | 0 << 16 | 1 << 24 + }; + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(PORT), + .sin_addr = ad + }; + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) err(errno, "Cannot connect to server"); + libac_config_t libac_conf = { + .tok = NULL, +#ifdef DEBUG + .out = stderr, +#else + .out = NULL +#endif + }; + if ((r = ac_init(&libac_conf))) errx(r, "Cannot start libac: %d.", r); + ac_connection_parameters_t param = { + .host = "127.0.0.1", + .port = PORT, + .id = (char *) id, + .token = (char *) token, + .version = 0, + .sock = &fd, + .on_send = on_send + }; + if ((r = ac_connect(param, &connection))) errx(r, "Cannot connect to server: %d.", r); + struct sigaction sa; + sa.sa_handler = intr; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGINT, &sa, NULL) == -1) err(errno, "Cannot set SIGINT handler."); + if (sigaction(SIGTERM, &sa, NULL) == -1) err(errno, "Cannot set SIGTERM handler."); +} + +int main(void) { + init(); + int r; + int bytes; + uint8_t buffer[1024U]; + ac_obj_t *obj = NULL; + bool ready = false; + while (1) { + if ((bytes = (int) recv(fd, buffer, sizeof(buffer), 0)) <= 0) { + if (bytes < 0) { + if (errno == EAGAIN) continue; + if (errno == EINTR) { + printf("Exit.\n"); + return 0; + } + err(errno, "Cannot read from socket"); + } + errx(1, "Connection aborted"); + } + size_t pos = 0; + size_t read = 0; + bool again = false; + while (pos < bytes || again) { + if (again) { + r = ac_receive(connection, NULL, 0, 0, &obj, NULL); + again = false; + } else { + r = ac_receive(connection, buffer, pos, bytes, &obj, &read); + pos += read; + } + if (r == AC_E_AGAIN) { + again = true; + } else if (r) { + errx(r, "Cannot parse server response: %d.", r); + } + if (!ready) { + enum ac_connection_state state; + if ((r = ac_get_state(connection, &state))) { + errx(r, "Cannot read state: %d.", r); + } + if (state == AC_STATE_READY) { + printf("Connected.\n"); + ready = true; + continue; + } + } + if (!obj) { + continue; + } + } + } +} diff --git a/client/applets/applet.h b/client/applets/applet.h new file mode 100644 index 0000000..a17be52 --- /dev/null +++ b/client/applets/applet.h @@ -0,0 +1,20 @@ +/** + * Usage: + * ID= TOKEN= ./here # Will connect to 127.0.0.1:PORT + * + * In Minecraft, say `.h'. + */ + +#ifndef APPLET_H +#define APPLET_H + +#include + +#define PORT 25575 + +extern void *connection; + +const char *get_world(enum ac_world world); +void handle(ac_obj_t *obj); + +#endif /* APPLET_H */ diff --git a/client/applets/here.c b/client/applets/here.c new file mode 100644 index 0000000..d2f61ef --- /dev/null +++ b/client/applets/here.c @@ -0,0 +1,92 @@ +#include "applet.h" + +#include +#include +#include + +static int snd(const ac_entity_t *player, const char *prefix, const char *postfix) { + char m[512]; + if (prefix && !postfix) { + snprintf(m, 512, + "tellraw @a " + "[\"\",{\"text\":\"%s %s @ %s[x:%.0lf, y:%.0lf, z:%.0lf]\"}]", + player->name, + prefix, + get_world(player->world), + player->pos.x, + player->pos.y, + player->pos.z + ); + } else if (prefix && postfix) { + snprintf(m, 512, + "tellraw @a " + "[\"\",{\"text\":\"%s %s @ %s[x:%.0lf, y:%.0lf, z:%.0lf]: %s\"}]", + player->name, + prefix, + get_world(player->world), + player->pos.x, + player->pos.y, + player->pos.z, + postfix + ); + } else { + snprintf(m, 512, + "tellraw @a " + "[\"\",{\"text\":\"%s @ %s[x:%.0lf, y:%.0lf, z:%.0lf]\"}]", + player->name, + get_world(player->world), + player->pos.x, + player->pos.y, + player->pos.z + ); + } + ac_request_cmd_t cmd = { + .type = AC_REQUEST_CMD, + .id = 1, + .config = NULL, + .cmd = m + }; + int r; + if ((r = ac_request(connection, (ac_request_t *) &cmd))) { + return r; + } + return 0; +} + +void handle(ac_obj_t *obj) { + int r = 0; + switch (obj->type) { + case AC_RESPONSE_ERROR: { + ac_response_error_t *err = (ac_response_error_t *) obj; + fprintf(stderr, "Err: %s (%d).\n", err->message, err->code); + break; + } + case AC_EVENT_PLAYER_JOIN: { + ac_event_player_join_t *join = (ac_event_player_join_t *) obj; + r = snd(&join->player, "joined", NULL); + break; + } + case AC_EVENT_PLAYER_MESSAGE: { + ac_event_player_message_t *msg = (ac_event_player_message_t *) obj; + if (strncmp(".h", msg->text, 2)) { + break; + } + r = snd(&msg->player, NULL, NULL); + break; + } + case AC_RESPONSE_CMD_OUT: { + ac_response_cmd_out_t *out = (ac_response_cmd_out_t *) obj; + fprintf(stderr, "%s\n", out->out); + break; + } + case AC_RESPONSE_CMD_RESULT: { + ac_response_cmd_result_t *res = (ac_response_cmd_result_t *) obj; + if (!res->success) { + fprintf(stderr, "CmdErr: %d.\n", res->result); + } + break; + } + } + ac_object_free(obj); + if (r) errx(r, "Cannot send message: %d.", r); +} diff --git a/client/applets/meson.build b/client/applets/meson.build new file mode 100644 index 0000000..b89b9dc --- /dev/null +++ b/client/applets/meson.build @@ -0,0 +1,21 @@ +project('acron-applets', 'c', + version : '1.0', + license : 'GPL-2.0-only') + +xtra_link_args = [] +xtra_c_args = [ '-D_POSIX_C_SOURCE=200809L' ] +if get_option('debug') == true + xtra_c_args += '-DDEBUG' +endif + +libac_dep = dependency('ac', + fallback : ['libacron', 'libac_dep'], + default_options: [ 'default_library=static' ] +) + +executable('here', [ 'applet.c', 'here.c' ], + dependencies : [ libac_dep ], + link_args : xtra_link_args, + c_args : xtra_c_args, + install : true +) diff --git a/client/applets/subprojects/libacron b/client/applets/subprojects/libacron new file mode 120000 index 0000000..7af75e9 --- /dev/null +++ b/client/applets/subprojects/libacron @@ -0,0 +1 @@ +../../libacron \ No newline at end of file -- cgit v1.2.3