From 662d78b9a5bebdb98dc178ebd1f8e3384793c104 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Sun, 17 Mar 2019 15:30:43 +0200 Subject: Initial commit --- src/db.c | 153 ++++++++++++++ src/db.h | 49 +++++ src/game.c | 629 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/game.h | 195 ++++++++++++++++++ src/login.c | 362 +++++++++++++++++++++++++++++++++ src/mitm.c | 243 ++++++++++++++++++++++ src/packet.c | 167 +++++++++++++++ src/packet.h | 535 ++++++++++++++++++++++++++++++++++++++++++++++++ src/sw-crypt.c | 25 +++ src/xor.c | 20 ++ 10 files changed, 2378 insertions(+) create mode 100644 src/db.c create mode 100644 src/db.h create mode 100644 src/game.c create mode 100644 src/game.h create mode 100644 src/login.c create mode 100644 src/mitm.c create mode 100644 src/packet.c create mode 100644 src/packet.h create mode 100644 src/sw-crypt.c create mode 100644 src/xor.c (limited to 'src') diff --git a/src/db.c b/src/db.c new file mode 100644 index 0000000..76ebaf7 --- /dev/null +++ b/src/db.c @@ -0,0 +1,153 @@ +#include "db.h" +#include +#include +#include +#include + +#include +#include + +static int +busy_handler(void *dummy, int times_invoked) +{ + (void)dummy; + uint32_t sleep_s = (times_invoked > 4 ? 4 : times_invoked) + 1; + warnx("sqlite3 db is busy :(... sleeping for %u seconds", sleep_s); + sleep(sleep_s); + return 1; +} + +void +db_init(struct db *db, const char *target) +{ + assert(db && target); + *db = (struct db){0}; + + if (sqlite3_open(target, (sqlite3**)&db->handle) != SQLITE_OK) + errx(EXIT_FAILURE, "sqlite3_open: %s", sqlite3_errmsg(db->handle)); + + if (sqlite3_busy_handler(db->handle, busy_handler, NULL) != SQLITE_OK) + errx(EXIT_FAILURE, "sqlite3_busy_handler"); +} + +static struct db_query_arg +stmt_column_to_arg(sqlite3_stmt *stmt, const int index) +{ + assert(stmt); + switch (sqlite3_column_type(stmt, index)) { + case SQLITE_NULL: + return (struct db_query_arg){ .type = DB_ARG_NULL }; + case SQLITE_INTEGER: + return (struct db_query_arg){ .u.i32 = sqlite3_column_int(stmt, index), .type = DB_ARG_I32 }; + case SQLITE_FLOAT: + return (struct db_query_arg){ .u.f64 = sqlite3_column_double(stmt, index), .type = DB_ARG_F64 }; + case SQLITE_BLOB: + return (struct db_query_arg){ .u.blob = { .data = sqlite3_column_blob(stmt, index), .sz = sqlite3_column_bytes(stmt, index) }, .type = DB_ARG_BLOB }; + case SQLITE_TEXT: + return (struct db_query_arg){ .u.blob = { .data = sqlite3_column_text(stmt, index), .sz = sqlite3_column_bytes(stmt, index) }, .type = DB_ARG_UTF8 }; + } + assert(0 && "should not happen"); + return (struct db_query_arg){0}; +} + +static int +bind_arg_to_stmt(const struct db_query_arg *arg, const int index, sqlite3_stmt *stmt) +{ + assert(arg && stmt); + switch (arg->type) { + case DB_ARG_NULL: + return sqlite3_bind_null(stmt, index); + case DB_ARG_I32: + return sqlite3_bind_int(stmt, index, arg->u.i32); + case DB_ARG_F64: + return sqlite3_bind_double(stmt, index, arg->u.f64); + case DB_ARG_BLOB: + if (arg->u.blob.sz && !arg->u.blob.data) + return sqlite3_bind_zeroblob(stmt, index, arg->u.blob.sz); + return sqlite3_bind_blob(stmt, index, arg->u.blob.data, arg->u.blob.sz, NULL); + case DB_ARG_UTF8: + assert(!arg->u.blob.sz || arg->u.blob.data); + return sqlite3_bind_text64(stmt, index, arg->u.blob.data, arg->u.blob.sz, NULL, SQLITE_UTF8); + } + assert(0 && "should not happen"); + return SQLITE_ERROR; +} + +struct db_query +db_query_begin(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args) +{ + assert(db && sql); + assert(args || !num_args); + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL) != SQLITE_OK) + errx(EXIT_FAILURE, "sqlite3_prepare_v2: %s", sqlite3_errmsg(db->handle)); + + for (int i = 0; i < num_args; ++i) { + if (bind_arg_to_stmt(&args[i], i + 1, stmt) != SQLITE_OK) + errx(EXIT_FAILURE, "sqlite3_bind: %s", sqlite3_errmsg(db->handle)); + } + + return (struct db_query){stmt}; +} + +int +db_query_fetch(struct db_query *query, struct db_query_arg columns[], const int num_columns) +{ + assert(query); + assert(columns || !num_columns); + + int ret; + if ((ret = sqlite3_step(query->handle)) != SQLITE_ROW && ret != SQLITE_DONE) + errx(EXIT_FAILURE, "sqlite3_step: %s", sqlite3_errstr(ret)); + + if (ret == SQLITE_DONE) + return 0; + + int count = sqlite3_column_count(query->handle); + + if (!num_columns) + return count; + + count = (count > num_columns ? num_columns : count); + for (int i = 0; i < count; ++i) + columns[i] = stmt_column_to_arg(query->handle, i); + + return count; +} + +void +db_query_end(struct db_query *query) +{ + if (!query) + return; + + sqlite3_finalize(query->handle); + *query = (struct db_query){0}; +} + +int +db_query_single(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args, struct db_query_arg columns[], const int num_columns) +{ + db_query_end(&db->single); + db->single = db_query_begin(db, sql, args, num_args); + return db_query_fetch(&db->single, columns, num_columns); +} + +void +db_gc(struct db *db) +{ + assert(db); + db_query_end(&db->single); +} + +void +db_release(struct db *db) +{ + if (!db) + return; + + db_query_end(&db->single); + sqlite3_close(db->handle); + *db = (struct db){0}; +} diff --git a/src/db.h b/src/db.h new file mode 100644 index 0000000..7f01cbf --- /dev/null +++ b/src/db.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +struct db_query { + void *handle; +}; + +struct db { + void *handle; + struct db_query single; +}; + +struct db_query_arg { + union { + int32_t i32; + double f64; + struct { const void *data; int sz; } blob; // used for text as well + } u; + enum { + DB_ARG_NULL, + DB_ARG_I32, + DB_ARG_F64, + DB_ARG_BLOB, + DB_ARG_UTF8, + } type; +}; + +void +db_init(struct db *db, const char *target); + +struct db_query +db_query_begin(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args); + +int +db_query_fetch(struct db_query *query, struct db_query_arg columns[], const int num_columns); + +void +db_query_end(struct db_query *query); + +int +db_query_single(struct db *db, const char *sql, const struct db_query_arg args[], const int num_args, struct db_query_arg columns[], const int num_columns); + +void +db_gc(struct db *db); + +void +db_release(struct db *db); 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 +#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; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..5a18cdb --- /dev/null +++ b/src/game.h @@ -0,0 +1,195 @@ +#pragma once + +static const struct { + float box[2][3]; + const char *name; + uint32_t id; +} ZONES[] = { + { .id = 21100, .name = "ABANDONEDGRAVE", .box = { { 71765.359,42776.707,11445.727 }, { 71965.359,42976.707,11545.727 } } }, + { .id = 24611, .name = "ACHERON_EP_01", .box = { { 1624.729,3726.533,4000.000 }, { 1824.729,3926.533,4100.000 } } }, + { .id = 24621, .name = "ACHERON_EP_02", .box = { { 5730.963,5195.774,4000.000 }, { 5930.963,5395.774,4100.000 } } }, + { .id = 24631, .name = "ACHERON_EP_03", .box = { { 755.223,4541.032,4000.000 }, { 955.223,4741.032,4100.000 } } }, + { .id = 24641, .name = "ACHERON_EP_04", .box = { { 4373.032,9195.787,4002.171 }, { 4573.032,9395.787,4102.171 } } }, + { .id = 21015, .name = "ALTAROFVOID", .box = { { 32667.709,42776.703,12349.053 }, { 32867.711,42976.703,12449.053 } } }, + { .id = 23015, .name = "ALTAROFVOID_RANK", .box = { { 71611.805,42767.980,11445.727 }, { 71811.805,42967.980,11545.727 } } }, + { .id = 23611, .name = "AREA9_EP_01", .box = { { 45635.855,59211.266,12818.359 }, { 45735.855,59311.266,12918.359 } } }, + { .id = 23621, .name = "AREA9_EP_02", .box = { { 14942.157,39259.754,7475.312 }, { 15042.157,39359.754,7575.312 } } }, + { .id = 23631, .name = "AREA9_EP_03", .box = { { 14942.157,39259.754,7475.312 }, { 15042.157,39359.754,7575.312 } } }, + { .id = 23641, .name = "AREA9_EP_04", .box = { { 96694.180,21313.873,7475.312 }, { 96794.180,21413.873,7575.312 } } }, + { .id = 24711, .name = "ARKSHIP_EP_01", .box = { { 21512.742,48088.230,32086.607 }, { 21612.742,48188.230,32186.607 } } }, + { .id = 24721, .name = "ARKSHIP_EP_02", .box = { { 19919.807,48580.500,33337.152 }, { 20019.807,48680.500,33437.152 } } }, + { .id = 24731, .name = "ARKSHIP_EP_03", .box = { { 24215.566,53405.254,33760.469 }, { 24315.566,53505.254,33860.469 } } }, + { .id = 24741, .name = "ARKSHIP_EP_04", .box = { { 8349.707,47541.566,33755.699 }, { 8449.707,47641.566,33855.699 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_BLUE", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_DARK", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_GREEN", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_LIGHT", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_RED", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21013, .name = "BANDITSHIGHWAY_WHITE", .box = { { 19029.584,86538.711,9137.494 }, { 19129.584,86638.711,9237.494 } } }, + { .id = 21001, .name = "BATTLECOURT", .box = { { 9094.386,6584.809,2525.728 }, { 10294.386,7184.809,2825.728 } } }, + { .id = 21321, .name = "BESTSHOWTIME_EP_01", .box = { { 20309.111,13561.008,255.900 }, { 20409.111,13661.008,355.900 } } }, + { .id = 21421, .name = "BESTSHOWTIME_EP_02", .box = { { 6560.845,28540.777,4810.046 }, { 6660.845,28640.777,4910.046 } } }, + { .id = 21431, .name = "BESTSHOWTIME_EP_03", .box = { { 22493.889,11822.354,242.868 }, { 22593.889,11922.354,342.868 } } }, + { .id = 21431, .name = "BESTSHOWTIME_EP_04", .box = { { 22421.064,11822.354,242.868 }, { 22521.064,11922.354,342.868 } } }, + { .id = 26311, .name = "BETRAYERSARMY_EP_01", .box = { { 25004.813,65614.461,49524.094 }, { 25104.813,65714.461,49624.094 } } }, + { .id = 26321, .name = "BETRAYERSARMY_EP_02", .box = { { 23016.813,65614.461,49524.094 }, { 23116.813,65714.461,49624.094 } } }, + { .id = 26331, .name = "BETRAYERSARMY_EP_03", .box = { { 14449.455,59277.570,49524.094 }, { 14549.455,59377.570,49624.094 } } }, + { .id = 26341, .name = "BETRAYERSARMY_EP_04", .box = { { 17666.430,61969.102,49524.094 }, { 17766.430,62069.102,49624.094 } } }, + { .id = 22511, .name = "BREAKOUT_EP_01", .box = { { 19948.703,25796.875,1573.456 }, { 20048.703,25896.875,1673.456 } } }, + { .id = 22521, .name = "BREAKOUT_EP_02", .box = { { 48158.621,37457.180,1879.832 }, { 48258.621,37557.180,1979.832 } } }, + { .id = 22531, .name = "BREAKOUT_EP_03", .box = { { 18132.455,35004.230,1254.304 }, { 18232.455,35104.230,1354.304 } } }, + { .id = 22541, .name = "BREAKOUT_EP_04", .box = { { 6282.323,85145.914,5692.782 }, { 6382.323,85245.914,5792.782 } } }, + { .id = 24211, .name = "BUSTERCORE_EP_01", .box = { { 10492.922,13735.813,8956.066 }, { 10592.922,13835.813,9056.066 } } }, + { .id = 24221, .name = "BUSTERCORE_EP_02", .box = { { 43661.035,54645.117,9911.659 }, { 43761.035,54745.117,10011.659 } } }, + { .id = 24231, .name = "BUSTERCORE_EP_03", .box = { { 43656.527,54700.977,9908.667 }, { 43756.527,54800.977,10008.667 } } }, + { .id = 24241, .name = "BUSTERCORE_EP_04", .box = { { 47319.137,38907.820,9902.705 }, { 47419.137,39007.820,10002.705 } } }, + { .id = 10021, .name = "CANDUSCITY", .box = { { 79697.867,75670.805,1716.357 }, { 80497.867,76170.805,2016.357 } } }, + { .id = 23411, .name = "COLDRAIN_EP_01", .box = { { 8901.713,37108.027,1000.000 }, { 9101.713,37308.027,1100.000 } } }, + { .id = 23421, .name = "COLDRAIN_EP_02", .box = { { 10396.657,47618.223,999.999 }, { 10596.657,47818.223,1099.999 } } }, + { .id = 23431, .name = "COLDRAIN_EP_03", .box = { { 13391.982,64837.066,1000.000 }, { 13591.982,65037.066,1100.000 } } }, + { .id = 23441, .name = "COLDRAIN_EP_04", .box = { { 8899.502,73578.008,1000.000 }, { 9099.502,73778.008,1100.000 } } }, + { .id = 24311, .name = "COLDREVENGE_EP_01", .box = { { 4503.840,8090.859,1000.003 }, { 4603.840,8190.859,1100.003 } } }, + { .id = 24321, .name = "COLDREVENGE_EP_02", .box = { { 5997.292,9230.892,1000.713 }, { 6097.292,9330.892,1100.713 } } }, + { .id = 24331, .name = "COLDREVENGE_EP_03", .box = { { 1544.485,8084.674,1000.000 }, { 1644.485,8184.674,1100.000 } } }, + { .id = 24341, .name = "COLDREVENGE_EP_04", .box = { { 23842.326,22021.086,1000.000 }, { 23942.326,22121.086,1100.000 } } }, + { .id = 22111, .name = "CONCRETEJUNGLE_EP_01", .box = { { 15219.798,16879.947,5846.374 }, { 15319.798,16979.947,5946.374 } } }, + { .id = 22121, .name = "CONCRETEJUNGLE_EP_02", .box = { { 27579.756,46421.961,5847.986 }, { 27679.756,46521.961,5947.986 } } }, + { .id = 22131, .name = "CONCRETEJUNGLE_EP_03", .box = { { 30245.184,14571.286,7174.145 }, { 30345.184,14671.286,7274.145 } } }, + { .id = 22141, .name = "CONCRETEJUNGLE_EP_04", .box = { { 88305.086,30384.449,5857.868 }, { 88405.086,30484.449,5957.868 } } }, + { .id = 24111, .name = "CONTROLBASE_EP_01", .box = { { 2192.828,4900.000,100.000 }, { 2392.828,5100.000,200.000 } } }, + { .id = 24121, .name = "CONTROLBASE_EP_02", .box = { { 20117.607,34891.281,1800.000 }, { 20317.607,35091.281,1900.000 } } }, + { .id = 24131, .name = "CONTROLBASE_EP_03", .box = { { 11036.818,29912.539,100.000 }, { 11236.818,30112.539,200.000 } } }, + { .id = 24141, .name = "CONTROLBASE_EP_04", .box = { { 13107.208,18393.436,102.000 }, { 13307.208,18593.436,202.000 } } }, + { .id = 21211, .name = "CONTROLZONE43_EP01", .box = { { 9062.172,25195.049,10.000 }, { 9162.172,25295.049,110.000 } } }, + { .id = 21221, .name = "CONTROLZONE43_EP02", .box = { { 18912.811,20551.725,10.000 }, { 19012.811,20651.725,110.000 } } }, + { .id = 21231, .name = "CONTROLZONE43_EP03", .box = { { 9062.172,25195.049,85.824 }, { 9162.172,25295.049,185.824 } } }, + { .id = 21241, .name = "CONTROLZONE43_EP04", .box = { { 9062.172,25195.049,10.000 }, { 9162.172,25295.049,110.000 } } }, + { .id = 25511, .name = "CORRUPTEDRECORD_EP_01", .box = { { 9512.718,29377.615,8458.003 }, { 9612.718,29477.615,8558.003 } } }, + { .id = 25521, .name = "CORRUPTEDRECORD_EP_02", .box = { { 6385.772,27405.297,7528.966 }, { 6485.772,27505.297,7628.966 } } }, + { .id = 25531, .name = "CORRUPTEDRECORD_EP_03", .box = { { 15489.951,23631.916,4587.734 }, { 15589.951,23731.916,4687.734 } } }, + { .id = 25541, .name = "CORRUPTEDRECORD_EP_04", .box = { { 10602.390,13711.694,6064.722 }, { 10702.390,13811.694,6164.722 } } }, + { .id = 25541, .name = "CORRUPTEDRECORD_EP_05", .box = { { 60908.660,13351.867,5204.879 }, { 61008.660,13451.867,5304.879 } } }, + { .id = 25411, .name = "DEADMEATFACTORY_EP_01", .box = { { 9030.938,25519.398,9058.996 }, { 9130.938,25619.398,9158.996 } } }, + { .id = 25421, .name = "DEADMEATFACTORY_EP_02", .box = { { 10115.220,21198.301,7870.886 }, { 10215.220,21298.301,7970.886 } } }, + { .id = 25431, .name = "DEADMEATFACTORY_EP_03", .box = { { 20989.686,16702.158,7850.639 }, { 21089.686,16802.158,7950.639 } } }, + { .id = 25441, .name = "DEADMEATFACTORY_EP_04", .box = { { 26273.787,21810.379,7933.864 }, { 26373.787,21910.379,8033.864 } } }, + { .id = 24511, .name = "DEEPCORE_EP_01", .box = { { 7948.050,31228.832,37232.531 }, { 8148.050,31428.832,37332.531 } } }, + { .id = 24521, .name = "DEEPCORE_EP_02", .box = { { 33796.648,22982.727,36583.289 }, { 33996.648,23182.727,36683.289 } } }, + { .id = 24531, .name = "DEEPCORE_EP_03", .box = { { 23454.346,28538.414,36953.910 }, { 23654.346,28738.414,37053.910 } } }, + { .id = 24541, .name = "DEEPCORE_EP_04", .box = { { 6059.498,28585.688,37236.887 }, { 6259.498,28785.688,37336.887 } } }, + { .id = 11001, .name = "DF01_GOLDENCITADEL", .box = { { 18630.219,17783.496,4563.968 }, { 19030.219,18183.496,4663.968 } } }, + { .id = 21101, .name = "DIMENTIONSHUTER", .box = { { 18597.697,20098.609,11448.418 }, { 18697.697,20198.609,11548.418 } } }, + { .id = 10061, .name = "DIPLUCEHORIZON", .box = { { 53620.297,43903.453,28561.432 }, { 54020.297,44303.453,28661.432 } } }, + { .id = 30021, .name = "DISTRICT6", .box = { { 42440.641,69162.828,15509.875 }, { 43240.641,69962.828,15709.875 } } }, + { .id = 30021, .name = "DISTRICT6_THETHING", .box = { { 42440.641,69162.828,15509.875 }, { 43240.641,69962.828,15709.875 } } }, + { .id = 21051, .name = "DR01_GOLDENCITADEL", .box = { { 18630.664,17167.801,4917.395 }, { 19130.664,17667.801,5017.395 } } }, + { .id = 25111, .name = "DREADFULECHO_EP_01", .box = { { 18156.283,18184.186,103.000 }, { 18256.283,18284.186,203.000 } } }, + { .id = 25121, .name = "DREADFULECHO_EP_02", .box = { { 10753.356,28643.342,100.000 }, { 10853.356,28743.342,200.000 } } }, + { .id = 25131, .name = "DREADFULECHO_EP_03", .box = { { 32069.379,24957.131,7174.459 }, { 32169.379,25057.131,7274.459 } } }, + { .id = 25131, .name = "DREADFULECHO_EP_04", .box = { { 32497.932,50652.387,7123.774 }, { 32597.932,50752.387,7223.774 } } }, + { .id = 23511, .name = "FLAMEBREAKER_EP_01", .box = { { 7976.539,18972.150,10000.000 }, { 8076.539,19072.150,10100.000 } } }, + { .id = 23521, .name = "FLAMEBREAKER_EP_02", .box = { { 21035.801,37906.941,10019.125 }, { 21135.801,38006.941,10119.125 } } }, + { .id = 23531, .name = "FLAMEBREAKER_EP_03", .box = { { 12187.206,16561.766,9299.523 }, { 12287.206,16661.766,9399.523 } } }, + { .id = 23541, .name = "FLAMEBREAKER_EP_04", .box = { { 19837.080,46038.531,9117.851 }, { 19937.080,46138.531,9217.851 } } }, + { .id = 24411, .name = "FORGOTTENARMORY_EP_01", .box = { { 1629.080,35858.887,41931.645 }, { 1729.080,35958.887,42031.645 } } }, + { .id = 24421, .name = "FORGOTTENARMORY_EP_02", .box = { { 8336.573,35812.203,41931.645 }, { 8436.573,35912.203,42031.645 } } }, + { .id = 24431, .name = "FORGOTTENARMORY_EP_03", .box = { { 51145.582,16388.814,43146.242 }, { 51245.582,16488.814,43246.242 } } }, + { .id = 24441, .name = "FORGOTTENARMORY_EP_04", .box = { { -15494.930,16379.199,43131.645 }, { -15394.930,16479.199,43231.645 } } }, + { .id = 23111, .name = "FRONTLINE_EP_01", .box = { { 32402.020,9755.272,10406.177 }, { 32602.020,9855.272,10506.177 } } }, + { .id = 23121, .name = "FRONTLINE_EP_02", .box = { { 32402.020,15875.220,10406.177 }, { 32602.020,16075.220,10506.177 } } }, + { .id = 23131, .name = "FRONTLINE_EP_03", .box = { { 36353.066,26745.096,11463.154 }, { 36553.066,26945.096,11563.154 } } }, + { .id = 23141, .name = "FRONTLINE_EP_04", .box = { { 32152.996,31490.635,10405.436 }, { 32352.996,31690.635,10505.436 } } }, + { .id = 21121, .name = "GLUTONWORLD", .box = { { 7973.539,4432.462,511.082 }, { 8073.539,4532.462,611.082 } } }, + { .id = 21051, .name = "GOLDENCITADEL_ATTRIBUTE", .box = { { 18630.664,17167.801,4917.395 }, { 19130.664,17667.801,5017.395 } } }, + { .id = 10031, .name = "GRACECITY", .box = { { 42690.777,45873.383,4003.472 }, { 43390.777,46573.383,4103.472 } } }, + { .id = 10051, .name = "GRASSCOVERCAMP", .box = { { 92328.406,36190.465,9170.499 }, { 92728.406,36590.465,9270.499 } } }, + { .id = 26411, .name = "HOLYGROUND_EP_01", .box = { { 18899.211,74448.578,10505.964 }, { 18999.211,74548.578,10605.964 } } }, + { .id = 26421, .name = "HOLYGROUND_EP_02", .box = { { 17072.000,65081.383,10397.242 }, { 17172.000,65181.383,10497.242 } } }, + { .id = 26431, .name = "HOLYGROUND_EP_03", .box = { { 15204.037,67246.602,10408.680 }, { 15304.037,67346.602,10508.680 } } }, + { .id = 26441, .name = "HOLYGROUND_EP_04", .box = { { 12404.172,68310.719,10406.670 }, { 12504.172,68410.719,10506.670 } } }, + { .id = 21071, .name = "INNOCENTDAYDREAM", .box = { { 37215.270,19902.627,103.000 }, { 37415.270,20102.627,203.000 } } }, + { .id = 21014, .name = "IRONCASTLE", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_BLUE", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_DARK", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_GREEN", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_LIGHT", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_RED", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21014, .name = "IRONCASTLE_WHITE", .box = { { 9079.846,6939.715,5005.002 }, { 9179.846,7039.715,5105.002 } } }, + { .id = 21012, .name = "JUNKHIVE", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_BLUE", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_DARK", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_GREEN", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_LIGHT", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_RED", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 21012, .name = "JUNKHIVE_WHITE", .box = { { 11757.481,19716.148,5547.168 }, { 11857.481,19816.148,5647.168 } } }, + { .id = 22211, .name = "JUNKPOOL_EP_01", .box = { { 58542.820,91217.766,13228.068 }, { 58642.820,91317.766,13328.068 } } }, + { .id = 22221, .name = "JUNKPOOL_EP_02", .box = { { 16546.227,20914.391,10627.595 }, { 16646.227,21014.391,10727.595 } } }, + { .id = 22231, .name = "JUNKPOOL_EP_03", .box = { { 16902.471,21893.471,10252.203 }, { 17002.471,21993.471,10352.203 } } }, + { .id = 22241, .name = "JUNKPOOL_EP_04", .box = { { 14310.878,34588.246,13161.544 }, { 14410.878,34688.246,13261.544 } } }, + { .id = 21011, .name = "LASTCARNIVAL", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 24011, .name = "LASTCARNIVAL_BLUE", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 23011, .name = "LASTCARNIVAL_DARK", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 27011, .name = "LASTCARNIVAL_GREEN", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 22011, .name = "LASTCARNIVAL_LIGHT", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 25011, .name = "LASTCARNIVAL_RED", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 25011, .name = "LASTCARNIVAL_WHITE", .box = { { 88539.133,10735.693,1000.000 }, { 88639.133,10835.693,1100.000 } } }, + { .id = 21131, .name = "LONELYCHRISTMAS", .box = { { 39545.043,34364.980,10025.864 }, { 39645.043,34464.980,10125.864 } } }, + { .id = 25211, .name = "MANEATERGARDEN_EP_01", .box = { { 10116.053,27610.877,7528.966 }, { 10216.053,27710.877,7628.966 } } }, + { .id = 25321, .name = "MANEATERGARDEN_EP_02", .box = { { 9460.236,29377.686,8458.003 }, { 9560.236,29477.686,8558.003 } } }, + { .id = 25331, .name = "MANEATERGARDEN_EP_03", .box = { { 7182.635,27487.670,7528.966 }, { 7282.635,27587.670,7628.966 } } }, + { .id = 25341, .name = "MANEATERGARDEN_EP_04", .box = { { 3921.480,29388.898,8458.003 }, { 4021.480,29488.898,8558.003 } } }, + { .id = 21151, .name = "MEMORIALHALL", .box = { { 40449.270,25302.699,3998.000 }, { 40549.270,25402.699,4098.000 } } }, + { .id = 21161, .name = "MEMORIALHALL_NEW", .box = { { 40449.270,25302.699,3998.000 }, { 40549.270,25402.699,4098.000 } } }, + { .id = 20011, .name = "MYROOM", .box = { { 685.412,-627.910,3717.242 }, { 785.412,-527.910,3817.242 } } }, + { .id = 22411, .name = "N102SHELTER_EP_01", .box = { { 4353.347,13664.854,1001.668 }, { 4453.347,13764.854,1101.668 } } }, + { .id = 22421, .name = "N102SHELTER_EP_02", .box = { { 4353.347,13664.854,1001.668 }, { 4453.347,13764.854,1101.668 } } }, + { .id = 22431, .name = "N102SHELTER_EP_03", .box = { { 9949.543,19557.410,1001.668 }, { 10049.543,19657.410,1101.668 } } }, + { .id = 22441, .name = "N102SHELTER_EP_04", .box = { { 4374.573,25620.955,1001.668 }, { 4474.573,25720.955,1101.668 } } }, + { .id = 23211, .name = "NEDCOMPANY_EP_01", .box = { { 21500.287,2064.431,46.542 }, { 21600.287,2164.431,146.542 } } }, + { .id = 23221, .name = "NEDCOMPANY_EP_02", .box = { { 130.467,18080.516,46.541 }, { 230.467,18180.516,146.541 } } }, + { .id = 23231, .name = "NEDCOMPANY_EP_03", .box = { { 2503.138,11995.378,5175.642 }, { 2603.138,12095.378,5275.642 } } }, + { .id = 23241, .name = "NEDCOMPANY_EP_04", .box = { { 2625.382,18907.770,46.541 }, { 2725.382,19007.770,146.541 } } }, + { .id = 25211, .name = "PERFORATEDSTREET_EP_01", .box = { { 17903.576,29655.496,4577.099 }, { 18003.576,29755.496,4677.099 } } }, + { .id = 25221, .name = "PERFORATEDSTREET_EP_02", .box = { { 17179.973,30236.596,4576.098 }, { 17279.973,30336.596,4676.098 } } }, + { .id = 25231, .name = "PERFORATEDSTREET_EP_03", .box = { { 18533.256,32822.043,6479.744 }, { 18633.256,32922.043,6579.744 } } }, + { .id = 25241, .name = "PERFORATEDSTREET_EP_04", .box = { { 14017.632,33327.297,6695.974 }, { 14117.632,33427.297,6795.974 } } }, + { .id = 10003, .name = "ROCCOTOWN", .box = { { 10230.798,10058.951,90.452 }, { 10630.798,10458.951,190.452 } } }, + { .id = 21311, .name = "RSQUARE_EP_01", .box = { { 29821.670,19728.236,103.000 }, { 30021.670,19928.236,203.000 } } }, + { .id = 21321, .name = "RSQUARE_EP_02", .box = { { 23113.805,28637.383,103.740 }, { 23313.805,28837.383,203.740 } } }, + { .id = 21331, .name = "RSQUARE_EP_03", .box = { { 17325.986,31142.398,100.000 }, { 17525.986,31342.398,200.000 } } }, + { .id = 21341, .name = "RSQUARE_EP_04", .box = { { 20078.764,30885.338,101.646 }, { 20178.764,30985.338,201.646 } } }, + { .id = 10041, .name = "RUINFORTRESS", .box = { { 23580.543,21138.908,3665.746 }, { 24380.543,21938.908,3865.746 } } }, + { .id = 21171, .name = "RUMBLEVACATION", .box = { { 44935.207,48635.254,8572.622 }, { 44035.207,48735.254,8672.622 } } }, + { .id = 20101, .name = "S01_STEELGRAVE", .box = { { 18188.992,6668.367,4040.148 }, { 18288.992,6768.367,4140.147 } } }, + { .id = 20201, .name = "S02_STEELGRAVE", .box = { { 18188.992,6652.533,4040.148 }, { 18288.992,6752.533,4140.147 } } }, + { .id = 20201, .name = "S03_STEELGRAVE", .box = { { 18188.992,6652.533,4040.148 }, { 18288.992,6752.533,4140.147 } } }, + { .id = 21141, .name = "SKYCLOCKPALACE", .box = { { 7960.293,5898.859,500.000 }, { 8060.293,5998.859,600.000 } } }, + { .id = 26111, .name = "SKYWALKER_EP_01", .box = { { 24683.631,44180.051,75493.656 }, { 24783.631,44280.051,75593.656 } } }, + { .id = 26121, .name = "SKYWALKER_EP_02", .box = { { 15332.727,48277.594,74982.211 }, { 15432.727,48377.594,75082.211 } } }, + { .id = 26131, .name = "SKYWALKER_EP_03", .box = { { 23075.510,49482.254,74981.094 }, { 23175.510,49582.254,75081.094 } } }, + { .id = 26141, .name = "SKYWALKER_EP_04", .box = { { 21541.547,47776.758,74981.680 }, { 21641.547,47876.758,75081.680 } } }, + { .id = 21111, .name = "T01_TUTORIAL", .box = { { 90376.422,83963.500,26236.639 }, { 90476.422,84063.500,26336.639 } } }, + { .id = 21112, .name = "T02_TUTORIAL", .box = { { 2970.878,2450.023,3751.836 }, { 3070.878,2550.023,3851.836 } } }, + { .id = 21113, .name = "T03_TUTORIAL", .box = { { 19050.854,32111.730,9052.000 }, { 19150.854,32211.730,9152.000 } } }, + { .id = 10001, .name = "TESTFIELD", .box = { { 17023.758,12062.019,3849.282 }, { 19023.758,14062.019,4149.282 } } }, + { .id = 22431, .name = "THEBIGMOUTH_EP_01", .box = { { 3477.031,12583.577,4655.882 }, { 3577.031,12683.577,4755.882 } } }, + { .id = 22321, .name = "THEBIGMOUTH_EP_02", .box = { { 7704.808,14387.982,4655.882 }, { 7804.808,14487.982,4755.882 } } }, + { .id = 22331, .name = "THEBIGMOUTH_EP_03", .box = { { 16668.258,9277.498,5387.734 }, { 16768.258,9377.498,5487.734 } } }, + { .id = 22341, .name = "THEBIGMOUTH_EP_04", .box = { { 25708.998,19023.846,6417.370 }, { 25808.998,19123.846,6517.370 } } }, + { .id = 21061, .name = "THEPRIMAL", .box = { { 19970.262,14934.548,10689.821 }, { 20070.262,15034.548,10789.821 } } }, + { .id = 21062, .name = "THEPRIMAL_EV", .box = { { 19970.262,14934.548,10689.821 }, { 20070.262,15034.548,10789.821 } } }, + { .id = 21511, .name = "TOWEROFGREED_EP_01", .box = { { 2895.304,7615.922,1020.000 }, { 2995.304,7715.922,1120.000 } } }, + { .id = 21521, .name = "TOWEROFGREED_EP_02", .box = { { 2900.648,2807.235,1158.859 }, { 3000.648,2907.235,1258.859 } } }, + { .id = 21531, .name = "TOWEROFGREED_EP_03", .box = { { -578.867,2820.871,1000.000 }, { -478.867,2920.871,1100.000 } } }, + { .id = 21541, .name = "TOWEROFGREED_EP_04", .box = { { 2911.050,7461.119,1030.000 }, { 3011.050,7561.119,1130.000 } } }, + { .id = 26211, .name = "TRANSPORTFLEET_EP_01", .box = { { 21268.977,87809.406,5999.974 }, { 21368.977,87909.406,6099.974 } } }, + { .id = 26221, .name = "TRANSPORTFLEET_EP_02", .box = { { 37043.418,25033.854,5159.079 }, { 37143.418,25133.854,5259.079 } } }, + { .id = 26231, .name = "TRANSPORTFLEET_EP_03", .box = { { 26224.074,74082.203,4734.167 }, { 26324.074,74182.203,4834.167 } } }, + { .id = 26241, .name = "TRANSPORTFLEET_EP_04", .box = { { 43514.754,72758.508,6418.685 }, { 43614.754,72858.508,6518.685 } } }, + { .id = 23311, .name = "WOLFCRY_EP_01", .box = { { 16683.711,70794.195,1999.813 }, { 16783.711,70894.195,2099.813 } } }, + { .id = 23321, .name = "WOLFCRY_EP_02", .box = { { 8451.205,64828.133,1999.813 }, { 8551.205,64928.133,2099.813 } } }, + { .id = 23331, .name = "WOLFCRY_EP_03", .box = { { 22846.598,33700.957,3776.000 }, { 22946.598,33800.957,3876.000 } } }, + { .id = 23341, .name = "WOLFCRY_EP_04", .box = { { 35017.801,43299.293,3775.757 }, { 35117.801,43399.293,3875.757 } } }, +}; + diff --git a/src/login.c b/src/login.c new file mode 100644 index 0000000..a804d33 --- /dev/null +++ b/src/login.c @@ -0,0 +1,362 @@ +#include "db.h" +#include "packet.h" +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +static struct { + struct { struct db login; } db; + uint32_t session; +} server; + +static void +flush_packet(union packet *packet) +{ + packet_crypt(packet); + fwrite(packet->buf, 1, packet->hdr.size, stdout); +} + +static void +handle_select_character_req(union packet *packet, bool prologue) +{ + uint32_t char_id; + { + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_read(&pbuf, &char_id, sizeof(char_id)); + } + + // TODO: check that account actually has the character + + db_query_single(&server.db.login, "update sessions set char_id = ? where id = ?", + (struct db_query_arg[]){ + { .type = DB_ARG_I32, .u.i32 = char_id }, + { .type = DB_ARG_I32, .u.i32 = server.session } + }, 2, NULL, 0); + + struct db_query_arg args[] = { + { .type = DB_ARG_I32, .u.i32 = server.session } + }, cols[2]; + + const int results = db_query_single(&server.db.login, + "select host, port from servers where name = (select server from sessions where id = ?) limit 1", + args, ARRAY_SIZE(args), cols, ARRAY_SIZE(cols)); + + assert(results == ARRAY_SIZE(cols)); + assert(cols[0].type == DB_ARG_UTF8 || cols[0].type == DB_ARG_NULL); + assert(cols[1].type == DB_ARG_I32); + + const char *host = getenv("TCPREMOTEIP"); + if (!host || strcmp(host, "127.0.0.1")) { + const char *host = getenv("GAME_SERVER"); + host = (host ? host : cols[0].u.blob.data); + } + + warnx("connecting to game server: %s:%u", (host ? host : (char*)cols[0].u.blob.data), cols[1].u.i32); + + packet->hdr.type = (prologue ? PACKET_CHARACTER_ENTER_PROLOGUE : PACKET_SELECT_CHARACTER_RES); + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_write(&pbuf, &char_id, sizeof(char_id)); + for (size_t i = 0; i < 32; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write_str(&pbuf, (host ? host : cols[0].u.blob.data)); // server ip + pbuf_write(&pbuf, (uint16_t[]){cols[1].u.i32}, sizeof(uint16_t)); // port + for (size_t i = 0; i < 38; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown + pbuf_flush(&pbuf); + flush_packet(packet); +} + +static void +handle_create_character_req(union packet *packet) +{ + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf.cursor += 4; // unknown; + + struct pbuf_string name; + uint8_t model; + pbuf_read_str_utf16(&pbuf, &name); + pbuf_read(&pbuf, &model, 1); + + db_query_single(&server.db.login, + "insert into characters (account_id, model, name) values ((select account_id from sessions where id = ? limit 1), ?, ?)", + (struct db_query_arg[]){ + { .type = DB_ARG_I32, .u.i32 = server.session }, + { .type = DB_ARG_I32, .u.i32 = model }, + { .type = DB_ARG_UTF8, .u.blob = { .data = name.data, .sz = name.len } } + }, 3, NULL, 0); +} + +static void +handle_delete_character_req(union packet *packet) +{ + packet->hdr.type = PACKET_DELETE_CHARACTER_RES; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_flush(&pbuf); + flush_packet(packet); +} + +static void +handle_charlist(union packet *packet) +{ + packet->hdr.type = PACKET_CHARACTER_LIST_RES; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + + struct db_query_arg args[] = {{ .type = DB_ARG_I32, .u.i32 = server.session }}, cols[5]; + struct db_query query = db_query_begin(&server.db.login, + "select id, model, name, evolution, lvl from characters where account_id = (select account_id from sessions where id = ? limit 1) order by id", + args, ARRAY_SIZE(args)); + + pbuf_write(&pbuf, (uint8_t[]){0}, 1); // num characters (placeholder) + for (uint8_t i = 0; db_query_fetch(&query, cols, ARRAY_SIZE(cols)) == ARRAY_SIZE(cols); ++i) { + 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); + pbuf_write(&pbuf, (uint32_t[]){cols[0].u.i32}, sizeof(uint32_t)); // charid + pbuf_write_str_len_utf16(&pbuf, cols[2].u.blob.data, cols[2].u.blob.sz); // char name UTF16 + pbuf_write(&pbuf, (uint8_t[]){cols[1].u.i32}, 1); // char model (lily, haru, ...), 0 for no char + pbuf_write(&pbuf, (uint8_t[]){cols[3].u.i32}, 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, (uint8_t[]){cols[4].u.i32}, 1); // char lvl + for (size_t i = 0; i < 526; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // fashion? + pbuf_write(&pbuf, (uint8_t[]){i + 1}, 1); // char slot + packet->buf[sizeof(packet->hdr)] = i + 1; + } + pbuf_flush(&pbuf); + flush_packet(packet); +} + +static void +handle_enter_server_req(union packet *packet) +{ + { + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_read(&pbuf, &server.session, sizeof(server.session)); + } + + packet->hdr.type = PACKET_ENTER_SERVER_RES; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // error code maybe? + pbuf_write(&pbuf, &server.session, sizeof(server.session)); + pbuf_write(&pbuf, (uint8_t[]){0xff, 0xff}, 2); // unknown + pbuf_flush(&pbuf); + flush_packet(packet); + + packet->hdr.type = PACKET_SYSTEM_SERVER_OPTION_UPDATE; + pbuf = (struct pbuf){ .packet = packet, .cursor = sizeof(packet->hdr) }; + // TODO: figure out what these options are, some of them disable second password + pbuf_write(&pbuf, (uint8_t[]){0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01}, 14); + pbuf_flush(&pbuf); + flush_packet(packet); +} + +static void +handle_server_connect_req(union packet *packet) +{ + { + uint8_t selected; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_read(&pbuf, &selected, sizeof(selected)); + db_query_single(&server.db.login, "update sessions set server = (select name from servers order by id limit 1 offset ?) where id = ?", + (struct db_query_arg[]){ + { .type = DB_ARG_I32, .u.i32 = (int)selected + 1 }, + { .type = DB_ARG_I32, .u.i32 = server.session } + }, 2, NULL, 0); + + warnx("moving to the authentication part: %u", selected); + } + + packet->hdr.type = PACKET_ENTER_SERVER; + packet->hdr.size = sizeof(packet->hdr); + flush_packet(packet); +} + +static void +handle_server_list_req(union packet *packet) +{ + uint8_t num_chars; + { + struct db_query_arg col = {0}; + db_query_single(&server.db.login, + "select count(id) from characters where account_id = (select account_id from sessions where id = ? limit 1)", + (struct db_query_arg[]){{ .type = DB_ARG_I32, .u.i32 = server.session }}, 1, &col, 1); + num_chars = col.u.i32; + } + + uint16_t port; + const char *host = getenv("TCPREMOTEIP"); + { + struct db_query_arg cols[2]; + const int results = db_query_single(&server.db.login, + "select host, port from servers limit 1", + NULL, 0, cols, ARRAY_SIZE(cols)); + + assert(results == ARRAY_SIZE(cols)); + assert(cols[0].type == DB_ARG_UTF8 || cols[0].type == DB_ARG_NULL); + assert(cols[1].type == DB_ARG_I32); + port = cols[1].u.i32; + + if (!host || strcmp(host, "127.0.0.1")) + host = (cols[0].u.blob.data ? cols[0].u.blob.data : "127.0.0.1"); + } + + packet->hdr.type = PACKET_SERVER_LIST; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // num servers (placeholder) + + struct db_query_arg cols[2]; + struct db_query query = db_query_begin(&server.db.login, "select name, status from servers order by id limit 255 offset 1", NULL, 0); + for (uint8_t i = 0; db_query_fetch(&query, cols, ARRAY_SIZE(cols)) == ARRAY_SIZE(cols); ++i) { + assert(cols[0].type == DB_ARG_UTF8); + assert(cols[1].type == DB_ARG_I32); + warnx("%s: %u", (char*)cols[0].u.blob.data, cols[1].u.i32); + pbuf_write(&pbuf, (uint8_t[]){i}, 1); // server index + pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write(&pbuf, &port, sizeof(port)); // port + pbuf_write_str_len(&pbuf, cols[0].u.blob.data, cols[0].u.blob.sz); // server name + pbuf_write_str(&pbuf, host); // server ip + pbuf_write(&pbuf, (uint32_t[]){cols[1].u.i32}, sizeof(uint32_t)); // server status + for (size_t i = 0; i < 4; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write(&pbuf, &num_chars, 1); // num characters + packet->buf[sizeof(packet->hdr) + 1] = i + 1; + } + db_query_end(&query); + pbuf_flush(&pbuf); + flush_packet(packet); +} + +static void +handle_login_req(union packet *packet) +{ + struct pbuf_string username, password, mac = {0}; + { + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_read_str_utf16(&pbuf, &username); + pbuf_read_str_utf16(&pbuf, &password); + pbuf_read_str_utf16(&pbuf, &mac); + } + + struct db_query_arg args[] = { + { .type = DB_ARG_UTF8, .u.blob = { .data = username.data, .sz = username.len } }, + { .type = DB_ARG_UTF8, .u.blob = { .data = password.data, .sz = password.len } }, + }, cols[1]; + + const int results = db_query_single(&server.db.login, + "select id from accounts where username = ? and password = ? limit 1", + args, ARRAY_SIZE(args), cols, ARRAY_SIZE(cols)); + + warnx("%s login: %d", username.data, results); + + enum { + OK = 0x00, + INVALID_CREDENTIALS = 0x01, + ALREADY_CONNECTED = 0x02, + ACCOUNT_BLOCKED = 0x03, + IP_BLOCKED = 0x04, + MAC_BLOCKED = 0x05, + MAC_INVALID = 0x06, + } status = OK; + + if (mac.len != 17) { + status = MAC_INVALID; + } else if (!results) { + status = INVALID_CREDENTIALS; + } else { + const uint32_t account_id = cols[0].u.i32; + + db_query_single(&server.db.login, + "update accounts set last_login = current_timestamp where username = ? and password = ?", + args, ARRAY_SIZE(args), NULL, 0); + + do { + server.session = rand(); + } while (db_query_single(&server.db.login, "select id from sessions where id = ?", + (struct db_query_arg[]){{ .type = DB_ARG_I32, .u.i32 = server.session }}, 1, NULL, 0)); + + db_query_single(&server.db.login, + "insert into sessions (id, account_id, server) values (?, ?, (select name from servers order by id limit 1))", + (struct db_query_arg[]){ + { .type = DB_ARG_I32, .u.i32 = server.session }, + { .type = DB_ARG_I32, .u.i32 = account_id } + }, 2, NULL, 0); + } + + packet->hdr.type = PACKET_LOGIN_RESULT; + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_write(&pbuf, &server.session, sizeof(uint32_t)); // session token + pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write(&pbuf, mac.data, 17); // mac + for (size_t i = 0; i < 3; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_write(&pbuf, (uint8_t[]){status}, 1); // login status + for (size_t i = 0; i < 4; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown (but needs to be 0) + pbuf_write_str_len_utf16(&pbuf, username.data, username.len); // username + for (size_t i = 0; i < 13; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + pbuf_flush(&pbuf); + flush_packet(packet); +} + +int +main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + db_init(&server.db.login, "login.db"); + server.session = ~(uint32_t)0; + + union packet packet; + for (size_t psz; (psz = fread(packet.buf, 1, sizeof(packet.hdr), stdin)) > 0; ) { + if (!packet_verify(&packet)) + errx(EXIT_FAILURE, "invalid packet"); + + psz += fread(packet.buf + sizeof(packet.hdr), 1, packet.hdr.size - sizeof(packet.hdr), stdin); + if (psz != packet.hdr.size) + errx(EXIT_FAILURE, "packet size doesn't match, got %zu, expected %u", psz, packet.hdr.size); + + packet_crypt(&packet); + warnx("got packet of type 0x%.4x", packet.hdr.type); + + switch (packet.hdr.type) { + /** LOGIN */ + case PACKET_LOGIN_REQ: + handle_login_req(&packet); + break; + case PACKET_SERVER_LIST_REQ: + handle_server_list_req(&packet); + break; + case PACKET_SERVER_CONNECT_REQ: + handle_server_connect_req(&packet); + break; + /** AUTH */ + case PACKET_ENTER_SERVER_REQ: + handle_enter_server_req(&packet); + break; + case PACKET_CHARACTER_LIST_REQ: + handle_charlist(&packet); + break; + case PACKET_CHARACTER_UPDATE_SPECIAL_OPTION_LIST: // ??? + break; + case PACKET_SECOND_PASSWORD: + warnx("we shouldn't get this packet as we told client to not send it"); + break; + case PACKET_CREATE_CHARACTER_REQ: + handle_create_character_req(&packet); + handle_charlist(&packet); + break; + case PACKET_DELETE_CHARACTER_REQ: + handle_delete_character_req(&packet); + handle_charlist(&packet); + break; + case PACKET_SELECT_CHARACTER_REQ: + handle_select_character_req(&packet, false); + break; + default: + warnx("unknown packet"); + break; + } + + db_gc(&server.db.login); + } + + db_release(&server.db.login); + return EXIT_SUCCESS; +} diff --git a/src/mitm.c b/src/mitm.c new file mode 100644 index 0000000..5966de4 --- /dev/null +++ b/src/mitm.c @@ -0,0 +1,243 @@ +#include "packet.h" +#include +#include +#include +#include + +#include +#include +#include +#include + +struct proc { + pid_t pid; + int fds[2]; +}; + +static void +close_fd(int *fd) +{ + if (*fd >= 0) + close(*fd); +} + +static void +proc_close(struct proc *proc) +{ + if (proc->pid) + waitpid(proc->pid, NULL, 0); + + close_fd(&proc->fds[0]); + close_fd(&proc->fds[1]); + *proc = (struct proc){0}; +} + +static bool +proc_open(const char *file, char *const argv[], struct proc *out_proc) +{ + *out_proc = (struct proc){0}; + + int pipes[4]; + if (pipe(&pipes[0]) != 0 /* parent */ || pipe(&pipes[2]) != 0 /* child */) { + proc_close(out_proc); + return false; + } + + if ((out_proc->pid = fork()) > 0) { + out_proc->fds[0] = pipes[3]; + out_proc->fds[1] = pipes[0]; + close(pipes[1]); + close(pipes[2]); + return true; + } else { + close(pipes[0]); + close(pipes[3]); + dup2(pipes[2], 0); + dup2(pipes[1], 1); + close(pipes[2]); + close(pipes[1]); + execvp(file, argv); + _exit(0); + } + + out_proc->fds[0] = pipes[3]; + out_proc->fds[1] = pipes[0]; + close(pipes[1]); + close(pipes[2]); + return true; +} + +static 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); +} + +static void +dump_packet(const uint8_t *bytes, const size_t sz, const char *prefix, const uint16_t type) +{ + const char *local = getenv("TCPLOCALPORT"); + + char path[4096]; + snprintf(path, sizeof(path), "packets/%s-%s-0x%.4x.raw", local, prefix, type); + + FILE *f; + if (!(f = fopen(path, "wb"))) + err(EXIT_FAILURE, "fopen"); + + if (fwrite(bytes, 1, sz, f) != sz) + err(EXIT_FAILURE, "fwrite"); + + fclose(f); +} + +static void +handle_select_character_res(union packet *packet, bool prologue) +{ + packet->hdr.type = (prologue ? PACKET_CHARACTER_ENTER_PROLOGUE : PACKET_SELECT_CHARACTER_RES); + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf.cursor += sizeof(uint32_t); // char id + for (size_t i = 0; i < 32; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown + const char *server_ip = getenv("SERVER_IP"); + server_ip = (server_ip ? server_ip : "127.0.0.1"); + pbuf_write_str(&pbuf, server_ip); // server ip + pbuf_write(&pbuf, (uint16_t[]){10100}, sizeof(uint16_t)); // port + for (size_t i = 0; i < 38; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown + pbuf_flush(&pbuf); +} + +static void +handle_character_enter_prologue(union packet *packet) +{ + handle_select_character_res(packet, true); +} + +static void +handle_server_list(union packet *packet) +{ + struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) }; + pbuf_write(&pbuf, (uint8_t[]){0x00, 0x01, 0x00, 0x00}, 4); // unknown + pbuf_write(&pbuf, (uint16_t[]){10000}, sizeof(uint16_t)); // port + pbuf_write_str(&pbuf, "benis"); // server name + const char *server_ip = getenv("SERVER_IP"); + server_ip = (server_ip ? server_ip : "127.0.0.1"); + pbuf_write_str(&pbuf, server_ip); // server ip + pbuf_write(&pbuf, (uint8_t[]){0x01, 0x00, 0x00, 0x00, 0x9f, 0x01, 0x00, 0x00, 0x00}, 9); + pbuf_flush(&pbuf); +} + +static bool +intercept(union packet *packet) +{ + packet_crypt(packet); + + bool ret = true; + switch (packet->hdr.type) { + case PACKET_SERVER_LIST: + handle_server_list(packet); + break; + case PACKET_SELECT_CHARACTER_RES: + handle_select_character_res(packet, false); + break; + case PACKET_CHARACTER_ENTER_PROLOGUE: + handle_character_enter_prologue(packet); + break; + default: + ret = false; + break; + } + + packet_crypt(packet); + return ret; +} + +static uint16_t +decrypted_packet_type(union packet *packet) +{ + const uint16_t size = packet->hdr.size; + packet->hdr.size = sizeof(packet->hdr); + packet_crypt(packet); + const uint16_t type = packet->hdr.type; + packet_crypt(packet); + packet->hdr.size = size; + return type; +} + +int +main(int argc, char *const argv[]) +{ + for (int i = 1; i < argc; ++i) { + if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) + errx(EXIT_SUCCESS, "usage: %s cmd [args]", argv[0]); + } + + struct proc server = {0}; + if (argc >= 2 && !proc_open(argv[1], argv + 1, &server)) + errx(EXIT_FAILURE, "failed to execute: %s", argv[1]); + + setvbuf(stdout, NULL, _IONBF, 0); + + struct pollfd pfd[] = { + { .fd = STDIN_FILENO, .events = POLLIN }, + { .fd = server.fds[1], .events = POLLIN }, + }; + + union packet packet; + while (poll(pfd, 1 + !!server.pid, -1) > 0) { + if (pfd[0].revents & POLLIN) { + if (fread(packet.buf, 1, sizeof(packet.hdr), stdin) < sizeof(packet.hdr)) + errx(EXIT_SUCCESS, "client close"); + + if (!packet_verify(&packet)) { + for (size_t i = 0; i < 7; ++i) fprintf(stderr, "%.2x ", packet.buf[i]); + errx(EXIT_FAILURE, "invalid packet"); + } + + if (fread(packet.buf + sizeof(packet.hdr), 1, packet.hdr.size - sizeof(packet.hdr), stdin) != packet.hdr.size - sizeof(packet.hdr)) + errx(EXIT_SUCCESS, "failed to read packet from client"); + + const uint16_t type = decrypted_packet_type(&packet); + dump_packet(packet.buf, packet.hdr.size, "client", type); + + if (intercept(&packet)) { + dump_packet(packet.buf, packet.hdr.size, "client-intercepted", type); + warnx("intercepted client packet 0x%.4x", type); + } else { + warnx("client packet 0x%.4x (%u)",type, packet.hdr.size); + } + + if (server.pid && write(server.fds[0], packet.buf, packet.hdr.size) != packet.hdr.size) + err(EXIT_FAILURE, "failed to write to server"); + } else if (pfd[1].revents & POLLIN) { + if (read(server.fds[1], packet.buf, sizeof(packet.hdr)) < (ssize_t)sizeof(packet.hdr)) + errx(EXIT_SUCCESS, "server close"); + + if (!packet_verify(&packet)) { + for (size_t i = 0; i < 7; ++i) fprintf(stderr, "%.2x ", packet.buf[i]); + errx(EXIT_FAILURE, "invalid packet"); + } + + if (safe_read(server.fds[1], packet.buf + sizeof(packet.hdr), packet.hdr.size - sizeof(packet.hdr)) != (packet.hdr.size - sizeof(packet.hdr))) + errx(EXIT_SUCCESS, "failed to read packet from server"); + + const uint16_t type = decrypted_packet_type(&packet); + dump_packet(packet.buf, packet.hdr.size, "server", type); + + if (intercept(&packet)) { + dump_packet(packet.buf, packet.hdr.size, "server-intercepted", type); + warnx("intercepted server packet 0x%.4x",type); + } else { + warnx("server packet 0x%.4x (%u)", type, packet.hdr.size); + } + + if (fwrite(packet.buf, 1, packet.hdr.size, stdout) != packet.hdr.size) + err(EXIT_FAILURE, "failed to write to client"); + } + } + + proc_close(&server); + return EXIT_SUCCESS; +} diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..8ab6ae6 --- /dev/null +++ b/src/packet.c @@ -0,0 +1,167 @@ +#include "packet.h" +#include +#include +#include +#include + +#include + +_Static_assert(sizeof(((union packet*)0)->buf) >= sizeof(((union packet*)0)->hdr), "packet.hdr must fit packet"); +_Static_assert(sizeof(((union packet*)0)->buf) == sizeof(*((union packet*)0)), "packet.buf must be sizeof(packet)"); + +bool +packet_verify(const union packet *packet) +{ + assert(packet); + + if (packet->hdr.size < sizeof(packet->hdr) || packet->hdr.size > sizeof(packet->buf)) + return false; + + static const uint8_t hdr[] = { 0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff }; // 0xff are wildcards + _Static_assert(sizeof(packet->hdr) >= sizeof(hdr), "sizeof(packet->hdr) != sizeof(hdr)"); + + for (const uint8_t *b = packet->buf, *h = hdr; (size_t)(h - hdr) < sizeof(hdr) ; ++b, ++h) { + if (*h != 0xff && *b != *h) + return false; + } + + return true; +} + +void +packet_crypt(union packet *packet) +{ + assert(packet); + static const uint8_t key[] = { 0x57, 0x19, 0xc6, 0x2d, 0x56, 0x68, 0x3a, 0xcc, 0x60, 0x3b, 0x0b, 0xb1, 0x90, 0x5c, 0x4a, 0xf8, 0x80, 0x28, 0xb1, 0x45, 0xb6, 0x85, 0xe7, 0x4c, 0x06, 0x2d, 0x55, 0x83, 0xaf, 0x44, 0x99 }; + const int16_t salt = packet->hdr.salt + packet->hdr.salt + packet->hdr.salt * 2; + for (size_t i = 5 /* skip salt && size && unknown[0] */, c = 0; i < packet->hdr.size; ++i, ++c) { + const uint32_t hi = (uint64_t)(0x55555556 * c) >> 32; + int32_t magic = (hi >> sizeof(key)) + hi; + magic = salt - (magic + magic * 2); + assert(magic + c < sizeof(key)); + packet->buf[i] ^= key[magic + c]; + } +} + +static size_t +utf16_to_utf8(const uint8_t *utf16, const size_t utf16_sz, uint8_t *utf8, const size_t utf8_sz) +{ + assert(utf16 && utf8 && utf8_sz); + + if (!utf16_sz) { + utf8[0] = 0; + return 0; + } + + static iconv_t cd; + if (!cd && !(cd = iconv_open("utf8", "utf16le"))) + err(EXIT_FAILURE, "iconv_open"); + + size_t isz = utf16_sz, osz = utf8_sz - 1; + iconv(cd, (char*[]){(uint8_t*)utf16}, &isz, (char*[]){utf8}, &osz); + const size_t len = (utf8_sz - 1) - osz; + utf8[len] = 0; + return len; +} + +static size_t +utf8_to_utf16(const uint8_t *utf8, const size_t utf8_sz, uint8_t *utf16, const size_t utf16_sz) +{ + assert(utf8 && utf16 && utf16_sz); + + if (!utf8_sz) + return 0; + + static iconv_t cd; + if (!cd && !(cd = iconv_open("utf16le", "utf8"))) + err(EXIT_FAILURE, "iconv_open"); + + size_t isz = utf8_sz, osz = utf16_sz; + iconv(cd, (char*[]){(uint8_t*)utf8}, &isz, (char*[]){utf16}, &osz); + const size_t len = utf16_sz - osz; + return len; +} + +void +pbuf_flush(struct pbuf *pbuf) +{ + pbuf->packet->hdr.size = pbuf->cursor; + pbuf->cursor = 0; +} + +void +pbuf_write(struct pbuf *pbuf, const void *data, const size_t sz) +{ + assert(pbuf && pbuf->packet); + assert(sz <= sizeof(pbuf->packet->buf) && pbuf->cursor <= sizeof(pbuf->packet->buf) - sz); + memcpy(pbuf->packet->buf + pbuf->cursor, data, sz); + pbuf->cursor += sz; +} + +void +pbuf_write_str_len(struct pbuf *pbuf, const void *str, const uint16_t len) +{ + pbuf_write(pbuf, &len, sizeof(len)); + pbuf_write(pbuf, str, len); +} + +void +pbuf_write_str(struct pbuf *pbuf, const char *str) +{ + assert(str); + pbuf_write_str_len(pbuf, str, strlen(str)); +} + +void +pbuf_write_str_len_utf16(struct pbuf *pbuf, const void *str, const uint16_t len) +{ + struct pbuf_string u16; + u16.len = utf8_to_utf16(str, len, u16.data, sizeof(u16.data)); + pbuf_write(pbuf, &u16.len, sizeof(u16.len)); + pbuf_write(pbuf, u16.data, u16.len); +} + +void +pbuf_write_str_utf16(struct pbuf *pbuf, const char *str) +{ + assert(str); + pbuf_write_str_len_utf16(pbuf, str, strlen(str)); +} + +size_t +pbuf_read_safe(struct pbuf *pbuf, void *data, const size_t data_sz, const size_t sz) +{ + assert(pbuf && pbuf->packet); + assert(sz <= sizeof(pbuf->packet->buf) && pbuf->cursor <= sizeof(pbuf->packet->buf) - sz); + const size_t to_copy = (sz > data_sz ? data_sz : sz); + memcpy(data, pbuf->packet->buf + pbuf->cursor, to_copy); + pbuf->cursor += sz; + return to_copy; +} + +void +pbuf_read(struct pbuf *pbuf, void *data, const size_t sz) +{ + pbuf_read_safe(pbuf, data, sz, sz); +} + +void +pbuf_read_str(struct pbuf *pbuf, struct pbuf_string *str) +{ + assert(str); + pbuf_read(pbuf, &str->len, sizeof(str->len)); + str->len = pbuf_read_safe(pbuf, str->data, sizeof(str->data) - 1, str->len); + assert(str->len < sizeof(str->data)); + str->data[str->len] = 0; +} + +void +pbuf_read_str_utf16(struct pbuf *pbuf, struct pbuf_string *str) +{ + assert(str); + struct pbuf_string u16; + pbuf_read(pbuf, &u16.len, sizeof(u16.len)); + u16.len = pbuf_read_safe(pbuf, u16.data, sizeof(u16.data) - 1, u16.len); + assert(u16.len < sizeof(u16.data)); + str->len = utf16_to_utf8(u16.data, u16.len, str->data, sizeof(str->data)); +} diff --git a/src/packet.h b/src/packet.h new file mode 100644 index 0000000..2219a4a --- /dev/null +++ b/src/packet.h @@ -0,0 +1,535 @@ +#pragma once + +#include +#include +#include + +// packet types, matching sw's strings +enum { + // (send_eSUB_)?CMD_* ids crawled with find-client-packets.bash + PACKET_EVENT_SPAWN_BOX = 0x3111, + PACKET_EVENT_SCENE_DIRECTING = 0x3311, + PACKET_MONSTER_CLIENT_SPAWN = 0x7111, + PACKET_MAZE_ENTER_PARTY_RES = 0x4811, + PACKET_LOGIN_REQ = 0x0102, + PACKET_SERVER_LIST_REQ = 0x0302, + PACKET_SERVER_CONNECT_REQ = 0x0502, + PACKET_MOBILE_AUTH = 0x3302, + PACKET_ENTER_WAIT_CANCEL = 0x3502, + PACKET_CHARACTER_LIST_REQ = 0x1103, + PACKET_CREATE_CHARACTER_REQ = 0x0103, + PACKET_DELETE_CHARACTER_REQ = 0x0203, + PACKET_SELECT_CHARACTER_REQ = 0x1303, + PACKET_CHARACTER_CHANGE_SLOT = 0x0603, + PACKET_CHARACTER_INFO_REQ = 0x3103, + PACKET_ITEM_INVEN_INFO = 0x0108, // freezes + PACKET_ITEM_BANK_INFO = 0x1008, + PACKET_ITEM_MOVE = 0x0208, + PACKET_ITEM_COMBINE = 0x0308, + PACKET_ITEM_DIVIDE = 0x0408, + PACKET_ITEM_BREAK = 0x0508, + PACKET_ITEM_USE = 0x1108, + PACKET_ITEM_USE_SELECT = 0x6708, + PACKET_ITEM_MOVE_MONEY = 0x2408, + PACKET_ITEM_ADD_SLOT = 0x0f08, + PACKET_ITEM_LINE_UP = 0x2508, + PACKET_WORLD_DISTRICT_TRANSPORT_REQ = 0x4004, + PACKET_MAZE_CREATE_REQ = 0x4104, + PACKET_COMPLETE_MAZE_REQ = 0x2211, + PACKET_COMPLETE_MAZE_START_GAME = 0x2511, + PACKET_EXIT_MAZE_REQ = 0x2311, + PACKET_MAZE_SWITCH_NPC_CLICK_REQ = 0x7c11, + PACKET_MAZE_LUA_FUNCTION_REQ = 0x7e11, + PACKET_OPERATION_END_REQ = 0x6211, + PACKET_MAZE_INTERACTION_CLICK_REQ = 0x7711, + PACKET_MAZE_INTERACTION_MOTION = 0x7911, + PACKET_QUEST_MOVE_CHECK_REQ = 0x6511, + PACKET_CHECK_EVENT_SPAWN_BOX_REQ = 0x3511, + PACKET_MAZE_SECTOR_ERROR_FIX = 0x0311, + PACKET_PARTY_INVITE = 0x0112, + PACKET_PARTY_ACCEPT = 0x0212, + PACKET_PARTY_CHANGE_MASTER = 0x0312, + PACKET_PARTY_KICK_OUT = 0x0412, + PACKET_PARTY_LEAVE = 0x0512, + PACKET_SHOP_BUY = 0x0109, + PACKET_SHOP_SELL = 0x0209, + PACKET_SHOP_REPURCHASER_LIST = 0x0309, + PACKET_SHOP_REPURCHASER = 0x0409, + PACKET_SHOP_CASH_LOAD = 0x2009, + PACKET_SHOP_CASH_BUY = 0x2109, + PACKET_SHOP_CASH_SET = 0x2309, + PACKET_SHOP_CASH_SET_DEL = 0x2409, + PACKET_SHOP_CASH_GIFT = 0x2509, + PACKET_TRADE_REQ = 0x2a00, + PACKET_TRADE_ACCEPT = 0x2a00, + PACKET_TRADE_UPDATE_ITEM = 0x2a00, + PACKET_TRADE_UPDATE_MONEY = 0x2a00, + PACKET_TRADE_CHECK = 0x2a00, + PACKET_TRADE_CONFIRM = 0x2a00, + PACKET_TRADE_CANCEL = 0x2a00, + PACKET_PRIVATE_SHOP_START = 0x2a00, + PACKET_PRIVATE_SHOP_STATE = 0x2a00, + PACKET_PRIVATE_SHOP_ITEM = 0x2a00, + PACKET_PRIVATE_SHOP_SELECT = 0x2a00, + PACKET_OPTION_UPDATE = 0x0201, + PACKET_LOGIN_OPTION_UPDATE = 0x3202, + PACKET_CHAT_NORMAL = 0x0107, + PACKET_CHAT_WHISPER = 0x0207, + PACKET_CHAT_TRADE = 0x0307, + PACKET_CHAT_GM_COMMAND = 0x0607, + PACKET_ITEM_UPGRADE = 0x0218, + PACKET_ITEM_RESTORE = 0x2218, + PACKET_ITEM_UNSEAL = 0x2418, + PACKET_ITEM_USE_EFFECT = 0x2618, + PACKET_ITEM_EXCHANGE = 0x0318, + PACKET_ITEM_DISASSEMBLE = 0x0418, + PACKET_DROP_PICK_UP = 0x0214, + PACKET_ITEM_REPAIR = 0x0818, + PACKET_ITEM_REPAIR_NPC = 0x0918, + PACKET_ITEM_REPAIR_EQUIP = 0x1018, + PACKET_ITEM_REPAIR_ALL = 0x1118, + PACKET_ITEM_RENOVATE = 0x2718, + PACKET_ITEM_REFINE = 0x2918, + PACKET_ITEM_EVOLUTION = 0x1318, + PACKET_ITEM_AKASHIC_MAKE_EX = 0x2518, + PACKET_ITEM_AKASHIC_DISASSEMBLE = 0x1518, + PACKET_ITEM_AKASHIC_COMPOSE = 0x1718, + PACKET_ITEM_AKASHIC_COMPOSE_EX = 0x3318, + PACKET_ITEM_DISASSEMBLE_EX = 0x1818, + PACKET_QUEST_ACCEPT = 0x0315, + PACKET_QUEST_EPISODE_COMPLETE = 0x0515, + PACKET_QUEST_GIVE_UP = 0x0615, + PACKET_QUEST_EVENT_UPDATE = 0x0815, + PACKET_QUEST_HELPER = 0x0915, + PACKET_QUEST_FAIL = 0x1115, + PACKET_DAILY_MISSION_ACCEPT = 0x0224, + PACKET_DAILY_MISSION_HELPER = 0x0424, + PACKET_WEEKLY_MISSION_REWARD = 0x0332, + PACKET_WEEKLY_MISSION_REWARD_WEEK = 0x0432, + PACKET_CHARACTER_LOAD_TITLE = 0x2303, + PACKET_CHARACTER_GET_REWARD_SHARE_POINT = 0x6303, + PACKET_CHARACTER_UPDATE_TITLE = 0x2503, + PACKET_CHARACTER_FAVORITE_TITLE = 0x2a03, + PACKET_FRIEND_INVITE = 0x1119, + PACKET_FRIEND_INVITE_ACCEPT = 0x1319, + PACKET_FRIEND_DELETE = 0x1519, + PACKET_FRIEND_BLOCK_ADD = 0x2119, + PACKET_FRIEND_BLOCK_DEL = 0x2219, + PACKET_FRIEND_RECOMMAND_LIST = 0x5119, + PACKET_FRIEND_INFO = 0x3219, + PACKET_POST_SEND_LIST = 0x1020, + PACKET_POST_RECV_LIST = 0x0220, + PACKET_POST_SAVE_LIST = 0x0120, + PACKET_POST_READ = 0x0420, + PACKET_POST_ACCOUNT_READ = 0x1620, + PACKET_POST_SEND = 0x0320, + PACKET_POST_RECEIPT = 0x0520, + PACKET_POST_ACCOUNT_RECEIPT = 0x1720, + PACKET_POST_SENDBACK = 0x0820, + PACKET_POST_SEND_DEL = 0x0620, + PACKET_POST_RECV_DEL = 0x0720, + PACKET_POST_SAVE = 0x1820, + PACKET_POST_LIST_REFRESH = 0x1920, + PACKET_POST_RECEIPT_ALL = 0x2120, + PACKET_ENTER_SERVER_REQ = 0x1302, + PACKET_ENTER_GAMESERVER_REQ = 0x2103, + PACKET_ITEM_UPDATE_QUICKSLOT_CARD = 0x2708, + PACKET_ITEM_UPDATE_QUICKSLOT_ITEM = 0x2808, + PACKET_ITEM_MAZE_REWARD_ITEM = 0x4708, + PACKET_ITEM_APPEARANCE_EQUIP = 0x5208, + PACKET_ITEM_NAME_CHANGE = 0x5208, + PACKET_ITEM_UPDATE_CASH = 0x5308, + PACKET_ITEM_MAKE = 0x0118, + PACKET_ITEM_SOCKET_EQUIP = 0x0518, + PACKET_ITEM_SOCKET_ACTIVE = 0x0618, + PACKET_ITEM_SOCKET_DETACH = 0x0718, + PACKET_ITEM_BROACH_EQUIP = 0x2018, + PACKET_ITEM_BROACH_ACTIVE = 0x2118, + PACKET_ITEM_BROACH_COMPOSE = 0x2318, + PACKET_ITEM_BROACH_REMOVE = 0x2818, + PACKET_ITEM_SOCKET_EXCHANGE = 0x3018, + PACKET_ITEM_SOCKET_UPGRADE = 0x3118, + PACKET_ITEM_SOCKET_EXTRACT = 0x3218, + PACKET_ITEM_TITLE_CHANGE = 0x3718, + PACKET_ITEM_DYE = 0x3618, + PACKET_MODE_MAZE_MATCHING_ENTER = 0x0133, + PACKET_MODE_MAZE_MATCHING_EXIT = 0x0333, + PACKET_SOULMETRY_COMPLETE = 0x0521, + PACKET_VACCUM_CLICK_START = 0x0125, + PACKET_VACCUM_CLICK_CANCEL = 0x0225, + PACKET_LEAGUE_CREATE = 0x0122, + PACKET_LEAGUE_DELETE = 0x0222, + PACKET_LEAGUE_LIST = 0x0322, + PACKET_LEAGUE_INVITE = 0x1322, + PACKET_LEAGUE_INVITE_ACCEPT = 0x1422, + PACKET_LEAGUE_INVITE_REJECT = 0x1522, + PACKET_LEAGUE_WITHDRAW = 0x0922, + PACKET_LEAGUE_KICK = 0x0f22, + PACKET_LEAGUE_APPLICANT = 0x0522, + PACKET_LEAGUE_APPLICANT_ACCEPT = 0x1822, + PACKET_LEAGUE_APPLICANT_REJECT = 0x1922, + PACKET_LEAGUE_DELEGATE = 0x0a22, + PACKET_LEAGUE_BOARD = 0x0822, + PACKET_LEAGUE_SEARCH = 0x2022, + PACKET_LEAGUE_OVERLAP_NAME = 0x2122, + PACKET_LEAGUE_INFO = 0x0722, + PACKET_LEAGUE_AUTH_CHANGE = 0x3222, + PACKET_LEAGUE_NOTICE_CHANGE = 0x2922, + PACKET_LEAGUE_POSITION_NAME_CHANGE = 0x3322, + PACKET_LEAGUE_CARD_CHANGE = 0x3122, + PACKET_LEAGUE_SKILL_LEARN = 0x5322, + PACKET_LEAGUE_INVENTORY_MOVE = 0x5422, + PACKET_LEAGUE_NAME_CHANGE = 0x3022, + PACKET_LEAGUE_INVENTORY_INFO = 0x5622, + PACKET_LEAGUE_MEMBER_POSITION_CHANGE = 0x3922, + PACKET_LEAGUE_OPEN_OR_NOT = 0x4622, + PACKET_LEAGUE_RECRUIT_NOTICE = 0x4722, + PACKET_CHARATER_CHANGE_SERVER = 0x6003, + PACKET_CHARACTER_UPDATE_SPECIAL_OPTION_LIST = 0x4703, + PACKET_CHARACTER_CHECK_ENTER_MAZE = 0x4003, + PACKET_CHARACTER_PROFILE_PHOTO_FAVORITE = 0x0b03, + PACKET_CHARACTER_PROFILE_PHOTO_CAHNGE = 0x0c03, + PACKET_ACHIEVE_REWARD = 0x7403, + PACKET_DO_GESTURE = 0x0123, + PACKET_GESTURE_SLOT_UPDATE = 0x0323, + PACKET_OTHER_CHARACTER_INFO = 0x7503, + PACKET_CHARACTER_COMMUNITY = 0x7703, + PACKET_PARTY_CANCEL = 0x0812, + PACKET_PARTY_UPDATE_INFO = 0x0912, + PACKET_PARTY_MATCHING_ENTER = 0x3012, + PACKET_PARTY_MATCHING_EXIT = 0x3112, + PACKET_PARTY_MATCHING_CHECK = 0x3212, + PACKET_SYSTEM_XIGNCODE = 0x0301, + PACKET_PARTY_RECRUIT_LIST = 0x3612, + PACKET_PARTY_RECRUIT_ADD = 0x3712, + PACKET_PARTY_RECRUIT_APPLY = 0x3812, + PACKET_PARTY_RECRUIT_APPLY_ACCEPT = 0x3912, + PACKET_PARTY_RECRUIT_APPLY_REJECT = 0x3a12, + PACKET_PARTY_RECRUIT_DEL = 0x3c12, + PACKET_PARTY_RECRUIT_APPLY_LIST = 0x3d12, + PACKET_PARTY_AWAITER_LIST = 0x4212, + PACKET_PARTY_AWAITER_ADD = 0x4012, + PACKET_PARTY_AWAITER_DEL = 0x4112, + PACKET_PARTY_RECRUIT_APPLY_INFO = 0x4312, + PACKET_PARTY_AWAITER_INFO = 0x4412, + PACKET_FRIEND_RECRUIT_LIST = 0x4119, + PACKET_FRIEND_RECRUIT_ADD = 0x4219, + PACKET_FRIEND_RECRUIT_DEL = 0x4319, + PACKET_FRIEND_RECRUIT_INFO = 0x4419, + PACKET_FRIEND_FIND = 0x3319, + PACKET_MYROOM_ENTER_REQ = 0x1126, + PACKET_MYROOM_CREATE_REQ = 0x1026, + PACKET_MYROOM_EDIT_START = 0x0526, + PACKET_MYROOM_EDIT_END = 0x0626, + PACKET_MYROOM_RECOMMEND = 0x4026, + PACKET_MYROOM_FAVORITE = 0x4126, + PACKET_MYROOM_WRITE_BOARD = 0x4226, + PACKET_MYROOM_WRITE_INFO = 0x4326, + PACKET_MYROOM_BOARD_LIST = 0x4426, + PACKET_MYROOM_RANK_INFO = 0x4626, + PACKET_MYROOM_FAVORITE_INFO = 0x4726, + PACKET_MYROOM_RANK_REWARD = 0x4826, + PACKET_HELPER_SUMMON = 0x0227, + PACKET_HELPER_SUPPORT_INFO = 0x0527, + PACKET_HELPER_SUPPORT_REGISTER = 0x0627, + PACKET_HELPER_SUPPORT_REWARD = 0x0727, + PACKET_HELPER_SUPPORT_LIST = 0x0827, + PACKET_HELPER_SUPPORT_EQUIP = 0x0927, + PACKET_HELPER_EQUIP = 0x1027, + PACKET_HELPER_CHANGE_ORDER = 0x1027, + PACKET_HELPER_ALL_RELEASE = 0x1227, + PACKET_HELPER_AUTO_SUMMON = 0x1427, + PACKET_MYROOM_EXIT_REQ = 0x1326, + PACKET_MYROOM_SETUP = 0x1426, + PACKET_MYROOM_KICK_OUT = 0x1526, + PACKET_MYROOM_ITEM_ADD = 0x2026, + PACKET_MYROOM_ITEM_DEL = 0x2126, + PACKET_MYROOM_ITEM_USE = 0x2226, + PACKET_MYROOM_ITEM_RELEASE_USED = 0x2326, + PACKET_MYROOM_DOOR_STATE = 0x2626, + PACKET_MYROOM_POLLEN_ADD = 0x3126, + PACKET_MYROOM_POLLEN_CULTIVATION = 0x3226, + PACKET_MYROOM_POLLEN_HARVEST = 0x3326, + PACKET_MYROOM_POLLEN_HELP = 0x3426, + PACKET_MYROOM_POLLEN_ITEM_USE = 0x3526, + PACKET_MYROOM_PLLLEN_CANCEL = 0x3726, + PACKET_INFINITE_TOWER_ENTER_CHAPTER = 0x0228, + PACKET_INFINITE_TOWER_ENTER_NEXT_STAGE = 0x0328, + PACKET_SECOND_PASSWORD = 0x1703, + PACKET_TRADE_PASSWORD = 0x1803, + PACKET_CHARACTER_UPDATE_CUTSCENE = 0x1903, + PACKET_SHOP_GACHA = 0x2609, + PACKET_EXCHANGE_SEARCH = 0x012b, + PACKET_EXCHANGE_PRICE_HISTORY = 0x022b, + PACKET_EXCHANGE_INTEREST_LIST = 0x032b, + PACKET_EXCHANGE_INTEREST_ITEM = 0x042b, + PACKET_EXCHANGE_SELL_REGISTER = 0x052b, + PACKET_EXCHANGE_ITEM_BUY = 0x062b, + PACKET_EXCHANGE_ITEM_RECALL = 0x072b, + PACKET_EXCHANGE_MY_LIST = 0x082b, + PACKET_SOCIAL_ITEM_START = 0x012d, + PACKET_SOCIAL_ITEM_USE = 0x022d, + PACKET_SOCIAL_ITEM_STOP = 0x032d, + PACKET_RANKING_LIST = 0x012c, + PACKET_RANKING_REWARD = 0x022c, + PACKET_FORCE_INVITE = 0x012e, + PACKET_FORCE_ACCEPT = 0x022e, + PACKET_FORCE_CHANGE_MASTER = 0x032e, + PACKET_FORCE_KICK_OUT = 0x042e, + PACKET_FORCE_LEAVE = 0x052e, + PACKET_FORCE_CANCEL = 0x082e, + PACKET_FORCE_MATCHING_ENTER = 0x302e, + PACKET_FORCE_MATCHING_EXIT = 0x312e, + PACKET_EVENT_USE_COUPON_CODE = 0x212a, + PACKET_EVENT_NET_CAFE_ITEM_BUY = 0x2a2a, + PACKET_EVENT_WORLD_EVENT_INFO = 0x222a, + PACKET_EVENT_WORLD_EVENT_REGISTER = 0x232a, + PACKET_EVENT_WORLD_EVENT_REWARD = 0x242a, + PACKET_EVENT_WORLD_EVENT_DAILY_REWARD = 0x252a, + PACKET_EVENT_ROULETTE_USE = 0x292a, + PACKET_FORCE_MATCHING_CHECK = 0x322e, + PACKET_PROJECTILE_BT = 0x0112, + PACKET_CHAIN_BT = 0x0112, + + // guesswork sends + PACKET_PROB_DISCONNECT = 0x1904, + + // receives (manually figured out, the low byte is usually +1 of client REQ) + PACKET_LOGIN_RESULT = 0x0202, + PACKET_SERVER_LIST = 0x0402, + PACKET_ENTER_SERVER = 0x1102, + PACKET_ENTER_SERVER_RES = 0x1402, + PACKET_OPTION_LOAD = 0x3102, + PACKET_ENTER_WAIT_CHECK = 0x3402, + PACKET_SYSTEM_KEEP_ALIVE = 0x0501, + PACKET_SYSTEM_SERVER_OPTION_UPDATE = 0x0701, + PACKET_SYSTEM_EVENT = 0x0901, + PACKET_CHARACTER_LIST_RES = 0x1203, + PACKET_DELETE_CHARACTER_RES = 0x0303, + PACKET_SELECT_CHARACTER_RES = 0x1403, + PACKET_CHARACTER_ENTER_PROLOGUE = 0x1503, + PACKET_CHARACTER_ENTER_BATTLE_ZONE = 0x1603, + PACKET_CHARACTER_PLAY_CUTSCENE = 0x2003, + + PACKET_ENTER_GAMESERVER_RES = 0x2203, + PACKET_CHARACTER_ADD_TITLE = 0x2403, + PACKET_CHARACTER_UPDATE_TITLE_BT = 0x2603, + PACKET_CHARACTER_CLEAR_TITLE = 0x2703, + PACKET_CHARACTER_UPDATE_OPEN_TITLE = 0x2903, + PACKET_CHARACTER_DB_LOAD_SYNC = 0x3003, + PACKET_CHARACTER_INFO_RES = 0x3203, + PACKET_CHARACTER_UPDATE_STAT_LIST = 0x3403, + PACKET_CHARACTER_UPDATE_ORIGIN_STAT = 0x5103, + PACKET_CHARACTER_CHECK_NAME = 0x5703, + PACKET_DAILY_MISSION_UPDATE = 0x3204, + PACKET_EVENT_ATTENDANCE_LOAD = 0x012a, + PACKET_EVENT_ROULETTE_MY_INFO = 0x282a, + PACKET_ITEM_UPDATE_CASH_RES = 0x3308, + PACKET_ITEM_AKASHIC_GETINFO_LOAD = 0x3518, + PACKET_INFINITE_TOWER_LOAD_INFO = 0x0128, + PACKET_CHARACTER_LOAD_DISTRICT_STATE = 0x6103, + PACKET_CHARACTER_LOAD_MAZE_STATE = 0x6203, + PACKET_MAZE_CLEAR_INFO = 0x6411, + PACKET_ENTER_MAZE_LIMIT_COUNT_LOAD = 0x4304, + PACKET_SOULMETRY_LIST = 0x0121, + PACKET_SOULMETRY_COMPLETE_LIST = 0x0221, + PACKET_ACHIEVE_SELECT = 0x7003, + PACKET_SHOP_BANNER_LOAD = 0x2809, + PACKET_SHOP_CASH_SET_LOAD = 0x2209, + PACKET_ITEM_APPEARANCE_LOAD = 0x5008, + PACKET_NPC_CREDIT_LOAD = 0x6503, + PACKET_SHOP_ITEM_LOAD = 0x1009, + PACKET_MYROOM_LOAD_INDEX = 0x1226, + PACKET_FRIEND_LOAD = 0x0119, + PACKET_FRIEND_LOAD_BLOCKLIST = 0x0219, + PACKET_DAILY_MISSION_LIST = 0x0124, + PACKET_HELPER_LIST_LOAD = 0x0127, + PACKET_ACHIEVE_UPDATE = 0x7103, + PACKET_WORLD_VERSION = 0x0404, + PACKET_EVENT_DAY_EVENT_BOOSTER_LIST = 0x202a, + PACKET_LEAGUE_INFO_ALERT = 0x4422, + PACKET_SHOP_CASH_BUY_COUNT_LOAD = 0x3009, + PACKET_SHOP_CASH_TAB_LOAD = 0x2909, + PACKET_IN_PC_INFO = 0x1104, + PACKET_OTHER_PC_INFOS = 0x2104, + PACKET_OTHER_INFOS_NPC = 0x2204, + PACKET_QUEST_LIST = 0x0215, + PACKET_GAME_WORLD_ENTER_RES = 0x0204, + PACKET_ITEM_CREATE = 0x0608, + PACKET_ITEM_MOVE_BT = 0xd08, + PACKET_GESTURES = 0x0223, + + PACKET_SOME_PC_PACKET = 0x5104, + + PACKET_UNKNOWN = 0x0601, // sent a lot + PACKET_AUTH_UNKNOWN1 = 0x0304, // freezes + PACKET_GAME_UNKNOWN1 = 0x0633, // sent after 2203 and 4703 + PACKET_GAME_UNKNOWN2 = 0x6608, + PACKET_GAME_UNKNOWN3 = 0x6808, + PACKET_GAME_UNKNOWN4 = 0x1406, + PACKET_GAME_UNKNOWN5 = 0x0903, + PACKET_SKILLS = 0x7006, + PACKET_GAME_UNKNOWN10 = 0x4a08, + PACKET_GAME_UNKNOWN11 = 0x5708, + PACKET_GAME_UNKNOWN12 = 0x4808, + PACKET_GAME_UNKNOWN13 = 0x0604, + PACKET_GAME_UNKNOWN14 = 0x5317, + PACKET_GAME_UNKNOWN15 = 0x0705, + PACKET_GAME_UNKNOWN16 = 0x9203, + PACKET_POST_ACCOUNT_LIST = 0x1420, + PACKET_POST_ACCOUNT_RECV = 0x1520, + PACKET_DROP_INFOS = 0x0114, + PACKET_EVENT_ATTENDANCE_REWARD = 0x022a, + PACKET_QUEST_COMPLETE_LIST = 0x0115, + PACKET_GAME_UNKNOWN22 = 0x7551, + PACKET_GAME_UNKNOWN24 = 0x2106, + PACKET_BOOSTER_LIST_LOAD = 0x0129, + PACKET_CHANNEL_INFO = 0x01f1, + PACKET_GAME_UNKNOWN27 = 0x0205, + PACKET_BOOSTER_ADD = 0x0229, + PACKET_DROP_DELETE = 0x0314, + PACKET_SOULMETRY_ADD = 0x0321, + PACKET_BOOSTER_REMOVE = 0x0329, + PACKET_ATTENDANCE_CONTINUE_REWARD = 0x032a, + PACKET_GAME_UNKNOWN33 = 0x0405, + PACKET_HELPER_USER_STAT_UPDATE = 0x0427, + PACKET_NOTICE = 0x0507, + PACKET_EVENT_ATTENDANCE_PLAY_TIME_INIT = 0x052a, + PACKET_GAME_UNKNOWN37 = 0x0605, + PACKET_SOCIAL_ITEM_INFOS = 0x062d, + PACKET_GAME_UNKNOWN39 = 0x0706, + PACKET_QUEST_UPDATE = 0x0715, + PACKET_GAME_UNKNOWN41 = 0x0803, + PACKET_WORLD_WARP_RES = 0x0804, + PACKET_GAME_UNKNOWN43 = 0x0805, + PACKET_WORLD_WARP_ENABLE_RES = 0x0904, + PACKET_CHARACTER_ACTION_REQ = 0x0806, + PACKET_CHARACTER_ACTION_RES = 0x0906, + PACKET_GAME_UNKNOWN46 = 0x0c05, + PACKET_GAME_UNKNOWN47 = 0x0d05, + PACKET_WORLD_WARP_MAZE_RES = 0x0e04, + PACKET_ITEM_OPEN_SLOT_INFO = 0x0e08, + PACKET_GAME_UNKNOWN50 = 0x0f05, + PACKET_GAME_UNKNOWN51 = 0x1006, + PACKET_GAME_UNKNOWN52 = 0x1105, + PACKET_GAME_UNKNOWN53 = 0x1117, + PACKET_OUT_INFO_PC = 0x1204, + PACKET_ITEM_REDUCE = 0x1208, + PACKET_GAME_UNKNOWN56 = 0x1217, + PACKET_ITEM_ENDURANCE = 0x1218, + PACKET_GAME_UNKNOWN58 = 0x1305, + PACKET_GAME_UNKNOWN59 = 0x1306, + PACKET_GAME_UNKNOWN60 = 0x1308, + PACKET_VACCUM_INFOS = 0x1325, + PACKET_GAME_UNKNOWN62 = 0x1405, + PACKET_GAME_UNKNOWN63 = 0x1408, + PACKET_IN_INFO_MONSTER = 0x1504, + PACKET_GAME_UNKNOWN65 = 0x1506, + PACKET_OUT_INFO_MONSTER = 0x1604, + PACKET_MOVE_TRANSPORT_TAKE = 0x1605, + PACKET_MOVE_TRANSPORT_OFF = 0x1705, + PACKET_RECEIVE_ZN = 0x2008, + PACKET_GAME_UNKNOWN70 = 0x2108, + PACKET_GAME_UNKNOWN71 = 0x2111, + PACKET_GAME_UNKNOWN72 = 0x2217, + PACKET_OTHER_INFOS_MONSTER = 0x2304, + PACKET_GAME_UNKNOWN74 = 0x2404, // load quickslot + PACKET_GAME_UNKNOWN75 = 0x2608, // ^ + PACKET_GAME_UNKNOWN76 = 0x3206, // ^ some of these + PACKET_GAME_UNKNOWN77 = 0x3208, + PACKET_MOVE_LOOP_MOTION_END_BT = 0x3305, + PACKET_GAME_UNKNOWN79 = 0x3317, + PACKET_MOVE_ATTACED_BT = 0x3405, + PACKET_MOVE_ATTACED_END_BT = 0x3505, + PACKET_GAME_UNKNOWN82 = 0x3603, + PACKET_RECEIVE_XP = 0x3703, + PACKET_ENTER_MAZE_LIMIT_COUNT_RESET = 0x4404, + PACKET_GAME_UNKNOWN85 = 0x4606, + PACKET_GAME_UNKNOWN86 = 0x4906, + PACKET_GAME_UNKNOWN87 = 0x5204, + PACKET_GAME_UNKNOWN88 = 0x5211, // bgm stuff? + PACKET_GAME_UNKNOWN89 = 0x5311, + PACKET_GAME_UNKNOWN90 = 0x5411, + PACKET_GAME_UNKNOWN91 = 0x5508, + PACKET_GAME_UNKNOWN92 = 0x5511, // portal stuff? + PACKET_ITEM_BROACH_LOAD = 0x5608, + PACKET_GAME_UNKNOWN94 = 0x5611, + PACKET_GAME_UNKNOWN95 = 0x5711, + PACKET_CHARACTER_UPDATE_SHARE_POINT = 0x5803, + PACKET_GAME_UNKNOWN97 = 0x5811, + PACKET_ITEM_SOCKET_UPDATE = 0x6108, + PACKET_ITEM_BROACH_UPDATE = 0x6208, + PACKET_CHARACTER_FP_UPDATE = 0x6403, + PACKET_GAME_UNKNOWN101 = 0x6611, // event box stuff + PACKET_GAME_UNKNOWN102 = 0x6811, + PACKET_GAME_UNKNOWN103 = 0x7011, + PACKET_GAME_UNKNOWN104 = 0x7306, + PACKET_GAME_UNKNOWN105 = 0x7511, // end maze / receive reward + PACKET_GAME_UNKNOWN106 = 0x7906, + PACKET_GAME_UNKNOWN107 = 0x7e06, + PACKET_GAME_UNKNOWN108 = 0x7f11, // event box stuff + PACKET_GAME_UNKNOWN109 = 0x2205, + PACKET_CHARACTER_MOVE = 0x105, + PACKET_CHARACTER_MOVE_RES = 0x205, + PACKET_CHARACTER_STOP_MOVE = 0x305, + PACKET_CHARACTER_STOP_MOVE_RES = 0x405, + PACKET_CHARACTER_JUMP = 0x0505, + PACKET_CHARACTER_JUMP_RES = 0x0605, + + // 0x8001 and above make client try to execute a script? +}; + +union packet { + struct { + uint16_t salt, size; + uint8_t unknown; + uint16_t type; + } __attribute__((packed)) hdr; + uint8_t buf[4096 * 4]; +}; + +struct pbuf { + union packet *packet; + size_t cursor; +}; + +struct pbuf_string { + char data[256]; + uint16_t len; +}; + +bool +packet_verify(const union packet *packet); + +void +packet_crypt(union packet *packet); + +void +pbuf_flush(struct pbuf *pbuf); + +void +pbuf_write(struct pbuf *pbuf, const void *data, const size_t sz); + +void +pbuf_write_str_len(struct pbuf *pbuf, const void *str, const uint16_t len); + +void +pbuf_write_str(struct pbuf *pbuf, const char *str); + +void +pbuf_write_str_len_utf16(struct pbuf *pbuf, const void *str, const uint16_t len); + +void +pbuf_write_str_utf16(struct pbuf *pbuf, const char *str); + +size_t +pbuf_read_safe(struct pbuf *pbuf, void *data, const size_t data_sz, const size_t sz); + +void +pbuf_read(struct pbuf *pbuf, void *data, const size_t sz); + +void +pbuf_read_str(struct pbuf *pbuf, struct pbuf_string *str); + +void +pbuf_read_str_utf16(struct pbuf *pbuf, struct pbuf_string *str); diff --git a/src/sw-crypt.c b/src/sw-crypt.c new file mode 100644 index 0000000..04c9d0f --- /dev/null +++ b/src/sw-crypt.c @@ -0,0 +1,25 @@ +#include "packet.h" +#include +#include +#include + +int +main(void) +{ + setvbuf(stdout, NULL, _IONBF, 0); + + union packet packet; + for (size_t psz; (psz = fread(packet.buf, 1, sizeof(packet.hdr), stdin)) > 0;) { + if (!packet_verify(&packet)) + errx(EXIT_FAILURE, "invalid packet"); + + psz += fread(packet.buf + sizeof(packet.hdr), 1, packet.hdr.size - sizeof(packet.hdr), stdin); + if (psz != packet.hdr.size) + errx(EXIT_FAILURE, "packet size doesn't match, got %zu, expected %u", psz, packet.hdr.size); + + packet_crypt(&packet); + fwrite(packet.buf, 1, packet.hdr.size, stdout); + } + + return EXIT_SUCCESS; +} diff --git a/src/xor.c b/src/xor.c new file mode 100644 index 0000000..15411ab --- /dev/null +++ b/src/xor.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s hex ... < input > output", argv[0]); + + uint8_t buf[4096]; + for (size_t ret, c = 0; (ret = fread(buf, 1, sizeof(buf), stdin)) > 0;) { + for (size_t i = 0; i < ret; ++i, c = (c + 1) % (argc - 1)) + buf[i] ^= strtoull(argv[c + 1], 0, 16); + fwrite(buf, 1, ret, stdout); + } + + return EXIT_SUCCESS; +} -- cgit v1.2.3