implement patch serving

This commit is contained in:
Martin Michelsen
2022-08-06 00:36:56 -07:00
parent 69d2c6d95c
commit c62f1e9fa0
16 changed files with 201 additions and 55 deletions
+4
View File
@@ -8,6 +8,7 @@
#include "CommandFormats.hh"
#include "FunctionCompiler.hh"
#include "License.hh"
#include "PatchFileIndex.hh"
#include "Player.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
@@ -74,6 +75,9 @@ struct Client {
uint32_t proxy_destination_address;
uint16_t proxy_destination_port;
// Patch server
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
// Lobby/positioning
float x;
float z;
+5 -3
View File
@@ -88,8 +88,8 @@ struct ClientConfigBB {
// Patch server commands
// The patch protocol is nearly identical between PSO PC and PSO BB (the only
// versions on which it is used). Only the client's 04 command differs.
// The patch protocol is identical between PSO PC and PSO BB (the only versions
// on which it is used).
// A patch server session generally goes like this:
// Server: 02 (unencrypted)
@@ -157,12 +157,14 @@ struct S_ServerInit_Patch_02 {
// Client will respond with an 04 command.
// 04 (C->S): Log in (patch)
// The email field is always blank on BB. It may be blank on PC too, so this
// cannot be used to determine the game version used by a patch client.
struct C_Login_Patch_04 {
parray<le_uint32_t, 3> unused;
ptext<char, 0x10> username;
ptext<char, 0x10> password;
ptext<char, 0x40> email; // Note: this field is blank on BB
ptext<char, 0x40> email;
};
// 05 (S->C): Unknown
+2
View File
@@ -16,6 +16,7 @@ PrefixedLogger function_compiler_log ("[FunctionCompiler] ", LogLevel::USE_DEFAU
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
PrefixedLogger license_log ("[LicenseManager] " , LogLevel::USE_DEFAULT);
PrefixedLogger lobby_log ("" , LogLevel::USE_DEFAULT);
PrefixedLogger patch_index_log ("[PatchFileIndex] " , LogLevel::USE_DEFAULT);
PrefixedLogger player_data_log ("" , LogLevel::USE_DEFAULT);
PrefixedLogger proxy_server_log ("[ProxyServer] " , LogLevel::USE_DEFAULT);
PrefixedLogger replay_log ("[ReplaySession] " , LogLevel::USE_DEFAULT);
@@ -51,6 +52,7 @@ void set_log_levels_from_json(shared_ptr<JSONObject> json) {
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
set_log_level_from_json(license_log , json, "LicenseManager");
set_log_level_from_json(lobby_log , json, "Lobbies");
set_log_level_from_json(patch_index_log , json, "PatchFileIndex");
set_log_level_from_json(player_data_log , json, "PlayerData");
set_log_level_from_json(proxy_server_log , json, "ProxyServer");
set_log_level_from_json(replay_log , json, "Replay");
+1
View File
@@ -15,6 +15,7 @@ extern PrefixedLogger function_compiler_log;
extern PrefixedLogger ip_stack_simulator_log;
extern PrefixedLogger license_log;
extern PrefixedLogger lobby_log;
extern PrefixedLogger patch_index_log;
extern PrefixedLogger player_data_log;
extern PrefixedLogger proxy_server_log;
extern PrefixedLogger replay_log;
+13
View File
@@ -496,6 +496,19 @@ int main(int argc, char** argv) {
state->dol_file_index.reset(new DOLFileIndex());
}
if (isdir("system/patch-pc")) {
config_log.info("Indexing PSO PC patch files");
state->pc_patch_file_index.reset(new PatchFileIndex("system/patch-pc"));
} else {
config_log.info("PSO PC patch files not present");
}
if (isdir("system/patch-bb")) {
config_log.info("Indexing PSO BB patch files");
state->bb_patch_file_index.reset(new PatchFileIndex("system/patch-bb"));
} else {
config_log.info("PSO BB patch files not present");
}
config_log.info("Creating menus");
state->create_menus(config_json);
+107 -18
View File
@@ -83,8 +83,10 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
}
break;
case ServerBehavior::PATCH_SERVER_BB:
c->flags |= Client::Flag::BB_PATCH;
case ServerBehavior::PATCH_SERVER_PC:
case ServerBehavior::DATA_SERVER_BB:
case ServerBehavior::PATCH_SERVER:
case ServerBehavior::LOBBY_SERVER:
send_server_init(s, c, false, false);
break;
@@ -2349,13 +2351,40 @@ void process_encryption_ok_patch(shared_ptr<ServerState>, shared_ptr<Client> c,
send_command(c, 0x04, 0x00); // This requests the user's login information
}
static void change_to_directory_patch(
shared_ptr<Client> c,
vector<string>& client_path_directories,
const vector<string>& file_path_directories) {
// First, exit all leaf directories that don't match the desired path
while (!client_path_directories.empty() &&
((client_path_directories.size() > file_path_directories.size()) ||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
send_command(c, 0x0A, 0x00);
client_path_directories.pop_back();
}
// At this point, client_path_directories should be a prefix of
// file_path_directories (or should match exactly)
if (client_path_directories.size() > file_path_directories.size()) {
throw logic_error("did not exit all necessary directories");
}
for (size_t x = 0; x < client_path_directories.size(); x++) {
if (client_path_directories[x] != file_path_directories[x]) {
throw logic_error("intermediate path is not a prefix of final path");
}
}
// Second, enter all necessary leaf directories
while (client_path_directories.size() < file_path_directories.size()) {
const string& dir = file_path_directories[client_path_directories.size()];
send_enter_directory_patch(c, dir);
client_path_directories.emplace_back(dir);
}
}
void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
if (cmd.email.len() == 0) {
c->flags |= Client::Flag::BB_PATCH;
}
check_size_v(data.size(), sizeof(C_Login_Patch_04));
// 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.
@@ -2365,16 +2394,72 @@ void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_message_box(c, message.c_str());
}
// TODO: Implement patch serving for realz.
send_enter_directory_patch(c, ".");
send_enter_directory_patch(c, "data");
send_enter_directory_patch(c, "scene");
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
auto index = (c->flags & Client::Flag::BB_PATCH) ?
s->bb_patch_file_index : s->pc_patch_file_index;
if (index.get()) {
send_command(c, 0x0B, 0x00); // Start patch session; go to root directory
vector<string> path_directories;
for (const auto& file : index->files) {
change_to_directory_patch(c, path_directories, file->path_directories);
S_FileChecksumRequest_Patch_0C req = {
c->patch_file_checksum_requests.size(), file->name};
send_command_t(c, 0x0C, 0x00, req);
c->patch_file_checksum_requests.emplace_back(file);
}
change_to_directory_patch(c, path_directories, {});
send_command(c, 0x0D, 0x00); // End of checksum requests
} else {
// No patch index present: just do something that will satisfy the client
// without actually checking or downloading any files
send_enter_directory_patch(c, ".");
send_enter_directory_patch(c, "data");
send_enter_directory_patch(c, "scene");
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
send_command(c, 0x0A, 0x00);
send_command(c, 0x12, 0x00);
}
}
void process_file_checksum_result_patch(shared_ptr<ServerState>,
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) { // 0F
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
req.crc32 = cmd.checksum;
req.size = cmd.size;
req.response_received = true;
}
void process_file_checksum_results_done_patch(shared_ptr<ServerState>,
shared_ptr<Client> c, uint16_t, uint32_t, const string&) { // 10
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
for (const auto& req : c->patch_file_checksum_requests) {
if (!req.response_received) {
throw runtime_error("client did not respond to checksum request");
}
if (req.needs_update()) {
start_cmd.total_bytes += req.file->size;
start_cmd.num_files++;
}
}
if (start_cmd.num_files) {
send_command_t(c, 0x11, 0x00, start_cmd);
vector<string> path_directories;
for (const auto& req : c->patch_file_checksum_requests) {
if (req.needs_update()) {
change_to_directory_patch(c, path_directories, req.file->path_directories);
send_patch_file(c, req.file);
}
}
change_to_directory_patch(c, path_directories, {});
}
// This command terminates the patch connection successfully. PSOBB complains
// if we don't check the above directories before sending this though
send_command(c, 0x12, 0x00);
}
@@ -2832,11 +2917,15 @@ static process_command_t patch_handlers[0x100] = {
// 00
nullptr, nullptr, process_encryption_ok_patch, nullptr,
process_login_patch, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, process_file_checksum_result_patch,
// 10
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
process_file_checksum_results_done_patch, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
// 20
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+22
View File
@@ -445,6 +445,28 @@ void send_enter_directory_patch(shared_ptr<Client> c, const string& dir) {
send_command_t(c, 0x09, 0x00, cmd);
}
void send_patch_file(shared_ptr<Client> c, shared_ptr<const PatchFileIndex::File> f) {
S_OpenFile_Patch_06 open_cmd = {0, f->size, f->name};
send_command_t(c, 0x06, 0x00, open_cmd);
for (size_t x = 0; x < f->chunks.size(); x++) {
const auto& chunk = f->chunks[x];
// TODO: The use of StringWriter here is... unfortunate. Write a version of
// Channel::send that takes iovecs or something to avoid these dumb massive
// string copies.
StringWriter w;
S_WriteFileHeader_Patch_07 write_cmd_header = {
x, chunk.crc32, chunk.data.size()};
w.put(write_cmd_header);
w.write(chunk.data);
send_command(c, 0x07, 0x00, w.str());
}
S_CloseCurrentFile_Patch_08 close_cmd = {0};
send_command_t(c, 0x08, 0x00, close_cmd);
}
////////////////////////////////////////////////////////////////////////////////
+1
View File
@@ -129,6 +129,7 @@ void send_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_bb(std::shared_ptr<Client> c);
void send_enter_directory_patch(std::shared_ptr<Client> c, const std::string& dir);
void send_patch_file(std::shared_ptr<Client> c, std::shared_ptr<const PatchFileIndex::File> f);
void send_message_box(std::shared_ptr<Client> c, const std::u16string& text);
void send_lobby_name(std::shared_ptr<Client> c, const std::u16string& text);
+2
View File
@@ -51,6 +51,8 @@ struct ServerState {
RunShellBehavior run_shell_behavior;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Ep3DataIndex> ep3_data_index;
std::shared_ptr<const QuestIndex> quest_index;
+9 -5
View File
@@ -113,8 +113,10 @@ const char* name_for_server_behavior(ServerBehavior behavior) {
return "lobby_server";
case ServerBehavior::DATA_SERVER_BB:
return "data_server_bb";
case ServerBehavior::PATCH_SERVER:
return "patch_server";
case ServerBehavior::PATCH_SERVER_PC:
return "patch_server_pc";
case ServerBehavior::PATCH_SERVER_BB:
return "patch_server_bb";
case ServerBehavior::PROXY_SERVER:
return "proxy_server";
default:
@@ -131,11 +133,13 @@ ServerBehavior server_behavior_for_name(const char* name) {
return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "data_server_bb") || !strcasecmp(name, "data_server") || !strcasecmp(name, "data")) {
return ServerBehavior::DATA_SERVER_BB;
} else if (!strcasecmp(name, "patch_server") || !strcasecmp(name, "patch")) {
return ServerBehavior::PATCH_SERVER;
} else if (!strcasecmp(name, "patch_server_pc") || !strcasecmp(name, "patch_pc")) {
return ServerBehavior::PATCH_SERVER_PC;
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
return ServerBehavior::PATCH_SERVER_BB;
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
return ServerBehavior::PROXY_SERVER;
} else {
throw invalid_argument("incorrect server behavior name");
}
}
}
+2 -1
View File
@@ -18,7 +18,8 @@ enum class ServerBehavior {
LOGIN_SERVER,
LOBBY_SERVER,
DATA_SERVER_BB,
PATCH_SERVER,
PATCH_SERVER_PC,
PATCH_SERVER_BB,
PROXY_SERVER,
};