diff options
Diffstat (limited to 'src/game.c')
| -rw-r--r-- | src/game.c | 629 | 
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; +} | 
