add Ep3 USA patch function
This commit is contained in:
@@ -64,6 +64,7 @@ Client::Client(
|
||||
can_chat(true),
|
||||
pending_bb_save_player_index(0),
|
||||
proxy_block_events(false),
|
||||
proxy_block_function_calls(false),
|
||||
proxy_save_files(false),
|
||||
proxy_suppress_remote_login(false),
|
||||
proxy_zero_remote_guild_card(false),
|
||||
|
||||
+19
-15
@@ -33,40 +33,43 @@ struct Client {
|
||||
// Note that this flag is NOT set for Episode 3 Trial Edition clients, since
|
||||
// that version is similar enough to the release version of Episode 3 that
|
||||
// newserv does not have to change its behavior at all.
|
||||
IS_TRIAL_EDITION = 0x2000,
|
||||
IS_TRIAL_EDITION = 0x2000,
|
||||
// Client is DC v1
|
||||
IS_DC_V1 = 0x0010,
|
||||
IS_DC_V1 = 0x0010,
|
||||
// For patch server clients, client is Blue Burst rather than PC
|
||||
IS_BB_PATCH = 0x0001,
|
||||
IS_BB_PATCH = 0x0001,
|
||||
// After joining a lobby, client will no longer send D6 commands when they
|
||||
// close message boxes
|
||||
NO_D6_AFTER_LOBBY = 0x0002,
|
||||
NO_D6_AFTER_LOBBY = 0x0002,
|
||||
// Client has the above flag and has already joined a lobby, or is not GC
|
||||
NO_D6 = 0x0004,
|
||||
NO_D6 = 0x0004,
|
||||
// Client is Episode 3, should be able to see CARD lobbies, and should only
|
||||
// be able to see/join games with the EPISODE_3_ONLY flag
|
||||
IS_EPISODE_3 = 0x0008,
|
||||
IS_EPISODE_3 = 0x0008,
|
||||
// Client disconnects if it receives B2 (send_function_call)
|
||||
NO_SEND_FUNCTION_CALL = 0x0200,
|
||||
NO_SEND_FUNCTION_CALL = 0x0200,
|
||||
// Client requires doubly-encrypted code section in send_function_call
|
||||
ENCRYPTED_SEND_FUNCTION_CALL = 0x0800,
|
||||
ENCRYPTED_SEND_FUNCTION_CALL = 0x0800,
|
||||
// Client supports send_function_call but does not actually run the code
|
||||
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x1000,
|
||||
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x1000,
|
||||
// Client is vulnerable to a buffer overflow that we can use to enable
|
||||
// send_function_call
|
||||
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x8000,
|
||||
|
||||
// Client is loading into a game
|
||||
LOADING = 0x0020,
|
||||
LOADING = 0x0020,
|
||||
// Client is loading a quest
|
||||
LOADING_QUEST = 0x0040,
|
||||
LOADING_QUEST = 0x0040,
|
||||
// Client is in the information menu (login server only)
|
||||
IN_INFORMATION_MENU = 0x0080,
|
||||
IN_INFORMATION_MENU = 0x0080,
|
||||
// Client is at the welcome message (login server only)
|
||||
AT_WELCOME_MESSAGE = 0x0100,
|
||||
AT_WELCOME_MESSAGE = 0x0100,
|
||||
// Client has already received a 97 (enable saves) command, so don't show
|
||||
// the programs menu anymore
|
||||
SAVE_ENABLED = 0x0400,
|
||||
SAVE_ENABLED = 0x0400,
|
||||
// Client has received newserv's Episode 3 card definitions, so don't send
|
||||
// them again
|
||||
HAS_EP3_CARD_DEFS = 0x4000,
|
||||
HAS_EP3_CARD_DEFS = 0x4000,
|
||||
};
|
||||
|
||||
uint64_t id;
|
||||
@@ -121,6 +124,7 @@ struct Client {
|
||||
uint8_t pending_bb_save_player_index;
|
||||
|
||||
bool proxy_block_events;
|
||||
bool proxy_block_function_calls;
|
||||
bool proxy_save_files;
|
||||
bool proxy_suppress_remote_login;
|
||||
bool proxy_zero_remote_guild_card;
|
||||
|
||||
@@ -1513,7 +1513,8 @@ struct C_Register_BB_9C {
|
||||
struct C_Login_DC_PC_GC_9D {
|
||||
le_uint32_t player_tag; // 0x00010000 if guild card is set (via 04)
|
||||
le_uint32_t guild_card_number; // 0xFFFFFFFF if not set
|
||||
le_uint64_t unused;
|
||||
le_uint32_t unused1;
|
||||
le_uint32_t unused2;
|
||||
le_uint32_t sub_version;
|
||||
uint8_t is_extended; // If 1, structure has extended format
|
||||
uint8_t language; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES
|
||||
|
||||
@@ -141,6 +141,12 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
||||
s->item_tracking_enabled = true;
|
||||
}
|
||||
|
||||
try {
|
||||
s->episode_3_send_function_call_enabled = d.at("EnableEpisode3SendFunctionCall")->as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
s->episode_3_send_function_call_enabled = false;
|
||||
}
|
||||
|
||||
shared_ptr<JSONObject> log_levels_json;
|
||||
try {
|
||||
log_levels_json = d.at("LogLevels");
|
||||
|
||||
+4
-3
@@ -59,9 +59,10 @@ namespace ProxyOptionsMenuItemID {
|
||||
constexpr uint32_t INFINITE_TP = 0xAA2222AA;
|
||||
constexpr uint32_t SWITCH_ASSIST = 0xAA3333AA;
|
||||
constexpr uint32_t BLOCK_EVENTS = 0xAA4444AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA5555AA;
|
||||
constexpr uint32_t SUPPRESS_LOGIN = 0xAA6666AA;
|
||||
constexpr uint32_t SKIP_CARD = 0xAA7777AA;
|
||||
constexpr uint32_t BLOCK_PATCHES = 0xAA5555AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA6666AA;
|
||||
constexpr uint32_t SUPPRESS_LOGIN = 0xAA7777AA;
|
||||
constexpr uint32_t SKIP_CARD = 0xAA8888AA;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+55
-45
@@ -172,7 +172,8 @@ static HandlerResult S_G_9A(shared_ptr<ServerState>,
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = session.remote_guild_card_number;
|
||||
}
|
||||
cmd.unused = 0;
|
||||
cmd.unused1 = 0;
|
||||
cmd.unused2 = 0;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.is_extended = (session.remote_guild_card_number < 0) ? 0 : 1;
|
||||
cmd.language = session.language;
|
||||
@@ -322,7 +323,8 @@ static HandlerResult S_V123P_02_17(
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = session.remote_guild_card_number;
|
||||
}
|
||||
cmd.unused = 0;
|
||||
cmd.unused1 = 0;
|
||||
cmd.unused2 = 0;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.is_extended = 0;
|
||||
cmd.language = session.language;
|
||||
@@ -371,7 +373,8 @@ static HandlerResult S_V123P_02_17(
|
||||
C_LoginExtended_GC_9E cmd;
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = guild_card_number;
|
||||
cmd.unused = 0;
|
||||
cmd.unused1 = 0;
|
||||
cmd.unused2 = 0;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.is_extended = 0;
|
||||
cmd.language = session.language;
|
||||
@@ -936,14 +939,14 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
|
||||
template <typename T>
|
||||
static HandlerResult S_44_A6(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
|
||||
if (session.save_files) {
|
||||
const auto& cmd = check_size_t<S_OpenFile_PC_V3_44_A6>(data);
|
||||
bool is_download_quest = (command == 0xA6);
|
||||
const auto& cmd = check_size_t<S_OpenFile_PC_V3_44_A6>(data);
|
||||
|
||||
string filename = cmd.filename;
|
||||
string output_filename = string_printf("%s.%s.%" PRIu64,
|
||||
string filename = cmd.filename;
|
||||
string output_filename;
|
||||
if (session.save_files) {
|
||||
output_filename = string_printf("%s.%s.%" PRIu64,
|
||||
filename.c_str(),
|
||||
is_download_quest ? "download" : "online", now());
|
||||
(command == 0xA6) ? "download" : "online", now());
|
||||
for (size_t x = 0; x < output_filename.size(); x++) {
|
||||
if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') {
|
||||
output_filename[x] = '_';
|
||||
@@ -952,12 +955,17 @@ static HandlerResult S_44_A6(shared_ptr<ServerState>,
|
||||
if (output_filename[0] == '.') {
|
||||
output_filename[0] = '_';
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::SavingFile sf(
|
||||
cmd.filename, output_filename, cmd.file_size);
|
||||
session.saving_files.emplace(cmd.filename, move(sf));
|
||||
session.log.info("Opened file %s", output_filename.c_str());
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::SavingFile sf(
|
||||
cmd.filename, output_filename, cmd.file_size);
|
||||
session.saving_files.emplace(cmd.filename, move(sf));
|
||||
if (session.save_files) {
|
||||
session.log.info("Opened file %s", output_filename.c_str());
|
||||
} else {
|
||||
session.log.info("Tracking file %s", filename.c_str());
|
||||
}
|
||||
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
@@ -967,39 +975,41 @@ constexpr on_command_t S_B_44_A6 = &S_44_A6<S_OpenFile_BB_44_A6>;
|
||||
|
||||
static HandlerResult S_13_A7(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
|
||||
if (session.save_files) {
|
||||
const auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
|
||||
auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
|
||||
bool modified = false;
|
||||
|
||||
ProxyServer::LinkedSession::SavingFile* sf = nullptr;
|
||||
try {
|
||||
sf = &session.saving_files.at(cmd.filename);
|
||||
} catch (const out_of_range&) {
|
||||
string filename = cmd.filename;
|
||||
session.log.warning("Received data for non-open file %s", filename.c_str());
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
size_t bytes_to_write = cmd.data_size;
|
||||
if (bytes_to_write > 0x400) {
|
||||
session.log.warning("Chunk data size is invalid; truncating to 0x400");
|
||||
bytes_to_write = 0x400;
|
||||
}
|
||||
|
||||
session.log.info("Writing %zu bytes to %s", bytes_to_write, sf->output_filename.c_str());
|
||||
fwritex(sf->f.get(), cmd.data, bytes_to_write);
|
||||
if (bytes_to_write > sf->remaining_bytes) {
|
||||
session.log.warning("Chunk size extends beyond original file size; file may be truncated");
|
||||
sf->remaining_bytes = 0;
|
||||
} else {
|
||||
sf->remaining_bytes -= bytes_to_write;
|
||||
}
|
||||
|
||||
if (sf->remaining_bytes == 0) {
|
||||
session.log.info("File %s is complete", sf->output_filename.c_str());
|
||||
session.saving_files.erase(cmd.filename);
|
||||
}
|
||||
ProxyServer::LinkedSession::SavingFile* sf = nullptr;
|
||||
try {
|
||||
sf = &session.saving_files.at(cmd.filename);
|
||||
} catch (const out_of_range&) {
|
||||
string filename = cmd.filename;
|
||||
session.log.warning("Received data for non-open file %s", filename.c_str());
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
return HandlerResult::Type::FORWARD;
|
||||
|
||||
if (cmd.data_size > sf->remaining_bytes) {
|
||||
session.log.warning("Chunk size extends beyond original file size; truncating file");
|
||||
cmd.data_size = sf->remaining_bytes;
|
||||
modified = true;
|
||||
} else if (cmd.data_size > 0x400) {
|
||||
session.log.warning("Chunk data size is invalid; truncating to 0x400");
|
||||
cmd.data_size = 0x400;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (sf->f.get()) {
|
||||
session.log.info("Writing %" PRIu32 " bytes to %s",
|
||||
cmd.data_size.load(), sf->output_filename.c_str());
|
||||
fwritex(sf->f.get(), cmd.data, cmd.data_size);
|
||||
}
|
||||
sf->remaining_bytes -= cmd.data_size;
|
||||
|
||||
if (sf->remaining_bytes == 0) {
|
||||
session.log.info("Closing file %s", sf->output_filename.c_str());
|
||||
session.saving_files.erase(cmd.filename);
|
||||
}
|
||||
|
||||
return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
static HandlerResult S_G_B7(shared_ptr<ServerState>,
|
||||
|
||||
+5
-2
@@ -626,8 +626,11 @@ ProxyServer::LinkedSession::SavingFile::SavingFile(
|
||||
uint32_t remaining_bytes)
|
||||
: basename(basename),
|
||||
output_filename(output_filename),
|
||||
remaining_bytes(remaining_bytes),
|
||||
f(fopen_unique(this->output_filename, "wb")) { }
|
||||
remaining_bytes(remaining_bytes) {
|
||||
if (!this->output_filename.empty()) {
|
||||
this->f = fopen_unique(this->output_filename, "wb");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -659,9 +659,9 @@ string Quest::decode_gci(
|
||||
} else if (header.game_id[2] == 'S') { // Episode 3
|
||||
// The first 0x10 bytes in the data segment appear to be unused. In most
|
||||
// files I've seen, the last half of it (8 bytes) are duplicates of the
|
||||
// first 8 bytes of the unscrambled, compressed data, though this is likely
|
||||
// the result of an uninitialized memory bug when the client encodes the
|
||||
// file and not an actual constraint on what should be in these 8 bytes.
|
||||
// first 8 bytes of the unscrambled, compressed data, though this is the
|
||||
// result of an uninitialized memory bug when the client encodes the file
|
||||
// and not an actual constraint on what should be in these 8 bytes.
|
||||
r.skip(16);
|
||||
// The game treats this field as a 16-byte string (including the \0). The 8
|
||||
// bytes after it appear to be completely unused.
|
||||
|
||||
+36
-3
@@ -67,7 +67,8 @@ static const unordered_map<uint32_t, const char16_t*> proxy_options_menu_descrip
|
||||
{ProxyOptionsMenuItemID::INFINITE_HP, u"If enabled, the proxy\nwill restore your HP\nwhen you are hit by\nan enemy or trap,\nbut cannot revive\nyou from one-hit\nkills"},
|
||||
{ProxyOptionsMenuItemID::INFINITE_TP, u"If enabled, the proxy\nwill restore your TP\nwhen you cast any\ntechnique"},
|
||||
{ProxyOptionsMenuItemID::SWITCH_ASSIST, u"If enabled, the proxy\nwill attempt to\nunlock 2-player\ndoors when you step\non both switches\nsequentially"},
|
||||
{ProxyOptionsMenuItemID::BLOCK_EVENTS, u"If enabled, season\nevents in the lobby\nand in games are\ndisabled."},
|
||||
{ProxyOptionsMenuItemID::BLOCK_EVENTS, u"If enabled, seasonal\nevents in the lobby\nand in games are\ndisabled."},
|
||||
{ProxyOptionsMenuItemID::BLOCK_PATCHES, u"If enabled, patches\nsent by the remote\nserver are blocked."},
|
||||
{ProxyOptionsMenuItemID::SAVE_FILES, u"If enabled, the proxy\nwill save local\ncopies of files from\nthe remote server\n(quests, etc.)"},
|
||||
{ProxyOptionsMenuItemID::SUPPRESS_LOGIN, u"If enabled, the proxy\nwill use an alternate\nlogin sequence"},
|
||||
{ProxyOptionsMenuItemID::SKIP_CARD, u"If enabled, the proxy\nwill use an alternate\nvalue for your initial\nGuild Card"},
|
||||
@@ -91,6 +92,8 @@ static vector<MenuItem> proxy_options_menu_for_client(
|
||||
}
|
||||
ret.emplace_back(ProxyOptionsMenuItemID::BLOCK_EVENTS,
|
||||
c->proxy_block_events ? u"Block events ON" : u"Block events OFF", u"", 0);
|
||||
ret.emplace_back(ProxyOptionsMenuItemID::BLOCK_PATCHES,
|
||||
c->proxy_block_function_calls ? u"Block patches ON" : u"Block patches OFF", u"", 0);
|
||||
ret.emplace_back(ProxyOptionsMenuItemID::SAVE_FILES,
|
||||
c->proxy_save_files ? u"Save files ON" : u"Save files OFF", u"", 0);
|
||||
ret.emplace_back(ProxyOptionsMenuItemID::SUPPRESS_LOGIN,
|
||||
@@ -127,6 +130,9 @@ static void send_client_to_proxy_server(shared_ptr<ServerState> s, shared_ptr<Cl
|
||||
if (c->proxy_block_events) {
|
||||
session->override_lobby_event = 0;
|
||||
}
|
||||
if (c->proxy_block_function_calls) {
|
||||
session->function_call_return_value = 0xFFFFFFFF;
|
||||
}
|
||||
if (c->proxy_zero_remote_guild_card) {
|
||||
session->remote_guild_card_number = 0;
|
||||
} else {
|
||||
@@ -152,6 +158,20 @@ static void send_proxy_destinations_menu(shared_ptr<ServerState> s, shared_ptr<C
|
||||
} catch (const out_of_range&) { }
|
||||
}
|
||||
|
||||
static bool send_enable_send_function_call_if_applicable(
|
||||
shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
if (c->flags & Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL) {
|
||||
if (s->episode_3_send_function_call_enabled) {
|
||||
send_quest_buffer_overflow(s, c);
|
||||
} else {
|
||||
c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL;
|
||||
}
|
||||
c->flags &= ~Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -210,6 +230,9 @@ void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
(c->flags & Client::Flag::NO_D6) ||
|
||||
!(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) {
|
||||
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
|
||||
if (send_enable_send_function_call_if_applicable(s, c)) {
|
||||
send_update_client_config(c);
|
||||
}
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
} else {
|
||||
send_message_box(c, s->welcome_message.c_str());
|
||||
@@ -637,6 +660,13 @@ static void on_login_d_e_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client>
|
||||
}
|
||||
|
||||
set_console_client_flags(c, base_cmd->sub_version);
|
||||
// See system/ppc/Episode3USAQuestBufferOverflow.s for where this value gets
|
||||
// set. We use this to determine if the client has already run the code or
|
||||
// not; sending it again when the client has already run it will likely cause
|
||||
// the client to crash.
|
||||
if (base_cmd->unused1 == 0x5F5CA297) {
|
||||
c->flags &= ~(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL | Client::Flag::NO_SEND_FUNCTION_CALL);
|
||||
}
|
||||
|
||||
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
@@ -687,7 +717,6 @@ static void on_login_d_e_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client>
|
||||
}
|
||||
|
||||
send_update_client_config(c);
|
||||
|
||||
on_login_complete(s, c);
|
||||
}
|
||||
|
||||
@@ -985,9 +1014,10 @@ static void on_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client>
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version()));
|
||||
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
send_enable_send_function_call_if_applicable(s, c);
|
||||
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
|
||||
send_update_client_config(c);
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1301,6 +1331,9 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
case ProxyOptionsMenuItemID::BLOCK_EVENTS:
|
||||
c->proxy_block_events = !c->proxy_block_events;
|
||||
goto resend_proxy_options_menu;
|
||||
case ProxyOptionsMenuItemID::BLOCK_PATCHES:
|
||||
c->proxy_block_function_calls = !c->proxy_block_function_calls;
|
||||
goto resend_proxy_options_menu;
|
||||
case ProxyOptionsMenuItemID::SAVE_FILES:
|
||||
c->proxy_save_files = !c->proxy_save_files;
|
||||
goto resend_proxy_options_menu;
|
||||
|
||||
+68
-39
@@ -243,6 +243,74 @@ void send_update_client_config(shared_ptr<Client> c) {
|
||||
|
||||
|
||||
|
||||
template <typename CommandT>
|
||||
void send_quest_open_file_t(
|
||||
shared_ptr<Client> c,
|
||||
const string& quest_name,
|
||||
const string& filename,
|
||||
uint32_t file_size,
|
||||
QuestFileType type) {
|
||||
CommandT cmd;
|
||||
uint8_t command_num;
|
||||
switch (type) {
|
||||
case QuestFileType::ONLINE:
|
||||
command_num = 0x44;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 2;
|
||||
break;
|
||||
case QuestFileType::GBA_DEMO:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "GBA Demo";
|
||||
cmd.flags = 2;
|
||||
break;
|
||||
case QuestFileType::DOWNLOAD:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 0;
|
||||
break;
|
||||
case QuestFileType::EPISODE_3:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 3;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid quest file type");
|
||||
}
|
||||
cmd.unused.clear(0);
|
||||
cmd.file_size = file_size;
|
||||
cmd.filename = filename.c_str();
|
||||
send_command_t(c, command_num, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_quest_buffer_overflow(
|
||||
shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
// TODO: Figure out a way to share this state across sessions. Maybe we could
|
||||
// e.g. modify send_1D to send a nonzero flag value, which we could use to
|
||||
// know that the client already has this patch? Or just add another command in
|
||||
// the login sequence?
|
||||
|
||||
// PSO Episode 3 USA doesn't natively support the B2 command, but we can add
|
||||
// it back to the game with some tricky commands. For details on how this
|
||||
// works, see system/ppc/Episode3USAQuestBufferOverflow.s.
|
||||
auto fn = s->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow");
|
||||
if (fn->code.size() > 0x400) {
|
||||
throw runtime_error("Episode 3 buffer overflow code must be a single segment");
|
||||
}
|
||||
|
||||
static const string filename = "m999999p_e.bin";
|
||||
send_quest_open_file_t<S_OpenFile_PC_V3_44_A6>(
|
||||
c, "BufferOverflow", filename, 0x18, QuestFileType::EPISODE_3);
|
||||
|
||||
S_WriteFile_13_A7 cmd;
|
||||
cmd.filename = filename;
|
||||
memcpy(cmd.data, fn->code.data(), fn->code.size());
|
||||
if (fn->code.size() < 0x400) {
|
||||
memset(&cmd.data[fn->code.size()], 0, 0x400 - fn->code.size());
|
||||
}
|
||||
cmd.data_size = fn->code.size();
|
||||
send_command_t(c, 0xA7, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_function_call(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<CompiledFunctionCode> code,
|
||||
@@ -1727,45 +1795,6 @@ void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key) {
|
||||
|
||||
|
||||
|
||||
template <typename CommandT>
|
||||
void send_quest_open_file_t(
|
||||
shared_ptr<Client> c,
|
||||
const string& quest_name,
|
||||
const string& filename,
|
||||
uint32_t file_size,
|
||||
QuestFileType type) {
|
||||
CommandT cmd;
|
||||
uint8_t command_num;
|
||||
switch (type) {
|
||||
case QuestFileType::ONLINE:
|
||||
command_num = 0x44;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 2;
|
||||
break;
|
||||
case QuestFileType::GBA_DEMO:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "GBA Demo";
|
||||
cmd.flags = 2;
|
||||
break;
|
||||
case QuestFileType::DOWNLOAD:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 0;
|
||||
break;
|
||||
case QuestFileType::EPISODE_3:
|
||||
command_num = 0xA6;
|
||||
cmd.name = "PSO/" + quest_name;
|
||||
cmd.flags = 3;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid quest file type");
|
||||
}
|
||||
cmd.unused.clear(0);
|
||||
cmd.file_size = file_size;
|
||||
cmd.filename = filename.c_str();
|
||||
send_command_t(c, command_num, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_quest_file_chunk(
|
||||
shared_ptr<Client> c,
|
||||
const string& filename,
|
||||
|
||||
@@ -122,6 +122,8 @@ void send_server_init(
|
||||
uint8_t flags);
|
||||
void send_update_client_config(std::shared_ptr<Client> c);
|
||||
|
||||
void send_quest_buffer_overflow(
|
||||
std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void send_function_call(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<CompiledFunctionCode> code,
|
||||
|
||||
@@ -22,6 +22,7 @@ ServerState::ServerState()
|
||||
allow_unregistered_users(false),
|
||||
allow_saving(true),
|
||||
item_tracking_enabled(true),
|
||||
episode_3_send_function_call_enabled(false),
|
||||
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
|
||||
pre_lobby_event(0),
|
||||
ep3_menu_song(-1) {
|
||||
|
||||
@@ -49,6 +49,7 @@ struct ServerState {
|
||||
bool allow_unregistered_users;
|
||||
bool allow_saving;
|
||||
bool item_tracking_enabled;
|
||||
bool episode_3_send_function_call_enabled;
|
||||
RunShellBehavior run_shell_behavior;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
|
||||
@@ -70,6 +70,9 @@ uint16_t flags_for_version(GameVersion version, int64_t sub_version) {
|
||||
Client::Flag::IS_EPISODE_3 |
|
||||
Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL;
|
||||
case 0x41: // GC Ep3 US
|
||||
return Client::Flag::NO_D6_AFTER_LOBBY |
|
||||
Client::Flag::IS_EPISODE_3 |
|
||||
Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL;
|
||||
case 0x43: // GC Ep3 EU
|
||||
return Client::Flag::NO_D6_AFTER_LOBBY |
|
||||
Client::Flag::IS_EPISODE_3 |
|
||||
|
||||
Reference in New Issue
Block a user