implement patch serving
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -18,7 +18,8 @@ enum class ServerBehavior {
|
||||
LOGIN_SERVER,
|
||||
LOBBY_SERVER,
|
||||
DATA_SERVER_BB,
|
||||
PATCH_SERVER,
|
||||
PATCH_SERVER_PC,
|
||||
PATCH_SERVER_BB,
|
||||
PROXY_SERVER,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user