#include "CatSession.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Loggers.hh" #include "PSOProtocol.hh" #include "SendCommands.hh" #include "ReceiveCommands.hh" #include "ReceiveSubcommands.hh" #include "ProxyCommands.hh" using namespace std; CatSession::CatSession( shared_ptr base, const struct sockaddr_storage& remote, GameVersion version, shared_ptr bb_key_file) : Shell(base), log("[CatSession] ", proxy_server_log.min_level), channel( version, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"), bb_key_file(bb_key_file) { if (remote.ss_family != AF_INET) { throw runtime_error("remote is not AF_INET"); } string netloc_str = render_sockaddr_storage(remote); this->log.info("Connecting to %s", netloc_str.c_str()); struct bufferevent* bev = bufferevent_socket_new( this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); if (!bev) { throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR())); } this->channel.set_bufferevent(bev); if (bufferevent_socket_connect(this->channel.bev.get(), reinterpret_cast(&remote), sizeof(struct sockaddr_in)) != 0) { throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR())); } } void CatSession::dispatch_on_channel_input( Channel& ch, uint16_t command, uint32_t flag, std::string& data) { auto* session = reinterpret_cast(ch.context_obj); session->on_channel_input(command, flag, data); } void CatSession::on_channel_input( uint16_t command, uint32_t flag, std::string& data) { if (this->channel.version != GameVersion::BB) { if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { const auto& cmd = check_size_t(data, sizeof(S_ServerInitDefault_DC_PC_V3_02_17_91_9B), 0xFFFF); if ((this->channel.version == GameVersion::GC) || (this->channel.version == GameVersion::XB)) { this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); this->channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")", cmd.server_key.load(), cmd.client_key.load()); } else { // PC, DC, or patch server this->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); this->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")", cmd.server_key.load(), cmd.client_key.load()); } } } else { // BB if (command == 0x03 || command == 0x9B) { if (!this->bb_key_file) { throw runtime_error("BB encryption requires a key file"); } const auto& cmd = check_size_t(data, sizeof(S_ServerInitDefault_BB_03_9B), 0xFFFF); this->channel.crypt_in.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key))); this->channel.crypt_out.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key))); this->log.info("Enabled BB encryption"); } } string full_cmd = prepend_command_header( this->channel.version, this->channel.crypt_in.get(), command, flag, data); print_data(stdout, full_cmd, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::OFFSET_16_BITS); } void CatSession::dispatch_on_channel_error(Channel& ch, short events) { auto* session = reinterpret_cast(ch.context_obj); session->on_channel_error(events); } void CatSession::on_channel_error(short events) { if (events & BEV_EVENT_CONNECTED) { this->log.info("Channel connected"); } if (events & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); this->log.warning("Error %d (%s) in unlinked client stream", err, evutil_socket_error_to_string(err)); } if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) { this->log.info("Session endpoint has disconnected"); this->channel.disconnect(); event_base_loopexit(this->base.get(), nullptr); } } void CatSession::print_prompt() { } void CatSession::execute_command(const std::string& command) { string full_cmd = parse_data_string(command); send_command_with_header(this->channel, full_cmd.data(), full_cmd.size()); }