summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/peer.c156
-rw-r--r--src/server.c230
-rw-r--r--src/str.c83
3 files changed, 469 insertions, 0 deletions
diff --git a/src/peer.c b/src/peer.c
new file mode 100644
index 0000000..f719622
--- /dev/null
+++ b/src/peer.c
@@ -0,0 +1,156 @@
+// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include "peer.h"
+
+void peer_init(peer *p, int socket)
+{
+ p->socket = socket;
+ p->disco = false;
+
+ p->in.header = true;
+ p->in.len = 0;
+ p->in.promised = 0;
+ p->in.buffer = malloc(PEER_INBUFFER_SIZE);
+
+ p->out.cursor = 0;
+ p->out.avail = 0;
+ p->out.buffer = malloc(PEER_OUTBUFFER_SIZE);
+}
+
+void peer_free(peer *p)
+{
+ close(p->socket);
+ free(p->in.buffer);
+ free(p->out.buffer);
+}
+
+// in
+
+static void next_in(peer *p, bool header, size_t len)
+{
+ p->in.header = header;
+ p->in.len = 0;
+ p->in.buffer = malloc(p->in.promised = len);
+}
+
+static bool peer_in_ready(peer *p)
+{
+ ssize_t got = read(p->socket, p->in.buffer + p->in.len, p->in.promised - p->in.len);
+ if (got < 0) {
+ switch (errno) {
+ case ECONNRESET:
+ p->disco = true;
+ return true;
+ case EINTR:
+ return peer_in_ready(p); // retry
+ default:
+ perror("read");
+ return false;
+ }
+ }
+
+ p->in.len += got;
+ if (p->in.len == p->in.promised && p->in.len != 0) {
+ if (!p->in.header)
+ return true;
+
+ size_t len = *(pkt_header *) p->in.buffer;
+ if (len > PEER_INBUFFER_SIZE)
+ // TODO: figure out what to do if packet too large (disconnect?)
+ next_in(p, true, sizeof(pkt_header));
+ else
+ next_in(p, false, len);
+ }
+
+ return false;
+}
+
+// out
+
+static void send_raw(peer *p, uint8_t *data, size_t len)
+{
+ memcpy(p->out.buffer + p->out.cursor + p->out.avail, data, len);
+ p->out.avail += len;
+}
+
+static bool out_space(peer *p, size_t len)
+{
+ if (len + p->out.avail > PEER_OUTBUFFER_SIZE)
+ return false;
+
+ if (p->out.cursor + p->out.avail + len > PEER_OUTBUFFER_SIZE) {
+ memmove(p->out.buffer, p->out.buffer + p->out.cursor, p->out.avail);
+ p->out.cursor = 0;
+ }
+
+ return true;
+}
+
+bool peer_send(peer *p, uint8_t *data, size_t len)
+{
+ if (len > PEER_INBUFFER_SIZE)
+ return false;
+
+ pkt_header hdr = (pkt_header) len;
+
+ if (!out_space(p, sizeof hdr + len))
+ return false;
+
+ send_raw(p, (uint8_t *) &hdr, sizeof hdr);
+ send_raw(p, data, len);
+
+ return true;
+}
+
+static bool peer_out_ready(peer *p)
+{
+ ssize_t written = write(p->socket, p->out.buffer + p->out.cursor, p->out.avail);
+ if (written < 0) {
+ switch (errno) {
+ case ECONNRESET:
+ p->disco = true;
+ return true;
+ case EINTR:
+ return peer_out_ready(p);
+ default:
+ perror("write");
+ return false;
+ }
+ }
+
+ p->out.avail -= written;
+ if (p->out.avail == 0)
+ p->out.cursor = 0;
+
+ return false;
+}
+
+// poll
+
+short peer_prepare(peer *p)
+{
+ if (p->in.len == p->in.promised)
+ next_in(p, true, sizeof(pkt_header));
+
+ return POLLIN | (p->out.avail ? POLLOUT : 0);
+}
+
+bool peer_ready(peer *p, short revents)
+{
+ bool x = false;
+
+ if (revents & POLLIN)
+ x = x || peer_in_ready(p);
+ if (revents & POLLOUT)
+ x = x || peer_out_ready(p);
+
+ return x;
+}
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..73a6475
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,230 @@
+// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <png.h>
+#include <endian.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "content.h"
+#include "peer.h"
+#include "str.h"
+
+typedef enum {
+ MAP_VALLEY,
+ MAP_ISLAND,
+ MAP_MOUNTAIN,
+ MAP_CAVE,
+ MAP_NUM,
+} map_type;
+
+typedef struct {
+ const char *filename;
+ str name;
+ map_type type;
+ uint32_t width;
+ uint32_t height;
+ int32_t off_x;
+ int32_t off_y;
+ int8_t z; // caves: -1
+ node *nodes;
+} map;
+
+typedef struct {
+ str motd;
+ array(map) maps;
+} game;
+
+typedef struct {
+ str name;
+ int32_t x, y;
+ peer net;
+} player;
+
+node *map_local_node(map *m, int32_t x, int32_t y)
+{
+ if (x < 0 || y < 0 || (uint32_t) x >= m->width || (uint32_t) y >= m->height)
+ return NULL;
+
+ node *n = &m->nodes[x*m->width+y];
+ if (!n->present)
+ return NULL;
+
+ return n;
+}
+
+node *map_node(game *g, int32_t x, int32_t y, int8_t z)
+{
+ for (size_t i = 0; i < g->maps.len; i++) {
+ map *m = &g->maps.data[i];
+ if (m->z != z)
+ continue;
+ node *n = map_local_node(m, x - m->off_x, y - m->off_y);
+ if (n != NULL)
+ return n;
+ }
+ return NULL;
+}
+
+/*
+void map_load_node(map *m, uint32_t x, uint32_t y, uint32_t color)
+{
+
+}*/
+
+bool map_load(map *m)
+{
+#define TRY(expr, ...) if (!(expr)) { \
+ fprintf(stderr, __VA_ARGS__); \
+ if (file != NULL) fclose(file); \
+ png_destroy_read_struct(&png, &info, NULL); \
+ return false; }
+
+ FILE *file = NULL;
+ png_structp png = NULL;
+ png_infop info = NULL;
+
+ TRY((file = fopen(m->filename, "r")) != NULL, "failed to open %s\n", m->filename);
+
+ TRY(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL),
+ "png_create_read_struct failed\n");
+
+ TRY(info = png_create_info_struct(png), "png_create_info_struct failed\n");
+
+ png_init_io(png, file);
+ png_read_info(png, info);
+
+ png_uint_32 width = png_get_image_width(png, info);
+ png_uint_32 height = png_get_image_height(png, info);
+ png_byte color_type = png_get_color_type(png, info);
+
+ TRY(width == m->width, "%s: width mismatch\n", m->filename);
+ TRY(height == m->height, "%s: height mismatch\n", m->filename);
+ TRY(color_type == PNG_COLOR_TYPE_RGB, "%s: color type is not RGB\n", m->filename);
+
+ uint32_t colors[100] = {0};
+
+ png_uint_32 pitch = png_get_rowbytes(png, info);
+ png_byte row[pitch];
+ for (png_uint_32 y = 0; y < height; y++) {
+ png_read_row(png, row, NULL);
+
+ for (png_uint_32 x = 0; x < width; x++) {
+ png_bytep p = &row[x*3];
+
+ uint32_t color = ((uint32_t) p[2]) | (((uint32_t) p[1]) << 8) | (((uint32_t) p[0]) << 16);
+ for (size_t i = 0; i < 100; i++) {
+ if (colors[i] == color) {
+ break;
+ } else if (colors[i] == 0) {
+ colors[i] = color;
+ printf("#%06x\n", color);
+ break;
+ }
+ }
+ }
+ }
+
+ fclose(file);
+ png_destroy_read_struct(&png, &info, NULL);
+ return true;
+#undef TRY
+}
+
+int net_listen(const char *host, const char *port)
+{
+ struct addrinfo *info = NULL, hints = {0};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err;
+ if ((err = getaddrinfo(host, port, &hints, &info))) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
+ return -1;
+ }
+
+#define TRY(op, val) if ((val) < 0) { perror(op); freeaddrinfo(info); return -1; }
+
+ int fd;
+ TRY("socket", fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol))
+
+ int flag = 1;
+ TRY("setsockopt", setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &flag, sizeof flag));
+
+ TRY("bind", bind(fd, info->ai_addr, info->ai_addrlen))
+ TRY("listen", listen(fd, 3))
+
+#undef TRY
+
+ freeaddrinfo(info);
+ return fd;
+
+}
+
+int game_exit(game *g, int ret)
+{
+ free(g->motd.data);
+
+ if (g->maps.data)
+ for (size_t i = 0; i < g->maps.len; i++) {
+ free(g->maps.data[i].name.data);
+ free(g->maps.data[i].nodes);
+ }
+
+ free(g->maps.data);
+
+ exit(ret);
+}
+
+int main()
+{
+ game g = {0};
+
+ g.motd = str_clone(S("Welcome to test server"));
+
+ g.maps.len = 1;
+ g.maps.data = malloc(g.maps.len * sizeof *g.maps.data);
+ g.maps.data[0] = (map) {
+ .filename = "assets/map_valley.png",
+ .name = str_clone(S("Valley")),
+ .type = MAP_VALLEY,
+ .off_x = -50,
+ .off_y = -50,
+ .z = 0,
+ .width = 100,
+ .height = 100,
+ .nodes = NULL,
+ };
+
+ if (!map_load(&g.maps.data[0]))
+ game_exit(&g, EXIT_FAILURE);
+
+ int accept_fd = net_listen("0.0.0.0", "4560");
+ if (accept_fd < 0)
+ game_exit(&g, EXIT_FAILURE);
+
+ for (;;) {
+ int clt_sock = accept(accept_fd, NULL, NULL);
+ if (clt_sock < 0) {
+ perror("accept");
+ continue;
+ }
+
+ if (fcntl(clt_sock, F_SETFL, fcntl(clt_sock, F_GETFL, 0) | O_NONBLOCK) < 0) {
+ perror("fcntl");
+ continue;
+ }
+ }
+
+}
diff --git a/src/str.c b/src/str.c
new file mode 100644
index 0000000..4f6d5c7
--- /dev/null
+++ b/src/str.c
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: 2024 Lizzy Fleckenstein <lizzy@vlhl.dev>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+#include <stdlib.h>
+#include <string.h>
+#include "str.h"
+
+int str_cmp(str s1, str s2)
+{
+ if (s1.len != s2.len)
+ return (int) s1.len - (int) s2.len;
+
+ return memcmp(s1.data, s2.data, s1.len);
+}
+
+static bool match_char(char c, str tokens)
+{
+ for (size_t t = 0; t < tokens.len; t++)
+ if (c == tokens.data[t])
+ return true;
+
+ return false;
+}
+
+size_t str_find(str s, str tokens)
+{
+ for (size_t i = 0; i < s.len; i++)
+ if (match_char(s.data[i], tokens))
+ return i;
+
+ return s.len;
+}
+
+str str_walk(str *s, str sep)
+{
+ if (s->len == 0)
+ return NILS;
+
+ size_t x = str_find(*s, sep);
+ size_t o = x + (x < s->len);
+
+ *s = str_advance(*s, o);
+
+ if (x == 0)
+ return str_walk(s, sep);
+ else
+ return (str) { x, s->data - o };
+}
+
+str str_eat(str s, str tokens)
+{
+ while (s.len > 0 && match_char(s.data[0], tokens))
+ s = str_advance(s, 1);
+ return s;
+}
+
+str str_advance(str s, size_t x)
+{
+ s.len -= x;
+ s.data += x;
+ return s;
+}
+
+bool str_start(str s, str start)
+{
+ if (s.len < start.len)
+ return false;
+ s.len = start.len;
+ return str_cmp(s, start) == 0;
+}
+
+str str_intro(char *c)
+{
+ return (str) { strlen(c), c };
+}
+
+str str_clone(str s)
+{
+ str c = { s.len, malloc(s.len) };
+ memcpy(c.data, s.data, s.len);
+ return c;
+}