aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/acronc/distro/arch/PKGBUILD2
-rw-r--r--client/applets/applet.c151
-rw-r--r--client/applets/applet.h20
-rw-r--r--client/applets/here.c92
-rw-r--r--client/applets/meson.build21
l---------client/applets/subprojects/libacron1
6 files changed, 286 insertions, 1 deletions
diff --git a/client/acronc/distro/arch/PKGBUILD b/client/acronc/distro/arch/PKGBUILD
index 7eeefe8..c5af468 100644
--- a/client/acronc/distro/arch/PKGBUILD
+++ b/client/acronc/distro/arch/PKGBUILD
@@ -7,7 +7,7 @@ arch=(x86_64)
url="https://yuuta.moe/acron"
license=('LGPL')
groups=()
-depends=(json-c libacron)
+depends=(json-c libacron libuv)
makedepends=(git meson ninja)
provides=()
conflicts=()
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 <libac.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <err.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+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=<ID> TOKEN=<TOKEN> ./here # Will connect to 127.0.0.1:PORT
+ *
+ * In Minecraft, say `.h'.
+ */
+
+#ifndef APPLET_H
+#define APPLET_H
+
+#include <libac.h>
+
+#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 <stdio.h>
+#include <string.h>
+#include <err.h>
+
+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