add DOL file loader
This commit is contained in:
+3
-1
@@ -45,7 +45,9 @@ Client::Client(
|
||||
infinite_hp(false),
|
||||
infinite_tp(false),
|
||||
switch_assist(false),
|
||||
can_chat(true) {
|
||||
can_chat(true),
|
||||
pending_bb_save_player_index(0),
|
||||
dol_base_addr(0) {
|
||||
this->last_switch_enabled_command.subcommand = 0;
|
||||
int fd = bufferevent_getfd(this->bev);
|
||||
if (fd < 0) {
|
||||
|
||||
+14
-9
@@ -4,13 +4,13 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "License.hh"
|
||||
#include "Player.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
#include "Text.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
|
||||
|
||||
@@ -41,15 +41,16 @@ struct Client {
|
||||
IN_INFORMATION_MENU = 0x0080,
|
||||
// Client is at the welcome message (login server only)
|
||||
AT_WELCOME_MESSAGE = 0x0100,
|
||||
// Client disconnect if it receives B2 (send_function_call)
|
||||
DOES_NOT_SUPPORT_SEND_FUNCTION_CALL = 0x0200,
|
||||
|
||||
// Note: There isn't a good way to detect Episode 3 until the player data is
|
||||
// sent (via a 61 command), so the IS_EPISODE_3 flag is set in that handler
|
||||
DEFAULT_V1 = DCV1,
|
||||
// TODO: Do DCv1 and PC support send_function_call? Here we assume they don't
|
||||
DEFAULT_V1 = DCV1 | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL,
|
||||
DEFAULT_V2_DC = 0x0000,
|
||||
DEFAULT_V2_PC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION,
|
||||
DEFAULT_V2_PC = NO_MESSAGE_BOX_CLOSE_CONFIRMATION | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL,
|
||||
DEFAULT_V3_GC = 0x0000,
|
||||
DEFAULT_V3_GC_PLUS = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN,
|
||||
DEFAULT_V3_GC_EP3 = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | EPISODE_3,
|
||||
DEFAULT_V3_GC_PLUS = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL,
|
||||
DEFAULT_V3_GC_EP3 = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | EPISODE_3 | DOES_NOT_SUPPORT_SEND_FUNCTION_CALL,
|
||||
DEFAULT_V4_BB = NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN | NO_MESSAGE_BOX_CLOSE_CONFIRMATION,
|
||||
};
|
||||
|
||||
@@ -104,6 +105,10 @@ struct Client {
|
||||
std::string pending_bb_save_username;
|
||||
uint8_t pending_bb_save_player_index;
|
||||
|
||||
// DOL file loading state
|
||||
uint32_t dol_base_addr;
|
||||
std::shared_ptr<DOLFileIndex::DOLFile> loading_dol_file;
|
||||
|
||||
Client(struct bufferevent* bev, GameVersion version,
|
||||
ServerBehavior server_behavior);
|
||||
|
||||
|
||||
@@ -520,7 +520,10 @@ struct S_ReconnectSplit_19 {
|
||||
// 1E: Invalid command
|
||||
|
||||
// 1F (S->C): Information menu
|
||||
// Same format and usage as 07 command
|
||||
// Same format and usage as 07 command, except:
|
||||
// - The menu title will say "Information" instead of "Ship Select".
|
||||
// - There is no way to request information before selecting a menu item (the
|
||||
// client will not send 09 commands).
|
||||
|
||||
// 20: Invalid command
|
||||
|
||||
@@ -1306,7 +1309,7 @@ struct S_ExecuteCode_Footer_BB_B2 : S_ExecuteCode_Footer_B2<le_uint32_t> { };
|
||||
// B3 (C->S): Execute code and/or checksum memory result
|
||||
// GC v1.0/v1.1 and BB only.
|
||||
|
||||
struct C_ExecuteCodeResult_GC_BB_B3 {
|
||||
struct C_ExecuteCodeResult_B3 {
|
||||
le_uint32_t return_value; // 0 if no code was run
|
||||
le_uint32_t checksum; // 0 if no checksum was computed
|
||||
};
|
||||
|
||||
+105
-15
@@ -26,9 +26,9 @@ bool function_compiler_available() {
|
||||
|
||||
|
||||
|
||||
std::string CompiledFunctionCode::generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const std::string& suffix) const {
|
||||
string CompiledFunctionCode::generate_client_command(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const string& suffix) const {
|
||||
S_ExecuteCode_Footer_GC_B2 footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear();
|
||||
@@ -66,18 +66,39 @@ std::string CompiledFunctionCode::generate_client_command(
|
||||
return move(w.str());
|
||||
}
|
||||
|
||||
shared_ptr<CompiledFunctionCode> compile_function_code(const std::string& text) {
|
||||
shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
const string& directory, const string& name, const string& text) {
|
||||
#ifndef HAVE_RESOURCE_FILE
|
||||
(void)directory;
|
||||
(void)name;
|
||||
(void)text;
|
||||
throw runtime_error("PowerPC assembler is not available");
|
||||
|
||||
#else
|
||||
auto assembled = PPC32Emulator::assemble(text);
|
||||
std::unordered_set<string> get_include_stack; // For mutual recursion detection
|
||||
function<string(const string&)> get_include = [&](const string& name) -> string {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("mutual recursion between includes");
|
||||
}
|
||||
|
||||
string filename = directory + "/" + name + ".inc.s";
|
||||
if (isfile(filename)) {
|
||||
return PPC32Emulator::assemble(load_file(filename), get_include).code;
|
||||
}
|
||||
filename = directory + "/" + name + ".inc.bin";
|
||||
if (isfile(filename)) {
|
||||
return load_file(filename);
|
||||
}
|
||||
throw runtime_error("data not found for include " + name);
|
||||
};
|
||||
|
||||
shared_ptr<CompiledFunctionCode> ret(new CompiledFunctionCode());
|
||||
ret->name = name;
|
||||
ret->index = 0;
|
||||
|
||||
auto assembled = PPC32Emulator::assemble(text, get_include);
|
||||
ret->code = move(assembled.code);
|
||||
ret->label_offsets = move(assembled.label_offsets);
|
||||
ret->index = 0xFF;
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
@@ -110,32 +131,101 @@ shared_ptr<CompiledFunctionCode> compile_function_code(const std::string& text)
|
||||
|
||||
|
||||
|
||||
FunctionCodeIndex::FunctionCodeIndex(const std::string& directory) {
|
||||
this->index_to_function.resize(0x100);
|
||||
|
||||
FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
if (!function_compiler_available()) {
|
||||
log(INFO, "Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& filename : list_directory(directory)) {
|
||||
if (!ends_with(filename, ".s")) {
|
||||
if (!ends_with(filename, ".s") || ends_with(filename, ".inc.s")) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
bool is_patch = ends_with(filename, ".patch.s");
|
||||
string name = filename.substr(0, filename.size() - (is_patch ? 8 : 2));
|
||||
|
||||
try {
|
||||
string path = directory + "/" + filename;
|
||||
string text = load_file(path);
|
||||
auto code = compile_function_code(text);
|
||||
if (code->index < 0xFF) {
|
||||
this->index_to_function.at(code->index) = code;
|
||||
auto code = compile_function_code(directory, name, text);
|
||||
if (code->index != 0) {
|
||||
if (!this->index_to_function.emplace(code->index, code).second) {
|
||||
throw runtime_error(string_printf(
|
||||
"duplicate function index: %08" PRIX32, code->index));
|
||||
}
|
||||
}
|
||||
this->name_to_function.emplace(name, code);
|
||||
log(WARNING, "Compiled function %s", name.c_str());
|
||||
if (is_patch) {
|
||||
this->menu_item_id_to_patch_function.emplace(next_menu_item_id++, code);
|
||||
this->name_to_patch_function.emplace(name, code);
|
||||
}
|
||||
if (code->index) {
|
||||
log(INFO, "Compiled function %02X => %s", code->index, name.c_str());
|
||||
} else {
|
||||
log(INFO, "Compiled function %s", name.c_str());
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to compile function %s: %s", name.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const auto& fn = it.second;
|
||||
ret.emplace_back(fn->menu_item_id, decode_sjis(fn->name), u"",
|
||||
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!function_compiler_available()) {
|
||||
log(INFO, "Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
if (!isdir(directory)) {
|
||||
log(INFO, "DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& filename : list_directory(directory)) {
|
||||
if (!ends_with(filename, ".dol")) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
|
||||
try {
|
||||
shared_ptr<DOLFile> dol(new DOLFile());
|
||||
dol->menu_item_id = next_menu_item_id++;
|
||||
dol->name = name;
|
||||
|
||||
string path = directory + "/" + filename;
|
||||
dol->data = load_file(path);
|
||||
|
||||
this->name_to_file.emplace(dol->name, dol);
|
||||
this->item_id_to_file.emplace_back(dol);
|
||||
log(WARNING, "Loaded DOL file %s", filename.c_str());
|
||||
|
||||
} catch (const exception& e) {
|
||||
log(WARNING, "Failed to load DOL file %s: %s", filename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+39
-3
@@ -4,9 +4,12 @@
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
|
||||
|
||||
bool function_compiler_available();
|
||||
@@ -21,14 +24,19 @@ struct CompiledFunctionCode {
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset;
|
||||
uint8_t index; // 00-FE (FF = index not specified)
|
||||
std::string name;
|
||||
uint32_t index; // 0 = unused (not registered in index_to_function)
|
||||
uint32_t menu_item_id;
|
||||
|
||||
std::string generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes = {},
|
||||
const std::string& suffix = "") const;
|
||||
};
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(const std::string& text);
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
const std::string& directory,
|
||||
const std::string& name,
|
||||
const std::string& text);
|
||||
|
||||
|
||||
|
||||
@@ -36,5 +44,33 @@ struct FunctionCodeIndex {
|
||||
FunctionCodeIndex(const std::string& directory);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CompiledFunctionCode>> name_to_function;
|
||||
std::vector<std::shared_ptr<CompiledFunctionCode>> index_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::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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct DOLFileIndex {
|
||||
struct DOLFile {
|
||||
uint32_t menu_item_id;
|
||||
std::string name;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<DOLFile>> item_id_to_file;
|
||||
std::map<std::string, std::shared_ptr<DOLFile>> name_to_file;
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
+6
-94
@@ -83,100 +83,6 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
||||
s->common_item_creator.reset(new CommonItemCreator(enemy_categories,
|
||||
box_categories, unit_types));
|
||||
|
||||
shared_ptr<vector<MenuItem>> information_menu_pc(new vector<MenuItem>());
|
||||
shared_ptr<vector<MenuItem>> information_menu_gc(new vector<MenuItem>());
|
||||
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
|
||||
|
||||
information_menu_gc->emplace_back(INFORMATION_MENU_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_pc->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), 0);
|
||||
information_menu_gc->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++;
|
||||
}
|
||||
}
|
||||
s->information_menu_pc = information_menu_pc;
|
||||
s->information_menu_gc = information_menu_gc;
|
||||
s->information_contents = information_contents;
|
||||
|
||||
s->proxy_destinations_menu_pc.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
|
||||
u"Go back", u"Return to the\nmain menu", 0);
|
||||
s->proxy_destinations_menu_gc.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
|
||||
u"Go back", u"Return to the\nmain menu", 0);
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("ProxyDestinations-GC")->as_dict()) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
s->proxy_destinations_menu_gc.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(netloc_str), 0);
|
||||
s->proxy_destinations_gc.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("ProxyDestinations-PC")->as_dict()) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
s->proxy_destinations_menu_pc.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(netloc_str), 0);
|
||||
s->proxy_destinations_pc.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-Patch")->as_string();
|
||||
s->proxy_destination_patch = parse_netloc(netloc_str);
|
||||
log(INFO, "Patch server proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : s->name_to_port_config) {
|
||||
if (it.second->version == GameVersion::PATCH) {
|
||||
it.second->behavior = ServerBehavior::PROXY_SERVER;
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
s->proxy_destination_patch.first = "";
|
||||
s->proxy_destination_patch.second = 0;
|
||||
}
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-BB")->as_string();
|
||||
s->proxy_destination_bb = parse_netloc(netloc_str);
|
||||
log(INFO, "BB proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : s->name_to_port_config) {
|
||||
if (it.second->version == GameVersion::BB) {
|
||||
it.second->behavior = ServerBehavior::PROXY_SERVER;
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
s->proxy_destination_bb.first = "";
|
||||
s->proxy_destination_bb.second = 0;
|
||||
}
|
||||
|
||||
s->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
|
||||
u"Join the lobby", 0);
|
||||
s->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information",
|
||||
u"View server information", MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
|
||||
if (!s->proxy_destinations_pc.empty()) {
|
||||
s->main_menu.emplace_back(MAIN_MENU_PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::PC_ONLY);
|
||||
}
|
||||
if (!s->proxy_destinations_gc.empty()) {
|
||||
s->main_menu.emplace_back(MAIN_MENU_PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::GC_ONLY);
|
||||
}
|
||||
s->main_menu.emplace_back(MAIN_MENU_DOWNLOAD_QUESTS, u"Download quests",
|
||||
u"Download quests", MenuItem::Flag::INVISIBLE_ON_BB);
|
||||
s->main_menu.emplace_back(MAIN_MENU_DISCONNECT, u"Disconnect",
|
||||
u"Disconnect", 0);
|
||||
|
||||
try {
|
||||
s->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
auto local_address_str = d.at("LocalAddress")->as_string();
|
||||
try {
|
||||
s->local_address = s->all_addresses.at(local_address_str);
|
||||
@@ -437,6 +343,12 @@ int main(int argc, char** argv) {
|
||||
log(INFO, "Compiling client functions");
|
||||
state->function_code_index.reset(new FunctionCodeIndex("system/ppc"));
|
||||
|
||||
log(INFO, "Loading DOL files");
|
||||
state->dol_file_index.reset(new DOLFileIndex("system/dol"));
|
||||
|
||||
log(INFO, "Creating menus");
|
||||
state->create_menus(config_json);
|
||||
|
||||
shared_ptr<DNSServer> dns_server;
|
||||
if (state->dns_server_port) {
|
||||
log(INFO, "Starting DNS server");
|
||||
|
||||
+43
-14
@@ -6,21 +6,49 @@
|
||||
|
||||
|
||||
|
||||
#define MAIN_MENU_ID 0x11000011
|
||||
#define INFORMATION_MENU_ID 0x22000022
|
||||
#define LOBBY_MENU_ID 0x33000033
|
||||
#define GAME_MENU_ID 0x44000044
|
||||
#define QUEST_MENU_ID 0x55000055
|
||||
#define QUEST_FILTER_MENU_ID 0x66000066
|
||||
#define PROXY_DESTINATIONS_MENU_ID 0x77000077
|
||||
// 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
|
||||
// aren't namespaced unless they're enum classes), so we can't use enums. But we
|
||||
// also want to be able to use non-enum values in switch statements without
|
||||
// casting values all over the place, so we can't use enum classes either.
|
||||
|
||||
#define MAIN_MENU_GO_TO_LOBBY 0x11AAAA11
|
||||
#define MAIN_MENU_INFORMATION 0x11BBBB11
|
||||
#define MAIN_MENU_DOWNLOAD_QUESTS 0x11CCCC11
|
||||
#define MAIN_MENU_PROXY_DESTINATIONS 0x11DDDD11
|
||||
#define MAIN_MENU_DISCONNECT 0x11EEEE11
|
||||
#define INFORMATION_MENU_GO_BACK 0x22FFFF22
|
||||
#define PROXY_DESTINATIONS_MENU_GO_BACK 0x77FFFF77
|
||||
namespace MenuID {
|
||||
constexpr uint32_t MAIN = 0x11000011;
|
||||
constexpr uint32_t INFORMATION = 0x22000022;
|
||||
constexpr uint32_t LOBBY = 0x33000033;
|
||||
constexpr uint32_t GAME = 0x44000044;
|
||||
constexpr uint32_t QUEST = 0x55000055;
|
||||
constexpr uint32_t QUEST_FILTER = 0x66000066;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
|
||||
constexpr uint32_t PROGRAMS = 0x88000088;
|
||||
constexpr uint32_t PATCHES = 0x99000099;
|
||||
}
|
||||
|
||||
namespace MainMenuItemID {
|
||||
constexpr uint32_t GO_TO_LOBBY = 0x11222211;
|
||||
constexpr uint32_t INFORMATION = 0x11333311;
|
||||
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
|
||||
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
|
||||
constexpr uint32_t PATCHES = 0x11666611;
|
||||
constexpr uint32_t PROGRAMS = 0x11777711;
|
||||
constexpr uint32_t DISCONNECT = 0x11888811;
|
||||
}
|
||||
|
||||
namespace InformationMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x22FFFF22;
|
||||
};
|
||||
|
||||
namespace ProxyDestinationsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x77FFFF77;
|
||||
};
|
||||
|
||||
namespace ProgramsMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x88FFFF88;
|
||||
};
|
||||
|
||||
namespace PatchesMenuItemID {
|
||||
constexpr uint32_t GO_BACK = 0x99FFFF99;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +63,7 @@ struct MenuItem {
|
||||
GC_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_BB,
|
||||
BB_ONLY = INVISIBLE_ON_DC | INVISIBLE_ON_PC | INVISIBLE_ON_GC,
|
||||
REQUIRES_MESSAGE_BOXES = 0x10,
|
||||
REQUIRES_SEND_FUNCTION_CALL = 0x20,
|
||||
};
|
||||
|
||||
uint32_t item_id;
|
||||
|
||||
@@ -462,7 +462,7 @@ static bool process_server_B2(shared_ptr<ServerState>,
|
||||
|
||||
if (session.function_call_return_value >= 0) {
|
||||
session.log(INFO, "Blocking function call from server");
|
||||
C_ExecuteCodeResult_GC_BB_B3 cmd;
|
||||
C_ExecuteCodeResult_B3 cmd;
|
||||
cmd.return_value = session.function_call_return_value;
|
||||
cmd.checksum = 0;
|
||||
session.send_to_end(true, 0xB3, flag, &cmd, sizeof(cmd));
|
||||
|
||||
+238
-61
@@ -10,13 +10,14 @@
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "PSOProtocol.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "Text.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "ChatCommands.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -112,7 +113,7 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
(c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION) ||
|
||||
!(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) {
|
||||
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
|
||||
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu, false);
|
||||
} else {
|
||||
send_message_box(c, s->welcome_message.c_str());
|
||||
}
|
||||
@@ -538,10 +539,10 @@ void process_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // D6
|
||||
check_size_v(data.size(), 0);
|
||||
if (c->flags & Client::Flag::IN_INFORMATION_MENU) {
|
||||
send_menu(c, u"Information", INFORMATION_MENU_ID,
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version), false);
|
||||
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
|
||||
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu, false);
|
||||
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
|
||||
send_update_client_config(c);
|
||||
}
|
||||
@@ -552,31 +553,16 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
const auto& cmd = check_size_t<C_MenuItemInfoRequest_09>(data);
|
||||
|
||||
switch (cmd.menu_id) {
|
||||
case MAIN_MENU_ID:
|
||||
switch (cmd.item_id) {
|
||||
case MAIN_MENU_GO_TO_LOBBY:
|
||||
send_ship_info(c, u"Go to the lobby.");
|
||||
break;
|
||||
case MAIN_MENU_INFORMATION:
|
||||
send_ship_info(c, u"View server\ninformation.");
|
||||
break;
|
||||
case MAIN_MENU_PROXY_DESTINATIONS:
|
||||
send_ship_info(c, u"Connect to another\nserver.");
|
||||
break;
|
||||
case MAIN_MENU_DOWNLOAD_QUESTS:
|
||||
send_ship_info(c, u"Download a quest.");
|
||||
break;
|
||||
case MAIN_MENU_DISCONNECT:
|
||||
send_ship_info(c, u"End your session.");
|
||||
break;
|
||||
default:
|
||||
send_ship_info(c, u"Incorrect menu item ID.");
|
||||
break;
|
||||
case MenuID::MAIN:
|
||||
for (const auto& item : s->main_menu) {
|
||||
if (item.item_id == cmd.item_id) {
|
||||
send_ship_info(c, item.description);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case INFORMATION_MENU_ID:
|
||||
if (cmd.item_id == INFORMATION_MENU_GO_BACK) {
|
||||
case MenuID::INFORMATION:
|
||||
if (cmd.item_id == InformationMenuItemID::GO_BACK) {
|
||||
send_ship_info(c, u"Return to the\nmain menu.");
|
||||
} else {
|
||||
try {
|
||||
@@ -588,8 +574,8 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
}
|
||||
break;
|
||||
|
||||
case PROXY_DESTINATIONS_MENU_ID:
|
||||
if (cmd.item_id == PROXY_DESTINATIONS_MENU_GO_BACK) {
|
||||
case MenuID::PROXY_DESTINATIONS:
|
||||
if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) {
|
||||
send_ship_info(c, u"Return to the\nmain menu.");
|
||||
} else {
|
||||
try {
|
||||
@@ -602,7 +588,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
}
|
||||
break;
|
||||
|
||||
case QUEST_MENU_ID: {
|
||||
case MenuID::QUEST: {
|
||||
if (!s->quest_index) {
|
||||
send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id);
|
||||
break;
|
||||
@@ -616,6 +602,97 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuID::GAME: {
|
||||
shared_ptr<Lobby> game;
|
||||
try {
|
||||
game = s->find_lobby(cmd.item_id);
|
||||
} catch (const out_of_range& e) {
|
||||
send_ship_info(c, u"$C4Game no longer\nexists.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!game->is_game()) {
|
||||
send_ship_info(c, u"$C4Incorrect game ID");
|
||||
|
||||
} else {
|
||||
string info;
|
||||
for (size_t x = 0; x < game->max_clients; x++) {
|
||||
const auto& game_c = game->clients[x];
|
||||
if (game_c.get()) {
|
||||
auto player = game_c->game_data.player();
|
||||
auto name = encode_sjis(player->disp.name);
|
||||
if (game->flags & Lobby::Flag::EPISODE_3_ONLY) {
|
||||
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
|
||||
x + 1, name.c_str(), player->disp.level + 1);
|
||||
} else {
|
||||
info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n",
|
||||
x + 1, name.c_str(),
|
||||
abbreviation_for_char_class(player->disp.char_class),
|
||||
player->disp.level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int episode = game->episode;
|
||||
if (episode == 3) {
|
||||
episode = 4;
|
||||
} else if (episode == 0xFF) {
|
||||
episode = 3;
|
||||
}
|
||||
string secid_str = name_for_section_id(game->section_id);
|
||||
info += string_printf("Ep%d %c %s %s\n",
|
||||
episode,
|
||||
abbreviation_for_difficulty(game->difficulty),
|
||||
abbreviation_for_game_mode(game->mode),
|
||||
secid_str.c_str());
|
||||
|
||||
bool cheats_enabled = game->flags & Lobby::Flag::CHEATS_ENABLED;
|
||||
bool locked = !game->password.empty();
|
||||
if (cheats_enabled && locked) {
|
||||
info += "$C4Locked$C7, $C6cheats enabled$C7\n";
|
||||
} else if (cheats_enabled) {
|
||||
info += "$C6Cheats enabled$C7\n";
|
||||
} else if (locked) {
|
||||
info += "$C4Locked$C7\n";
|
||||
}
|
||||
|
||||
if (game->loading_quest) {
|
||||
if (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) {
|
||||
info += "$C6Quest: " + encode_sjis(game->loading_quest->name);
|
||||
} else {
|
||||
info += "$C4Quest: " + encode_sjis(game->loading_quest->name);
|
||||
}
|
||||
} else if (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) {
|
||||
info += "$C6Quest in progress";
|
||||
} else if (game->flags & Lobby::Flag::QUEST_IN_PROGRESS) {
|
||||
info += "$C4Quest in progress";
|
||||
}
|
||||
|
||||
send_ship_info(c, decode_sjis(info));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MenuID::PATCHES:
|
||||
// TODO: Find a way to provide desccriptions 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;
|
||||
}
|
||||
|
||||
default:
|
||||
send_ship_info(c, u"Incorrect menu ID.");
|
||||
break;
|
||||
@@ -630,9 +707,9 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
sizeof(C_MenuSelection), sizeof(C_MenuSelection) + 0x10 * (1 + uses_unicode));
|
||||
|
||||
switch (cmd.menu_id) {
|
||||
case MAIN_MENU_ID: {
|
||||
case MenuID::MAIN: {
|
||||
switch (cmd.item_id) {
|
||||
case MAIN_MENU_GO_TO_LOBBY: {
|
||||
case MainMenuItemID::GO_TO_LOBBY: {
|
||||
static const vector<string> version_to_port_name({
|
||||
"dc-lobby", "pc-lobby", "bb-lobby", "gc-lobby", "bb-lobby"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
@@ -642,18 +719,18 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case MAIN_MENU_INFORMATION:
|
||||
send_menu(c, u"Information", INFORMATION_MENU_ID,
|
||||
case MainMenuItemID::INFORMATION:
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version), true);
|
||||
c->flags |= Client::Flag::IN_INFORMATION_MENU;
|
||||
break;
|
||||
|
||||
case MAIN_MENU_PROXY_DESTINATIONS:
|
||||
send_menu(c, u"Proxy server", PROXY_DESTINATIONS_MENU_ID,
|
||||
case MainMenuItemID::PROXY_DESTINATIONS:
|
||||
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
|
||||
s->proxy_destinations_menu_for_version(c->version), false);
|
||||
break;
|
||||
|
||||
case MAIN_MENU_DOWNLOAD_QUESTS:
|
||||
case MainMenuItemID::DOWNLOAD_QUESTS:
|
||||
if (c->flags & Client::Flag::EPISODE_3) {
|
||||
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
|
||||
auto quests = s->quest_index->filter(
|
||||
@@ -665,14 +742,23 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// is always the download quest menu. (Episode 3 does actually
|
||||
// have online quests, but they don't use the file download
|
||||
// paradigm that all other versions use.)
|
||||
send_quest_menu(c, QUEST_MENU_ID, quests, true);
|
||||
send_quest_menu(c, MenuID::QUEST, quests, true);
|
||||
}
|
||||
} else {
|
||||
send_quest_menu(c, QUEST_FILTER_MENU_ID, quest_download_menu, true);
|
||||
send_quest_menu(c, MenuID::QUEST_FILTER, quest_download_menu, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case MAIN_MENU_DISCONNECT:
|
||||
case MainMenuItemID::PATCHES:
|
||||
send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu());
|
||||
break;
|
||||
|
||||
case MainMenuItemID::PROGRAMS:
|
||||
send_menu(c, u"Programs", MenuID::PROGRAMS,
|
||||
s->dol_file_index->menu(), false);
|
||||
break;
|
||||
|
||||
case MainMenuItemID::DISCONNECT:
|
||||
c->should_disconnect = true;
|
||||
break;
|
||||
|
||||
@@ -683,10 +769,10 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case INFORMATION_MENU_ID: {
|
||||
if (cmd.item_id == INFORMATION_MENU_GO_BACK) {
|
||||
case MenuID::INFORMATION: {
|
||||
if (cmd.item_id == InformationMenuItemID::GO_BACK) {
|
||||
c->flags &= ~Client::Flag::IN_INFORMATION_MENU;
|
||||
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu, false);
|
||||
|
||||
} else {
|
||||
try {
|
||||
@@ -698,9 +784,9 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case PROXY_DESTINATIONS_MENU_ID: {
|
||||
if (cmd.item_id == PROXY_DESTINATIONS_MENU_GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||
case MenuID::PROXY_DESTINATIONS: {
|
||||
if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu, false);
|
||||
|
||||
} else {
|
||||
const pair<string, uint16_t>* dest = nullptr;
|
||||
@@ -736,7 +822,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case GAME_MENU_ID: {
|
||||
case MenuID::GAME: {
|
||||
auto game = s->find_lobby(cmd.item_id);
|
||||
if (!game) {
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it no\nlonger exists.");
|
||||
@@ -799,7 +885,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case QUEST_FILTER_MENU_ID: {
|
||||
case MenuID::QUEST_FILTER: {
|
||||
if (!s->quest_index) {
|
||||
send_lobby_message_box(c, u"$C6Quests are not available.");
|
||||
break;
|
||||
@@ -815,11 +901,11 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
// Hack: assume the menu to be sent is the download quest menu if the
|
||||
// client is not in any lobby
|
||||
send_quest_menu(c, QUEST_MENU_ID, quests, !c->lobby_id);
|
||||
send_quest_menu(c, MenuID::QUEST, quests, !c->lobby_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case QUEST_MENU_ID: {
|
||||
case MenuID::QUEST: {
|
||||
if (!s->quest_index) {
|
||||
send_lobby_message_box(c, u"$C6Quests are not available.");
|
||||
break;
|
||||
@@ -903,8 +989,40 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case LOBBY_MENU_ID:
|
||||
// TODO;
|
||||
case MenuID::PATCHES:
|
||||
if (cmd.item_id == PatchesMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
|
||||
} else {
|
||||
if (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL) {
|
||||
throw runtime_error("client does not support send_function_call");
|
||||
}
|
||||
|
||||
send_function_call(
|
||||
c, s->function_code_index->menu_item_id_to_patch_function.at(cmd.item_id));
|
||||
send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu());
|
||||
}
|
||||
break;
|
||||
|
||||
case MenuID::PROGRAMS:
|
||||
if (cmd.item_id == ProgramsMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu, false);
|
||||
|
||||
} else {
|
||||
if (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL) {
|
||||
throw runtime_error("client does not support send_function_call");
|
||||
}
|
||||
|
||||
c->loading_dol_file = s->dol_file_index->item_id_to_file.at(cmd.item_id);
|
||||
|
||||
// Send the first function call, which triggers the process of loading a
|
||||
// DOL file. This function call determines the necessary base address
|
||||
// for loading the file.
|
||||
send_function_call(
|
||||
c,
|
||||
s->function_code_index->name_to_function.at("ReadMemoryWord"),
|
||||
{{"address", 0x80000034}}); // ArenaHigh from GC globals
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -917,6 +1035,11 @@ void process_change_lobby(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 84
|
||||
const auto& cmd = check_size_t<C_LobbySelection_84>(data);
|
||||
|
||||
if (cmd.menu_id != MenuID::LOBBY) {
|
||||
send_message_box(c, u"Incorrect menu ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> new_lobby;
|
||||
try {
|
||||
new_lobby = s->find_lobby(cmd.item_id);
|
||||
@@ -942,7 +1065,7 @@ void process_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
void process_information_menu_request_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 1F
|
||||
check_size_v(data.size(), 0);
|
||||
send_menu(c, u"Information", INFORMATION_MENU_ID,
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version), true);
|
||||
}
|
||||
|
||||
@@ -977,6 +1100,57 @@ void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
process_change_ship(s, c, command, flag, data);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DOL loading commands
|
||||
|
||||
static void send_dol_file_chunk(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint32_t start_addr) {
|
||||
size_t offset = start_addr - c->dol_base_addr;
|
||||
if (offset >= c->loading_dol_file->data.size()) {
|
||||
throw logic_error("DOL file offset beyond end of data");
|
||||
}
|
||||
size_t bytes_to_send = min<size_t>(0x7800, c->loading_dol_file->data.size() - offset);
|
||||
string data_to_send = c->loading_dol_file->data.substr(offset, bytes_to_send);
|
||||
|
||||
auto fn = s->function_code_index->name_to_function.at("WriteMemory");
|
||||
unordered_map<string, uint32_t> label_writes(
|
||||
{{"dest_addr", start_addr}, {"size", bytes_to_send}});
|
||||
send_function_call(c, fn, label_writes, data_to_send);
|
||||
|
||||
size_t progress_percent = ((offset + bytes_to_send) * 100) / c->loading_dol_file->data.size();
|
||||
string info = string_printf("Loading $C6%s$C7\n%zu%%%% complete",
|
||||
c->loading_dol_file->name.c_str(), progress_percent);
|
||||
send_ship_info(c, decode_sjis(info));
|
||||
}
|
||||
|
||||
void process_function_call_result(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t flag, const string& data) { // B3
|
||||
const auto& cmd = check_size_t<C_ExecuteCodeResult_B3>(data);
|
||||
if (flag == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto called_fn = s->function_code_index->index_to_function.at(flag);
|
||||
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);
|
||||
} else if (called_fn->name == "WriteMemory") {
|
||||
if (cmd.return_value >= c->dol_base_addr + c->loading_dol_file->data.size()) {
|
||||
auto fn = s->function_code_index->name_to_function.at("RunDOL");
|
||||
unordered_map<string, uint32_t> label_writes(
|
||||
{{"dol_base_ptr", c->dol_base_addr}});
|
||||
send_function_call(c, fn, label_writes);
|
||||
// The client will stop running PSO after this, so disconnect them
|
||||
c->should_disconnect = true;
|
||||
|
||||
} else {
|
||||
send_dol_file_chunk(s, c, cmd.return_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Quest commands
|
||||
|
||||
@@ -1018,7 +1192,7 @@ void process_quest_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
}
|
||||
|
||||
send_quest_menu(c, QUEST_FILTER_MENU_ID, *menu, false);
|
||||
send_quest_menu(c, MenuID::QUEST_FILTER, *menu, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,6 +1279,9 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
case GameVersion::GC: {
|
||||
const PSOPlayerDataGC* pd;
|
||||
if (flag == 4) { // Episode 3
|
||||
if (!(c->flags & Client::Flag::EPISODE_3)) {
|
||||
throw runtime_error("non-Episode 3 client sent Episode 3 player data");
|
||||
}
|
||||
const auto* pd3 = &check_size_t<PSOPlayerDataGCEp3>(data);
|
||||
c->game_data.ep3_config.reset(new Ep3Config(pd3->ep3_config));
|
||||
pd = reinterpret_cast<const PSOPlayerDataGC*>(pd3);
|
||||
@@ -1890,7 +2067,7 @@ static process_command_t dc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, process_server_time_request, nullptr, nullptr,
|
||||
nullptr, process_server_time_request, nullptr, process_function_call_result,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
@@ -1973,7 +2150,7 @@ static process_command_t pc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, process_server_time_request, nullptr, nullptr,
|
||||
nullptr, process_server_time_request, nullptr, process_function_call_result,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
@@ -2057,7 +2234,7 @@ static process_command_t gc_handlers[0x100] = {
|
||||
process_quest_barrier, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, process_server_time_request, nullptr, process_ignored_command,
|
||||
nullptr, process_server_time_request, nullptr, process_function_call_result,
|
||||
nullptr, nullptr, nullptr, process_ignored_command,
|
||||
process_ignored_command, nullptr, process_ep3_jukebox, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
@@ -2146,7 +2323,7 @@ static process_command_t bb_handlers[0x100] = {
|
||||
process_quest_barrier, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
nullptr, nullptr, nullptr, process_ignored_command,
|
||||
nullptr, nullptr, nullptr, process_function_call_result,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
+7
-6
@@ -332,7 +332,7 @@ void send_function_call(
|
||||
}
|
||||
|
||||
string data;
|
||||
uint8_t index = 0xFF;
|
||||
uint32_t index = 0;
|
||||
if (code.get()) {
|
||||
data = code->generate_client_command(label_writes, suffix);
|
||||
index = code->index;
|
||||
@@ -688,7 +688,7 @@ void send_card_search_result_t(
|
||||
location_string = string_printf(",BLOCK00,%s", encoded_server_name.c_str());
|
||||
}
|
||||
cmd.location_string = location_string;
|
||||
cmd.menu_id = LOBBY_MENU_ID;
|
||||
cmd.menu_id = MenuID::LOBBY;
|
||||
cmd.lobby_id = result->lobby_id;
|
||||
cmd.name = result->game_data.player()->disp.name;
|
||||
|
||||
@@ -789,7 +789,8 @@ void send_menu_t(
|
||||
((c->version == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) ||
|
||||
((c->version == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) ||
|
||||
((c->version == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) ||
|
||||
((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION))) {
|
||||
((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION)) ||
|
||||
((item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) && (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL))) {
|
||||
continue;
|
||||
}
|
||||
auto& e = entries.emplace_back();
|
||||
@@ -820,7 +821,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
vector<S_GameMenuEntry<CharT>> entries;
|
||||
{
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = GAME_MENU_ID;
|
||||
e.menu_id = MenuID::GAME;
|
||||
e.game_id = 0x00000000;
|
||||
e.difficulty_tag = 0x00;
|
||||
e.num_players = 0x00;
|
||||
@@ -839,7 +840,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
}
|
||||
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = GAME_MENU_ID;
|
||||
e.menu_id = MenuID::GAME;
|
||||
e.game_id = l->lobby_id;
|
||||
e.difficulty_tag = (l_is_ep3 ? 0x0A : (l->difficulty + 0x22));
|
||||
e.num_players = l->count_clients();
|
||||
@@ -941,7 +942,7 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
continue;
|
||||
}
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = LOBBY_MENU_ID;
|
||||
e.menu_id = MenuID::LOBBY;
|
||||
e.item_id = l->lobby_id;
|
||||
e.unused = 0;
|
||||
}
|
||||
|
||||
+111
-2
@@ -3,10 +3,11 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Network.hh>
|
||||
|
||||
#include "SendCommands.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "IPStackSimulator.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
@@ -237,3 +238,111 @@ void ServerState::set_port_configuration(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
const auto& d = config_json->as_dict();
|
||||
|
||||
shared_ptr<vector<MenuItem>> information_menu_pc(new vector<MenuItem>());
|
||||
shared_ptr<vector<MenuItem>> information_menu_gc(new vector<MenuItem>());
|
||||
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
|
||||
|
||||
information_menu_gc->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_pc->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), 0);
|
||||
information_menu_gc->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++;
|
||||
}
|
||||
}
|
||||
this->information_menu_pc = information_menu_pc;
|
||||
this->information_menu_gc = information_menu_gc;
|
||||
this->information_contents = information_contents;
|
||||
|
||||
this->proxy_destinations_menu_pc.emplace_back(ProxyDestinationsMenuItemID::GO_BACK,
|
||||
u"Go back", u"Return to the\nmain menu", 0);
|
||||
this->proxy_destinations_menu_gc.emplace_back(ProxyDestinationsMenuItemID::GO_BACK,
|
||||
u"Go back", u"Return to the\nmain menu", 0);
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("ProxyDestinations-GC")->as_dict()) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
this->proxy_destinations_menu_gc.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(netloc_str), 0);
|
||||
this->proxy_destinations_gc.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("ProxyDestinations-PC")->as_dict()) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
this->proxy_destinations_menu_pc.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(netloc_str), 0);
|
||||
this->proxy_destinations_pc.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-Patch")->as_string();
|
||||
this->proxy_destination_patch = parse_netloc(netloc_str);
|
||||
log(INFO, "Patch server proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : this->name_to_port_config) {
|
||||
if (it.second->version == GameVersion::PATCH) {
|
||||
it.second->behavior = ServerBehavior::PROXY_SERVER;
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
this->proxy_destination_patch.first = "";
|
||||
this->proxy_destination_patch.second = 0;
|
||||
}
|
||||
try {
|
||||
const string& netloc_str = d.at("ProxyDestination-BB")->as_string();
|
||||
this->proxy_destination_bb = parse_netloc(netloc_str);
|
||||
log(INFO, "BB proxy is enabled with destination %s", netloc_str.c_str());
|
||||
for (auto& it : this->name_to_port_config) {
|
||||
if (it.second->version == GameVersion::BB) {
|
||||
it.second->behavior = ServerBehavior::PROXY_SERVER;
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
this->proxy_destination_bb.first = "";
|
||||
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::REQUIRES_MESSAGE_BOXES);
|
||||
if (!this->proxy_destinations_pc.empty()) {
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::PC_ONLY);
|
||||
}
|
||||
if (!this->proxy_destinations_gc.empty()) {
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::GC_ONLY);
|
||||
}
|
||||
this->main_menu.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests",
|
||||
u"Download quests", MenuItem::Flag::INVISIBLE_ON_BB);
|
||||
if (!this->dol_file_index->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);
|
||||
}
|
||||
this->main_menu.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect",
|
||||
u"Disconnect", 0);
|
||||
|
||||
try {
|
||||
this->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());
|
||||
} catch (const out_of_range&) { }
|
||||
}
|
||||
|
||||
+8
-4
@@ -2,20 +2,21 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "Items.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "License.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "FunctionCompiler.hh"
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +49,7 @@ struct ServerState {
|
||||
RunShellBehavior run_shell_behavior;
|
||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const DOLFileIndex> dol_file_index;
|
||||
std::shared_ptr<const Ep3DataIndex> ep3_data_index;
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table;
|
||||
@@ -111,4 +113,6 @@ struct ServerState {
|
||||
|
||||
void set_port_configuration(
|
||||
const std::vector<PortConfiguration>& port_configs);
|
||||
|
||||
void create_menus(std::shared_ptr<const JSONObject> config_json);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -253,6 +255,93 @@ uint8_t npc_for_name(const u16string& name) {
|
||||
|
||||
|
||||
|
||||
const char* name_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
"HUmar",
|
||||
"HUnewearl",
|
||||
"HUcast",
|
||||
"RAmar",
|
||||
"RAcast",
|
||||
"RAcaseal",
|
||||
"FOmarl",
|
||||
"FOnewm",
|
||||
"FOnewearl",
|
||||
"HUcaseal",
|
||||
"FOmar",
|
||||
"RAmarl",
|
||||
};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* abbreviation_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
"HUmr",
|
||||
"HUnl",
|
||||
"HUcs",
|
||||
"RAmr",
|
||||
"RAcs",
|
||||
"RAcl",
|
||||
"FOml",
|
||||
"FOnm",
|
||||
"FOnl",
|
||||
"HUcl",
|
||||
"FOmr",
|
||||
"RAml",
|
||||
};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char* name_for_difficulty(uint8_t difficulty) {
|
||||
static const array<const char*, 4> names = {
|
||||
"Normal",
|
||||
"Hard",
|
||||
"Very Hard",
|
||||
"Ultimate",
|
||||
};
|
||||
try {
|
||||
return names.at(difficulty);
|
||||
} catch (const out_of_range&) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
char abbreviation_for_difficulty(uint8_t difficulty) {
|
||||
static const array<char, 4> names = {'N', 'H', 'V', 'U'};
|
||||
try {
|
||||
return names.at(difficulty);
|
||||
} catch (const out_of_range&) {
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char* abbreviation_for_game_mode(uint8_t mode) {
|
||||
static const array<const char*, 4> names = {
|
||||
"Nml",
|
||||
"Btl",
|
||||
"Chl",
|
||||
"Solo",
|
||||
};
|
||||
try {
|
||||
return names.at(mode);
|
||||
} catch (const out_of_range&) {
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t stack_size_for_item(uint8_t data0, uint8_t data1) {
|
||||
if (data0 == 4) {
|
||||
return 999999;
|
||||
|
||||
@@ -40,4 +40,12 @@ std::u16string u16name_for_npc(uint8_t npc);
|
||||
uint8_t npc_for_name(const std::string& name);
|
||||
uint8_t npc_for_name(const std::u16string& name);
|
||||
|
||||
const char* name_for_char_class(uint8_t cls);
|
||||
const char* abbreviation_for_char_class(uint8_t cls);
|
||||
|
||||
const char* name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
const char* abbreviation_for_game_mode(uint8_t);
|
||||
|
||||
std::string name_for_item(const ItemData& item, bool include_color_codes);
|
||||
|
||||
Reference in New Issue
Block a user