add DOL file loader

This commit is contained in:
Martin Michelsen
2022-06-01 13:00:43 -07:00
parent 40aa08bd4f
commit 562bc4a40c
20 changed files with 948 additions and 264 deletions
+3 -1
View File
@@ -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
View File
@@ -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);
+5 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+89
View File
@@ -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;
+8
View File
@@ -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);