implement version-specific patches; clean up menu abstraction

This commit is contained in:
Martin Michelsen
2023-05-26 09:55:12 -07:00
parent 03c26b587a
commit dbd6c59a0b
25 changed files with 548 additions and 394 deletions
+62 -14
View File
@@ -259,26 +259,74 @@ static void proxy_command_auction(shared_ptr<ServerState>,
static void server_command_patch(shared_ptr<ServerState> s, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
std::shared_ptr<CompiledFunctionCode> fn;
try {
fn = s->function_code_index->name_to_function.at(encode_sjis(args));
send_function_call(c, fn);
} catch (const out_of_range&) {
send_text_message(c, u"Invalid patch name");
string basename = encode_sjis(args);
auto send_call = [s, basename, wc = weak_ptr<Client>(c)]() {
auto c = wc.lock();
if (!c) {
return;
}
try {
auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at(
string_printf("%s-%08" PRIX32, basename.c_str(), c->specific_version));
send_function_call(c, fn);
} catch (const out_of_range&) {
send_text_message(c, u"Invalid patch name");
}
};
send_cache_patch_if_needed(s, c);
if (c->version() == GameVersion::GC &&
c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) {
send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect"));
c->on_version_detect_response = send_call;
} else {
send_call();
}
}
static void empty_patch_return_handler(uint32_t, uint32_t) {}
static void proxy_command_patch(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, const std::u16string& args) {
std::shared_ptr<CompiledFunctionCode> fn;
try {
fn = s->function_code_index->name_to_function.at(encode_sjis(args));
string basename = encode_sjis(args);
auto send_call = [s, basename, &session](uint32_t specific_version, uint32_t) {
try {
if (session.newserv_client_config.cfg.specific_version != specific_version) {
session.newserv_client_config.cfg.specific_version = specific_version;
session.log.info("Version detected as %08" PRIX32, session.newserv_client_config.cfg.specific_version);
}
auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at(
string_printf("%s-%08" PRIX32, basename.c_str(), session.newserv_client_config.cfg.specific_version));
send_function_call(
session.client_channel, session.newserv_client_config.cfg.flags, fn);
// Don't forward the patch response to the server
session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler);
} catch (const out_of_range&) {
send_text_message(session.client_channel, u"Invalid patch name");
}
};
// This mirrors the implementation in send_cache_patch_if_needed
if (!(session.newserv_client_config.cfg.flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) {
send_function_call(
session.client_channel, session.newserv_client_config.cfg.flags, fn);
// Don't forward the patch response to the server
session.should_forward_function_call_return_queue.emplace_back(false);
} catch (const out_of_range&) {
send_text_message(session.client_channel, u"Invalid patch name");
session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2634EC);
send_function_call(
session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase2"));
session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler);
session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler);
session.newserv_client_config.cfg.flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
}
if (session.version == GameVersion::GC &&
session.newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) {
send_function_call(
session.client_channel,
session.newserv_client_config.cfg.flags,
s->function_code_index->name_to_function.at("VersionDetect"));
session.function_call_return_handler_queue.emplace_back(send_call);
} else {
send_call(session.newserv_client_config.cfg.specific_version, 0);
}
}
+9 -1
View File
@@ -33,6 +33,7 @@ ClientOptions::ClientOptions()
enable_chat_commands(true),
enable_chat_filter(true),
enable_player_notifications(false),
suppress_client_pings(false),
suppress_remote_login(false),
zero_remote_guild_card(false),
ep3_infinite_meseta(false),
@@ -45,9 +46,10 @@ Client::Client(
GameVersion version,
ServerBehavior server_behavior)
: id(next_id++),
log("", client_log.min_level),
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
bb_game_state(0),
flags(flags_for_version(version, -1)),
specific_version(default_specific_version_for_version(version, -1)),
channel(bev, version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
server_behavior(server_behavior),
should_disconnect(false),
@@ -81,6 +83,8 @@ Client::Client(
struct timeval tv = usecs_to_timeval(60000000); // 1 minute
event_add(this->save_game_data_event.get(), &tv);
}
this->log.info("Created");
}
Client::~Client() {
@@ -90,6 +94,8 @@ Client::~Client() {
this->log.warning(" %s", it.first.c_str());
}
}
this->log.info("Deleted");
}
void Client::set_license(shared_ptr<const License> l) {
@@ -104,6 +110,7 @@ ClientConfig Client::export_config() const {
ClientConfig cc;
cc.magic = CLIENT_CONFIG_MAGIC;
cc.flags = this->flags;
cc.specific_version = this->specific_version;
cc.proxy_destination_address = this->proxy_destination_address;
cc.proxy_destination_port = this->proxy_destination_port;
cc.unused.clear(0xFF);
@@ -124,6 +131,7 @@ void Client::import_config(const ClientConfig& cc) {
throw invalid_argument("invalid client config");
}
this->flags = cc.flags;
this->specific_version = cc.specific_version;
this->proxy_destination_address = cc.proxy_destination_address;
this->proxy_destination_port = cc.proxy_destination_port;
}
+7 -3
View File
@@ -35,6 +35,7 @@ struct ClientOptions {
bool enable_chat_commands;
bool enable_chat_filter;
bool enable_player_notifications;
bool suppress_client_pings;
bool suppress_remote_login;
bool zero_remote_guild_card;
bool ep3_infinite_meseta;
@@ -75,9 +76,9 @@ struct Client {
ENCRYPTED_SEND_FUNCTION_CALL = 0x00000800,
// Client supports send_function_call but does not actually run the code
SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x00001000,
// Client supports send_function_call but doesn't clear its caches properly
// before calling the function (so we need to patch it)
SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH = 0x00020000,
// Client supports send_function_call and clears its caches properly before
// calling the function (so we don't need to patch it)
SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x00020000,
// Client is vulnerable to a buffer overflow that we can use to enable
// send_function_call
USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x00008000,
@@ -111,6 +112,7 @@ struct Client {
// all of that space.
uint8_t bb_game_state;
uint32_t flags;
uint32_t specific_version;
// Network
Channel channel;
@@ -141,6 +143,7 @@ struct Client {
uint16_t card_battle_table_seat_number;
uint16_t card_battle_table_seat_state;
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<const Menu> last_menu_sent;
// Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give
@@ -148,6 +151,7 @@ struct Client {
bool can_chat;
std::string pending_bb_save_username;
uint8_t pending_bb_save_player_index;
std::function<void()> on_version_detect_response;
// File loading state
uint32_t dol_base_addr;
+2 -1
View File
@@ -108,9 +108,10 @@ enum ClientStateBB : uint8_t {
struct ClientConfig {
uint64_t magic;
uint32_t flags;
uint32_t specific_version;
uint32_t proxy_destination_address;
uint16_t proxy_destination_port;
parray<uint8_t, 0x0E> unused;
parray<uint8_t, 0x0A> unused;
} __packed__;
struct ClientConfigBB {
+47 -19
View File
@@ -176,6 +176,20 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
bool is_patch = ends_with(filename, ".patch.s");
string name = filename.substr(0, filename.size() - (is_patch ? 8 : 2));
// Check for specific_version token
uint32_t specific_version = 0;
string patch_name = name;
if (is_patch &&
(filename.size() >= 13) &&
(filename[filename.size() - 13] == '.') &&
isdigit(filename[filename.size() - 12]) &&
(filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') &&
(filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') &&
isdigit(filename[filename.size() - 9])) {
specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9];
patch_name = filename.substr(0, filename.size() - 13);
}
try {
string path = directory + "/" + filename;
string text = load_file(path);
@@ -187,15 +201,19 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
"duplicate function index: %08" PRIX32, code->index));
}
}
code->specific_version = specific_version;
code->patch_name = patch_name;
this->name_to_function.emplace(name, code);
if (is_patch) {
code->menu_item_id = next_menu_item_id++;
this->menu_item_id_to_patch_function.emplace(code->menu_item_id, code);
this->name_to_patch_function.emplace(name, code);
this->menu_item_id_and_specific_version_to_patch_function.emplace(
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
this->name_and_specific_version_to_patch_function.emplace(
string_printf("%s-%08" PRIX32, patch_name.c_str(), specific_version), code);
}
string index_prefix = code->index ? string_printf("%02X => ", code->index) : "";
string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "] ", code->menu_item_id) : "";
string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
function_compiler_log.info("Compiled function %s%s%s (%s)",
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
@@ -205,19 +223,30 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
}
}
vector<MenuItem> FunctionCodeIndex::patch_menu() const {
vector<MenuItem> ret;
ret.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"", 0);
for (const auto& it : this->name_to_patch_function) {
shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version) const {
auto suffix = string_printf("-%08" PRIX32, specific_version);
shared_ptr<Menu> ret(new Menu(MenuID::PATCHES, u"Patches"));
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"", 0);
for (const auto& it : this->name_and_specific_version_to_patch_function) {
const auto& fn = it.second;
if (!fn->hide_from_patches_menu) {
ret.emplace_back(fn->menu_item_id, decode_sjis(fn->name), u"",
if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) {
ret->items.emplace_back(fn->menu_item_id, decode_sjis(fn->patch_name), u"",
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
}
return ret;
}
bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
for (const auto& it : this->menu_item_id_and_specific_version_to_patch_function) {
if ((it.first & 0xFF000000) == (specific_version & 0xFF000000)) {
return false;
}
}
return true;
}
DOLFileIndex::DOLFileIndex(const string& directory) {
if (!function_compiler_available()) {
function_compiler_log.info("Function compiler is not available");
@@ -228,6 +257,10 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
return;
}
shared_ptr<Menu> menu(new Menu(MenuID::PROGRAMS, u"Programs"));
this->menu = menu;
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"", 0);
uint32_t next_menu_item_id = 0;
for (const auto& filename : list_directory(directory)) {
if (!ends_with(filename, ".dol")) {
@@ -245,6 +278,11 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
this->name_to_file.emplace(dol->name, dol);
this->item_id_to_file.emplace_back(dol);
string size_str = format_size(dol->data.size());
string description = string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str());
menu->items.emplace_back(dol->menu_item_id, decode_sjis(dol->name),
decode_sjis(description), MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
function_compiler_log.info("Loaded DOL file %s", filename.c_str());
} catch (const exception& e) {
@@ -252,13 +290,3 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
}
}
}
vector<MenuItem> DOLFileIndex::menu() const {
vector<MenuItem> ret;
ret.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"", 0);
for (const auto& dol : this->item_id_to_file) {
ret.emplace_back(dol->menu_item_id, decode_sjis(dol->name), u"",
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
return ret;
}
+8 -8
View File
@@ -28,9 +28,11 @@ struct CompiledFunctionCode {
std::unordered_map<std::string, uint32_t> label_offsets;
uint32_t entrypoint_offset_offset;
std::string name;
std::string patch_name; // Blank if not a patch
uint32_t index; // 0 = unused (not registered in index_to_function)
uint32_t menu_item_id;
bool hide_from_patches_menu;
uint32_t specific_version;
bool is_big_endian() const;
@@ -59,14 +61,12 @@ struct FunctionCodeIndex {
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
std::unordered_map<uint32_t, std::shared_ptr<CompiledFunctionCode>> index_to_function;
std::unordered_map<uint32_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_to_patch_function;
std::unordered_map<uint64_t, std::shared_ptr<CompiledFunctionCode>> menu_item_id_and_specific_version_to_patch_function;
// Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_patch_function;
std::vector<MenuItem> patch_menu() const;
inline bool patch_menu_empty() const {
return this->name_to_patch_function.empty();
}
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
bool patch_menu_empty(uint32_t specific_version) const;
};
struct DOLFileIndex {
@@ -78,11 +78,11 @@ struct DOLFileIndex {
std::vector<std::shared_ptr<DOLFile>> item_id_to_file;
std::map<std::string, std::shared_ptr<DOLFile>> name_to_file;
std::shared_ptr<const Menu> menu;
DOLFileIndex() = default;
explicit DOLFileIndex(const std::string& directory);
std::vector<MenuItem> menu() const;
inline bool empty() const {
return this->name_to_file.empty() && this->item_id_to_file.empty();
}
+14
View File
@@ -8,4 +8,18 @@ MenuItem::MenuItem(
: item_id(item_id),
name(name),
description(description),
get_description(nullptr),
flags(flags) {}
MenuItem::MenuItem(
uint32_t item_id, const u16string& name,
std::function<std::u16string()> get_description, uint32_t flags)
: item_id(item_id),
name(name),
description(),
get_description(std::move(get_description)),
flags(flags) {}
Menu::Menu(uint32_t menu_id, const std::u16string& name)
: menu_id(menu_id),
name(name) {}
+26 -11
View File
@@ -2,7 +2,9 @@
#include <stdint.h>
#include <functional>
#include <string>
#include <vector>
// Note: These aren't enums because neither enum nor enum class does what we
// want. Specifically, we need GO_BACK to be valid in multiple enums (and enums
@@ -59,17 +61,18 @@ constexpr uint32_t GO_BACK = 0xAAFFFFAA;
constexpr uint32_t CHAT_COMMANDS = 0xAA0000AA;
constexpr uint32_t CHAT_FILTER = 0xAA1111AA;
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA2222AA;
constexpr uint32_t INFINITE_HP = 0xAA3333AA;
constexpr uint32_t INFINITE_TP = 0xAA4444AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA5555AA;
constexpr uint32_t BLOCK_EVENTS = 0xAA6666AA;
constexpr uint32_t BLOCK_PATCHES = 0xAA7777AA;
constexpr uint32_t SAVE_FILES = 0xAA8888AA;
constexpr uint32_t RED_NAME = 0xAA9999AA;
constexpr uint32_t BLANK_NAME = 0xAAAAAAAA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAABBBBAA;
constexpr uint32_t SKIP_CARD = 0xAACCCCAA;
constexpr uint32_t EP3_INFINITE_MESETA = 0xAADDDDAA;
constexpr uint32_t BLOCK_PINGS = 0xAA3333AA;
constexpr uint32_t INFINITE_HP = 0xAA4444AA;
constexpr uint32_t INFINITE_TP = 0xAA5555AA;
constexpr uint32_t SWITCH_ASSIST = 0xAA6666AA;
constexpr uint32_t BLOCK_EVENTS = 0xAA7777AA;
constexpr uint32_t BLOCK_PATCHES = 0xAA8888AA;
constexpr uint32_t SAVE_FILES = 0xAA9999AA;
constexpr uint32_t RED_NAME = 0xAAAAAAAA;
constexpr uint32_t BLANK_NAME = 0xAABBBBAA;
constexpr uint32_t SUPPRESS_LOGIN = 0xAACCCCAA;
constexpr uint32_t SKIP_CARD = 0xAADDDDAA;
constexpr uint32_t EP3_INFINITE_MESETA = 0xAAEEEEAA;
} // namespace ProxyOptionsMenuItemID
struct MenuItem {
@@ -96,8 +99,20 @@ struct MenuItem {
uint32_t item_id;
std::u16string name;
std::u16string description;
std::function<std::u16string()> get_description;
uint32_t flags;
MenuItem(uint32_t item_id, const std::u16string& name,
const std::u16string& description, uint32_t flags);
MenuItem(uint32_t item_id, const std::u16string& name,
std::function<std::u16string()> get_description, uint32_t flags);
};
struct Menu {
uint32_t menu_id;
std::u16string name;
std::vector<MenuItem> items;
Menu() = delete;
Menu(uint32_t menu_id, const std::u16string& name);
};
+31 -7
View File
@@ -120,6 +120,23 @@ static HandlerResult C_05(shared_ptr<ServerState>,
return HandlerResult::Type::FORWARD;
}
static HandlerResult C_1D(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
return session.options.suppress_client_pings
? HandlerResult::Type::SUPPRESS
: HandlerResult::Type::FORWARD;
}
static HandlerResult S_1D(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (session.options.suppress_client_pings) {
session.server_channel.send(0x1D);
return HandlerResult::Type::SUPPRESS;
} else {
return HandlerResult::Type::FORWARD;
}
}
static HandlerResult S_97(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string&) {
// If the client has already received a 97 command, block this one and
@@ -706,20 +723,27 @@ static HandlerResult S_B2(shared_ptr<ServerState>,
session.server_channel.send(0xB3, flag, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
session.should_forward_function_call_return_queue.emplace_back(true);
session.function_call_return_handler_queue.emplace_back(nullptr);
return HandlerResult::Type::FORWARD;
}
}
static HandlerResult C_B3(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (session.should_forward_function_call_return_queue.empty()) {
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
auto cmd = check_size_t<C_ExecuteCodeResult_B3>(data);
if (session.function_call_return_handler_queue.empty()) {
session.log.warning("Received function call result with empty result queue");
return HandlerResult::Type::FORWARD;
}
bool should_forward = session.should_forward_function_call_return_queue.front();
session.should_forward_function_call_return_queue.pop_front();
return should_forward ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS;
auto handler = std::move(session.function_call_return_handler_queue.front());
session.function_call_return_handler_queue.pop_front();
if (handler != nullptr) {
handler(cmd.return_value, cmd.checksum);
return HandlerResult::Type::SUPPRESS;
} else {
return HandlerResult::Type::FORWARD;
}
}
static HandlerResult S_B_E7(shared_ptr<ServerState>,
@@ -1718,7 +1742,7 @@ static on_command_t handlers[0x100][6][2] = {
/* 1A */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}},
/* 1B */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}},
/* 1C */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}},
/* 1D */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}},
/* 1D */ {{S_invalid, nullptr}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}},
/* 1E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 1F */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}},
// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB
+2 -1
View File
@@ -68,7 +68,8 @@ public:
int64_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
ClientConfigBB newserv_client_config;
std::deque<bool> should_forward_function_call_return_queue;
// A null handler in here means to forward the response to the remote server
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
G_SwitchStateChanged_6x05 last_switch_enabled_command;
PlayerInventoryItem next_drop_item;
uint32_t next_item_id;
+171 -168
View File
@@ -67,60 +67,58 @@ vector<MenuItem> quest_download_menu({
MenuItem(static_cast<uint32_t>(QuestCategory::DOWNLOAD), u"Download", u"$E$C6Quests to download\nto your Memory Card", 0),
});
static const unordered_map<uint32_t, const char16_t*> proxy_options_menu_descriptions({
{ProxyOptionsMenuItemID::GO_BACK, u"Return to the\nproxy menu"},
{ProxyOptionsMenuItemID::CHAT_COMMANDS, u"Enable chat\ncommands"},
{ProxyOptionsMenuItemID::CHAT_FILTER, u"Enable escape\nsequences in\nchat messages\nand info board"},
{ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, u"Show a message\nwhen other players\njoin or leave"},
{ProxyOptionsMenuItemID::INFINITE_HP, u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"},
{ProxyOptionsMenuItemID::INFINITE_TP, u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"},
{ProxyOptionsMenuItemID::SWITCH_ASSIST, u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"},
{ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, u"Fix Meseta value\nat 1,000,000"},
{ProxyOptionsMenuItemID::BLOCK_EVENTS, u"Disable seasonal\nevents in the lobby\nand in games"},
{ProxyOptionsMenuItemID::BLOCK_PATCHES, u"Disable patches sent\nby the remote server"},
{ProxyOptionsMenuItemID::SAVE_FILES, u"Save local copies of\nfiles from the\nremote server\n(quests, etc.)"},
{ProxyOptionsMenuItemID::RED_NAME, u"Set your name\ncolor to red"},
{ProxyOptionsMenuItemID::BLANK_NAME, u"Suppress your\ncharacter name\nduring login"},
{ProxyOptionsMenuItemID::SUPPRESS_LOGIN, u"Use an alternate\nlogin sequence"},
{ProxyOptionsMenuItemID::SKIP_CARD, u"Use an alternate\nvalue for your initial\nGuild Card"},
});
static vector<MenuItem> proxy_options_menu_for_client(
static shared_ptr<const Menu> proxy_options_menu_for_client(
shared_ptr<ServerState> s, shared_ptr<const Client> c) {
vector<MenuItem> ret;
shared_ptr<Menu> ret(new Menu(MenuID::PROXY_OPTIONS, u"Proxy options"));
// Note: The descriptions are instead in the map above, because this menu is
// dynamically created every time it's sent to the client. This is just one
// way in which the menu abstraction is currently insufficient (there is a
// TODO about this in README.md).
ret.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"", 0);
ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"Return to the\nProxy Server menu", 0);
auto add_option = [&](uint32_t item_id, bool is_enabled, const char16_t* text) -> void {
auto add_option = [&](uint32_t item_id, bool is_enabled, const char16_t* text, const char16_t* description) -> void {
u16string option = is_enabled ? u"* " : u"- ";
option += text;
ret.emplace_back(item_id, option, u"", 0);
ret->items.emplace_back(item_id, option, description, 0);
};
add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, u"Chat commands");
add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, u"Chat filter");
add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications, u"Player notifs");
add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands,
u"Chat commands", u"Enable chat\ncommands");
add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter,
u"Chat filter", u"Enable escape\nsequences in\nchat messages\nand info board");
add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications,
u"Player notifs", u"Show a message\nwhen other players\njoin or leave");
add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, c->options.suppress_client_pings,
u"Block pings", u"Block ping commands\nsent by the client");
if (!(c->flags & Client::Flag::IS_EPISODE_3)) {
add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, u"Infinite HP");
add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, u"Infinite TP");
add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, u"Switch assist");
add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp,
u"Infinite HP", u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills");
add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp,
u"Infinite TP", u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique");
add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist,
u"Switch assist", u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially");
} else {
// Note: Thie option's text is the maximum possible length for any menu item
add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, u"Infinite Meseta");
add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta,
u"Infinite Meseta", u"Fix Meseta value\nat 1,000,000");
}
add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), u"Block events");
add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0), u"Block patches");
add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0),
u"Block events", u"Disable seasonal\nevents in the lobby\nand in games");
add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0),
u"Block patches", u"Disable patches sent\nby the remote server");
if (s->proxy_allow_save_files) {
add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files, u"Save files");
add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files,
u"Save files", u"Save local copies of\nfiles from the\nremote server\n(quests, etc.)");
}
if (s->proxy_enable_login_options) {
add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name, u"Red name");
add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name, u"Blank name");
add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login, u"Skip login");
add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card, u"Skip card");
add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name,
u"Red name", u"Set your name\ncolor to red");
add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name,
u"Blank name", u"Suppress your\ncharacter name\nduring login");
add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login,
u"Skip login", u"Use an alternate\nlogin sequence");
add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card,
u"Skip card", u"Use an alternate\nvalue for your initial\nGuild Card");
}
return ret;
@@ -150,8 +148,7 @@ static void send_client_to_proxy_server(shared_ptr<ServerState> s, shared_ptr<Cl
}
static void send_proxy_destinations_menu(shared_ptr<ServerState> s, shared_ptr<Client> c) {
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
s->proxy_destinations_menu_for_version(c->version()));
send_menu(c, s->proxy_destinations_menu_for_version(c->version()));
}
static bool send_enable_send_function_call_if_applicable(
@@ -203,7 +200,72 @@ void on_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c) {
}
static void send_main_menu(shared_ptr<ServerState> s, shared_ptr<Client> c) {
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
shared_ptr<Menu> main_menu(new Menu(MenuID::MAIN, s->name));
main_menu->items.emplace_back(
MainMenuItemID::GO_TO_LOBBY, u"Go to lobby",
[s, wc = weak_ptr<Client>(c)]() -> u16string {
auto c = wc.lock();
if (!c) {
return u"";
}
size_t num_players = 0;
size_t num_games = 0;
size_t num_compatible_games = 0;
for (const auto& it : s->id_to_lobby) {
const auto& l = it.second;
if (l->is_game()) {
num_games++;
if (l->version == c->version() &&
(!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) {
num_compatible_games++;
}
}
for (const auto& c : l->clients) {
if (c) {
num_players++;
}
}
}
string info = string_printf(
"$C6%zu$C7 players online\n$C6%zu$C7 games\n$C6%zu$C7 compatible games",
num_players, num_games, num_compatible_games);
return decode_sjis(info);
},
0);
main_menu->items.emplace_back(MainMenuItemID::INFORMATION, u"Information",
u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
uint32_t proxy_destinations_menu_item_flags =
// DCNTE doesn't support multiple ship select menus without changing
// servers (via a 19 command) apparently :(
MenuItem::Flag::INVISIBLE_ON_DCNTE |
(s->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(s->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(s->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
(s->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
MenuItem::Flag::INVISIBLE_ON_BB;
main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
u"Connect to another\nserver", proxy_destinations_menu_item_flags);
main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests",
u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
if (!s->is_replay) {
if (!s->function_code_index->patch_menu_empty(c->specific_version)) {
main_menu->items.emplace_back(MainMenuItemID::PATCHES, u"Patches",
u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
if (!s->dol_file_index->empty()) {
main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, u"Programs",
u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED);
}
}
main_menu->items.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect",
u"Disconnect", 0);
main_menu->items.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license",
u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password",
MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
send_menu(c, main_menu);
}
void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
@@ -232,15 +294,6 @@ void on_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
send_change_event(c, s->pre_lobby_event);
}
if (function_compiler_available() && (c->flags & Client::Flag::SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH)) {
send_function_call(
c, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2634EC);
send_function_call(
c, s->function_code_index->name_to_function.at("CacheClearFix-Phase2"));
c->flags &= ~Client::Flag::SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH;
send_update_client_config(c);
}
if (s->welcome_message.empty() ||
(c->flags & Client::Flag::NO_D6) ||
!(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) {
@@ -293,6 +346,9 @@ static void set_console_client_flags(
}
}
c->flags |= flags_for_version(c->version(), sub_version);
if (c->specific_version == default_specific_version_for_version(c->version(), -1)) {
c->specific_version = default_specific_version_for_version(c->version(), sub_version);
}
}
static void on_DB_V3(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -552,8 +608,7 @@ static void on_9A(shared_ptr<ServerState> s, shared_ptr<Client> c,
static void on_9C(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(data);
c->flags |= flags_for_version(c->version(), cmd.sub_version);
set_console_client_flags(c, cmd.sub_version);
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
@@ -1398,8 +1453,7 @@ static void on_D6_V3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
check_size_v(data.size(), 0);
if (c->flags & Client::Flag::IN_INFORMATION_MENU) {
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version()));
send_menu(c, s->information_menu_for_version(c->version()));
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
send_enable_send_function_call_if_applicable(s, c);
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
@@ -1413,95 +1467,22 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& cmd = check_size_t<C_MenuItemInfoRequest_09>(data);
switch (cmd.menu_id) {
case MenuID::MAIN:
if (cmd.item_id == MainMenuItemID::GO_TO_LOBBY) {
size_t num_players = 0;
size_t num_games = 0;
size_t num_compatible_games = 0;
for (const auto& it : s->id_to_lobby) {
const auto& l = it.second;
if (l->is_game()) {
num_games++;
if (l->version == c->version() &&
(!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) {
num_compatible_games++;
}
}
for (const auto& c : l->clients) {
if (c) {
num_players++;
}
}
}
string info = string_printf(
"$C6%zu$C7 players online\n$C6%zu$C7 games\n$C6%zu$C7 compatible games",
num_players, num_games, num_compatible_games);
send_ship_info(c, decode_sjis(info));
} else {
for (const auto& item : s->main_menu) {
if (item.item_id == cmd.item_id) {
send_ship_info(c, item.description);
}
}
}
break;
case MenuID::INFORMATION:
if (cmd.item_id == InformationMenuItemID::GO_BACK) {
send_ship_info(c, u"Return to the\nmain menu.");
} else {
try {
// We use item_id + 1 here because "go back" is the first item
send_ship_info(c, s->information_menu_for_version(c->version())->at(cmd.item_id + 1).description.c_str());
} catch (const out_of_range&) {
send_ship_info(c, u"$C4Missing information\nmenu item");
}
}
break;
case MenuID::PROXY_DESTINATIONS:
if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) {
send_ship_info(c, u"Return to the\nmain menu");
} else if (cmd.item_id == ProxyDestinationsMenuItemID::OPTIONS) {
send_ship_info(c, u"Set proxy session\noptions");
} else {
try {
const auto& menu = s->proxy_destinations_menu_for_version(c->version());
// We use item_id + 2 here because "go back" and "options" are the
// first items
send_ship_info(c, menu.at(cmd.item_id + 2).description.c_str());
} catch (const out_of_range&) {
send_ship_info(c, u"$C4Missing proxy\ndestination");
}
}
break;
case MenuID::PROXY_OPTIONS:
try {
const auto* description = proxy_options_menu_descriptions.at(cmd.item_id);
send_ship_info(c, description);
} catch (const out_of_range&) {
send_ship_info(c, u"$C4Missing proxy\noption");
}
break;
case MenuID::QUEST_FILTER:
// Don't send anything here. The quest filter menu already has short
// descriptions included with the entries, which the client shows in the
// usual location on the screen.
break;
case MenuID::QUEST: {
if (!s->quest_index) {
send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id);
break;
} else {
auto q = s->quest_index->get(c->version(), cmd.item_id);
if (!q) {
send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id);
} else {
send_quest_info(c, q->long_description.c_str(), !c->lobby_id);
}
}
auto q = s->quest_index->get(c->version(), cmd.item_id);
if (!q) {
send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id);
break;
}
send_quest_info(c, q->long_description.c_str(), !c->lobby_id);
break;
}
@@ -1576,27 +1557,6 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_ship_info(c, decode_sjis(info));
}
break;
}
case MenuID::PATCHES:
// TODO: Find a way to provide descriptions for patches.
break;
case MenuID::PROGRAMS: {
if (cmd.item_id == ProgramsMenuItemID::GO_BACK) {
send_ship_info(c, u"Return to the\nmain menu.");
} else {
try {
auto dol = s->dol_file_index->item_id_to_file.at(cmd.item_id);
string size_str = format_size(dol->data.size());
string info = string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str());
send_ship_info(c, decode_sjis(info));
} catch (const out_of_range&) {
send_ship_info(c, u"Incorrect program ID.");
}
}
break;
}
case MenuID::TOURNAMENTS_FOR_SPEC:
@@ -1611,6 +1571,7 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
break;
}
case MenuID::TOURNAMENT_ENTRIES: {
if (!(c->flags & Client::Flag::IS_EPISODE_3)) {
send_ship_info(c, u"Incorrect menu ID");
@@ -1650,7 +1611,21 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
default:
send_ship_info(c, u"Incorrect menu ID");
if (!c->last_menu_sent || c->last_menu_sent->menu_id != cmd.menu_id) {
send_ship_info(c, u"Incorrect menu ID");
} else {
for (const auto& item : c->last_menu_sent->items) {
if (item.item_id == cmd.item_id) {
if (item.get_description != nullptr) {
send_ship_info(c, item.get_description());
} else {
send_ship_info(c, item.description);
}
return;
}
}
send_ship_info(c, u"$C4Incorrect menu\nitem ID");
}
break;
}
}
@@ -1712,8 +1687,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
case MainMenuItemID::INFORMATION:
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version()));
send_menu(c, s->information_menu_for_version(c->version()));
c->flags |= Client::Flag::IN_INFORMATION_MENU;
break;
@@ -1737,11 +1711,29 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
break;
case MainMenuItemID::PATCHES:
send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu());
if (!function_compiler_available()) {
throw runtime_error("function compiler not available");
}
if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) {
throw runtime_error("client does not support send_function_call");
}
send_cache_patch_if_needed(s, c);
if (c->version() == GameVersion::GC &&
c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) {
send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect"));
c->on_version_detect_response = [s, wc = weak_ptr<Client>(c)]() {
auto c = wc.lock();
if (c) {
send_menu(c, s->function_code_index->patch_menu(c->specific_version));
}
};
} else {
send_menu(c, s->function_code_index->patch_menu(c->specific_version));
}
break;
case MainMenuItemID::PROGRAMS:
send_menu(c, u"Programs", MenuID::PROGRAMS, s->dol_file_index->menu());
send_menu(c, s->dol_file_index->menu);
break;
case MainMenuItemID::DISCONNECT:
@@ -1789,6 +1781,9 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
case ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS:
c->options.enable_player_notifications = !c->options.enable_player_notifications;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::BLOCK_PINGS:
c->options.suppress_client_pings = !c->options.suppress_client_pings;
goto resend_proxy_options_menu;
case ProxyOptionsMenuItemID::INFINITE_HP:
c->options.infinite_hp = !c->options.infinite_hp;
goto resend_proxy_options_menu;
@@ -1830,8 +1825,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
case ProxyOptionsMenuItemID::SKIP_CARD:
c->options.zero_remote_guild_card = !c->options.zero_remote_guild_card;
resend_proxy_options_menu:
send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS,
proxy_options_menu_for_client(s, c));
send_menu(c, proxy_options_menu_for_client(s, c));
break;
default:
send_message_box(c, u"Incorrect menu item ID.");
@@ -1845,8 +1839,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_main_menu(s, c);
} else if (item_id == ProxyDestinationsMenuItemID::OPTIONS) {
send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS,
proxy_options_menu_for_client(s, c));
send_menu(c, proxy_options_menu_for_client(s, c));
} else {
const pair<string, uint16_t>* dest = nullptr;
@@ -2051,9 +2044,10 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
throw runtime_error("client does not support send_function_call");
}
uint64_t key = (static_cast<uint64_t>(item_id) << 32) | c->specific_version;
send_function_call(
c, s->function_code_index->menu_item_id_to_patch_function.at(item_id));
send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu());
c, s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key));
send_menu(c, s->function_code_index->patch_menu(c->specific_version));
}
break;
@@ -2191,8 +2185,7 @@ static void on_08_E6(shared_ptr<ServerState> s, shared_ptr<Client> c,
static void on_1F(shared_ptr<ServerState> s,
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) {
check_size_v(data.size(), 0);
send_menu(c, u"Information", MenuID::INFORMATION,
*s->information_menu_for_version(c->version()), true);
send_menu(c, s->information_menu_for_version(c->version()), true);
}
static void on_A0(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -2272,7 +2265,17 @@ static void on_B3(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
auto called_fn = s->function_code_index->index_to_function.at(flag);
if (c->loading_dol_file.get()) {
if (called_fn->name == "VersionDetect") {
// This is sent the first time the client chooses Patches from the main
// menu, so send the Patches menu when we get the response here
c->specific_version = cmd.return_value;
c->log.info("Version detected as %08" PRIX32, c->specific_version);
if (c->on_version_detect_response) {
c->on_version_detect_response();
c->on_version_detect_response = nullptr;
}
} else if (c->loading_dol_file.get()) {
if (called_fn->name == "ReadMemoryWord") {
c->dol_base_addr = (cmd.return_value - c->loading_dol_file->data.size()) & (~3);
send_dol_file_chunk(s, c, c->dol_base_addr);
+20 -15
View File
@@ -310,6 +310,17 @@ void send_quest_buffer_overflow(
send_command_t(c, 0xA7, 0x00, cmd);
}
void send_cache_patch_if_needed(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (!(c->flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) {
send_function_call(
c, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2634EC);
send_function_call(
c, s->function_code_index->name_to_function.at("CacheClearFix-Phase2"));
c->flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
send_update_client_config(c);
}
}
void send_function_call(
shared_ptr<Client> c,
shared_ptr<CompiledFunctionCode> code,
@@ -1004,23 +1015,17 @@ void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
// menus
template <typename EntryT>
void send_menu_t(
shared_ptr<Client> c,
const u16string& menu_name,
uint32_t menu_id,
const vector<MenuItem>& items,
bool is_info_menu) {
void send_menu_t(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info_menu) {
vector<EntryT> entries;
{
auto& e = entries.emplace_back();
e.menu_id = menu_id;
e.menu_id = menu->menu_id;
e.item_id = 0xFFFFFFFF;
e.flags = 0x0004;
e.text = menu_name;
e.text = menu->name;
}
for (const auto& item : items) {
for (const auto& item : menu->items) {
bool is_visible = true;
switch (c->version()) {
case GameVersion::DC:
@@ -1059,7 +1064,7 @@ void send_menu_t(
if (is_visible) {
auto& e = entries.emplace_back();
e.menu_id = menu_id;
e.menu_id = menu->menu_id;
e.item_id = item.item_id;
e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04;
e.text = item.name;
@@ -1067,16 +1072,16 @@ void send_menu_t(
}
send_command_vt(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries);
c->last_menu_sent = menu;
}
void send_menu(shared_ptr<Client> c, const u16string& menu_name,
uint32_t menu_id, const vector<MenuItem>& items, bool is_info_menu) {
void send_menu(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info_menu) {
if (c->version() == GameVersion::PC ||
c->version() == GameVersion::PATCH ||
c->version() == GameVersion::BB) {
send_menu_t<S_MenuEntry_PC_BB_07_1F>(c, menu_name, menu_id, items, is_info_menu);
send_menu_t<S_MenuEntry_PC_BB_07_1F>(c, menu, is_info_menu);
} else {
send_menu_t<S_MenuEntry_DC_V3_07_1F>(c, menu_name, menu_id, items, is_info_menu);
send_menu_t<S_MenuEntry_DC_V3_07_1F>(c, menu, is_info_menu);
}
}
+3 -2
View File
@@ -132,6 +132,8 @@ 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_cache_patch_if_needed(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
uint32_t send_cache_patch_if_needed(std::shared_ptr<ServerState> s, Channel& c, uint32_t flags);
void send_function_call(
Channel& ch,
uint64_t client_flags,
@@ -245,8 +247,7 @@ void send_guild_card(
uint8_t section_id,
uint8_t char_class);
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
void send_menu(std::shared_ptr<Client> c, const std::u16string& menu_name,
uint32_t menu_id, const std::vector<MenuItem>& items, bool is_info_menu = false);
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
void send_game_menu(
std::shared_ptr<Client> c,
std::shared_ptr<ServerState> s,
+26 -73
View File
@@ -309,7 +309,7 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
}
}
shared_ptr<const vector<MenuItem>> ServerState::information_menu_for_version(GameVersion version) {
std::shared_ptr<const Menu> ServerState::information_menu_for_version(GameVersion version) {
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
return this->information_menu_v2;
} else if ((version == GameVersion::GC) || (version == GameVersion::XB)) {
@@ -318,7 +318,7 @@ shared_ptr<const vector<MenuItem>> ServerState::information_menu_for_version(Gam
throw out_of_range("no information menu exists for this version");
}
const vector<MenuItem>& ServerState::proxy_destinations_menu_for_version(GameVersion version) {
shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(GameVersion version) {
switch (version) {
case GameVersion::DC:
return this->proxy_destinations_menu_dc;
@@ -386,25 +386,21 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
config_log.info("Creating menus");
const auto& d = config_json->as_dict();
this->main_menu.clear();
this->proxy_destinations_menu_dc.clear();
this->proxy_destinations_menu_pc.clear();
this->proxy_destinations_menu_gc.clear();
this->proxy_destinations_menu_xb.clear();
shared_ptr<vector<MenuItem>> information_menu_v2(new vector<MenuItem>());
shared_ptr<vector<MenuItem>> information_menu_v3(new vector<MenuItem>());
shared_ptr<Menu> information_menu_v2(new Menu(MenuID::INFORMATION, u"Information"));
shared_ptr<Menu> information_menu_v3(new Menu(MenuID::INFORMATION, u"Information"));
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
information_menu_v3->emplace_back(InformationMenuItemID::GO_BACK, u"Go back",
information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
{
uint32_t item_id = 0;
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
auto& v = item->as_list();
information_menu_v2->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
information_menu_v2->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
decode_sjis(v.at(1)->as_string()), 0);
information_menu_v3->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
information_menu_v3->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
decode_sjis(v.at(1)->as_string()), MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
item_id++;
@@ -414,53 +410,43 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
this->information_menu_v3 = information_menu_v3;
this->information_contents = information_contents;
auto generate_proxy_destinations_menu = [&](
vector<MenuItem>& ret_menu,
vector<pair<string, uint16_t>>& ret_pds,
const char* key) {
auto generate_proxy_destinations_menu = [&](vector<pair<string, uint16_t>>& ret_pds, const char* key) -> shared_ptr<const Menu> {
shared_ptr<Menu> ret(new Menu(MenuID::PROXY_DESTINATIONS, u"Proxy server"));
ret_pds.clear();
try {
map<string, shared_ptr<JSONObject>> sorted_jsons;
for (const auto& it : d.at(key)->as_dict()) {
sorted_jsons.emplace(it.first, it.second);
}
ret_menu.clear();
ret_pds.clear();
ret_menu.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back",
ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
ret_menu.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options",
u"Set proxy options", 0);
ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options",
u"Set proxy session\noptions", 0);
uint32_t item_id = 0;
for (const auto& item : sorted_jsons) {
const string& netloc_str = item.second->as_string();
const string& description = "$C7Remote server:\n$C6" + netloc_str;
ret_menu.emplace_back(item_id, decode_sjis(item.first),
ret->items.emplace_back(item_id, decode_sjis(item.first),
decode_sjis(description), 0);
ret_pds.emplace_back(parse_netloc(netloc_str));
item_id++;
}
} catch (const out_of_range&) {
}
return ret;
};
generate_proxy_destinations_menu(
this->proxy_destinations_menu_dc,
this->proxy_destinations_dc,
"ProxyDestinations-DC");
generate_proxy_destinations_menu(
this->proxy_destinations_menu_pc,
this->proxy_destinations_pc,
"ProxyDestinations-PC");
generate_proxy_destinations_menu(
this->proxy_destinations_menu_gc,
this->proxy_destinations_gc,
"ProxyDestinations-GC");
generate_proxy_destinations_menu(
this->proxy_destinations_menu_xb,
this->proxy_destinations_xb,
"ProxyDestinations-XB");
this->proxy_destinations_menu_dc = generate_proxy_destinations_menu(
this->proxy_destinations_dc, "ProxyDestinations-DC");
this->proxy_destinations_menu_pc = generate_proxy_destinations_menu(
this->proxy_destinations_pc, "ProxyDestinations-PC");
this->proxy_destinations_menu_gc = generate_proxy_destinations_menu(
this->proxy_destinations_gc, "ProxyDestinations-GC");
this->proxy_destinations_menu_xb = generate_proxy_destinations_menu(
this->proxy_destinations_xb, "ProxyDestinations-XB");
try {
const string& netloc_str = d.at("ProxyDestination-Patch")->as_string();
@@ -489,39 +475,6 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
this->proxy_destination_bb.second = 0;
}
this->main_menu.emplace_back(MainMenuItemID::GO_TO_LOBBY, u"Go to lobby",
u"Join the lobby", 0);
this->main_menu.emplace_back(MainMenuItemID::INFORMATION, u"Information",
u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
uint32_t proxy_destinations_menu_item_flags =
// DCNTE doesn't support multiple ship select menus without changing
// servers (via a 19 command) apparently :(
MenuItem::Flag::INVISIBLE_ON_DCNTE |
(this->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(this->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(this->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
(this->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
MenuItem::Flag::INVISIBLE_ON_BB;
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
u"Connect to another\nserver", proxy_destinations_menu_item_flags);
this->main_menu.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests",
u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
if (!this->is_replay) {
if (!this->function_code_index->patch_menu_empty()) {
this->main_menu.emplace_back(MainMenuItemID::PATCHES, u"Patches",
u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
if (!this->dol_file_index->empty()) {
this->main_menu.emplace_back(MainMenuItemID::PROGRAMS, u"Programs",
u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED);
}
}
this->main_menu.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect",
u"Disconnect", 0);
this->main_menu.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license",
u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password",
MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
try {
this->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());
} catch (const out_of_range&) {
+8 -9
View File
@@ -84,14 +84,13 @@ struct ServerState {
std::shared_ptr<LicenseManager> license_manager;
std::vector<MenuItem> main_menu;
std::shared_ptr<std::vector<MenuItem>> information_menu_v2;
std::shared_ptr<std::vector<MenuItem>> information_menu_v3;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<std::vector<std::u16string>> information_contents;
std::vector<MenuItem> proxy_destinations_menu_dc;
std::vector<MenuItem> proxy_destinations_menu_pc;
std::vector<MenuItem> proxy_destinations_menu_gc;
std::vector<MenuItem> proxy_destinations_menu_xb;
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
@@ -146,8 +145,8 @@ struct ServerState {
uint32_t connect_address_for_client(std::shared_ptr<Client> c);
std::shared_ptr<const std::vector<MenuItem>> information_menu_for_version(GameVersion version);
const std::vector<MenuItem>& proxy_destinations_menu_for_version(GameVersion version);
std::shared_ptr<const Menu> information_menu_for_version(GameVersion version);
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(GameVersion version);
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version);
void set_port_configuration(
+49 -10
View File
@@ -20,19 +20,23 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) {
case -1: // Initial check (before sub_version recognition)
switch (version) {
case GameVersion::DC:
return Client::Flag::NO_D6;
return Client::Flag::NO_D6 |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case GameVersion::GC:
case GameVersion::XB:
return 0;
case GameVersion::XB:
return Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case GameVersion::PC:
return Client::Flag::NO_D6 |
Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY;
Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case GameVersion::PATCH:
return Client::Flag::NO_D6 |
Client::Flag::NO_SEND_FUNCTION_CALL;
case GameVersion::BB:
return Client::Flag::NO_D6 |
Client::Flag::SAVE_ENABLED;
Client::Flag::SAVE_ENABLED |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
}
break;
@@ -46,18 +50,18 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) {
Client::Flag::NO_SEND_FUNCTION_CALL;
case 0x26: // DCv2 US
return Client::Flag::NO_D6;
return Client::Flag::NO_D6 |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case 0x29: // PC
return Client::Flag::NO_D6 |
Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY;
Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00
case 0x34: // GC Ep1&2 JP v1.03
return ((version == GameVersion::GC) && function_compiler_available())
? Client::Flag::SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH
: 0;
return 0;
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
return Client::Flag::NO_D6_AFTER_LOBBY;
@@ -80,7 +84,8 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) {
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;
Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL |
Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH;
case 0x43: // GC Ep3 EU
return Client::Flag::NO_D6_AFTER_LOBBY |
Client::Flag::IS_EPISODE_3 |
@@ -167,3 +172,37 @@ ServerBehavior server_behavior_for_name(const char* name) {
throw invalid_argument("incorrect server behavior name");
}
}
uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version) {
uint32_t base_specific_version = (static_cast<uint32_t>(version) + '0') << 24;
if (version == GameVersion::GC) {
// For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetect call.
switch (sub_version) {
case 0x36: // GC Ep1&2 US v1.02 (Plus)
return 0x334F4532; // 3OE2
case 0x39: // GC Ep1&2 JP v1.05 (Plus)
return 0x334F4A35; // 3OJ5
case 0x41: // GC Ep3 US
return 0x33534530; // 3SE0
case 0x43: // GC Ep3 EU
return 0x33535030; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
case 0x34: // GC Ep1&2 JP v1.03
case 0x35: // GC Ep1&2 JP v1.04 (Plus)
case 0x40: // GC Ep3 trial
case 0x42: // GC Ep3 JP
default:
return base_specific_version;
}
} else {
return base_specific_version;
}
}
+7 -5
View File
@@ -7,11 +7,11 @@
enum class GameVersion {
PATCH = 0,
DC,
PC,
GC,
XB,
BB,
DC = 1,
PC = 2,
GC = 3,
XB = 4,
BB = 5,
};
enum class ServerBehavior {
@@ -34,3 +34,5 @@ GameVersion version_for_name(const char* name);
const char* name_for_server_behavior(ServerBehavior behavior);
ServerBehavior server_behavior_for_name(const char* name);
uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version);
@@ -14,8 +14,6 @@ reloc0:
.offsetof start
start:
.include InitClearCaches
.include Episode3USAOnly
stwu [r1 - 0x20], r1
@@ -19,11 +19,6 @@ reloc0:
.offsetof start
start:
# Note: We don't actually need this for Episode 3, since all Episode 3
# versions correctly clear the caches before running code from a B2 command.
# But we leave it in to be consistent with patches for Episodes 1&2.
.include InitClearCaches
.include Episode3USAOnly
stwu [r1 - 0x20], r1
@@ -10,11 +10,6 @@ reloc0:
.offsetof start
start:
# Note: We don't actually need this for Episode 3, since all Episode 3
# versions correctly clear the caches before running code from a B2 command.
# But we leave it in to be consistent with patches for Episodes 1&2.
.include InitClearCaches
.include Episode3USAOnly
# Call seq_var_set(7000) - this gives the local player a VIP card
-18
View File
@@ -1,18 +0,0 @@
# This macro clears the data and instruction caches at the beginning of each
# function. This is necessary because apparently some versions of PSO don't do
# this correctly by themselves.
# This macro expects to be run immediately at the entrypoint (usually the start
# label) for all functions. It returns the original return address in r12, and
# the address of the start label in r11.
mflr r12 # r12 = address to return to
mfctr r3 # r3 = address of start label (this code is called via bctrl)
addi r4, r3, 0x7C00 # r4 = end of relevant region
InitClearCaches__next_cache_block:
dcbst r0, r3
sync
icbi r0, r3
addi r3, r3, 0x20
cmpl r3, r4
blt InitClearCaches__next_cache_block
isync
-2
View File
@@ -8,8 +8,6 @@ reloc0:
.offsetof start
start:
.include InitClearCaches
bl read
address:
.zero
-1
View File
@@ -8,7 +8,6 @@ reloc0:
.offsetof start
start:
.include InitClearCaches
disable_interrupts:
mfmsr r3
+30
View File
@@ -0,0 +1,30 @@
# This function returns the game version, with values more specific than can be
# detected by the sub_version field in various login commands.
# The returned value has the format 03GGRRVV, where:
# G = game (Ox4F (O) = Episodes 1&2, 0x53 (S) = Episode 3)
# R = region (0x45 (E), 0x4A (J), 0x50 (P))
# V = minor version (0 = 1.00, 1 = 1.01, 2 = 1.02, etc.)
newserv_index_E3:
entry_ptr:
reloc0:
.offsetof start
start:
lis r3, 0x8000
lwz r4, [r3]
lbz r5, [r3 + 7]
li r3, -1
rlwinm r0, r4, 16, 16, 31
cmplwi r0, 0x4750
bnelr
lis r3, 0x3300
rlwimi r3, r4, 8, 8, 23
rlwimi r3, r5, 0, 24, 31
ori r3, r3, 0x0030
blr
+26 -14
View File
@@ -2,11 +2,26 @@
# serve DOL files to GameCube clients.
# This is also the file I've chosen to document how to write code for newserv's
# functions subsystem. The code implemented in this file writes a
# variable-length block of data to a specified address in the client's memory.
# Note that WriteMemory is a general function that uses many of the subsystem's
# features. If you're writing a patch (not a general function), you cannot use
# the suffix or label_offsets features that are described here.
# functions subsystem. There are three kinds of functions: includes, patches,
# and general functions. This file, WriteMemory, is a general function. It
# writes a variable-length block of data to a specified address in the client's
# memory.
# Includes are snippets of code that are intended to be used as part of other
# functions and patches. These files' names end with .inc.s. These can be used
# with the .include directive; there is an example of this in the code below.
# Patches are functions that are available to run upon client request. They can
# be made available in the Patches menu or via the $patch command. In general,
# patches should be named like PATCHNAME.VXLS.patch.s, where V, X, L, and S
# denote which specific game version the patch is for. Specifically:
# V should be 3 for PSO GameCube
# X should be O for Episodes 1 & 2, and S for Episode 3
# L should be E, J, or P for USA, Japanese, or Europe
# S should be 0, 1, 2, etc. for the disc version (0 = v1.00, 1 = v1.01, etc.)
# If a label named hide_from_patches_menu is present anywhere in the code, the
# patch is only usable via the $patch command and does not appear in the Patches
# menu.
# For example, to use this function to write the bytes 38 00 00 05 to the
# address 8010521C, send_function_call could be called like this:
@@ -20,13 +35,16 @@
# label_writes, // Variables to pass in to the function's code
# suffix); // Data to append after the code (not all functions use this)
# The meanings of label_writes and suffix are described in the comments below.
# Note that there is no way to specify label_writes or suffix for patches
# requested by the client, so those features should only be used in general
# functions.
# A label newserv_index_XX tells newserv what value to use in the flag field
# when sending the B2 command. This is needed if the server needs to do
# something when the B3 response is received. For GameCube functions, if
# specified, the index must be in the range 01-FF. The DOL loading
# functionality, which this function is a part of, uses indexes E0, E1, and E2,
# but this function can also be used for other purposes.
# but the WriteMemory function can also be used for other purposes.
newserv_index_E1:
# The entry_ptr label is required for all functions. It should point to a
@@ -41,14 +59,6 @@ reloc0:
.offsetof start
start:
# A .include directive essentially pastes in the code from the referenced
# file. Here, we use the code from the file InitClearCaches.inc.s.
# PSO GC doesn't properly clear the data and instruction caches when it
# executes functions, so we use this include in all functions to do so. Since
# all functions do this, this makes it safe to use more than one function in
# each client's session.
.include InitClearCaches
bl get_block_ptr
mr r6, r3 # r6 = address of dest_addr label
@@ -67,6 +77,8 @@ copy_block__again:
# Flush the data cache and clear the instruction cache at the written region
lwz r3, [r6] # r3 = dest ptr
lwz r4, [r6 + 4] # r4 = size
# A .include directive essentially pastes in the code from the referenced
# file. Here, we use the code from the file FlushCachedCode.inc.s.
.include FlushCachedCode
# Return the address after the last byte written. The value returned in r3