summaryrefslogtreecommitdiff
path: root/src/game.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/game.c')
-rw-r--r--src/game.c629
1 files changed, 629 insertions, 0 deletions
diff --git a/src/game.c b/src/game.c
new file mode 100644
index 0000000..2337497
--- /dev/null
+++ b/src/game.c
@@ -0,0 +1,629 @@
+#include "packet.h"
+#include "db.h"
+#include "game.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <err.h>
+#include <assert.h>
+
+#include <unistd.h>
+#include <poll.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#define CENTER(x) (x[0][0] + (x[1][0] - x[0][0]) / 2), (x[0][1] + (x[1][1] - x[0][1]) / 2), (x[0][2] + (x[1][2] - x[0][2]) / 2)
+
+typedef nfds_t client_id;
+
+static struct {
+ struct {
+ char name[13];
+ float pos[3];
+ uint32_t id, zone;
+ uint8_t model, evolution, lvl;
+ } character[1024];
+ uint32_t session_id[1024];
+ struct pollfd pfds[1024];
+ nfds_t npfds;
+ struct { struct db login; } db;
+} server;
+
+enum {
+ FLUSH_TO = 1<<0,
+ FLUSH_ZONE = 1<<1,
+ FLUSH_ALL = 1<<2,
+ FLUSH_EXCLUDE_SENDER = 1<<3,
+};
+
+union flush_arg {
+ client_id to;
+ uint32_t zone;
+};
+
+static void
+flush_packet(client_id sender, union packet *packet, uint32_t filter, union flush_arg arg)
+{
+ packet_crypt(packet);
+
+ if (filter & FLUSH_TO) {
+ (void) !write(server.pfds[arg.to].fd, packet->buf, packet->hdr.size);
+ } else if (filter & FLUSH_ZONE || filter & FLUSH_ALL) {
+ for (nfds_t i = 1; i < server.npfds; ++i) {
+ if ((filter & FLUSH_EXCLUDE_SENDER) && i == sender)
+ continue;
+ if ((filter & FLUSH_ZONE) && server.character[sender].zone != server.character[i].zone)
+ continue;
+ (void) !write(server.pfds[i].fd, packet->buf, packet->hdr.size);
+ }
+ }
+}
+
+static void
+stub(client_id cid, union packet *packet, uint16_t type, uint8_t byte)
+{
+ packet->hdr.type = type;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ for (size_t i = 0; i < 1024; ++i)
+ pbuf_write(&pbuf, (uint8_t[]){byte}, 1); // unknown
+ pbuf_flush(&pbuf);
+ warnx("sending stub 0x%.4x", type);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+load_shop_banners(client_id cid, union packet *packet, const char *urls[], size_t num_urls)
+{
+ packet->hdr.type = PACKET_SHOP_BANNER_LOAD;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint32_t[]){num_urls}, sizeof(uint32_t)); // num banners
+ for (size_t i = 0; i < num_urls; ++i) {
+ pbuf_write_str(&pbuf, urls[i]); // url
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ }
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+load_pcs_in_zone(client_id cid, union packet *packet, uint32_t zone)
+{
+ packet->hdr.type = PACKET_OTHER_PC_INFOS;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint16_t[]){0x00}, sizeof(uint16_t)); // num pcs (placeholder);
+
+ for (uint16_t i = 1; i < server.npfds; ++i) {
+ if (i == cid || server.character[cid].zone != zone)
+ continue;
+
+ pbuf_write(&pbuf, &server.character[i].id, sizeof(uint32_t)); // charid
+ pbuf_write_str_utf16(&pbuf, server.character[i].name); // char name UTF16
+ pbuf_write(&pbuf, &server.character[i].model, 1); // char model (lily, haru, ...), 0 for no char
+ pbuf_write(&pbuf, &server.character[i].evolution, 1); // evolution lvl
+ for (size_t i = 0; i < 20; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // skin/eye/body/etc
+ pbuf_write(&pbuf, &server.character[i].lvl, 1); // char lvl
+ for (size_t i = 0; i < 452; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // fashion?
+ pbuf_write_str_len(&pbuf, NULL, 0); // guild name
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){155, 155}, sizeof(uint32_t) * 2); // hp, max hp
+ pbuf_write(&pbuf, (uint32_t[]){133, 133}, sizeof(uint32_t) * 2); // sf, max sf
+ for (size_t i = 0; i < 8; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){300, 400}, sizeof(uint32_t) * 2); // unknown
+ for (size_t i = 0; i < 10; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){0x42f0, 0x42d2}, sizeof(uint32_t) * 2); // movement speed?
+ for (size_t i = 0; i < 19; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, &zone, sizeof(zone)); // zone
+ for (size_t i = 0; i < 6; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, server.character[i].pos, sizeof(float) * 3); // xyz
+ for (size_t i = 0; i < 76; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // prologue?
+ pbuf_write(&pbuf, (uint8_t[]){0x01}, 1); // unknown
+ memcpy(packet->buf + sizeof(packet->hdr), &i, sizeof(uint16_t));
+ }
+
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+send_pc_info(client_id cid, union packet *packet, bool to_self)
+{
+ packet->hdr.type = (to_self ? PACKET_CHARACTER_INFO_RES : PACKET_IN_PC_INFO);
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &server.character[cid].id, sizeof(uint32_t)); // charid
+ pbuf_write_str_utf16(&pbuf, server.character[cid].name); // char name UTF16
+ pbuf_write(&pbuf, &server.character[cid].model, 1); // char model (lily, haru, ...), 0 for no char
+ pbuf_write(&pbuf, &server.character[cid].evolution, 1); // evolution lvl
+ for (size_t i = 0; i < 20; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // skin/eye/body/etc
+ pbuf_write(&pbuf, &server.character[cid].lvl, 1); // char lvl
+ for (size_t i = 0; i < 452; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // fashion?
+ pbuf_write_str_len(&pbuf, NULL, 0); // guild name
+ pbuf_write(&pbuf, (uint32_t[]){0x00}, sizeof(uint32_t)); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){155, 155}, sizeof(uint32_t) * 2); // hp, max hp
+ pbuf_write(&pbuf, (uint32_t[]){133, 133}, sizeof(uint32_t) * 2); // sf, max sf
+ for (size_t i = 0; i < 8; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){300, 400}, sizeof(uint32_t) * 2); // unknown
+ for (size_t i = 0; i < 10; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint32_t[]){0x42f0, 0x42d2}, sizeof(uint32_t) * 2); // movement speed?
+ for (size_t i = 0; i < 19; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, &server.character[cid].zone, sizeof(uint32_t)); // zone
+ for (size_t i = 0; i < 6; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, server.character[cid].pos, sizeof(float) * 3); // xyz
+ for (size_t i = 0; i < 76; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // prologue?
+ pbuf_write(&pbuf, (uint8_t[]){0x01}, 1); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, (to_self ? FLUSH_TO : FLUSH_ZONE | FLUSH_EXCLUDE_SENDER), (union flush_arg){ .to = cid });
+}
+
+static void
+send_despawn(client_id cid, union packet *packet)
+{
+ packet->hdr.type = PACKET_OUT_INFO_PC;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &server.character[cid].id, sizeof(uint32_t)); // charid
+ pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_ZONE | FLUSH_EXCLUDE_SENDER, (union flush_arg){0});
+}
+
+static void
+spawn_to(client_id cid, union packet *packet, uint32_t zone, float x, float y, float z)
+{
+ warnx("spawning to: 0x%.4x", zone);
+
+ send_despawn(cid, packet);
+ server.character[cid].zone = zone;
+ server.character[cid].pos[0] = x;
+ server.character[cid].pos[1] = y;
+ server.character[cid].pos[2] = z;
+ send_pc_info(cid, packet, true);
+
+ packet->hdr.type = PACKET_SKILLS;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ for (size_t i = 0; i < 232; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+
+}
+
+static void
+zone_to(client_id cid, union packet *packet, uint32_t zone, float x, float y, float z)
+{
+ warnx("zoning to: 0x%.4x", zone);
+
+ packet->hdr.type = PACKET_GAME_WORLD_ENTER_RES;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &server.character[cid].id, sizeof(uint32_t)); // charid
+ for (size_t i = 0; i < 20; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, &zone, sizeof(zone)); // zone
+ for (size_t i = 0; i < 8; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+
+ const char *host = getenv("TCPREMOTEIP");
+ if (!host || strcmp(host, "127.0.0.1")) {
+ host = getenv("GAME_SERVER");
+ host = (host ? host : "84.248.5.167");
+ }
+
+ pbuf_write_str(&pbuf, host); // server ip
+ pbuf_write(&pbuf, (uint16_t[]){10100}, sizeof(uint16_t)); // port
+ pbuf_write(&pbuf, (uint8_t[]){0xff, 0xff}, 2); // unknown
+ for (size_t i = 0; i < 8; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (float[]){x, y, z}, sizeof(float) * 3); // xyz
+ for (size_t i = 0; i < 17; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_write(&pbuf, (uint8_t[]){0x01}, sizeof(uint8_t)); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+
+ send_despawn(cid, packet);
+ server.character[cid].zone = zone;
+ server.character[cid].pos[0] = x;
+ server.character[cid].pos[1] = y;
+ server.character[cid].pos[2] = z;
+}
+
+static void
+send_channel_info(client_id cid, union packet *packet)
+{
+ packet->hdr.type = PACKET_CHANNEL_INFO;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint16_t[]){server.character[cid].zone}, sizeof(uint16_t)); // zone
+
+ uint8_t num_channels = 1;
+ pbuf_write(&pbuf, &num_channels, sizeof(uint8_t)); // number of channels
+ for (uint16_t i = 1; i <= num_channels; ++i) {
+ pbuf_write(&pbuf, &i, sizeof(uint16_t)); // channel index (dunno why 16bit)
+ pbuf_write(&pbuf, (uint8_t[]){0}, 1); // bar (0..3)
+ }
+
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+handle_character_action_req(client_id cid, union packet *packet)
+{
+ packet->hdr.type = PACKET_CHARACTER_ACTION_RES;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+#if 0
+ const uint8_t buf[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x65,
+ 0xeb, 0xa9, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xe0, 0x03, 0xdc, 0x19, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xdc, 0x19,
+ 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+ pbuf_write(&pbuf, buf, sizeof(buf));
+#endif
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+handle_shop_cash_load(client_id cid, union packet *packet)
+{
+ packet->hdr.type = PACKET_SHOP_CASH_LOAD;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint8_t[]){0x01, 0x00, 0x00, 0x00, 0x00}, 5); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+handle_event_use_coupon_code(client_id cid, union packet *packet)
+{
+ // string
+}
+
+static void
+handle_maze_create_req(client_id cid, union packet *packet)
+{
+ uint32_t zone;
+ memcpy(&zone, packet->buf + 20, sizeof(zone));
+
+ for (size_t i = 0; i < ARRAY_SIZE(ZONES); ++i) {
+ if (ZONES[i].id != zone)
+ continue;
+
+ union packet packet = { .hdr.salt = 2 };
+ zone_to(cid, &packet, ZONES[i].id, CENTER(ZONES[i].box));
+ break;
+ }
+}
+
+static void
+handle_character_info_req(client_id cid, union packet *packet)
+{
+ spawn_to(cid, packet, 10003, 10230, 10058, 90);
+}
+
+static void
+load_cash_shop(client_id cid, union packet *packet)
+{
+ load_shop_banners(cid, packet, (const char*[]){"https://cloudef.pw/armpit/sw-encryption.png"}, 1);
+ handle_shop_cash_load(cid, packet);
+
+ packet->hdr.type = PACKET_SHOP_CASH_TAB_LOAD;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint8_t[]){
+ 0x07, 0x00, 0x00, 0x00, 0xf2, 0x03, 0x00, 0x00, 0x01, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0xf3, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0xfd, 0x03, 0x00, 0x00, 0x01, 0x00, 0xfe, 0x03, 0x00, 0x00, 0x02, 0x00,
+ 0xff, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00,
+ 0x01, 0x04, 0x00, 0x00, 0x05, 0x00, 0x02, 0x04, 0x00, 0x00, 0x06, 0x00,
+ 0x03, 0x04, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x04, 0x00, 0x00, 0x03, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x07, 0x04,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04,
+ 0x00, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x11, 0x04, 0x00, 0x00,
+ 0x01, 0x00, 0x12, 0x04, 0x00, 0x00, 0x02, 0x00, 0x13, 0x04, 0x00, 0x00,
+ 0x03, 0x00, 0x14, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x04, 0x00, 0x00,
+ 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x1b, 0x04, 0x00, 0x00, 0x01, 0x00,
+ 0x1c, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x04, 0x00, 0x00, 0x06, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x25, 0x04, 0x00, 0x00, 0x01, 0x00, 0x26, 0x04,
+ 0x00, 0x00, 0x02, 0x00, 0x27, 0x04, 0x00, 0x00, 0x03, 0x00, 0x28, 0x04,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x2e, 0x04, 0x00, 0x00, 0x07, 0x00, 0x0a, 0x00,
+ 0x00, 0x00, 0x2f, 0x04, 0x00, 0x00, 0x01, 0x00, 0x30, 0x04, 0x00, 0x00,
+ 0x02, 0x00, 0x31, 0x04, 0x00, 0x00, 0x03, 0x00, 0x32, 0x04, 0x00, 0x00,
+ 0x04, 0x00, 0x33, 0x04, 0x00, 0x00, 0x05, 0x00, 0x34, 0x04, 0x00, 0x00,
+ 0x06, 0x00, 0x35, 0x04, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+ }, 494);
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+
+ packet->hdr.type = PACKET_SHOP_CASH_SET_LOAD;
+ pbuf = (struct pbuf){ .packet = packet, .cursor = sizeof(packet->hdr) };
+ for (size_t i = 0; i < 749; ++i) pbuf_write(&pbuf, (uint8_t[]){0}, 1);
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+
+ packet->hdr.type = PACKET_SHOP_CASH_BUY_COUNT_LOAD;
+ pbuf = (struct pbuf){ .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4);
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+handle_enter_gameserver_req(client_id cid, union packet *packet)
+{
+ {
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read(&pbuf, &server.session_id[cid], sizeof(uint32_t));
+ }
+
+ struct db_query_arg args[] = {{ .type = DB_ARG_I32, .u.i32 = server.session_id[cid] }}, cols[5];
+ const int results = db_query_single(&server.db.login,
+ "select id, model, name, evolution, lvl from characters where id = (select char_id from sessions where id = ? limit 1) order by id",
+ args, ARRAY_SIZE(args), cols, ARRAY_SIZE(cols));
+
+ assert(results == ARRAY_SIZE(cols));
+ assert(cols[0].type == DB_ARG_I32 && cols[1].type == DB_ARG_I32 && cols[3].type == DB_ARG_I32 && cols[4].type == DB_ARG_I32);
+ assert(cols[2].type == DB_ARG_UTF8 && (size_t)cols[2].u.blob.sz < sizeof(server.character[cid].name));
+
+ server.character[cid].id = cols[0].u.i32;
+ server.character[cid].model = cols[1].u.i32;
+ memcpy(server.character[cid].name, cols[2].u.blob.data, cols[2].u.blob.sz);
+ server.character[cid].evolution = cols[3].u.i32;
+ server.character[cid].lvl = cols[4].u.i32;
+
+ // stub(cid, packet, PACKET_AUTH_UNKNOWN1, 0x01);
+ // stub(cid, packet, PACKET_WORLD_VERSION, 0x01);
+ // stub(cid, packet, PACKET_AUTH_UNKNOWN17, 0x01);
+
+ stub(cid, packet, PACKET_ENTER_GAMESERVER_RES, 0x01);
+
+ // stub(cid, packet, PACKET_GAME_UNKNOWN1, 0x01);
+ // stub(cid, packet, PACKET_GAME_UNKNOWN2, 0x01);
+ // stub(cid, packet, PACKET_GAME_UNKNOWN3, 0x01);
+
+ stub(cid, packet, PACKET_CHARACTER_DB_LOAD_SYNC, 0x01);
+
+ // load_cash_shop(cid, packet);
+}
+
+static void
+inject(client_id cid, const char *arg)
+{
+ warnx("injecting: %s", arg);
+
+ FILE *f;
+ if (!(f = fopen(arg, "rb"))) {
+ warn("fopen(%s)", arg);
+ return;
+ }
+
+ union packet packet;
+ (void) !fread(packet.buf, 1, sizeof(packet.buf), f);
+ fclose(f);
+
+ packet_crypt(&packet);
+ flush_packet(cid, &packet, FLUSH_TO, (union flush_arg){ .to = cid });
+}
+
+static void
+zone(client_id cid, const char *arg)
+{
+ warnx("zone: %s", arg);
+
+ for (size_t i = 0; i < ARRAY_SIZE(ZONES); ++i) {
+ if (strcmp(ZONES[i].name, arg))
+ continue;
+
+ union packet packet = { .hdr.salt = 2 };
+ zone_to(cid, &packet, ZONES[i].id, CENTER(ZONES[i].box));
+ break;
+ }
+}
+
+static char *const *ARGV;
+
+static void
+rexec(void)
+{
+ warnx("running exec");
+ execv(ARGV[0], ARGV);
+}
+
+static void
+handle_chat_normal(client_id cid, union packet *packet)
+{
+ uint8_t unknown;
+ struct pbuf_string msg;
+ {
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read(&pbuf, &unknown, sizeof(unknown)); // unknown (type of msg?)
+ pbuf_read_str_utf16(&pbuf, &msg);
+ }
+
+ const struct {
+ const char *cmd;
+ void (*action)();
+ } map[] = {
+ { .cmd = "ex", .action = rexec },
+ { .cmd = "inject", .action = inject },
+ { .cmd = "zone", .action = zone },
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(map); ++i) {
+ if (strncmp(msg.data, map[i].cmd, strlen(map[i].cmd)))
+ continue;
+
+ map[i].action(cid, msg.data + strlen(map[i].cmd) + 1);
+ return;
+ }
+
+ packet->hdr.type = PACKET_CHAT_NORMAL;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &server.character[cid].id, sizeof(uint32_t)); // charid
+ pbuf_write(&pbuf, &unknown, 1); // msg type?
+ pbuf_write(&pbuf, (uint8_t[]){0, 0, 0}, 3); // unknown
+ pbuf_write_str_len_utf16(&pbuf, msg.data, msg.len); // msg text UTF16
+ pbuf_write(&pbuf, (uint8_t[]){0}, 1); // null terminate
+ pbuf_flush(&pbuf);
+ flush_packet(cid, packet, FLUSH_ALL, (union flush_arg){0});
+}
+
+static void
+handle_packet(client_id cid, union packet *packet)
+{
+ packet_crypt(packet);
+ warnx("got packet of type 0x%.4x", packet->hdr.type);
+
+ switch (packet->hdr.type) {
+ case PACKET_ENTER_GAMESERVER_REQ:
+ handle_enter_gameserver_req(cid, packet);
+ break;
+ case PACKET_CHARACTER_INFO_REQ:
+ handle_character_info_req(cid, packet);
+ break;
+ case PACKET_CHANNEL_INFO:
+ send_channel_info(cid, packet);
+ /* fallthrough */
+ case PACKET_COMPLETE_MAZE_REQ:
+ load_pcs_in_zone(cid, packet, server.character[cid].zone);
+ send_pc_info(cid, packet, false);
+ break;
+ case PACKET_CHARACTER_MOVE:
+ packet->hdr.type = PACKET_CHARACTER_MOVE_RES;
+ flush_packet(cid, packet, FLUSH_ZONE | FLUSH_EXCLUDE_SENDER, (union flush_arg){0});
+ break;
+ case PACKET_CHARACTER_STOP_MOVE:
+ packet->hdr.type = PACKET_CHARACTER_STOP_MOVE_RES;
+ flush_packet(cid, packet, FLUSH_ZONE | FLUSH_EXCLUDE_SENDER, (union flush_arg){0});
+ break;
+ case PACKET_CHARACTER_JUMP:
+ packet->hdr.type = PACKET_CHARACTER_JUMP_RES;
+ flush_packet(cid, packet, FLUSH_ZONE | FLUSH_EXCLUDE_SENDER, (union flush_arg){0});
+ break;
+ case PACKET_CHAT_NORMAL:
+ handle_chat_normal(cid, packet);
+ break;
+ case PACKET_MAZE_CREATE_REQ:
+ handle_maze_create_req(cid, packet);
+ break;
+ case PACKET_EVENT_USE_COUPON_CODE:
+ handle_event_use_coupon_code(cid, packet);
+ break;
+ case PACKET_SHOP_CASH_LOAD:
+ handle_shop_cash_load(cid, packet);
+ break;
+ case PACKET_CHARACTER_ACTION_REQ:
+ handle_character_action_req(cid, packet);
+ break;
+ default:
+ warnx("unknown packet");
+ break;
+ }
+}
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <fcntl.h>
+
+static inline size_t
+safe_read(int fd, void *dst, const size_t dst_size)
+{
+ size_t off = 0;
+ ssize_t ret = 0;
+ for (; off < dst_size && (ret = read(fd, (char*)dst + off, dst_size - off)) > 0; off += ret);
+ return (ret < 0 ? 0 : off);
+}
+
+int
+main(int argc, char *const argv[])
+{
+ (void)argc;
+ ARGV = argv;
+
+ db_init(&server.db.login, "login.db");
+
+ int sfd;
+ if ((sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
+ err(EXIT_FAILURE, "socket");
+
+ struct sockaddr_in serv_addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ .sin_port = htons(10100),
+ };
+
+ setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, (int[]){true}, sizeof(int));
+
+ if (bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) != 0)
+ err(EXIT_FAILURE, "bind");
+
+ if (listen(sfd, 0) != 0)
+ err(EXIT_FAILURE, "listen");
+
+ union packet packet;
+ server.pfds[0] = (struct pollfd){ .fd = sfd, .events = POLLIN };
+ server.npfds = 1;
+ while (poll(server.pfds, server.npfds, -1) > 0) {
+ if (server.pfds[0].revents & POLLIN) {
+ int cfd;
+ if ((cfd = accept(sfd, (struct sockaddr*)NULL, NULL)) == -1) {
+ warnx("failed to accept");
+ continue;
+ }
+
+ if (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (int[]){true}, sizeof(int)) != 0)
+ err(EXIT_FAILURE, "setsockopt");
+
+ if (fcntl(cfd, F_SETFL, fcntl(cfd, F_GETFL, 0) | O_NONBLOCK) == -1)
+ err(EXIT_FAILURE, "failed to set O_NONBLOCK");
+
+ server.pfds[server.npfds].fd = cfd;
+ server.pfds[server.npfds++].events = POLLIN;
+ warnx("new client connected");
+ }
+
+ for (nfds_t i = 1; i < server.npfds; ++i) {
+ if (!(server.pfds[i].revents & POLLIN))
+ continue;
+
+ if (safe_read(server.pfds[i].fd, packet.buf, sizeof(packet.hdr)) != sizeof(packet.hdr) || !packet_verify(&packet)) {
+ warnx("invalid packet");
+ close(server.pfds[i].fd);
+ memmove(&server.pfds[i], &server.pfds[i + 1], sizeof(struct pollfd) * server.npfds - i);
+ memmove(&server.session_id[i], &server.session_id[i + 1], sizeof(uint32_t) * server.npfds - i);
+ memmove(&server.character[i], &server.character[i + 1], sizeof(*server.character) * server.npfds - i);
+ --server.npfds;
+ continue;
+ }
+
+ safe_read(server.pfds[i].fd, packet.buf + sizeof(packet.hdr), packet.hdr.size - sizeof(packet.hdr));
+ handle_packet(i, &packet);
+ }
+
+ db_gc(&server.db.login);
+ }
+
+ db_release(&server.db.login);
+ return EXIT_SUCCESS;
+}