get PSO PC login sequence working
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user