diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 477d4eb7..07b5a718 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -347,7 +347,7 @@ struct S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B { // TODO: Are the DCv1 and DCv2 formats the same as this structure? struct C_LegacyLogin_PC_V3_03 { - /* 00 */ le_uint64_t unused = 0; // Same as unused field in 9D/9E + /* 00 */ be_uint64_t hardware_id; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; /* 0D */ uint8_t language = 0; @@ -403,7 +403,7 @@ struct S_ServerInitWithAfterMessageT_BB_03_9B { // TODO: Are the DCv1 and DCv2 formats the same as this structure? struct C_LegacyLogin_PC_V3_04 { - /* 00 */ le_uint64_t unused1 = 0; // Same as unused field in 9D/9E + /* 00 */ be_uint64_t hardware_id; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; /* 0D */ uint8_t language = 0; @@ -1594,7 +1594,7 @@ struct S_ArrowUpdateEntry_88 { // The server should respond with an 8A command. struct C_ConnectionInfo_DCNTE_8A { - pstring hardware_id; + be_uint64_t hardware_id; le_uint32_t sub_version = 0x20; le_uint32_t unknown_a1 = 0; pstring username; @@ -1624,7 +1624,7 @@ struct C_ConnectionInfo_DCNTE_8A { struct C_Login_DCNTE_8B { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - parray hardware_id; + be_uint64_t hardware_id; le_uint32_t sub_version = 0x20; uint8_t is_extended = 0; uint8_t language = 0; @@ -1685,13 +1685,13 @@ struct C_LoginV1_DC_PC_V3_90 { // 92 (C->S): Register (DC) struct C_RegisterV1_DC_92 { - parray unknown_a1; + be_uint64_t hardware_id; le_uint32_t sub_version; uint8_t is_extended = 0; // TODO: This is a guess uint8_t language = 0; // TODO: This is a guess; verify it parray unknown_a3; - pstring hardware_id; - pstring unknown_a4; + pstring serial_number2; + pstring access_key2; pstring email; // According to Sylverant documentation } __packed_ws__(C_RegisterV1_DC_92, 0xA0); @@ -1702,20 +1702,20 @@ struct C_RegisterV1_DC_92 { // 93 (C->S): Log in (DCv1) struct C_LoginV1_DC_93 { - le_uint32_t player_tag = 0x00010000; - le_uint32_t guild_card_number = 0; - le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; - le_uint32_t sub_version = 0; - uint8_t is_extended = 0; - uint8_t language = 0; - parray unused1; - pstring serial_number; - pstring access_key; - pstring hardware_id; - pstring unknown_a3; - pstring name; - parray unused2; + /* 00 */ le_uint32_t player_tag = 0x00010000; + /* 04 */ le_uint32_t guild_card_number = 0; + /* 08 */ be_uint64_t hardware_id; + /* 10 */ le_uint32_t sub_version = 0; + /* 14 */ uint8_t is_extended = 0; + /* 15 */ uint8_t language = 0; + /* 16 */ parray unused1; + /* 18 */ pstring serial_number; + /* 29 */ pstring access_key; + /* 3A */ pstring serial_number2; + /* 6A */ pstring access_key2; + /* 9A */ pstring name; + /* AA */ parray unused2; + /* AC */ } __packed_ws__(C_LoginV1_DC_93, 0xAC); struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 { @@ -1886,14 +1886,15 @@ struct C_Login_DC_PC_V3_9A { // It appears PSO GC sends uninitialized data in the header.flag field here. struct C_Register_DC_PC_V3_9C { - le_uint64_t unused = 0; - le_uint32_t sub_version = 0; - uint8_t unused1 = 0; - uint8_t language = 0; - parray unused2; - pstring serial_number; // On XB, this is the XBL gamertag - pstring access_key; // On XB, this is the XBL user ID - pstring password; // On XB, this contains "xbox-pso" + /* 00 */ be_uint64_t hardware_id; + /* 08 */ le_uint32_t sub_version = 0; + /* 0C */ uint8_t unused1 = 0; + /* 0D */ uint8_t language = 0; + /* 0E */ parray unused2; + /* 10 */ pstring serial_number; // On XB, this is the XBL gamertag + /* 40 */ pstring access_key; // On XB, this is the XBL user ID + /* 70 */ pstring password; // On XB, this contains "xbox-pso" + /* A0 */ } __packed_ws__(C_Register_DC_PC_V3_9C, 0xA0); struct C_Register_BB_9C { @@ -1925,8 +1926,17 @@ struct C_Register_BB_9C { struct C_Login_DC_PC_GC_9D { /* 00 */ le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04) /* 04 */ le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set - /* 08 */ le_uint32_t unused1 = 0; - /* 0C */ le_uint32_t unused2 = 0; + // The hardware ID is different for various PSO versions: + // - All DC versions: the hardware ID comes from the FUNC_SYSINFO_ID syscall + // (as KallistiOS refers to it), which returns a 64-bit integer. PSO uses + // the low 48 bits; the high 16 bits are masked out and are always zero. + // - PC V2: the hardware ID is always 0000FFFFFFFFFFFF + // - GC NTE: the last byte of the hardware ID is uninitialized memory from + // the TProtocol constructor's stack; the other bytes are all zeroes. + // - V3: the hardware ID is all zeroes. + // On the client, this is actually an array of 8 bytes, but we treat it as a + // single integer for simplicity. + /* 08 */ be_uint64_t hardware_id; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; // If 1, structure has extended format /* 15 */ uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES diff --git a/src/DownloadSession.cc b/src/DownloadSession.cc index ada5a035..c231780d 100644 --- a/src/DownloadSession.cc +++ b/src/DownloadSession.cc @@ -48,7 +48,7 @@ DownloadSession::DownloadSession( Version version, uint8_t language, std::shared_ptr bb_key_file, - uint32_t hardware_id, + uint32_t serial_number2, uint32_t serial_number, const std::string& access_key, const std::string& username, @@ -63,7 +63,7 @@ DownloadSession::DownloadSession( bool show_command_data) : output_dir(output_dir), bb_key_file(bb_key_file), - hardware_id(hardware_id), + serial_number2(serial_number2), serial_number(serial_number), access_key(access_key), username(username), @@ -86,6 +86,7 @@ DownloadSession::DownloadSession( phosg::render_sockaddr_storage(remote), show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END, show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END), + hardware_id(generate_random_hardware_id(this->channel.version)), guild_card_number(0), prev_cmd_data(0), client_config(0), @@ -103,7 +104,7 @@ DownloadSession::DownloadSession( switch (this->channel.version) { case Version::DC_V1: case Version::DC_V2: - if (this->hardware_id == 0 || this->serial_number == 0 || this->access_key.empty()) { + if (this->serial_number2 == 0 || this->serial_number == 0 || this->access_key.empty()) { throw runtime_error("missing credentials"); } break; @@ -163,12 +164,13 @@ void DownloadSession::send_93_9D_9E(bool extended) { C_LoginExtendedV1_DC_93 ret; ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000; ret.guild_card_number = this->guild_card_number; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.is_extended = extended ? 1 : 0; ret.language = this->channel.language; ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number)); ret.access_key.encode(this->access_key); - ret.hardware_id.encode(phosg::string_printf("%08" PRIX32, this->hardware_id)); + ret.serial_number2.encode(phosg::string_printf("%08" PRIX32, this->serial_number2)); ret.name.encode(this->character->disp.name.decode()); this->channel.send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93)); @@ -176,6 +178,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { C_LoginExtended_PC_9D ret; ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000; ret.guild_card_number = this->guild_card_number; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.is_extended = extended ? 1 : 0; ret.language = this->channel.language; @@ -193,6 +196,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { C_LoginExtended_GC_9E ret; ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000; ret.guild_card_number = this->guild_card_number; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.is_extended = extended ? 1 : 0; ret.language = this->channel.language; @@ -208,6 +212,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { C_LoginExtended_XB_9E ret; ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000; ret.guild_card_number = this->guild_card_number; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.is_extended = extended ? 1 : 0; ret.language = this->channel.language; @@ -377,13 +382,15 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str if (flag == 1) { if (is_v1(this->channel.version)) { C_RegisterV1_DC_92 ret; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.language = this->channel.language; - ret.hardware_id.encode(phosg::string_printf("%08" PRIX32, this->hardware_id)); + ret.serial_number2.encode(phosg::string_printf("%08" PRIX32, this->serial_number2)); this->channel.send(0x92, 0x00, ret); } else if (!is_v4(this->channel.version)) { C_Register_DC_PC_V3_9C ret; + ret.hardware_id = this->hardware_id; ret.sub_version = default_sub_version_for_version(this->channel.version); ret.language = this->channel.language; if (this->channel.version == Version::XB_V3) { diff --git a/src/DownloadSession.hh b/src/DownloadSession.hh index 2c7ad936..f1008122 100644 --- a/src/DownloadSession.hh +++ b/src/DownloadSession.hh @@ -24,7 +24,7 @@ public: Version version, uint8_t language, std::shared_ptr bb_key_file, - uint32_t hardware_id, + uint32_t serial_number2, uint32_t serial_number, const std::string& access_key, const std::string& username, @@ -47,7 +47,7 @@ protected: // Config (must be set by caller) std::string output_dir; std::shared_ptr bb_key_file; - uint32_t hardware_id; + uint32_t serial_number2; uint32_t serial_number; std::string access_key; std::string username; @@ -64,6 +64,7 @@ protected: phosg::PrefixedLogger log; std::shared_ptr base; Channel channel; + uint64_t hardware_id; uint32_t guild_card_number; parray prev_cmd_data; parray client_config; diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index c09d004b..b50ab1ba 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -734,7 +734,7 @@ phosg::JSON HTTPServer::generate_proxy_client_json_st(shared_ptrversion())}, {"SubVersion", ses->sub_version}, {"Name", ses->character_name}, - {"DCHardwareID", ses->hardware_id}, + {"DCSerialNumber2", ses->serial_number2}, {"RemoteGuildCardNumber", ses->remote_guild_card_number}, {"RemoteClientConfigData", phosg::format_data_string(&ses->remote_client_config_data[0], ses->remote_client_config_data.size())}, {"Config", HTTPServer::generate_client_config_json_st(ses->config)}, diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index d21e704a..713838cb 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -167,8 +167,7 @@ static HandlerResult S_G_9A(shared_ptr ses, uint16_t cmd.player_tag = 0x00010000; cmd.guild_card_number = ses->remote_guild_card_number; } - cmd.unused1 = 0; - cmd.unused2 = 0; + cmd.hardware_id = ses->hardware_id; cmd.sub_version = ses->effective_sub_version(); cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; cmd.language = ses->language(); @@ -277,15 +276,14 @@ static HandlerResult S_V123P_02_17( cmd.player_tag = 0x00010000; cmd.guild_card_number = ses->remote_guild_card_number; } - cmd.unknown_a1 = 0; - cmd.unknown_a2 = 0; + cmd.hardware_id = ses->hardware_id; cmd.sub_version = ses->effective_sub_version(); cmd.is_extended = 0; cmd.language = ses->language(); cmd.serial_number.encode(phosg::string_printf("%08" PRIX32 "", ses->login->dc_license->serial_number)); cmd.access_key.encode(ses->login->dc_license->access_key); cmd.access_key.clear_after_bytes(8); - cmd.hardware_id.encode(ses->hardware_id); + cmd.serial_number2.encode(ses->serial_number2); cmd.name.encode(ses->character_name); ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -334,7 +332,7 @@ static HandlerResult S_V123P_02_17( cmd.access_key.clear_after_bytes(8); } if (is_dc(ses->version())) { - cmd.serial_number2.encode(ses->hardware_id); + cmd.serial_number2.encode(ses->serial_number2); } else { cmd.serial_number2 = cmd.serial_number; } @@ -353,8 +351,7 @@ static HandlerResult S_V123P_02_17( cmd.player_tag = 0x00010000; cmd.guild_card_number = ses->remote_guild_card_number; } - cmd.unused1 = 0; - cmd.unused2 = 0; + cmd.hardware_id = ses->hardware_id; cmd.sub_version = ses->effective_sub_version(); cmd.is_extended = 0; cmd.language = ses->language(); @@ -364,7 +361,7 @@ static HandlerResult S_V123P_02_17( cmd.access_key.clear_after_bytes(8); } if (is_dc(ses->version())) { - cmd.serial_number2.encode(ses->hardware_id); + cmd.serial_number2.encode(ses->serial_number2); } else { cmd.serial_number2 = cmd.serial_number; } @@ -417,8 +414,7 @@ static HandlerResult S_V123P_02_17( C_LoginExtended_GC_9E cmd; cmd.player_tag = 0x00010000; cmd.guild_card_number = guild_card_number; - cmd.unused1 = 0; - cmd.unused2 = 0; + cmd.hardware_id = ses->hardware_id; cmd.sub_version = ses->effective_sub_version(); cmd.is_extended = 0; cmd.language = ses->language(); @@ -453,8 +449,7 @@ static HandlerResult S_V123P_02_17( cmd.player_tag = 0x00010000; cmd.guild_card_number = ses->remote_guild_card_number; } - cmd.unused1 = 0; - cmd.unused2 = 0; + cmd.hardware_id = ses->hardware_id; cmd.sub_version = ses->effective_sub_version(); cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; cmd.language = ses->language(); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index c2b5cf6e..65767a67 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -306,11 +306,13 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->log.info("Version changed to DC_NTE"); ses->config.specific_version = SPECIFIC_VERSION_DC_NTE; const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); - ses->set_login(s->account_index->from_dc_nte_credentials(cmd.serial_number.decode(), cmd.access_key.decode(), false)); + ses->set_login(s->account_index->from_dc_nte_credentials( + cmd.serial_number.decode(), cmd.access_key.decode(), false)); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); - // TODO: Parse cmd.hardware_id + ses->hardware_id = cmd.hardware_id; + } else if (command == 0x93) { // 11/2000 proto through DC V1 ses->channel.version = Version::DC_V1; ses->log.info("Version changed to DC_V1"); @@ -323,7 +325,9 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); - ses->hardware_id = cmd.hardware_id.decode(); + ses->serial_number2 = cmd.serial_number2.decode(); + ses->hardware_id = cmd.hardware_id; + } else if (command == 0x9D) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); if (cmd.sub_version >= 0x30) { @@ -345,6 +349,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); ses->config.set_flags_for_version(ses->version(), cmd.sub_version); + ses->hardware_id = cmd.hardware_id; + } else { throw runtime_error("command is not 93 or 9D"); } @@ -362,6 +368,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); + ses->hardware_id = cmd.hardware_id; break; } @@ -376,6 +383,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); + ses->hardware_id = cmd.hardware_id; ses->config.parse_from(cmd.client_config); if (cmd.sub_version >= 0x40) { ses->log.info("Version changed to GC_EP3"); @@ -402,6 +410,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->character_name = cmd.name.decode(ses->channel.language); ses->xb_netloc = cmd.netloc; ses->xb_9E_unknown_a1a = cmd.unknown_a1a; + ses->hardware_id = cmd.hardware_id; ses->channel.send(0x9F, 0x00); return; } else if (command == 0x9F) { @@ -478,6 +487,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->detector_crypt, ses->sub_version, ses->character_name, + ses->serial_number2, ses->hardware_id, ses->xb_netloc, ses->xb_9E_unknown_a1a); @@ -617,11 +627,13 @@ void ProxyServer::LinkedSession::resume( shared_ptr detector_crypt, uint32_t sub_version, const string& character_name, - const string& hardware_id, + const string& serial_number2, + uint64_t hardware_id, const XBNetworkLocation& xb_netloc, const parray& xb_9E_unknown_a1a) { this->sub_version = sub_version; this->character_name = character_name; + this->serial_number2 = serial_number2; this->hardware_id = hardware_id; this->xb_netloc = xb_netloc; this->xb_9E_unknown_a1a = xb_9E_unknown_a1a; diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 89b1d50a..5ca7ea21 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -58,7 +58,8 @@ public: uint32_t sub_version; std::string character_name; - std::string hardware_id; // Only used for DC sessions + std::string serial_number2; // Only used for DC sessions + uint64_t hardware_id; std::string login_command_bb; XBNetworkLocation xb_netloc; parray xb_9E_unknown_a1a; @@ -170,7 +171,8 @@ public: std::shared_ptr detector_crypt, uint32_t sub_version, const std::string& character_name, - const std::string& hardware_id, + const std::string& serial_number2, + uint64_t hardware_id, const XBNetworkLocation& xb_netloc, const parray& xb_9E_unknown_a1a); void resume( @@ -260,7 +262,8 @@ private: std::string character_name; Client::Config config; std::string login_command_bb; - std::string hardware_id; + std::string serial_number2; + uint64_t hardware_id; XBNetworkLocation xb_netloc; parray xb_9E_unknown_a1a; diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 01626fda..804b8618 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -703,11 +703,25 @@ CommandDefinition c_delete_license( fprintf(stderr, "Account %08" PRIX32 " updated\n", account->account_id); }); +CommandDefinition c_lookup( + "lookup", "lookup USER\n\ + Find the account for a logged-in user.", + true, + +[](CommandArgs& args) { + auto target = args.s->find_client(&args.args); + if (target->login) { + fprintf(stderr, "Found client %s with account ID %08" PRIX32 "\n", + target->channel.name.c_str(), target->login->account->account_id); + } else { + // This should be impossible + throw std::logic_error("find_client found user who is not logged in"); + } + }); CommandDefinition c_kick( "kick", "kick USER\n\ Disconnect a user from the server. USER may be an account ID, player name,\n\ or client ID (beginning with \"C-\"). This does not ban the user; they are\n\ - free to reconnect after doing this.\n", + free to reconnect after doing this.", true, +[](CommandArgs& args) { auto target = args.s->find_client(&args.args); diff --git a/src/Version.cc b/src/Version.cc index b6c010d0..f863fc16 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -382,3 +382,28 @@ const char* file_path_token_for_version(Version version) { throw runtime_error("invalid game version"); } } + +uint64_t generate_random_hardware_id(Version version) { + switch (version) { + case Version::DC_NTE: + case Version::DC_11_2000: + case Version::DC_V1: + case Version::DC_V2: + return phosg::random_object() & 0x0000FFFFFFFFFFFF; + case Version::PC_NTE: + case Version::PC_V2: + return 0x0000FFFFFFFFFFFF; + case Version::GC_NTE: + // On GC NTE, the low byte is uninitialized memory from the TProtocol + // constructor's stack + return phosg::random_object(); + case Version::GC_V3: + case Version::GC_EP3_NTE: + case Version::GC_EP3: + case Version::XB_V3: + case Version::BB_V4: + return 0; + default: + throw runtime_error("invalid game version"); + } +} diff --git a/src/Version.hh b/src/Version.hh index c45b642a..d40c9a0c 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -235,3 +235,5 @@ template <> ServerBehavior phosg::enum_for_name(const char* name); const char* file_path_token_for_version(Version version); + +uint64_t generate_random_hardware_id(Version version);