implement version-specific patches; clean up menu abstraction
This commit is contained in:
+62
-14
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user