diff --git a/CMakeLists.txt b/CMakeLists.txt index c4fb2771..f6f43a2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set(SOURCES src/ChoiceSearch.cc src/Client.cc src/ClientFunctionIndex.cc + src/CommandCensorData.cc src/CommonItemSet.cc src/Compression.cc src/DCSerialNumbers.cc diff --git a/src/Channel.cc b/src/Channel.cc index 8ae01d1c..080a4ede 100644 --- a/src/Channel.cc +++ b/src/Channel.cc @@ -7,6 +7,7 @@ #include #include +#include "CommandCensorData.hh" #include "Loggers.hh" #include "StaticGameData.hh" #include "Version.hh" @@ -20,12 +21,16 @@ Channel::Channel( Language language, const string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color) + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials) : version(version), language(language), name(name), terminal_send_color(terminal_send_color), - terminal_recv_color(terminal_recv_color) { + terminal_recv_color(terminal_recv_color), + censor_received_credentials(censor_received_credentials), + censor_sent_credentials(censor_sent_credentials) { } void Channel::send(uint16_t cmd, uint32_t flag, bool silent) { @@ -132,7 +137,20 @@ void Channel::send( command_data_log.info_f("Sending to {} (version={} command={:02X} flag={:02X})", this->name, phosg::name_for_enum(version), cmd, flag); } - phosg::print_data(stderr, send_data.data(), logical_size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + + struct iovec iov{.iov_base = send_data.data(), .iov_len = send_data.size()}; + + if (this->censor_sent_credentials) { + auto [censor_data, censor_size] = censor_data_for_client_command(this->version, cmd); + struct iovec censor_iovs[2] = { + // const_casts are OK here because print_data does not modify the buffers + {.iov_base = const_cast("\0\0\0\0\0\0\0\0"), .iov_len = static_cast(is_v4(this->version) ? 8 : 4)}, + {.iov_base = const_cast(censor_data), .iov_len = censor_size}}; + phosg::print_data(stderr, &iov, 1, 0, nullptr, 0, censor_iovs, 2, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); + } else { + phosg::print_data(stderr, &iov, 1, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); + } + if (use_terminal_colors && this->terminal_send_color != phosg::TerminalFormat::NORMAL) { print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END); } @@ -201,6 +219,7 @@ asio::awaitable Channel::recv() { } command_data.resize(command_logical_size - header_size); + uint16_t command = header.command(this->version); if (command_data_log.should_log(phosg::LogLevel::L_INFO) && (this->terminal_recv_color != phosg::TerminalFormat::END)) { if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) { print_color_escape(stderr, this->terminal_recv_color, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END); @@ -221,10 +240,20 @@ asio::awaitable Channel::recv() { header.flag(this->version)); } - vector iovs; - iovs.emplace_back(iovec{.iov_base = &header, .iov_len = header_size}); - iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()}); - phosg::print_data(stderr, iovs, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + struct iovec iovs[2] = { + {.iov_base = &header, .iov_len = header_size}, + {.iov_base = command_data.data(), .iov_len = command_data.size()}}; + + if (this->censor_received_credentials) { + auto [censor_data, censor_size] = censor_data_for_client_command(this->version, command); + struct iovec censor_iovs[2] = { + // const_casts are OK here because print_data does not modify the buffers + {.iov_base = const_cast("\0\0\0\0\0\0\0\0"), .iov_len = header_size}, + {.iov_base = const_cast(censor_data), .iov_len = censor_size}}; + phosg::print_data(stderr, iovs, 2, 0, nullptr, 0, censor_iovs, 2, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); + } else { + phosg::print_data(stderr, iovs, 2, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); + } if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) { phosg::print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END); @@ -232,7 +261,7 @@ asio::awaitable Channel::recv() { } co_return Message{ - .command = header.command(this->version), + .command = command, .flag = header.flag(this->version), .data = std::move(command_data), }; @@ -245,9 +274,19 @@ shared_ptr SocketChannel::create( Language language, const string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color) { + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials) { shared_ptr ret(new SocketChannel( - io_context, std::move(sock), version, language, name, terminal_send_color, terminal_recv_color)); + io_context, + std::move(sock), + version, + language, + name, + terminal_send_color, + terminal_recv_color, + censor_received_credentials, + censor_sent_credentials)); asio::co_spawn(*io_context, ret->send_task(), asio::detached); return ret; } @@ -259,8 +298,10 @@ SocketChannel::SocketChannel( Language language, const string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color) - : Channel(version, language, name, terminal_send_color, terminal_recv_color), + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials) + : Channel(version, language, name, terminal_send_color, terminal_recv_color, censor_received_credentials, censor_sent_credentials), sock(std::move(sock)), local_addr(this->sock->local_endpoint()), remote_addr(this->sock->remote_endpoint()), @@ -327,8 +368,10 @@ PeerChannel::PeerChannel( Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color) - : Channel(version, language, name, terminal_send_color, terminal_recv_color), + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials) + : Channel(version, language, name, terminal_send_color, terminal_recv_color, censor_received_credentials, censor_sent_credentials), send_buffer_nonempty_signal(io_context->get_executor()) {} void PeerChannel::link_peers(std::shared_ptr peer1, std::shared_ptr peer2) { diff --git a/src/Channel.hh b/src/Channel.hh index a8bda6b2..4ec5d3a9 100644 --- a/src/Channel.hh +++ b/src/Channel.hh @@ -19,6 +19,8 @@ public: std::string name; phosg::TerminalFormat terminal_send_color; phosg::TerminalFormat terminal_recv_color; + bool censor_received_credentials; + bool censor_sent_credentials; struct Message { uint16_t command; @@ -87,8 +89,10 @@ protected: Version version, Language language, const std::string& name, - phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, - phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); + phosg::TerminalFormat terminal_send_color, + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials); Channel(const Channel& other) = delete; Channel(Channel&& other) = delete; Channel& operator=(const Channel& other) = delete; @@ -114,9 +118,11 @@ public: std::unique_ptr&& sock, Version version, Language language, - const std::string& name = "", - phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, - phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); + const std::string& name, + phosg::TerminalFormat terminal_send_color, + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials); virtual std::string default_name() const; @@ -134,7 +140,9 @@ private: Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color); + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials); std::deque outbound_data; bool should_disconnect = false; @@ -152,9 +160,11 @@ public: std::shared_ptr io_context, Version version, Language language, - const std::string& name = "", - phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, - phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); + const std::string& name, + phosg::TerminalFormat terminal_send_color, + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials); static void link_peers(std::shared_ptr peer1, std::shared_ptr peer2); diff --git a/src/CommandCensorData.cc b/src/CommandCensorData.cc new file mode 100644 index 00000000..1393c243 --- /dev/null +++ b/src/CommandCensorData.cc @@ -0,0 +1,241 @@ +#include "CommandCensorData.hh" + +#include "CommandFormats.hh" + +std::pair censor_data_for_client_command(Version version, uint16_t command) { + switch (command) { + case 0x03: { + static const C_LegacyLogin_PC_V3_03 ret{ + .hardware_id = 0, + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused = 0, + .serial_number2 = 1, + .access_key2 = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x04: + if (is_patch(version)) { + static const C_Login_Patch_04 ret{.unused{0}, .username{1}, .password{1}, .email_address{1}}; + return std::make_pair(&ret, sizeof(ret)); + } else if (!is_v4(version)) { + static const C_LegacyLogin_PC_V3_04 ret{ + .hardware_id = 0, + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused = 0, + .serial_number = 1, + .access_key = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + static const C_LegacyLogin_BB_04 ret{ + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused = 0, + .username = 1, + .password = 1}; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x88: + if (version == Version::DC_NTE) { + static const C_Login_DCNTE_88 ret{.serial_number{1}, .access_key{1}}; + return std::make_pair(&ret, sizeof(ret)); + } else { + return std::make_pair(nullptr, 0); + } + case 0x8A: + if (version == Version::DC_NTE) { + static const C_ConnectionInfo_DCNTE_8A ret{ + .hardware_id = 0, .sub_version = 0, .unused = 0, .username = 1, .password = 1, .email_address = 1}; + return std::make_pair(&ret, sizeof(ret)); + } else { + return std::make_pair(nullptr, 0); + } + case 0x8B: + if (version == Version::DC_NTE) { + static const C_Login_DCNTE_8B ret{ + .player_tag = 0, + .guild_card_number = 0, + .hardware_id = 0, + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused1 = 0, + .serial_number = 1, + .access_key = 1, + .username = 1, + .password = 1, + .login_character_name = 0, + .unused = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + return std::make_pair(nullptr, 0); + } + case 0x90: { + static const C_LoginV1_DC_PC_V3_90 ret{.serial_number = 1, .access_key = 1}; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x92: { + static const C_RegisterV1_DC_92 ret{ + .hardware_id = 0, + .sub_version = 0, + .unused1 = 0, + .language = Language::JAPANESE, + .unused2 = 0, + .serial_number2 = 1, + .access_key2 = 1, + .email_address = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x93: + if (!is_v4(version)) { + static const C_LoginV1_DC_93 ret{ + .player_tag = 0, + .guild_card_number = 0, + .hardware_id = 0, + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused1 = 0, + .serial_number = 1, + .access_key = 1, + .serial_number2 = 1, + .access_key2 = 1, + .login_character_name = 0, + .unused2 = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + static const C_LoginBase_BB_93 ret{ + .player_tag = 0, + .guild_card_number = 0, + .sub_version = 0, + .language = Language::JAPANESE, + .character_slot = 0, + .connection_phase = 0, + .client_code = 0, + .security_token = 0, + .username = 1, + .password = 1, + .menu_id = 0, + .preferred_lobby_id = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x9A: { + static const C_Login_DC_PC_V3_9A ret{ + .v1_serial_number = 1, + .v1_access_key = 1, + .serial_number = 1, + .access_key = 1, + .player_tag = 0, + .guild_card_number = 0, + .sub_version = 0, + .serial_number2 = 1, + .access_key2 = 1, + .email_address = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x9C: + if (!is_v4(version)) { + static const C_Register_DC_PC_V3_9C ret{ + .hardware_id = 0, + .sub_version = 0, + .unused1 = 0, + .language = Language::JAPANESE, + .unused2 = 0, + .serial_number = 1, + .access_key = 1, + .password = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + static const C_Register_BB_9C ret{ + .sub_version = 0, + .unused1 = 0, + .language = Language::JAPANESE, + .unused2 = 0, + .username = 1, + .password = 1, + .game_tag = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0x9D: + case 0x9E: + if (!is_v4(version)) { + static const C_Login_DC_PC_GC_9D ret{ + .player_tag = 0, + .guild_card_number = 0, + .hardware_id = 0, + .sub_version = 0, + .is_extended = 0, + .language = Language::JAPANESE, + .unused3 = 0, + .v1_serial_number = 1, + .v1_access_key = 1, + .serial_number = 1, + .access_key = 1, + .serial_number2 = 1, + .access_key2 = 1, + .login_character_name = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + static const C_LoginExtended_BB_9E ret{ + .player_tag = 0, + .guild_card_number = 0, + .sub_version = 0, + .language32 = 0, + .unknown_a2 = 0, + .v1_serial_number = 1, + .v1_access_key = 1, + .serial_number = 1, + .access_key = 1, + .username = 1, + .password = 1, + .guild_card_number_str = 0, + .client_config = 0, + .extension{}, + }; + return std::make_pair(&ret, sizeof(ret)); + } + case 0xDB: + if (!is_v4(version)) { + static const C_VerifyAccount_V3_DB ret{ + .v1_serial_number = 1, + .v1_access_key = 1, + .serial_number = 1, + .access_key = 1, + .hardware_id = 0, + .sub_version = 0, + .serial_number2 = 1, + .access_key2 = 1, + .password = 1, + }; + return std::make_pair(&ret, sizeof(ret)); + } else { + static const C_VerifyAccount_BB_DB ret{ + .v1_serial_number = 1, + .v1_access_key = 1, + .serial_number = 1, + .access_key = 1, + .sub_version = 0, + .username = 1, + .password = 1, + .game_tag = 0, + }; + return std::make_pair(&ret, sizeof(ret)); + } + default: + return std::make_pair(nullptr, 0); + } +} diff --git a/src/CommandCensorData.hh b/src/CommandCensorData.hh new file mode 100644 index 00000000..d9f1e8bd --- /dev/null +++ b/src/CommandCensorData.hh @@ -0,0 +1,9 @@ +#pragma once + +#include + +#include + +#include "Version.hh" + +std::pair censor_data_for_client_command(Version version, uint16_t command); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index b58b99f7..95ea0426 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -318,7 +318,7 @@ struct S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B { // Internal name: SndRegist struct C_LegacyLogin_PC_V3_03 { - /* 00 */ be_uint64_t hardware_id; + /* 00 */ be_uint64_t hardware_id = 0; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; /* 0D */ Language language = Language::JAPANESE; @@ -364,7 +364,7 @@ struct S_ServerInitWithAfterMessageT_BB_03_9B { // likely a relic of an older, now-unused sequence. Like 03, this command isn't used by any known PSO version. struct C_LegacyLogin_PC_V3_04 { - /* 00 */ be_uint64_t hardware_id; + /* 00 */ be_uint64_t hardware_id = 0; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; /* 0D */ Language language = Language::JAPANESE; @@ -1509,7 +1509,7 @@ struct S_ArrowUpdateEntry_88 { // The server should respond with an 8A command. struct C_ConnectionInfo_DCNTE_8A { - be_uint64_t hardware_id; + be_uint64_t hardware_id = 0; le_uint32_t sub_version = 0x20; le_uint32_t unused = 0; pstring username; @@ -1536,7 +1536,7 @@ struct C_ConnectionInfo_DCNTE_8A { struct C_Login_DCNTE_8B { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - be_uint64_t hardware_id; + be_uint64_t hardware_id = 0; le_uint32_t sub_version = 0x20; uint8_t is_extended = 0; Language language = Language::JAPANESE; @@ -1589,7 +1589,7 @@ struct C_LoginV1_DC_PC_V3_90 { // 92 (C->S): Register (DC) struct C_RegisterV1_DC_92 { - be_uint64_t hardware_id; + be_uint64_t hardware_id = 0; le_uint32_t sub_version; uint8_t unused1 = 0; Language language = Language::JAPANESE; @@ -1608,7 +1608,7 @@ struct C_RegisterV1_DC_92 { struct C_LoginV1_DC_93 { /* 00 */ le_uint32_t player_tag = 0x00010000; /* 04 */ le_uint32_t guild_card_number = 0; - /* 08 */ be_uint64_t hardware_id; + /* 08 */ be_uint64_t hardware_id = 0; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; /* 15 */ Language language = Language::JAPANESE; @@ -1662,7 +1662,7 @@ struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 { struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 { // See the comment in the above structure. This format is used on newer client versions. - /* 7C */ be_uint64_t hardware_id; + /* 7C */ be_uint64_t hardware_id = 0; /* 84 */ parray client_config; /* AC */ } __packed_ws__(C_LoginWithHardwareInfo_BB_93, 0xAC); @@ -1773,7 +1773,7 @@ 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 { - /* 00 */ be_uint64_t hardware_id; + /* 00 */ be_uint64_t hardware_id = 0; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t unused1 = 0; /* 0D */ Language language = Language::JAPANESE; @@ -1819,7 +1819,7 @@ struct C_Login_DC_PC_GC_9D { // 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; + /* 08 */ be_uint64_t hardware_id = 0; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; // If 1, structure has extended format /* 15 */ Language language = Language::JAPANESE; diff --git a/src/DownloadSession.cc b/src/DownloadSession.cc index 0581b4f0..74ed8256 100644 --- a/src/DownloadSession.cc +++ b/src/DownloadSession.cc @@ -128,7 +128,9 @@ asio::awaitable DownloadSession::run() { this->language, netloc_str, this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END, - this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END); + this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END, + false, + false); this->log.info_f("Server channel connected"); while (this->channel->connected()) { @@ -529,7 +531,9 @@ asio::awaitable DownloadSession::on_message(Channel::Message& msg) { this->language, netloc_str, this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END, - this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END); + this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END, + false, + false); this->log.info_f("Server channel connected"); break; } diff --git a/src/Episode3/BattleRecord.cc b/src/Episode3/BattleRecord.cc index c0b5d9f6..7cbba147 100644 --- a/src/Episode3/BattleRecord.cc +++ b/src/Episode3/BattleRecord.cc @@ -10,9 +10,8 @@ using namespace std; namespace Episode3 { void BattleRecord::PlayerEntry::print(FILE* stream) const { - // TODO: Format this nicely somehow. Maybe factor out the functions in - // QuestScript that format some of these structures - phosg::print_data(stream, this, sizeof(*this), 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + // TODO: Format this nicely somehow. Maybe factor out the functions in QuestScript that format some of these structs + phosg::print_data(stream, this, sizeof(*this), 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); } BattleRecord::Event::Event(phosg::StringReader& r) { @@ -103,23 +102,23 @@ void BattleRecord::Event::print(FILE* stream) const { break; case Type::BATTLE_COMMAND: phosg::fwrite_fmt(stream, "BATTLE_COMMAND\n"); - phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); break; case Type::GAME_COMMAND: phosg::fwrite_fmt(stream, "GAME_COMMAND\n"); - phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); break; case Type::EP3_GAME_COMMAND: phosg::fwrite_fmt(stream, "EP3_GAME_COMMAND\n"); - phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); break; case Type::CHAT_MESSAGE: phosg::fwrite_fmt(stream, "CHAT_MESSAGE {:08X}\n", this->guild_card_number); - phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); break; case Type::SERVER_DATA_COMMAND: phosg::fwrite_fmt(stream, "SERVER_DATA_COMMAND\n"); - phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); break; default: throw runtime_error("unknown event type in battle record"); diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index c216006b..b8a8ab33 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -261,7 +261,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma } else if ((this->options.behavior_flags & BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING) && this->log().info_f("Generated command")) { - phosg::print_data(stderr, data, size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, data, size, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); } } diff --git a/src/GameServer.cc b/src/GameServer.cc index 8f0d4ed5..75b8f528 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -131,7 +131,9 @@ shared_ptr GameServer::create_client( Language::ENGLISH, "", phosg::TerminalFormat::FG_YELLOW, - phosg::TerminalFormat::FG_GREEN); + phosg::TerminalFormat::FG_GREEN, + this->state->censor_credentials, + false); auto c = make_shared(this->shared_from_this(), channel, listen_sock->behavior); this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name); diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index 492f849e..fb030676 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -165,8 +165,10 @@ IPSSChannel::IPSSChannel( Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, - phosg::TerminalFormat terminal_recv_color) - : Channel(version, language, name, terminal_send_color, terminal_recv_color), + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials) + : Channel(version, language, name, terminal_send_color, terminal_recv_color, censor_received_credentials, censor_sent_credentials), sim(sim), ipss_client(ipss_client), tcp_conn(tcp_conn), @@ -1364,7 +1366,17 @@ asio::awaitable IPStackSimulator::open_server_connection( } const auto& port_config = port_config_it->second; - conn->server_channel = make_shared(this->shared_from_this(), c, conn, port_config->version, Language::ENGLISH); + conn->server_channel = make_shared( + this->shared_from_this(), + c, + conn, + port_config->version, + Language::ENGLISH, + "", + phosg::TerminalFormat::END, + phosg::TerminalFormat::END, + false, + this->state->censor_credentials); if (!this->state->game_server.get()) { this->log.error_f("No server available for TCP connection {}", conn_str); diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 2c4f4844..4405e29f 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -94,10 +94,8 @@ struct IPSSClient : std::enable_shared_from_this { void reschedule_idle_timeout(); }; -// IPSSChannel provides an "unwrapped" connection to the rest of the server. It -// implements the Channel interface and can be used in place of an -// SocketChannel, so the rest of the server doesn't have to know about -// IPStackSimulator. +// IPSSChannel provides an "unwrapped" connection to the rest of the server. It implements the Channel interface and +// can be used in place of an SocketChannel, so the rest of the server doesn't have to know about IPStackSimulator. class IPSSChannel : public Channel { public: std::shared_ptr sim; @@ -110,18 +108,19 @@ public: std::weak_ptr tcp_conn, Version version, Language language, - const std::string& name = "", - phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, - phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); + const std::string& name, + phosg::TerminalFormat terminal_send_color, + phosg::TerminalFormat terminal_recv_color, + bool censor_received_credentials, + bool censor_sent_credentials); virtual std::string default_name() const; virtual bool connected() const; virtual void disconnect(); - // Adds inbound data, which will then be available via recv_raw(). This - // function is called by IPStackSimulator to forward "unwrapped" data to - // the game/proxy servers. + // Adds inbound data, which will then be available via recv_raw(). This function is called by IPStackSimulator to + // forward "unwrapped" data to the game/proxy servers. void add_inbound_data(const void* data, size_t size); virtual void send_raw(std::string&& data); @@ -134,9 +133,7 @@ private: size_t recv_buf_size = 0; }; -class IPStackSimulator - : public Server, - public std::enable_shared_from_this { +class IPStackSimulator : public Server, public std::enable_shared_from_this { public: IPStackSimulator(std::shared_ptr state); ~IPStackSimulator() = default; diff --git a/src/Main.cc b/src/Main.cc index 0de0342d..8c5a6915 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -3579,7 +3579,7 @@ Action a_check_quests( phosg::fwritex(stdout, vq->map_file->disassemble(false, vq->meta.version)); phosg::log_info_f("... BINDIFF:"); phosg::print_binary_diff( - stdout, dat.data(), dat.size(), serialized.data(), serialized.size(), isatty(fileno(stdout)), 3); + stdout, dat.data(), dat.size(), serialized.data(), serialized.size(), isatty(fileno(stdout))); phosg::log_info_f("... {} {} {} ({}) MAP FAILED", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), @@ -3821,9 +3821,9 @@ Action a_diff_executables( throw runtime_error("the two files are not the same type of executable, or are neither dol nor xbe"); } for (const auto& it : result) { - string b_str = phosg::format_data_string(it.b_data, nullptr, phosg::FormatDataFlags::HEX_ONLY); + string b_str = phosg::format_data_string(it.b_data, nullptr, phosg::FormatDataStringFlags::HEX_ONLY); if (show_pre) { - string a_str = phosg::format_data_string(it.a_data, nullptr, phosg::FormatDataFlags::HEX_ONLY); + string a_str = phosg::format_data_string(it.a_data, nullptr, phosg::FormatDataStringFlags::HEX_ONLY); phosg::fwrite_fmt(stdout, "{:08X}: {} => {}\n", it.address, a_str, b_str); } else { phosg::fwrite_fmt(stdout, "{:08X} {}\n", it.address, b_str); @@ -3962,7 +3962,7 @@ Action a_replay_ep3_battle_record( } if (output_queue->empty()) { phosg::fwrite_fmt(stderr, "Output queue is empty, but expected battle command:\n"); - phosg::print_data(stderr, ev.data, 0, nullptr, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII); + phosg::print_data(stderr, ev.data, 0, phosg::FormatDataFlags::OFFSET_16_BITS | phosg::FormatDataFlags::PRINT_ASCII); throw std::runtime_error("Output did not match expectations"); } // Hack: don't check the last field in 6xB4x46 since it contains a timestamp on non-NTE @@ -3977,11 +3977,10 @@ Action a_replay_ep3_battle_record( matched = (output_queue->front() == ev.data); } if (!matched) { - const void* prev = (ev.data.size() == output_queue->front().size()) ? ev.data.data() : nullptr; phosg::fwrite_fmt(stderr, "Output queue front did not match expected command; expected:\n"); - phosg::print_data(stderr, ev.data, 0, nullptr, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII); + phosg::print_data(stderr, ev.data, 0, phosg::FormatDataFlags::OFFSET_16_BITS | phosg::FormatDataFlags::PRINT_ASCII); phosg::fwrite_fmt(stderr, "Received:\n"); - phosg::print_data(stderr, output_queue->front(), 0, prev, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII); + phosg::print_data(stderr, output_queue->front(), 0, ev.data, phosg::FormatDataFlags::OFFSET_16_BITS | phosg::FormatDataFlags::PRINT_ASCII); throw std::runtime_error("Output did not match expectations"); } output_queue->pop_front(); diff --git a/src/Map.cc b/src/Map.cc index 4709d3ce..6d097fe6 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -6573,7 +6573,7 @@ void MapState::import_object_states_from_sync( throw logic_error("super object link is incorrect"); } if (obj_st->game_flags != entry.flags) { - this->log.warning_f("({:04X} => K-{:03X}) Game flags from client ({:04X}) do not match game flags from map ({:04X})", + this->log.info_f("({:04X} => K-{:03X}) Game flags from client ({:04X}) do not match game flags from map ({:04X})", object_index, obj_st->k_id, entry.flags, obj_st->game_flags); obj_st->game_flags = entry.flags; } @@ -6608,12 +6608,12 @@ void MapState::import_enemy_states_from_sync(Version from_version, const SyncEne // Only set the state if it's not an alias if (ene_st->super_ene == ene) { if (ene_st->game_flags != entry.flags) { - this->log.warning_f("({:04X} => E-{:03X}) Game flags from client ({:08X}) do not match game flags from map ({:08X})", + this->log.info_f("({:04X} => E-{:03X}) Game flags from client ({:08X}) do not match game flags from map ({:08X})", enemy_index, ene_st->e_id, entry.flags, ene_st->game_flags); ene_st->game_flags = entry.flags; } if (ene_st->total_damage != entry.total_damage) { - this->log.warning_f("({:04X} => E-{:03X}) Total damage from client ({}) does not match total damage from map ({})", + this->log.info_f("({:04X} => E-{:03X}) Total damage from client ({}) does not match total damage from map ({})", enemy_index, ene_st->e_id, entry.total_damage, ene_st->total_damage); ene_st->total_damage = entry.total_damage; } @@ -6665,7 +6665,7 @@ void MapState::import_flag_states_from_sync( throw logic_error("super object link is incorrect"); } if (obj_st->set_flags != set_flags) { - this->log.warning_f("({:04X} => K-{:03X}) Set flags from client ({:04X}) do not match set flags from map ({:04X})", + this->log.info_f("({:04X} => K-{:03X}) Set flags from client ({:04X}) do not match set flags from map ({:04X})", object_index, obj_st->k_id, set_flags, obj_st->set_flags); obj_st->set_flags = set_flags; } @@ -6701,7 +6701,7 @@ void MapState::import_flag_states_from_sync( throw logic_error("super enemy link is incorrect"); } if (ene_st->set_flags != set_flags) { - this->log.warning_f("({:04X} => E-{:03X}) Set flags from client ({:04X}) do not match set flags from map ({:04X})", + this->log.info_f("({:04X} => E-{:03X}) Set flags from client ({:04X}) do not match set flags from map ({:04X})", enemy_set_index, ene_st->e_id, set_flags, ene_st->set_flags); ene_st->set_flags = set_flags; } @@ -6733,7 +6733,7 @@ void MapState::import_flag_states_from_sync( const auto& ev = entities.events.at(event_index - base_indexes.base_event_index); auto& ev_st = this->event_states.at(fc.base_super_ids.base_event_index + ev->super_id); if (ev_st->flags != flags) { - this->log.warning_f("({:04X} => W-{:03X}) Set flags from client ({:04X}) do not match flags from map ({:04X})", + this->log.info_f("({:04X} => W-{:03X}) Set flags from client ({:04X}) do not match flags from map ({:04X})", event_index, ev_st->w_id, flags, ev_st->flags); ev_st->flags = flags; } diff --git a/src/PatchDownloadSession.cc b/src/PatchDownloadSession.cc index 204cab66..5b9ca745 100644 --- a/src/PatchDownloadSession.cc +++ b/src/PatchDownloadSession.cc @@ -64,7 +64,9 @@ asio::awaitable PatchDownloadSession::run() { Language::ENGLISH, netloc_str, this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END, - this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END); + this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END, + false, + false); this->log.info_f("Server channel connected"); while (this->channel->connected()) { @@ -267,7 +269,9 @@ asio::awaitable PatchDownloadSession::on_message(Channel::Message& msg) { this->channel->language, netloc_str, this->channel->terminal_send_color, - this->channel->terminal_recv_color); + this->channel->terminal_recv_color, + false, + false); this->channel = new_channel; old_channel->disconnect(); this->log.info_f("Server channel connected"); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 38d0c02d..3f92a2d2 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -883,7 +883,9 @@ static asio::awaitable S_19_U_14(shared_ptr c, Channel::M old_channel->language, std::format("C-{} proxy remote server at {}", c->id, netloc_str), old_channel->terminal_send_color, - old_channel->terminal_recv_color); + old_channel->terminal_recv_color, + old_channel->censor_received_credentials, + old_channel->censor_sent_credentials); c->proxy_session->server_channel = new_channel; asio::co_spawn(*s->io_context, handle_proxy_server_commands(c, c->proxy_session, new_channel), asio::detached); c->log.info_f("Server channel connected"); diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 818a6f4c..63c7b481 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -107,24 +107,18 @@ static string escape_string(const string& data, TextEncoding encoding = TextEnco } static string format_and_indent_data(const void* data, size_t size, uint64_t start_address) { - struct iovec iov; - iov.iov_base = const_cast(data); - iov.iov_len = size; - string ret = " "; - phosg::format_data( - [&ret](const void* vdata, size_t size) -> void { - const char* data = reinterpret_cast(vdata); - for (size_t z = 0; z < size; z++) { - if (data[z] == '\n') { - ret += "\n "; - } else { - ret.push_back(data[z]); - } - } - }, - &iov, 1, start_address, nullptr, 0, phosg::PrintDataFlags::PRINT_ASCII); - + auto write_fn = [&ret](const void* vdata, size_t size) -> void { + const char* data = reinterpret_cast(vdata); + for (size_t z = 0; z < size; z++) { + if (data[z] == '\n') { + ret += "\n "; + } else { + ret.push_back(data[z]); + } + } + }; + phosg::format_data_custom(write_fn, data, size, start_address, phosg::FormatDataFlags::PRINT_ASCII); phosg::strip_trailing_whitespace(ret); return ret; } @@ -3533,7 +3527,7 @@ std::string disassemble_quest_script( line_text = std::format(" {}", dasm_line); } else { size_t opcode_size = label_r.where() - opcode_start_offset; - string hex_data = phosg::format_data_string(label_r.preadx(opcode_start_offset, opcode_size), nullptr, phosg::FormatDataFlags::HEX_ONLY); + string hex_data = phosg::format_data_string(label_r.preadx(opcode_start_offset, opcode_size), nullptr, phosg::FormatDataStringFlags::HEX_ONLY); if (hex_data.size() > 14) { hex_data.resize(12); hex_data += "..."; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index a6f41b10..98e656cb 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -480,7 +480,9 @@ asio::awaitable start_proxy_session(shared_ptr c, const string& ho c->channel->language, std::format("C-{} proxy remote server at {}", c->id, netloc_str), phosg::TerminalFormat::FG_YELLOW, - phosg::TerminalFormat::FG_RED); + phosg::TerminalFormat::FG_RED, + false, + s->censor_credentials); c->proxy_session = make_shared(channel, pc); if (c->version() == Version::GC_EP3) { diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5e3637c0..77c414a0 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2247,7 +2247,7 @@ static asio::awaitable on_pick_up_item_generic( if (!l->item_exists(floor, item_id)) { // This can happen if the network is slow, and the client tries to pick up the same item multiple times. Or // multiple clients could try to pick up the same item at approximately the same time; only one should get it. - l->log.warning_f("Player {} requests to pick up {:08X}, but the item does not exist; dropping command", client_id, item_id); + l->log.info_f("Player {} requests to pick up {:08X}, but the item does not exist; dropping command", client_id, item_id); } else { // This is handled by the server on BB, and by the leader on other versions. However, the client's logic is to diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 1117ec31..0abf396a 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -50,7 +50,15 @@ ReplaySession::Client::Client(shared_ptr io_context, uint64_t : id(id), port(port), version(version), - channel(make_shared(io_context, this->version, Language::ENGLISH, std::format("R-{:X}", this->id))) {} + channel(make_shared( + io_context, + this->version, + Language::ENGLISH, + std::format("R-{:X}", this->id), + phosg::TerminalFormat::END, + phosg::TerminalFormat::END, + false, + false)) {} string ReplaySession::Client::str() const { return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version)); @@ -516,7 +524,7 @@ asio::awaitable ReplaySession::run() { "(ev-line {}) client connected to port missing from configuration", this->first_event->line_num)); } - auto server_channel = make_shared(this->state->io_context, port_config->version, c->channel->language); + auto server_channel = make_shared(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false); PeerChannel::link_peers(c->channel, server_channel); if (this->state->game_server.get()) { @@ -561,24 +569,24 @@ asio::awaitable ReplaySession::run() { this->bytes_received += full_command.size(); if (c->receive_events.empty()) { - phosg::print_data(stderr, full_command, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); throw runtime_error("received unexpected command for client"); } auto& ev = c->receive_events.front(); if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) { replay_log.error_f("Expected command:"); - phosg::print_data(stderr, ev->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); replay_log.error_f("Received command:"); - phosg::print_data(stderr, full_command, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); throw runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num)); } for (size_t x = 0; x < min(full_command.size(), ev->data.size()); x++) { if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) { replay_log.error_f("Expected command:"); - phosg::print_data(stderr, ev->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); replay_log.error_f("Received command:"); - phosg::print_data(stderr, full_command, 0, ev->data.data(), phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS); + phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); throw runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num)); } } diff --git a/src/ServerState.cc b/src/ServerState.cc index 401b7a51..cc1506ef 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -976,6 +976,7 @@ void ServerState::load_config_early() { this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", 0); this->ep3_card_auction_points = this->config_json->get_int("CardAuctionPoints", 0); this->hide_download_commands = this->config_json->get_bool("HideDownloadCommands", true); + this->censor_credentials = this->config_json->get_bool("CensorCredentials", true); this->proxy_allow_save_files = this->config_json->get_bool("ProxyAllowSaveFiles", true); try { diff --git a/src/ServerState.hh b/src/ServerState.hh index 2e73bb32..f21aa228 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -165,6 +165,7 @@ struct ServerState : public std::enable_shared_from_this { bool ep3_jukebox_is_free = false; uint32_t ep3_behavior_flags = 0; bool hide_download_commands = true; + bool censor_credentials = true; RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT; BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT; bool default_switch_assist_enabled = false; diff --git a/src/Text.hh b/src/Text.hh index d97d9f9d..a9707805 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -486,8 +486,14 @@ struct pstring { uint8_t data[Bytes]; - pstring() { - memset(this->data, 0, Bytes); + pstring(uint8_t v = 0) { + memset(this->data, v, Bytes); + } + pstring(const void* data, size_t size) { + memcpy(this->data, data, std::min(size, Bytes)); + if (size < Bytes) { + memset(this->data + size, 0, Bytes - size); + } } pstring(const pstring& other) { memcpy(this->data, other.data, Bytes); diff --git a/system/config.example.json b/system/config.example.json index 5eacfc13..1d3c5716 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -204,6 +204,10 @@ // default. If you're investigating or submitting a bug report that occurs on BB clients, set this to false to get a // full session log before submitting your report. "HideDownloadCommands": true, + // Some commands include user information such as passwords and access keys. By default, these are not shown in the + // log, but in certain debugging situations you may need to log them. Set this to false to show credentials in the + // command log. + "CensorCredentials": true, // If this option is disabled, the server only allows users who have accounts on the server to connect. If this is // enabled, all users will be allowed to connect even if they don't have accounts. When a user connects with an diff --git a/tests/config.json b/tests/config.json index 84f7cecf..50ac3dc1 100644 --- a/tests/config.json +++ b/tests/config.json @@ -185,6 +185,7 @@ "StaticGameData": "WARNING", }, "HideDownloadCommands": true, + "CensorCredentials": true, "AllowUnregisteredUsers": true, "UseTemporaryAccountsForPrototypes": true,