get PSO PC login sequence working

This commit is contained in:
Martin Michelsen
2022-04-01 23:44:12 -07:00
parent 46add5fb74
commit 76fd9c22bf
8 changed files with 263 additions and 120 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ struct Client {
// sent (via a 61 command), so the IS_EPISODE_3 flag is set in that handler
DEFAULT_V1 = DCV1,
DEFAULT_V2_DC = 0x0000,
DEFAULT_V2_PC = 0x0000,
DEFAULT_V2_PC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION,
DEFAULT_V3_GC = 0x0000,
DEFAULT_V3_GC_PLUS = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN,
DEFAULT_V3_GC_EP3 = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | EPISODE_3,
+30 -17
View File
@@ -47,7 +47,7 @@ struct SC_TextHeader_01_06_11_B0 {
// 02 (S->C): Start encryption (except on BB)
// Client will respond with an (encrypted) 9A, 9D, or 9E command
struct S_ServerInit_DC_GC_02_17 {
struct S_ServerInit_DC_PC_GC_02_17 {
ptext<char, 0x40> copyright;
le_uint32_t server_key; // Key for data sent by server
le_uint32_t client_key; // Key for data sent by client
@@ -313,15 +313,15 @@ struct SC_GameCardCheck_BB_22 {
struct C_GuildCardSearch_40 {
le_uint32_t player_tag;
le_uint32_t searcher_serial_number;
le_uint32_t target_serial_number;
le_uint32_t searcher_guild_card_number;
le_uint32_t target_guild_card_number;
} __attribute__((packed));
template <typename HeaderT, typename CharT>
struct S_GuildCardSearchResult {
le_uint32_t player_tag;
le_uint32_t searcher_serial_number;
le_uint32_t result_serial_number;
le_uint32_t searcher_guild_card_number;
le_uint32_t result_guild_card_number;
HeaderT reconnect_command_header;
S_Reconnect_19 reconnect_command;
ptext<char, 0x44> location_string;
@@ -534,9 +534,9 @@ struct S_LeaveLobby_66_69 {
struct SC_SimpleMail_GC_81 {
le_uint32_t player_tag;
le_uint32_t from_serial_number;
le_uint32_t from_guild_card_number;
ptext<char, 0x10> from_name;
le_uint32_t to_serial_number;
le_uint32_t to_guild_card_number;
ptext<char, 0x200> text;
};
@@ -568,7 +568,7 @@ struct C_LobbySelection_84 {
// Command is a list of these; header.flag is the entry count
struct S_ArrowUpdateEntry_88 {
le_uint32_t player_tag;
le_uint32_t serial_number;
le_uint32_t guild_card_number;
le_uint32_t arrow_color;
};
@@ -635,6 +635,12 @@ struct C_Login_DC_PC_GC_9A {
ptext<char, 0x20> unused;
ptext<char, 0x10> serial_number;
ptext<char, 0x10> access_key;
uint32_t player_tag;
uint32_t guild_card_number;
uint32_t sub_version;
ptext<char, 0x30> serial_number2;
ptext<char, 0x30> access_key2;
ptext<char, 0x30> email_address;
};
// 9B: Invalid command
@@ -654,12 +660,9 @@ struct C_Register_DC_PC_GC_9C {
ptext<char, 0x30> password;
};
// 9D (C->S): Log in with client config
// Same as 9E? (We treat them identicall in newserv)
// 9D (C->S): Log in
// 9E (C->S): Log in with client config
struct C_Login_PC_GC_9D_9E {
struct C_Login_PC_9D {
le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04)
le_uint32_t guild_card_number; // FF FF FF FF if not set
le_uint64_t unused;
@@ -670,13 +673,23 @@ struct C_Login_PC_GC_9D_9E {
ptext<char, 0x30> serial_number2;
ptext<char, 0x30> access_key2;
ptext<char, 0x10> name;
};
struct C_LoginWithUnusedSpace_PC_9D : C_Login_PC_9D {
parray<uint8_t, 0x84> unused_space;
};
// 9E (C->S): Log in with client config
// This struct is identical to PC's 9D command, but it has more data at the end
struct C_Login_GC_9E : C_Login_PC_9D {
union ClientConfigFields {
ClientConfig cfg;
parray<uint8_t, 0x20> data;
ClientConfigFields() : data() { }
} client_config;
parray<uint8_t, 0x64> unused4;
};
struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E {
parray<uint8_t, 0x64> unused_space;
};
// 9F: Invalid command
@@ -1186,7 +1199,7 @@ struct S_SendGuildCard_GC {
uint8_t subsize;
le_uint16_t unused;
le_uint32_t player_tag;
le_uint32_t serial_number;
le_uint32_t guild_card_number;
ptext<char, 0x18> name;
ptext<char, 0x6C> desc;
uint8_t reserved1;
@@ -1199,7 +1212,7 @@ struct S_SendGuildCard_BB {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t unused;
le_uint32_t serial_number;
le_uint32_t guild_card_number;
ptext<char16_t, 0x18> name;
ptext<char16_t, 0x10> team_name;
ptext<char16_t, 0x58> desc;
+6 -8
View File
@@ -29,14 +29,12 @@ struct MenuItem {
INVISIBLE_ON_DC = 0x01,
INVISIBLE_ON_PC = 0x02,
INVISIBLE_ON_GC = 0x04,
INVISIBLE_ON_GC_EPISODE_3 = 0x08,
INVISIBLE_ON_BB = 0x10,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB,
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB,
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC_EPISODE_3 | INVISIBLE_ON_BB,
GC_EPISODE_3_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_GC_EPISODE_3,
REQUIRES_MESSAGE_BOXES = 0x00010000,
INVISIBLE_ON_BB = 0x08,
DC_ONLY = INVISIBLE_ON_PC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
PC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_GC | INVISIBLE_ON_BB,
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_BB,
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC,
REQUIRES_MESSAGE_BOXES = 0x10,
};
uint32_t item_id;
+126 -36
View File
@@ -93,8 +93,8 @@ ProxyServer::ListeningSocket::ListeningSocket(
}
void ProxyServer::ListeningSocket::dispatch_on_listen_accept(
struct evconnlistener*, evutil_socket_t, struct sockaddr* address, int socklen, void* ctx) {
reinterpret_cast<ListeningSocket*>(ctx)->on_listen_accept(address, socklen);
struct evconnlistener*, evutil_socket_t fd, struct sockaddr*, int, void* ctx) {
reinterpret_cast<ListeningSocket*>(ctx)->on_listen_accept(fd);
}
void ProxyServer::ListeningSocket::dispatch_on_listen_error(
@@ -102,7 +102,7 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error(
reinterpret_cast<ListeningSocket*>(ctx)->on_listen_error();
}
void ProxyServer::ListeningSocket::on_listen_accept(struct sockaddr*, int fd) {
void ProxyServer::ListeningSocket::on_listen_accept(int fd) {
log(INFO, "[ProxyServer] Client connected on fd %d", fd);
auto* bev = bufferevent_socket_new(this->server->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
@@ -158,7 +158,7 @@ void ProxyServer::on_client_connect(
throw logic_error("linked session already exists for unlicensed client");
}
auto session = emplace_ret.first->second;
log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id);
log(INFO, "[ProxyServer/%08" PRIX64 "] Opened linked session", session->id);
session->resume(bev);
// If no default destination exists, create an unlinked session - we'll have
@@ -171,6 +171,7 @@ void ProxyServer::on_client_connect(
throw logic_error("stale unlinked session exists");
}
auto session = emplace_ret.first->second;
log(INFO, "[ProxyServer] Opened unlinked session");
switch (version) {
case GameVersion::PATCH:
@@ -181,11 +182,23 @@ void ProxyServer::on_client_connect(
uint32_t client_key = random_object<uint32_t>();
auto cmd = prepare_server_init_contents_dc_pc_gc(
false, server_key, client_key);
send_command(session->bev.get(), session->version,
session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd),
send_command(
session->bev.get(),
session->version,
session->crypt_out.get(),
0x02,
0,
&cmd,
sizeof(cmd),
"unlinked proxy client");
session->crypt_out.reset(new PSOGCEncryption(server_key));
session->crypt_in.reset(new PSOGCEncryption(client_key));
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
if (version == GameVersion::PC) {
session->crypt_out.reset(new PSOPCEncryption(server_key));
session->crypt_in.reset(new PSOPCEncryption(client_key));
} else {
session->crypt_out.reset(new PSOGCEncryption(server_key));
session->crypt_in.reset(new PSOGCEncryption(client_key));
}
break;
}
default:
@@ -230,17 +243,41 @@ void ProxyServer::UnlinkedSession::on_client_input() {
print_received_command(command, flag, data.data(), data.size(),
this->version, "unlinked proxy client");
if (this->version == GameVersion::GC) {
// We should really only get a 9E while the session is unlinked; if we
// get anything else, disconnect
if (command != 0x9E) {
if (this->version == GameVersion::PC) {
// We should only get a 9D while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9D) {
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
should_close_unlinked_session = true;
} else if (data.size() < sizeof(C_Login_PC_GC_9D_9E) - 0x64) {
} else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) {
log(ERROR, "[ProxyServer] Login command is too small");
should_close_unlinked_session = true;
} else {
const auto* cmd = reinterpret_cast<const C_Login_PC_GC_9D_9E*>(data.data());
const auto* cmd = reinterpret_cast<const C_Login_GC_9E*>(data.data());
uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16);
try {
license = this->server->state->license_manager->verify_pc(
serial_number, cmd->access_key.c_str(), nullptr);
sub_version = cmd->sub_version;
character_name = cmd->name;
client_config = cmd->client_config.cfg;
} catch (const exception& e) {
log(ERROR, "[ProxyServer] Unlinked client has no valid license");
should_close_unlinked_session = true;
}
}
} else if (this->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9E) {
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
should_close_unlinked_session = true;
} else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) {
log(ERROR, "[ProxyServer] Login command is too small");
should_close_unlinked_session = true;
} else {
const auto* cmd = reinterpret_cast<const C_Login_GC_9E*>(data.data());
uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16);
try {
license = this->server->state->license_manager->verify_gc(
@@ -253,6 +290,9 @@ void ProxyServer::UnlinkedSession::on_client_input() {
should_close_unlinked_session = true;
}
}
} else {
throw logic_error("unsupported unlinked session version");
}
});
@@ -269,6 +309,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
shared_ptr<LinkedSession> session;
try {
session = this->server->id_to_session.at(license->serial_number);
log(INFO, "[ProxyServer/%08" PRIX64 "] Resuming linked session from unlinked session", session->id);
} catch (const out_of_range&) {
// If there's no open session for this license, then there must be a valid
@@ -284,7 +325,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
license,
client_config));
this->server->id_to_session.emplace(license->serial_number, session);
log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id);
log(INFO, "[ProxyServer/%08" PRIX64 "] Opened licensed session for unlinked session", session->id);
}
}
@@ -355,6 +396,7 @@ ProxyServer::LinkedSession::LinkedSession(
std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
this->newserv_client_config = newserv_client_config;
memset(&this->next_destination, 0, sizeof(this->next_destination));
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&this->next_destination);
@@ -701,13 +743,16 @@ void ProxyServer::LinkedSession::on_server_input() {
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) {
if (data.size() < offsetof(S_ServerInit_DC_PC_GC_02_17, after_message)) {
throw std::runtime_error("init encryption command is too small");
}
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_GC_02_17*>(
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_PC_GC_02_17*>(
data.data());
if (!this->license) {
log(INFO, "[ProxyServer/%08" PRIX64 "] No license in linked session",
this->id);
if ((this->version == GameVersion::PC) || (this->version == GameVersion::PATCH)) {
this->server_input_crypt.reset(new PSOPCEncryption(cmd->server_key));
this->server_output_crypt.reset(new PSOPCEncryption(cmd->client_key));
@@ -724,6 +769,9 @@ void ProxyServer::LinkedSession::on_server_input() {
break;
} else {
log(INFO, "[ProxyServer/%08" PRIX64 "] Existing license in linked session",
this->id);
// This doesn't get forwarded to the client, so don't recreate the
// client's crypts
if (this->version == GameVersion::PATCH) {
@@ -740,26 +788,56 @@ void ProxyServer::LinkedSession::on_server_input() {
should_forward = false;
// If this is a 17, respond with a DB; otherwise respond with a 9E.
// We don't let the client do this because it believes it already
// did (when it was in an unlinked session).
if (command == 0x17) {
C_VerifyLicense_GC_DB cmd;
// Respond with an appropriate login command. We don't let the
// client do this because it believes it already did (when it was
// in an unlinked session).
if (this->version == GameVersion::PC) {
C_Login_PC_9D cmd;
if (this->guild_card_number == 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = this->guild_card_number;
}
cmd.unused = 0xFFFFFFFFFFFF0000;
cmd.sub_version = this->sub_version;
cmd.unused2.data()[1] = 1;
cmd.serial_number = string_printf("%08" PRIX32 "",
this->license->serial_number);
cmd.access_key = this->license->access_key;
cmd.sub_version = this->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = this->license->gc_password;
cmd.name = this->character_name;
send_command(this->server_bev.get(), this->version,
this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd),
this->server_output_crypt.get(), 0x9D, 0, &cmd, sizeof(cmd),
name.c_str());
break;
} else if (this->version == GameVersion::GC) {
if (command == 0x17) {
C_VerifyLicense_GC_DB cmd;
cmd.serial_number = string_printf("%08" PRIX32 "",
this->license->serial_number);
cmd.access_key = this->license->access_key;
cmd.sub_version = this->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = this->license->gc_password;
send_command(this->server_bev.get(), this->version,
this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd),
name.c_str());
break;
} else if (command == 0x02) {
// Command 02 should be handled like 9A at this point (we
// should send a 9E in response)
[[fallthrough]];
}
} else {
throw logic_error("invalid game version in server init handler");
}
// Command 02 should be handled like 9A at this point (we should
// send a 9E in response)
[[fallthrough]];
}
}
@@ -767,9 +845,11 @@ void ProxyServer::LinkedSession::on_server_input() {
if (!this->license) {
break;
}
if (this->version != GameVersion::GC) {
throw runtime_error("9A received in non-GC session");
}
should_forward = false;
C_Login_PC_GC_9D_9E cmd;
C_LoginWithUnusedSpace_GC_9E cmd;
if (this->guild_card_number == 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
@@ -796,16 +876,12 @@ void ProxyServer::LinkedSession::on_server_input() {
0x9E,
0x01,
&cmd,
this->guild_card_number ? offsetof(C_Login_PC_GC_9D_9E, unused4) : sizeof(cmd),
sizeof(C_LoginWithUnusedSpace_GC_9E) - (this->guild_card_number ? sizeof(cmd.unused_space) : 0),
name.c_str());
break;
}
case 0x04: {
if (this->version != GameVersion::GC) {
break;
}
// Some servers send a short 04 command if they don't use all of the
// 0x20 bytes available. We should be prepared to handle that.
if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) {
@@ -900,7 +976,7 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x1A:
case 0xD5: {
if (this->version != GameVersion::PATCH) {
if (this->version != GameVersion::GC) {
break;
}
@@ -1228,6 +1304,20 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
return this->id_to_session.begin()->second;
}
std::shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
std::shared_ptr<const License> l, uint16_t local_port, GameVersion version,
const ClientConfig& newserv_client_config) {
shared_ptr<LinkedSession> session(new LinkedSession(
this, local_port, version, l, newserv_client_config));
auto emplace_ret = this->id_to_session.emplace(session->id, session);
if (!emplace_ret.second) {
throw runtime_error("session already exists for this license");
}
log(INFO, "[ProxyServer/%08" PRIX64 "] Opening licensed session", session->id);
return emplace_ret.first->second;
}
void ProxyServer::delete_session(uint64_t id) {
if (this->id_to_session.erase(id)) {
log(WARNING, "[ProxyServer/%08" PRIX64 "] Closed session", id);
+6 -1
View File
@@ -132,6 +132,11 @@ public:
};
std::shared_ptr<LinkedSession> get_session();
std::shared_ptr<LinkedSession> create_licensed_session(
std::shared_ptr<const License> l,
uint16_t local_port,
GameVersion version,
const ClientConfig& newserv_client_config);
void delete_session(uint64_t id);
bool save_files;
@@ -155,7 +160,7 @@ private:
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr *address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
void on_listen_accept(struct sockaddr *address, int socklen);
void on_listen_accept(int fd);
void on_listen_error();
};
+75 -37
View File
@@ -247,6 +247,8 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 9A
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9A>(data);
c->flags |= flags_for_version(c->version, cmd.sub_version);
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
try {
if (c->version == GameVersion::GC) {
@@ -257,10 +259,10 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
cmd.access_key.c_str(), nullptr);
}
} catch (const exception& e) {
// The client should have sent a different command containing the password
// already, which should have created and added a temporary license. If no
// license exists, disconnect the client even if unregistered clients are
// allowed.
// On GC, the client should have sent a different command containing the
// password already, which should have created and added a temporary
// license. So, if no license exists at this point, disconnect the client
// even if unregistered clients are allowed.
u16string message = u"Login failed: " + decode_sjis(e.what());
send_message_box(c, message.c_str());
c->should_disconnect = true;
@@ -309,21 +311,45 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 9D 9E
// Sometimes the unused bytes aren't sent
const auto& cmd = check_size_t<C_Login_PC_GC_9D_9E>(data,
sizeof(C_Login_PC_GC_9D_9E) - 0x64, sizeof(C_Login_PC_GC_9D_9E));
uint16_t command, uint32_t, const string& data) { // 9D 9E
c->flags |= flags_for_version(c->version, cmd.sub_version);
// The client sends extra unused data the first time it sends these commands,
// hence the odd check_size calls here
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
const C_Login_PC_9D* base_cmd;
if (command == 0x9D) {
base_cmd = &check_size_t<C_Login_PC_9D>(data,
sizeof(C_Login_PC_9D), sizeof(C_Login_PC_9D) + 0x84);
} else if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_GC_9E>(data,
sizeof(C_Login_GC_9E), sizeof(C_Login_GC_9E) + 0x64);
base_cmd = &cmd;
try {
c->import_config(cmd.client_config.cfg);
} catch (const invalid_argument&) {
// If we can't import the config, assume that the client was not connected
// to newserv before, so we should show the welcome message.
c->flags |= Client::Flag::AT_WELCOME_MESSAGE;
c->bb_game_state = 0;
c->bb_player_index = 0;
}
} else {
throw logic_error("9D/9E handler called for incorrect command");
}
c->flags |= flags_for_version(c->version, base_cmd->sub_version);
uint32_t serial_number = strtoul(base_cmd->serial_number.c_str(), nullptr, 16);
try {
if (c->version == GameVersion::GC) {
c->license = s->license_manager->verify_gc(serial_number,
cmd.access_key.c_str(), nullptr);
base_cmd->access_key.c_str(), nullptr);
} else {
c->license = s->license_manager->verify_pc(serial_number,
cmd.access_key.c_str(), nullptr);
base_cmd->access_key.c_str(), nullptr);
}
} catch (const exception& e) {
// See comment in 9A handler about why we do this even if unregistered users
@@ -334,16 +360,6 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
return;
}
try {
c->import_config(cmd.client_config.cfg);
} catch (const invalid_argument&) {
// If we can't import the config, assume that the client was not connected
// to newserv before, so we should show the welcome message.
c->flags |= Client::Flag::AT_WELCOME_MESSAGE;
c->bb_game_state = 0;
c->bb_player_index = 0;
}
if ((c->flags & Client::Flag::EPISODE_3) && (s->ep3_menu_song >= 0)) {
send_ep3_change_music(c, s->ep3_menu_song);
}
@@ -726,17 +742,20 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
// the client to the proxy server instead (would have to provide
// license/char name/etc. for remote auth)
static const vector<string> version_to_port_name({
"dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
uint16_t local_port = s->name_to_port_config.at(port_name)->port;
c->proxy_destination_address = resolve_ipv4(dest->first);
c->proxy_destination_port = dest->second;
send_update_client_config(c);
s->proxy_server->delete_session(c->license->serial_number);
s->proxy_server->create_licensed_session(
c->license, local_port, c->version, c->export_config());
static const vector<string> version_to_port_name({
"dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
}
break;
@@ -1362,7 +1381,7 @@ void process_card_search(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 40
const auto& cmd = check_size_t<C_GuildCardSearch_40>(data);
try {
auto result = s->find_client(nullptr, cmd.target_serial_number);
auto result = s->find_client(nullptr, cmd.target_guild_card_number);
auto result_lobby = s->find_lobby(result->lobby_id);
send_card_search_result(s, c, result, result_lobby);
} catch (const out_of_range&) { }
@@ -1384,7 +1403,7 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& cmd = check_size_t<SC_SimpleMail_GC_81>(data);
auto target = s->find_client(nullptr, cmd.to_serial_number);
auto target = s->find_client(nullptr, cmd.to_guild_card_number);
// If the sender is blocked, don't forward the mail
for (size_t y = 0; y < 30; y++) {
@@ -1710,20 +1729,39 @@ void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c,
throw runtime_error("unknown patch server login format");
}
u16string message = u"\
// On BB we can use colors and newlines should be \n; on PC we can't use
// colors, the text is auto-word-wrapped, and newlines should be \r\n.
u16string message;
if (c->flags & Client::Flag::BB_PATCH) {
message = u"\
$C7newserv patch server\n\
\n\
This server is for private use only.\n\
This server is not affiliated with, sponsored by, or in any\n\
other way connected to SEGA or Sonic Team, and is owned\n\
and operated completely independently.\n\
\n\
License check: ";
\n";
} else {
message = u"\
newserv patch server\r\n\
\r\n\
This server is not affiliated with, sponsored by, or in any other way \
connected to SEGA or Sonic Team, and is owned and operated completely \
independently.\r\n\
\r\n";
}
message += u"License check ";
try {
c->license = s->license_manager->verify_bb(
cmd.username.c_str(), cmd.password.c_str());
if (c->flags & Client::Flag::BB_PATCH) {
c->license = s->license_manager->verify_bb(
cmd.username.c_str(), cmd.password.c_str());
} else {
uint32_t serial_number = strtoul(cmd.username.c_str(), nullptr, 16);
c->license = s->license_manager->verify_pc(
serial_number, cmd.password.c_str(), nullptr);
}
message += u"OK";
} catch (const exception& e) {
message += u"failed: ";
message += decode_sjis(e.what());
}
@@ -1735,8 +1773,8 @@ License check: ";
send_command(c, 0x0A);
send_command(c, 0x0A);
// this command terminates the patch connection successfully. PSO complains if
// we don't check the above directories though
// This command terminates the patch connection successfully. PSOBB complains
// if we don't check the above directories before sending this though
send_command(c, 0x0012, 0x00000000);
}
+15 -18
View File
@@ -154,11 +154,11 @@ static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyrigh
static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.";
static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001";
S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
bool initial_connection,
uint32_t server_key,
uint32_t client_key) {
S_ServerInit_DC_GC_02_17 cmd;
S_ServerInit_DC_PC_GC_02_17 cmd;
cmd.copyright = initial_connection
? dc_port_map_copyright : dc_lobby_server_copyright;
cmd.server_key = server_key;
@@ -169,9 +169,7 @@ S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
void send_server_init_dc_pc_gc(shared_ptr<Client> c,
bool initial_connection) {
// PC uses 17 for all server inits; GC uses it only for the first one
uint8_t command = (initial_connection || (c->version == GameVersion::PC))
? 0x17 : 0x02;
uint8_t command = initial_connection ? 0x17 : 0x02;
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
@@ -475,7 +473,7 @@ void send_text_message(shared_ptr<ServerState> s, const char16_t* text) {
}
}
void send_chat_message(shared_ptr<Client> c, uint32_t from_serial_number,
void send_chat_message(shared_ptr<Client> c, uint32_t from_guild_card_number,
const char16_t* from_name, const char16_t* text) {
u16string data;
if (c->version == GameVersion::BB) {
@@ -484,24 +482,24 @@ void send_chat_message(shared_ptr<Client> c, uint32_t from_serial_number,
data.append(remove_language_marker(from_name));
data.append(u"\x09\x09J");
data.append(text);
send_header_text(c, 0x06, from_serial_number, data.c_str());
send_header_text(c, 0x06, from_guild_card_number, data.c_str());
}
void send_simple_mail_gc(std::shared_ptr<Client> c, uint32_t from_serial_number,
void send_simple_mail_gc(std::shared_ptr<Client> c, uint32_t from_guild_card_number,
const char16_t* from_name, const char16_t* text) {
SC_SimpleMail_GC_81 cmd;
cmd.player_tag = 0x00010000;
cmd.from_serial_number = from_serial_number;
cmd.from_guild_card_number = from_guild_card_number;
cmd.from_name = from_name;
cmd.to_serial_number = c->license->serial_number;
cmd.to_guild_card_number = c->license->serial_number;
cmd.text = text;
send_command(c, 0x81, 0x00, cmd);
}
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_serial_number,
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_guild_card_number,
const char16_t* from_name, const char16_t* text) {
if (c->version == GameVersion::GC) {
send_simple_mail_gc(c, from_serial_number, from_name, text);
send_simple_mail_gc(c, from_guild_card_number, from_name, text);
} else {
throw logic_error("unimplemented versioned command");
}
@@ -549,8 +547,8 @@ void send_card_search_result_t(
shared_ptr<Lobby> result_lobby) {
S_GuildCardSearchResult<CommandHeaderT, CharT> cmd;
cmd.player_tag = 0x00010000;
cmd.searcher_serial_number = c->license->serial_number;
cmd.result_serial_number = result->license->serial_number;
cmd.searcher_guild_card_number = c->license->serial_number;
cmd.result_guild_card_number = result->license->serial_number;
cmd.reconnect_command_header.size = sizeof(cmd.reconnect_command_header) + sizeof(cmd.reconnect_command);
cmd.reconnect_command_header.command = 0x19;
cmd.reconnect_command_header.flag = 0x00;
@@ -608,7 +606,7 @@ void send_guild_card_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.player_tag = 0x00010000;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.serial_number = source->license->serial_number;
cmd.guild_card_number = source->license->serial_number;
cmd.name = source->player.disp.name;
remove_language_marker_inplace(cmd.name);
cmd.desc = source->player.guild_card_desc;
@@ -624,7 +622,7 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.unused = 0x0000;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.serial_number = source->license->serial_number;
cmd.guild_card_number = source->license->serial_number;
cmd.name = remove_language_marker(source->player.disp.name);
cmd.team_name = remove_language_marker(source->player.team_name);
cmd.desc = source->player.guild_card_desc;
@@ -670,7 +668,6 @@ void send_menu_t(
((c->version == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) ||
((c->version == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) ||
((c->version == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) ||
((c->flags & Client::Flag::EPISODE_3) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC_EPISODE_3)) ||
((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION))) {
continue;
}
@@ -1026,7 +1023,7 @@ void send_arrow_update(shared_ptr<Lobby> l) {
}
auto& e = entries.emplace_back();
e.player_tag = 0x00010000;
e.serial_number = l->clients[x]->license->serial_number;
e.guild_card_number = l->clients[x]->license->serial_number;
e.arrow_color = l->clients[x]->lobby_arrow_color;
}
+4 -2
View File
@@ -63,8 +63,10 @@ void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
bool initial_connection, uint32_t server_key, uint32_t client_key);
S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
bool initial_connection,
uint32_t server_key,
uint32_t client_key);
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
bool initial_connection);
void send_update_client_config(std::shared_ptr<Client> c);