#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; }