#include "packet.h" #include "db.h" #include "game.h" #include #include #include #include #include #include #include #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 #include #include #include 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; }