From 833bf90333b1a81d8f86558230d67f701fdec323 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 10 Nov 2018 23:52:23 -0800 Subject: [PATCH] make some progress toward getting psogc to work again --- DNSServer.cc | 7 ++- License.hh | 2 +- Lobby.cc | 6 +-- Main.cc | 80 ++++++++++++++++------------- Makefile | 2 +- NetworkAddresses.cc | 22 +++++++- NetworkAddresses.hh | 6 +++ PSOEncryption.cc | 13 ++--- Quest.cc | 5 +- ReceiveCommands.cc | 116 ++++++++++++++++++++++++++++++++---------- ReceiveSubcommands.cc | 7 ++- SendCommands.cc | 67 ++++++++++++++---------- SendCommands.hh | 9 +++- Server.cc | 16 +++++- ServerState.cc | 20 ++++++-- ServerState.hh | 6 ++- Text.cc | 4 +- Text.hh | 14 ++++- system/config.json | 6 ++- system/sjis-table.ini | 2 + 20 files changed, 286 insertions(+), 124 deletions(-) diff --git a/DNSServer.cc b/DNSServer.cc index 79f5bd53..a1a83a95 100644 --- a/DNSServer.cc +++ b/DNSServer.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -93,8 +94,9 @@ void DNSServer::run_thread() { if (bytes > 0) { input.resize(bytes); + uint32_t remote_address = bswap32(remote.sin_addr.s_addr); uint32_t connect_address; - if (is_local_address(remote.sin_addr.s_addr)) { + if (is_local_address(remote_address)) { connect_address = this->local_connect_address; } else { connect_address = this->external_connect_address; @@ -121,11 +123,12 @@ string DNSServer::build_response(const std::string& input, string ret; size_t name_len = strlen(input.data() + 0x0C) + 1; + uint32_t connect_address_be = bswap32(connect_address); ret.append(input.substr(0, 2)); ret.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10); ret.append(input.substr(12, name_len)); ret.append("\x00\x01\x00\x01\xC0\x0C\x00\x01\x00\x01\x00\x00\x00\x3C\x00\x04", 16); - ret.append(reinterpret_cast(&connect_address), 4); + ret.append(reinterpret_cast(&connect_address_be), 4); return ret; } diff --git a/License.hh b/License.hh index 9f8ea27b..2fe091a0 100644 --- a/License.hh +++ b/License.hh @@ -34,7 +34,7 @@ struct License { char gc_password[12]; // GC password uint32_t privileges; // privilege level uint64_t ban_end_time; // end time of ban (zero = not banned) -}; +} __attribute__((packed)); class LicenseManager { public: diff --git a/Lobby.cc b/Lobby.cc index ad5f8163..44b6d687 100644 --- a/Lobby.cc +++ b/Lobby.cc @@ -35,7 +35,7 @@ void Lobby::reassign_leader_on_client_departure_locked(size_t leaving_client_ind return; } } - throw out_of_range("no clients remain"); + this->leader_id = 0; } bool Lobby::any_client_loading() const { @@ -68,11 +68,11 @@ void Lobby::add_client(shared_ptr c) { } void Lobby::add_client_locked(shared_ptr c) { - rw_guard g(this->lock, true); size_t index; for (index = 0; index < this->max_clients; index++) { if (!this->clients[index].get()) { this->clients[index] = c; + break; } } if (index >= this->max_clients) { @@ -101,8 +101,6 @@ void Lobby::remove_client(shared_ptr c) { } void Lobby::remove_client_locked(shared_ptr c) { - rw_guard g(this->lock, true); - if (this->clients[c->lobby_client_id] != c) { throw logic_error("client\'s lobby client id does not match client list"); } diff --git a/Main.cc b/Main.cc index 987d5f01..c47fff9b 100644 --- a/Main.cc +++ b/Main.cc @@ -1,12 +1,15 @@ -#include #include +#include +#include #include #include #include +#include #include #include "NetworkAddresses.hh" +#include "SendCommands.hh" #include "DNSServer.hh" #include "ServerState.hh" #include "Server.hh" @@ -61,7 +64,7 @@ void populate_state_from_config(shared_ptr s, shared_ptr config_json) { const auto& d = config_json->as_dict(); - s->name = d.at("ServerName")->as_string(); + s->name = decode_sjis(d.at("ServerName")->as_string()); // TODO: make this configurable s->port_configuration = default_port_to_behavior; @@ -76,45 +79,50 @@ void populate_state_from_config(shared_ptr s, box_categories, unit_types)); shared_ptr> information_menu(new vector()); - shared_ptr> id_to_information_contents( - new unordered_map()); + shared_ptr> information_contents(new vector()); - uint32_t item_id = 1; + information_menu->emplace_back(INFORMATION_MENU_GO_BACK, u"Go back", + u"Return to the\nmain menu", 0); + + uint32_t item_id = 0; for (const auto& item : d.at("InformationMenuContents")->as_list()) { auto& v = item->as_list(); information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()), decode_sjis(v.at(1)->as_string()), MenuItemFlag::RequiresMessageBoxes); - id_to_information_contents->emplace(item_id, decode_sjis(v.at(2)->as_string())); + information_contents->emplace_back(decode_sjis(v.at(2)->as_string())); item_id++; } s->information_menu = information_menu; - s->id_to_information_contents = id_to_information_contents; + s->information_contents = information_contents; s->num_threads = d.at("Threads")->as_int(); auto local_address_str = d.at("LocalAddress")->as_string(); - uint32_t local_address = inet_addr(local_address_str.c_str()); - if (s->all_addresses.emplace(local_address).second) { - log(INFO, "added local address: %hhu.%hhu.%hhu.%hhu", - static_cast(local_address >> 24), static_cast(local_address >> 16), - static_cast(local_address >> 8), static_cast(local_address)); - } + s->local_address = address_for_string(local_address_str.c_str()); + s->all_addresses.emplace(s->local_address); + log(INFO, "added local address: %s", local_address_str.c_str()); - auto external_address_str = d.at("LocalAddress")->as_string(); - uint32_t external_address = inet_addr(external_address_str.c_str()); - if (s->all_addresses.emplace(external_address).second) { - log(INFO, "added external address: %hhu.%hhu.%hhu.%hhu", - static_cast(external_address >> 24), static_cast(external_address >> 16), - static_cast(external_address >> 8), static_cast(external_address)); + auto external_address_str = d.at("ExternalAddress")->as_string(); + s->external_address = address_for_string(external_address_str.c_str()); + s->all_addresses.emplace(s->external_address); + log(INFO, "added external address: %s", external_address_str.c_str()); + + try { + s->run_dns_server = d.at("RunDNSServer")->as_bool(); + } catch (const JSONObject::key_error&) { + s->run_dns_server = true; } } -int main(int argc,char* argv[]) { +int main(int argc, char* argv[]) { log(INFO, "fuzziqer software newserv"); signal(SIGPIPE, SIG_IGN); + if (evthread_use_pthreads()) { + log(ERROR, "cannot enable multithreading in libevent"); + } log(INFO, "creating server state"); shared_ptr state(new ServerState()); @@ -122,9 +130,8 @@ int main(int argc,char* argv[]) { log(INFO, "reading network addresses"); state->all_addresses = get_local_address_list(); for (uint32_t addr : state->all_addresses) { - log(INFO, "found address: %hhu.%hhu.%hhu.%hhu", - static_cast(addr >> 24), static_cast(addr >> 16), - static_cast(addr >> 8), static_cast(addr)); + string addr_str = string_for_address(addr); + log(INFO, "found address: %s", addr_str.c_str()); } log(INFO, "loading configuration"); @@ -143,15 +150,20 @@ int main(int argc,char* argv[]) { log(INFO, "collecting quest metadata"); state->quest_index.reset(new QuestIndex("system/quests")); - log(INFO, "starting dns server"); - DNSServer dns_server(state->local_address, state->external_address); - // TODO: call dns_server.listen appropriately - dns_server.start(); + shared_ptr dns_server; + if (state->run_dns_server) { + log(INFO, "starting dns server on port 53"); + dns_server.reset(new DNSServer(state->local_address, state->external_address)); + dns_server->listen("", 53); + dns_server->start(); + } log(INFO, "starting game server"); - Server game_server(state); - // TODO: call game_server.listen appropriately - game_server.start(); + shared_ptr game_server(new Server(state)); + for (const auto& it : state->port_configuration) { + game_server->listen("", it.second.port, it.second.version, it.second.behavior); + } + game_server->start(); for (;;) { sigset_t s; @@ -160,10 +172,10 @@ int main(int argc,char* argv[]) { } log(INFO, "waiting for servers to terminate"); - dns_server.schedule_stop(); - game_server.schedule_stop(); - dns_server.wait_for_stop(); - game_server.wait_for_stop(); + dns_server->schedule_stop(); + game_server->schedule_stop(); + dns_server->wait_for_stop(); + game_server->wait_for_stop(); return 0; } diff --git a/Makefile b/Makefile index 5d2a3601..55e05e68 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OBJECTS=FileContentsCache.o Menu.o PSOProtocol.o Client.o Lobby.o \ Text.o DNSServer.o Main.o CXX=g++ CXXFLAGS=-I/opt/local/include -I/usr/local/include -std=c++14 -g -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -Wall -Werror -LDFLAGS=-L/opt/local/lib -L/usr/local/lib -std=c++14 -levent -lphosg -lpthread +LDFLAGS=-L/opt/local/lib -L/usr/local/lib -std=c++14 -levent -levent_pthreads -lphosg -lpthread EXECUTABLE=newserv all: $(EXECUTABLE) diff --git a/NetworkAddresses.cc b/NetworkAddresses.cc index 6925cac8..833028d6 100644 --- a/NetworkAddresses.cc +++ b/NetworkAddresses.cc @@ -41,7 +41,7 @@ uint32_t resolve_address(const char* address) { } struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr; - return res_sin->sin_addr.s_addr; + return bswap32(res_sin->sin_addr.s_addr); } set get_local_address_list() { @@ -71,9 +71,27 @@ set get_local_address_list() { } bool is_local_address(uint32_t addr) { - uint8_t net = addr & 0xFF; + uint8_t net = (addr >> 24) & 0xFF; if ((net != 127) && (net != 172) && (net != 10) && (net != 192)) { return false; } return true; } + +bool is_local_address(const sockaddr_storage& daddr) { + if (daddr.ss_family != AF_INET) { + return false; + } + const sockaddr_in* sin = reinterpret_cast(&daddr); + return is_local_address(bswap32(sin->sin_addr.s_addr)); +} + +string string_for_address(uint32_t address) { + return string_printf("%hhu.%hhu.%hhu.%hhu", + static_cast(address >> 24), static_cast(address >> 16), + static_cast(address >> 8), static_cast(address)); +} + +uint32_t address_for_string(const char* address) { + return bswap32(inet_addr(address)); +} diff --git a/NetworkAddresses.hh b/NetworkAddresses.hh index 704c7684..e5090b05 100644 --- a/NetworkAddresses.hh +++ b/NetworkAddresses.hh @@ -1,8 +1,10 @@ #pragma once +#include #include #include +#include @@ -13,3 +15,7 @@ uint32_t resolve_address(const char* address); std::set get_local_address_list(); uint32_t get_connected_address(int fd); bool is_local_address(uint32_t daddr); +bool is_local_address(const sockaddr_storage& daddr); + +std::string string_for_address(uint32_t address); +uint32_t address_for_string(const char* address); diff --git a/PSOEncryption.cc b/PSOEncryption.cc index 2ab0bbbe..2a5673ac 100644 --- a/PSOEncryption.cc +++ b/PSOEncryption.cc @@ -130,25 +130,22 @@ PSOGCEncryption::PSOGCEncryption(uint32_t seed) : offset(0) { basekey = basekey & 0x7FFFFFFF; } } - this->stream[this->offset] = basekey; + this->stream[this->offset++] = basekey; } - this->stream[this->offset] = (((this->stream[0] >> 9) ^ (this->stream[this->offset] << 23)) ^ this->stream[15]); + this->stream[this->offset - 1] = (((this->stream[0] >> 9) ^ (this->stream[this->offset - 1] << 23)) ^ this->stream[15]); source1 = 0; source2 = 1; source3 = this->offset - 1; while (this->offset != GC_STREAM_LENGTH) { - this->stream[this->offset] = (this->stream[source3] ^ (((this->stream[source1] << 23) & 0xFF800000) ^ ((this->stream[source2] >> 9) & 0x007FFFFF))); - this->offset++; - source1++; - source2++; - source3++; + this->stream[this->offset++] = (this->stream[source3++] ^ (((this->stream[source1++] << 23) & 0xFF800000) ^ ((this->stream[source2++] >> 9) & 0x007FFFFF))); } - for (size_t x = 0; x < 4; x++) { + for (size_t x = 0; x < 3; x++) { this->update_stream(); } + this->offset = GC_STREAM_LENGTH - 1; } void PSOGCEncryption::encrypt(void* vdata, size_t size) { diff --git a/Quest.cc b/Quest.cc index a828b837..55a34563 100644 --- a/Quest.cc +++ b/Quest.cc @@ -308,7 +308,10 @@ shared_ptr Quest::dat_contents() const { QuestIndex::QuestIndex(const char* directory) : directory(directory) { - for (const auto& filename : list_directory(this->directory)) { + auto filename_set = list_directory(this->directory); + vector filenames(filename_set.begin(), filename_set.end()); + sort(filenames.begin(), filenames.end()); + for (const auto& filename : filenames) { string full_path = this->directory + "/" + filename; if (ends_with(filename, ".gba")) { diff --git a/ReceiveCommands.cc b/ReceiveCommands.cc index 25946436..d53e467d 100644 --- a/ReceiveCommands.cc +++ b/ReceiveCommands.cc @@ -43,9 +43,10 @@ void process_connect(std::shared_ptr s, std::shared_ptr c) switch (c->server_behavior) { case ServerBehavior::SplitReconnect: { uint16_t pc_port = s->port_configuration.at("pc-login").port; - send_pc_gc_split_reconnect(c, 0, pc_port); - c->server_behavior = ServerBehavior::LoginServer; - // intentional fallthrough + uint16_t gc_port = s->port_configuration.at("gc-jp10").port; + send_pc_gc_split_reconnect(c, s->connect_address_for_client(c), pc_port, gc_port); + c->should_disconnect = true; + break; } case ServerBehavior::LoginServer: @@ -68,7 +69,7 @@ void process_login_complete(shared_ptr s, shared_ptr c) { send_ep3_rank_update(c); } - send_menu(c, u"Main menu", MAIN_MENU_ID, s->main_menu, false); + send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false); } else if (c->server_behavior == ServerBehavior::LobbyServer) { @@ -146,6 +147,7 @@ void process_verify_license_gc(shared_ptr s, shared_ptr c, uint32_t sub_version; char unused3[0x60]; char password[0x10]; + char unused4[0x20]; }; check_size(size, sizeof(Cmd)); const auto* cmd = reinterpret_cast(data); @@ -242,6 +244,7 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, char unused3[0x60]; char name[0x10]; ClientConfig cfg; + uint8_t unused4[0x64]; }; check_size(size, sizeof(Cmd)); const auto* cmd = reinterpret_cast(data); @@ -315,7 +318,8 @@ void process_login_bb(shared_ptr s, shared_ptr c, switch (c->config.cfg.bb_game_state) { case ClientStateBB::InitialLogin: // first login? send them to the other port - send_reconnect(c, 0, s->port_configuration.at("bb-data1").port); + send_reconnect(c, s->connect_address_for_client(c), + s->port_configuration.at("bb-data1").port); break; case ClientStateBB::DownloadData: { @@ -340,13 +344,13 @@ void process_login_bb(shared_ptr s, shared_ptr c, break; default: - send_reconnect(c, 0, s->port_configuration.at("bb-login").port); + send_reconnect(c, s->connect_address_for_client(c), + s->port_configuration.at("bb-login").port); } } void process_client_checksum(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 96 - check_size(size, 0); send_command(c, 0x97, 0x01); } @@ -479,13 +483,61 @@ void process_message_box_closed(shared_ptr s, shared_ptr c, if (c->in_information_menu) { // add a reference to ensure it's not destroyed by another thread auto info_menu = s->information_menu; - send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, true); + send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, false); return; } } +void process_menu_item_info_request(shared_ptr s, shared_ptr c, + uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 09 + struct Cmd { + uint32_t menu_id; + uint32_t item_id; + }; + check_size(size, sizeof(Cmd), sizeof(Cmd)); + const auto* cmd = reinterpret_cast(data); + + switch (cmd->menu_id) { + case MAIN_MENU_ID: + switch (cmd->item_id) { + case MAIN_MENU_GO_TO_LOBBY: + send_ship_info(c, u"Go to the lobby."); + break; + case MAIN_MENU_INFORMATION: + send_ship_info(c, u"View server information."); + break; + case MAIN_MENU_DISCONNECT: + send_ship_info(c, u"End your session."); + break; + default: + send_ship_info(c, u"Incorrect menu item ID."); + break; + } + break; + + case INFORMATION_MENU_ID: + if (cmd->item_id == INFORMATION_MENU_GO_BACK) { + send_ship_info(c, u"Return to the\nmain menu."); + } else { + try { + // add a reference to ensure it's not destroyed by another thread + // we use item_id + 1 here because "go back" is the first item + auto info_menu = s->information_menu; + send_ship_info(c, info_menu->at(cmd->item_id + 1).description.c_str()); + } catch (const out_of_range&) { + send_ship_info(c, u"$C6No such information exists."); + } + } + break; + + default: + send_ship_info(c, u"Incorrect menu ID."); + break; + } +} + void process_menu_selection(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 09 10 + uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 10 bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB)); struct Cmd { @@ -504,16 +556,17 @@ void process_menu_selection(shared_ptr s, shared_ptr c, switch (cmd->item_id) { case MAIN_MENU_GO_TO_LOBBY: { static const vector version_to_port_name({ - "dc-lobby", "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"}); + "dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"}); const auto& port_name = version_to_port_name.at(static_cast(c->version)); - send_reconnect(c, 0, s->port_configuration.at(port_name).port); + send_reconnect(c, s->connect_address_for_client(c), + s->port_configuration.at(port_name).port); break; } case MAIN_MENU_INFORMATION: { auto info_menu = s->information_menu; - send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, true); + send_menu(c, u"Information", INFORMATION_MENU_ID, *info_menu, false); c->in_information_menu = true; break; } @@ -526,17 +579,18 @@ void process_menu_selection(shared_ptr s, shared_ptr c, send_message_box(c, u"Incorrect menu item ID."); break; } + break; } case INFORMATION_MENU_ID: { - if (cmd->item_id == 0) { + if (cmd->item_id == INFORMATION_MENU_GO_BACK) { c->in_information_menu = false; - send_menu(c, u"Main menu", MAIN_MENU_ID, s->main_menu, false); + send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false); } else { try { // add a reference to ensure it's not destroyed by another thread - auto info_menu = s->id_to_information_contents; + auto info_menu = s->information_contents; send_message_box(c, info_menu->at(cmd->item_id).c_str()); } catch (const out_of_range&) { send_message_box(c, u"$C6No such information exists."); @@ -720,14 +774,14 @@ void process_game_list_request(shared_ptr s, shared_ptr c, void process_change_ship(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, uint16_t size, const void* data) { // A0 - check_size(size, 0); send_message_box(c, u""); // we do this to avoid the "log window in message box" bug static const vector version_to_port_name({ - "dc-login", "dc-login", "pc-login", "bb-patch", "gc-login", "bb-login"}); + "dc-login", "pc-login", "bb-patch", "gc-us3", "bb-login"}); const auto& port_name = version_to_port_name.at(static_cast(c->version)); - send_reconnect(c, 0, s->port_configuration.at(port_name).port); + send_reconnect(c, s->connect_address_for_client(c), + s->port_configuration.at(port_name).port); } void process_change_block(shared_ptr s, shared_ptr c, @@ -850,18 +904,20 @@ void process_player_data(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, uint16_t size, const void* data) { // 61 98 { + // note: we add extra buffer on the end when checking sizes because the + // autoreply text is a variable length rw_guard g(c->lock, true); switch (c->version) { case GameVersion::PC: - check_size(size, sizeof(PSOPlayerDataPC)); + check_size(size, sizeof(PSOPlayerDataPC), sizeof(PSOPlayerDataPC) + 2 * 0xAC); c->player.import(*reinterpret_cast(data)); break; case GameVersion::GC: - check_size(size, sizeof(PSOPlayerDataGC)); + check_size(size, sizeof(PSOPlayerDataGC), sizeof(PSOPlayerDataGC) + 0xAC); c->player.import(*reinterpret_cast(data)); break; case GameVersion::BB: - check_size(size, sizeof(PSOPlayerDataBB)); + check_size(size, sizeof(PSOPlayerDataBB), sizeof(PSOPlayerDataBB) + 2 * 0xAC); c->player.import(*reinterpret_cast(data)); break; default: @@ -1609,7 +1665,7 @@ static process_command_t dc_handlers[0x100] = { // 00 NULL, NULL, NULL, NULL, NULL, process_ignored_command, process_chat_dc_gc, NULL, - process_game_list_request, process_menu_selection, NULL, NULL, + process_game_list_request, process_menu_item_info_request, NULL, NULL, NULL, NULL, NULL, NULL, // 10 @@ -1707,7 +1763,7 @@ static process_command_t pc_handlers[0x100] = { // 00 NULL, NULL, NULL, NULL, NULL, process_ignored_command, process_chat_pc_bb, NULL, - process_game_list_request, process_menu_selection, NULL, NULL, + process_game_list_request, process_menu_item_info_request, NULL, NULL, NULL, NULL, NULL, NULL, // 10 @@ -1805,7 +1861,7 @@ static process_command_t gc_handlers[0x100] = { // 00 NULL, NULL, NULL, NULL, NULL, process_ignored_command, process_chat_dc_gc, NULL, - process_game_list_request, process_menu_selection, NULL, NULL, + process_game_list_request, process_menu_item_info_request, NULL, NULL, NULL, NULL, NULL, NULL, // 10 @@ -1882,7 +1938,7 @@ static process_command_t gc_handlers[0x100] = { // D0 NULL, NULL, NULL, NULL, - NULL, NULL, process_ignored_command, NULL, + NULL, NULL, process_message_box_closed, NULL, process_info_board_request, process_write_info_board_dc_gc, NULL, process_verify_license_gc, process_ep3_menu_challenge, NULL, NULL, NULL, @@ -1903,7 +1959,7 @@ static process_command_t bb_handlers[0x100] = { // 00 NULL, NULL, NULL, NULL, NULL, process_ignored_command, process_chat_pc_bb, NULL, - process_game_list_request, process_menu_selection, NULL, NULL, + process_game_list_request, process_menu_item_info_request, NULL, NULL, NULL, NULL, NULL, NULL, // 10 @@ -2037,12 +2093,16 @@ static process_command_t patch_handlers[0x100] = { }; static process_command_t* handlers[6] = { - dc_handlers, dc_handlers, pc_handlers, patch_handlers, gc_handlers, bb_handlers}; + dc_handlers, pc_handlers, patch_handlers, gc_handlers, bb_handlers}; void process_command(shared_ptr s, shared_ptr c, uint16_t command, uint32_t flag, uint16_t size, const void* data) { + log(INFO, "received version=%d size=%04hX command=%04hX flag=%08X", + static_cast(c->version), size, command, flag); + print_data(stderr, data, size); + auto fn = handlers[static_cast(c->version)][command & 0xFF]; - if (!fn) { + if (fn) { fn(s, c, command, flag, size, data); } else { process_unimplemented_command(s, c, command, flag, size, data); diff --git a/ReceiveSubcommands.cc b/ReceiveSubcommands.cc index 81000d85..d1abf857 100644 --- a/ReceiveSubcommands.cc +++ b/ReceiveSubcommands.cc @@ -31,13 +31,15 @@ struct ItemSubcommand { void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) { if (size < min_size) { - throw runtime_error("command too small"); + throw runtime_error(string_printf("command too small (expected at least %zX bytes, got %zX bytes)", + min_size, size)); } if (max_size == 0) { max_size = min_size; } if (size > max_size) { - throw runtime_error("command too large"); + throw runtime_error(string_printf("command too large (expected at most %zX bytes, got %zX bytes)", + max_size, size)); } } @@ -64,6 +66,7 @@ void forward_subcommand(shared_ptr l, shared_ptr c, } send_command(target, command, flag, p, count * 4); } else { + // TODO: don't send the command back to the client it originated from send_command(l, command, flag, p, count * 4); } } diff --git a/SendCommands.cc b/SendCommands.cc index 98f45d99..25e2e3d2 100644 --- a/SendCommands.cc +++ b/SendCommands.cc @@ -1,6 +1,7 @@ #include "SendCommands.hh" #include +#include #include #include #include @@ -66,6 +67,9 @@ void send_command(shared_ptr c, uint16_t command, uint32_t flag, throw logic_error("unimplemented game version in send_command"); } + log(INFO, "sending command"); + print_data(stderr, send_data.data(), send_data.size()); + c->send(move(send_data)); } @@ -121,8 +125,19 @@ static void send_server_init_dc_pc_gc(shared_ptr c, const char* copyrigh send_command(c, command, 0x00, cmd); rw_guard g(c->lock, true); - c->crypt_out.reset(new PSOPCEncryption(server_key)); - c->crypt_in.reset(new PSOPCEncryption(client_key)); + switch (c->version) { + case GameVersion::DC: + case GameVersion::PC: + c->crypt_out.reset(new PSOPCEncryption(server_key)); + c->crypt_in.reset(new PSOPCEncryption(client_key)); + break; + case GameVersion::GC: + c->crypt_out.reset(new PSOGCEncryption(server_key)); + c->crypt_in.reset(new PSOGCEncryption(client_key)); + break; + default: + throw invalid_argument("incorrect client version"); + } } static void send_server_init_pc(shared_ptr c, bool initial_connection) { @@ -211,28 +226,18 @@ void send_update_client_config(shared_ptr c) { void send_reconnect(shared_ptr c, uint32_t address, uint16_t port) { - if (!address) { - const sockaddr_in* local_addr = reinterpret_cast(&c->local_addr); - address = local_addr->sin_addr.s_addr; - } - struct { uint32_t address; uint16_t port; uint16_t unused; - } cmd = {address, port, 0}; + } cmd = {bswap32(address), port, 0}; send_command(c, 0x19, 0x00, cmd); } // sends the command (first used by Schthack) that separates PC and GC users // that connect on the same port void send_pc_gc_split_reconnect(shared_ptr c, uint32_t address, - uint16_t pc_port) { - if (!address) { - const sockaddr_in* local_addr = reinterpret_cast(&c->local_addr); - address = local_addr->sin_addr.s_addr; - } - + uint16_t pc_port, uint16_t gc_port) { struct { uint32_t pc_address; uint16_t pc_port; @@ -240,13 +245,17 @@ void send_pc_gc_split_reconnect(shared_ptr c, uint32_t address, uint8_t gc_command; uint8_t gc_flag; uint16_t gc_size; - uint8_t unused2[0xB0 - 0x1D]; + uint32_t gc_address; + uint16_t gc_port; + uint8_t unused2[0xB0 - 0x23]; } __attribute__((packed)) cmd; memset(&cmd, 0, sizeof(cmd)); - cmd.pc_address = address; + cmd.pc_address = bswap32(address); cmd.pc_port = pc_port; cmd.gc_command = 0x19; cmd.gc_size = 0x97; + cmd.gc_address = bswap32(address); + cmd.gc_port = gc_port; send_command(c, 0x19, 0x00, cmd); } @@ -445,8 +454,7 @@ static void send_large_message_pc_patch_bb(shared_ptr c, uint8_t command {0, from_serial_number}; } data += text; - add_color_inplace(const_cast(data.data()) + - (include_header ? sizeof(LargeMessageOptionalHeader) : 0)); + add_color_inplace(data, (include_header ? sizeof(LargeMessageOptionalHeader) : 0)); data.resize((data.size() + 4) & ~3); send_command(c, command, 0x00, data); } @@ -460,8 +468,7 @@ static void send_large_message_dc_gc(shared_ptr c, uint8_t command, {0, from_serial_number}; } data += encode_sjis(text); - add_color_inplace(const_cast(data.data()) + - (include_header ? sizeof(LargeMessageOptionalHeader) : 0)); + add_color_inplace(data, (include_header ? sizeof(LargeMessageOptionalHeader) : 0)); data.resize((data.size() + 4) & ~3); send_command(c, command, 0x00, data); } @@ -517,7 +524,7 @@ void send_chat_message(shared_ptr c, uint32_t from_serial_number, data.append(u"\x09J"); } data.append(from_name); - data.append(u"\x09J"); + data.append(u"\x09\x09J"); data.append(text); send_large_message(c, 0x06, data.c_str(), from_serial_number, true); } @@ -637,18 +644,21 @@ static void send_card_search_result_dc_pc_gc(shared_ptr s, cmd.destination_command.dcgc_flag = 0x00; cmd.destination_command.dcgc_size = 0x000C; } + // TODO: make this actually make sense... currently we just take the sockname + // for the target client const sockaddr_in* local_addr = reinterpret_cast(&result->local_addr); cmd.destination_command.address = local_addr->sin_addr.s_addr; cmd.destination_command.port = ntohs(local_addr->sin_port); cmd.destination_command.unused = 0; + auto encoded_server_name = encode_sjis(s->name); if (result_lobby->is_game()) { string encoded_lobby_name = encode_sjis(result_lobby->name); snprintf(cmd.location_string, sizeof(cmd.location_string), - "%s, Block 00, ,%s", encoded_lobby_name.c_str(), s->name.c_str()); + "%s, Block 00, ,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str()); } else { snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s", - s->name.c_str()); + encoded_server_name.c_str()); } cmd.menu_id = LOBBY_MENU_ID; cmd.lobby_id = result->lobby_id; @@ -696,13 +706,14 @@ static void send_card_search_result_bb(shared_ptr s, cmd.destination_command.port = ntohs(local_addr->sin_port); cmd.destination_command.unused = 0; + auto encoded_server_name = encode_sjis(s->name); if (result_lobby->is_game()) { string encoded_lobby_name = encode_sjis(result_lobby->name); snprintf(cmd.location_string, sizeof(cmd.location_string), - "%s, Block 00, ,%s", encoded_lobby_name.c_str(), s->name.c_str()); + "%s, Block 00, ,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str()); } else { snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00, ,%s", - s->name.c_str()); + encoded_server_name.c_str()); } cmd.menu_id = LOBBY_MENU_ID; cmd.lobby_id = result->lobby_id; @@ -927,7 +938,7 @@ static void send_game_menu_pc(shared_ptr c, shared_ptr s) { auto& e = entries.back(); e.menu_id = GAME_MENU_ID; e.game_id = 0; - decode_sjis(e.name, s->name.c_str(), 0x10); + char16cpy(e.name, s->name.c_str(), 0x10); } for (shared_ptr l : s->all_lobbies()) { if (!l->is_game()) { @@ -970,7 +981,7 @@ static void send_game_menu_gc(shared_ptr c, shared_ptr s) { auto& e = entries.back(); e.menu_id = GAME_MENU_ID; e.game_id = 0; - strncpy(e.name, s->name.c_str(), 0x10); + encode_sjis(e.name, s->name.c_str(), 0x10); e.flags = 0x0004; } for (shared_ptr l : s->all_lobbies()) { @@ -1019,7 +1030,7 @@ static void send_game_menu_bb(shared_ptr c, shared_ptr s) { e.menu_id = GAME_MENU_ID; e.game_id = 0; e.flags = 0x0004; - decode_sjis(e.name, s->name.c_str(), 0x10); + char16cpy(e.name, s->name.c_str(), 0x10); } for (shared_ptr l : s->all_lobbies()) { if (!l->is_game()) { diff --git a/SendCommands.hh b/SendCommands.hh index 1ef107f4..88c2ef1f 100644 --- a/SendCommands.hh +++ b/SendCommands.hh @@ -23,6 +23,7 @@ #define QUEST_MENU_ID 0x7F02CA94 #define QUEST_FILTER_MENU_ID 0xC38CA039 +#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF #define MAIN_MENU_GO_TO_LOBBY 0x00000001 #define MAIN_MENU_INFORMATION 0x00000002 #define MAIN_MENU_DISCONNECT 0x00000003 @@ -41,6 +42,12 @@ void send_command(std::shared_ptr c, uint16_t command, uint32_t flag, send_command(c, command, flag, &data, sizeof(data)); } +template +void send_command(std::shared_ptr c, uint16_t command, uint32_t flag, + const std::string& data) { + send_command(c, command, flag, data.data(), data.size()); +} + template void send_command(std::shared_ptr c, uint16_t command, uint32_t flag, const std::vector& data) { @@ -65,7 +72,7 @@ void send_update_client_config(std::shared_ptr c); void send_reconnect(std::shared_ptr c, uint32_t address, uint16_t port); void send_pc_gc_split_reconnect(std::shared_ptr c, uint32_t address, - uint16_t pc_port); + uint16_t pc_port, uint16_t gc_port); void send_client_init_bb(std::shared_ptr c, uint32_t error); void send_team_and_key_config_bb(std::shared_ptr c); diff --git a/Server.cc b/Server.cc index 565dabdc..e0e38de6 100644 --- a/Server.cc +++ b/Server.cc @@ -143,6 +143,12 @@ void Server::on_client_input(Server::WorkerThread& wt, c->last_recv_time = now(); this->receive_and_process_commands(c, bev); + + if (c->should_disconnect) { + wt.disconnect_client(bev); + this->process_client_disconnect(c); + return; + } } void Server::on_client_error(Server::WorkerThread& wt, @@ -206,8 +212,14 @@ void Server::receive_and_process_commands(shared_ptr c, struct buffereve // to call string functions on the buffer in command handlers string data = c->recv_buffer.substr(offset + header_size, size - header_size); data.append(4, '\0'); - process_command(this->state, c, header->command(c->version), - header->flag(c->version), size - header_size, data.data()); + try { + process_command(this->state, c, header->command(c->version), + header->flag(c->version), size - header_size, data.data()); + } catch (const exception& e) { + log(INFO, "[Server] error in client stream: %s", e.what()); + c->should_disconnect = true; + return; + } // BB pads commands to 8-byte boundaries, so if we see a shorter command, // skip over the padding diff --git a/ServerState.cc b/ServerState.cc index c87bfba2..8e13eec9 100644 --- a/ServerState.cc +++ b/ServerState.cc @@ -3,13 +3,15 @@ #include #include "SendCommands.hh" +#include "NetworkAddresses.hh" #include "Text.hh" using namespace std; -ServerState::ServerState() : next_lobby_id(1), next_game_id(-1) { +ServerState::ServerState() : run_dns_server(true), next_lobby_id(1), + next_game_id(-1) { this->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby", u"Join the lobby.", 0); this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information", @@ -85,7 +87,9 @@ void ServerState::send_lobby_join_notifications(shared_ptr l, shared_ptr joining_client) { rw_guard g2(l->lock, false); for (auto& other_client : l->clients) { - if (other_client == joining_client) { + if (!other_client) { + continue; + } else if (other_client == joining_client) { send_join_lobby(joining_client, l); } else { send_player_join_notification(other_client, l, joining_client); @@ -172,4 +176,14 @@ shared_ptr ServerState::find_client(const char16_t* identifier, } throw out_of_range("client not found"); -} \ No newline at end of file +} + +uint32_t ServerState::connect_address_for_client(std::shared_ptr c) { + // TODO: we can do something much smarter here, like use the sockname to find + // out which interface the client is connected to, and return that address + if (is_local_address(c->remote_addr)) { + return this->local_address; + } else { + return this->external_address; + } +} diff --git a/ServerState.hh b/ServerState.hh index 7b603765..469bf39b 100644 --- a/ServerState.hh +++ b/ServerState.hh @@ -26,7 +26,7 @@ struct PortConfiguration { }; struct ServerState { - std::string name; + std::u16string name; std::unordered_map port_configuration; std::shared_ptr quest_index; std::shared_ptr level_table; @@ -37,7 +37,7 @@ struct ServerState { std::vector main_menu; std::shared_ptr> information_menu; - std::shared_ptr> id_to_information_contents; + std::shared_ptr> information_contents; size_t num_threads; @@ -71,4 +71,6 @@ struct ServerState { std::shared_ptr find_client(const char16_t* identifier = NULL, uint64_t serial_number = 0, std::shared_ptr l = NULL); + + uint32_t connect_address_for_client(std::shared_ptr c); }; diff --git a/Text.cc b/Text.cc index f450363e..f6c81c05 100644 --- a/Text.cc +++ b/Text.cc @@ -153,7 +153,7 @@ void add_language_marker_inplace(char* a, char e, size_t dest_count) { if (existing_count > dest_count - 3) { existing_count = dest_count - 3; } - memmove(&a[2], a, existing_count + 1); + memmove(&a[2], a, (existing_count + 1) * sizeof(char)); a[0] = '\t'; a[1] = e; a[existing_count + 2] = 0; @@ -168,7 +168,7 @@ void add_language_marker_inplace(char16_t* a, char16_t e, size_t dest_count) { if (existing_count > dest_count - 3) { existing_count = dest_count - 3; } - memmove(&a[2], a, existing_count + 1); + memmove(&a[2], a, (existing_count + 1) * sizeof(char16_t)); a[0] = '\t'; a[1] = e; a[existing_count + 2] = 0; diff --git a/Text.hh b/Text.hh index 62cde056..d949a499 100644 --- a/Text.hh +++ b/Text.hh @@ -40,8 +40,9 @@ void replace_char_inplace(T* a, T f, T r) { } template -void add_color_inplace(T* a) { +size_t add_color_inplace(T* a) { T* d = a; + T* orig_d = d; while (*a) { if (*a == '$') { @@ -59,7 +60,18 @@ void add_color_inplace(T* a) { } else { *(d++) = *a; } + } else { + *(d++) = *a; } a++; } + *d = 0; + + return d - orig_d; +} + +template +void add_color_inplace(std::basic_string& a, size_t header_bytes) { + size_t count = add_color_inplace(const_cast(a.data() + header_bytes)); + a.resize(count + header_bytes); } diff --git a/system/config.json b/system/config.json index 6892f2aa..412502ff 100755 --- a/system/config.json +++ b/system/config.json @@ -11,11 +11,13 @@ // Server's name (max. 16 characters) "ServerName": "Alexandria", // Address to connect local clients to - "LocalAddress": "10.0.1.6", + "LocalAddress": "192.168.0.5", // Address to connect external clients to "ExternalAddress": "10.0.1.6", // Number of worker threads to run "Threads": 1, + // Set to false to disable the DNS server + "RunDNSServer": false, // **************** // INFORMATION MENU @@ -23,7 +25,7 @@ // Each entry is a 3-list of [title, short-description, full-contents]. "InformationMenuContents": [ - ["Text", "$C7Some things you\nmay need to know\nabout text on\nthis server", "$C7Everything you type will be filtered.\n\nDollar signs will become tab chars, which can be\nused to color team names and info boards.\nTo color your text, type %sCx, where x is a\nvalue from the Text Colors list.\n\nPound signs (number signs) will become returns\n(newlines).\n\nA percent sign will create a special character.\nTyping a percent sign followed by one of these\nletters will make one of these special characters:\n%%d = %d %%x = %x %%p = %p %%+ = %+\n%%1 = %1 %%2 = %2 %%3 = %3 %%c = %c\n%%l = %l %%y = %y %%X = %X %%Y = %Y\n%%Z = %Z %%? = %? %%C = %C %%R = %R\n%%s = %s %%%% = %% %%n = %n"], + ["Text", "$C7Some things you\nmay need to know\nabout text on\nthis server", "$C7Everything you type will be filtered.\n\nDollar signs will become tab chars, which can be\nused to color team names and info boards.\nTo color your text, type %sCx, where x is a\nvalue from the Text Colors list.\n\nPound signs (number signs) will become returns\n(newlines), the sequence %%s will become %s,\nand the sequence %%%% will become %%."], ["Text colors", "$C7Display color values", "These values can be used to color text.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"], ["Lobby commands", "$C7Display commands\nfor use in the\nlobby", "Lobby commands: you must be a moderator to use\nthese commands.\n\n%sallevent - change the server's event\n%sevent - change this lobby's event\n%stype - change this lobby's type\n%sann - announce a message\n%sax - send a message to the server"], ["Game commands", "$C7Display commands\nfor use in games", "Game commands: you must be the game leader to\nuse these commands.\n\n%spassword - set the game's password\n%smaxlevel <%n> - set the game's maximum level\n%sminlevel <%n> - set the game's minimum level\n%scheat - enable or disable cheat mode"], diff --git a/system/sjis-table.ini b/system/sjis-table.ini index 3f5e161a..b32da831 100644 --- a/system/sjis-table.ini +++ b/system/sjis-table.ini @@ -1,3 +1,5 @@ +0x09 0x0009 # TAB +0x0A 0x000A # NEWLINE 0x20 0x0020 # SPACE 0x21 0x0021 # EXCLAMATION MARK 0x22 0x0022 # QUOTATION MARK