refactor GameVersion and QuestScriptVersion into a single enum

This commit is contained in:
Martin Michelsen
2023-11-24 23:33:57 -08:00
parent 9097abf307
commit de4cb26c34
64 changed files with 3608 additions and 3431 deletions
+1 -1
View File
@@ -141,7 +141,7 @@ foreach(LogTestCase IN ITEMS ${LogTestCases})
add_test(
NAME ${LogTestCase}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_BINARY_DIR}/newserv replay-log ${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LogTestCase} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
endforeach()
file(GLOB ScriptTestCases ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
+1 -1
View File
@@ -3,6 +3,6 @@
#include <stdexcept>
#include <string>
inline void run_ar_code_translator(const std::string&) {
void run_ar_code_translator(const string& initial_directory, const string& use_file, const string&) {
throw std::runtime_error("resource_file is not available; install it and rebuild newserv");
}
+50 -39
View File
@@ -6,7 +6,7 @@
using namespace std;
void run_ar_code_translator(const std::string& initial_directory) {
void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command) {
string directory = initial_directory;
while (ends_with(directory, "/")) {
directory.resize(directory.size() - 1);
@@ -93,51 +93,62 @@ void run_ar_code_translator(const std::string& initial_directory) {
throw runtime_error("scan field too long; too many matches");
};
while (!feof(stdin)) {
if (!source_filename.empty()) {
fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str());
} else {
fprintf(stdout, "ar-trans:%s> ", directory.c_str());
auto handle_command = [&](const string& command) -> void {
auto tokens = split(command, ' ');
if (tokens.empty()) {
throw runtime_error("no command given");
}
fflush(stdout);
strip_trailing_whitespace(tokens[tokens.size() - 1]);
string command = fgets(stdin);
try {
strip_trailing_whitespace(command);
auto tokens = split(command, ' ');
if (tokens.empty()) {
throw runtime_error("no command given");
if (tokens[0] == "use") {
source_filename = tokens.at(1);
source_file = files.at(source_filename);
} else if (tokens[0] == "match") {
if (!source_file) {
throw runtime_error("no source file selected");
}
} else if (tokens[0] == "use") {
source_filename = tokens.at(1);
source_file = files.at(source_filename);
} else if (tokens[0] == "match") {
if (!source_file) {
throw runtime_error("no source file selected");
}
uint32_t source_addr = stoul(tokens.at(1), nullptr, 16);
for (const auto& it : files) {
if (it.second == source_file) {
log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr);
} else {
try {
uint32_t match_addr = find_match(it.second, source_addr);
log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr);
} catch (const exception& e) {
log.error("(%s) failed: %s", it.first.c_str(), e.what());
}
uint32_t source_addr = stoul(tokens.at(1), nullptr, 16);
for (const auto& it : files) {
if (it.second == source_file) {
log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr);
} else {
try {
uint32_t match_addr = find_match(it.second, source_addr);
log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr);
} catch (const exception& e) {
log.error("(%s) failed: %s", it.first.c_str(), e.what());
}
}
} else if (!tokens[0].empty()) {
throw runtime_error("unknown command");
}
} catch (const exception& e) {
log.error("Failed: %s", e.what());
} else if (!tokens[0].empty()) {
throw runtime_error("unknown command");
}
};
if (!use_file.empty()) {
source_filename = use_file;
source_file = files.at(source_filename);
}
fputc('\n', stdout);
if (!command.empty()) {
handle_command(command);
} else {
while (!feof(stdin)) {
if (!source_filename.empty()) {
fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str());
} else {
fprintf(stdout, "ar-trans:%s> ", directory.c_str());
}
fflush(stdout);
string command = fgets(stdin);
try {
handle_command(command);
} catch (const exception& e) {
log.error("Failed: %s", e.what());
}
}
fputc('\n', stdout);
}
}
+1 -1
View File
@@ -2,4 +2,4 @@
#include <string>
void run_ar_code_translator(const std::string& directory);
void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command);
+4 -5
View File
@@ -34,10 +34,10 @@ using namespace std;
CatSession::CatSession(
shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
GameVersion version,
Version version,
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
: Shell(base),
log(string_printf("[CatSession:%s] ", name_for_version(version)), proxy_server_log.min_level),
log(string_printf("[CatSession:%s] ", name_for_enum(version)), proxy_server_log.min_level),
channel(
version,
1,
@@ -74,11 +74,10 @@ void CatSession::dispatch_on_channel_input(
void CatSession::on_channel_input(
uint16_t command, uint32_t flag, std::string& data) {
if (this->channel.version != GameVersion::BB) {
if (!uses_v4_encryption(this->channel.version)) {
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
if ((this->channel.version == GameVersion::GC) ||
(this->channel.version == GameVersion::XB)) {
if (uses_v3_encryption(this->channel.version)) {
this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
this->channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
+1 -1
View File
@@ -21,7 +21,7 @@ public:
CatSession(
std::shared_ptr<struct event_base> base,
const struct sockaddr_storage& remote,
GameVersion version,
Version version,
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
virtual ~CatSession() = default;
+26 -18
View File
@@ -23,7 +23,7 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) {
}
Channel::Channel(
GameVersion version,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
@@ -44,7 +44,7 @@ Channel::Channel(
Channel::Channel(
struct bufferevent* bev,
GameVersion version,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
@@ -157,7 +157,7 @@ void Channel::disconnect() {
Channel::Message Channel::recv() {
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
size_t header_size = (this->version == GameVersion::BB) ? 8 : 4;
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
throw out_of_range("no command available");
@@ -172,7 +172,7 @@ Channel::Message Channel::recv() {
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if encryption
// is not yet enabled.
size_t command_physical_size = (this->crypt_in.get() && (version == GameVersion::BB))
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
? ((command_logical_size + 7) & ~7)
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
@@ -214,7 +214,7 @@ Channel::Message Channel::recv() {
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == GameVersion::BB) {
if (version == Version::BB_V4) {
command_data_log.info(
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(),
@@ -224,7 +224,7 @@ Channel::Message Channel::recv() {
command_data_log.info(
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(),
name_for_version(this->version),
name_for_enum(this->version),
header.command(this->version),
header.flag(this->version));
}
@@ -265,11 +265,20 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
size_t logical_size;
size_t send_data_size = 0;
switch (this->version) {
case GameVersion::DC:
case GameVersion::GC:
case GameVersion::XB: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (this->crypt_out.get()) {
if (this->crypt_out.get() &&
(this->version != Version::DC_NTE) &&
(this->version != Version::DC_V1_12_2000_PROTOTYPE) &&
(this->version != Version::DC_V1)) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
@@ -281,9 +290,9 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case GameVersion::PC:
case GameVersion::PATCH: {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3;
@@ -297,8 +306,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case GameVersion::BB: {
case Version::BB_V4: {
// BB has an annoying behavior here: command lengths must be multiples of
// 4, but the actual data length must be a multiple of 8. If the size
// field is not divisible by 8, 4 extra bytes are sent anyway. This
@@ -340,12 +348,12 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
}
if (version == GameVersion::BB) {
if (version == Version::BB_V4) {
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
this->name.c_str(), cmd, flag);
} else {
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
this->name.c_str(), name_for_version(version), cmd, flag);
this->name.c_str(), name_for_enum(version), cmd, flag);
}
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
@@ -370,7 +378,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent)
}
void Channel::send(const void* data, size_t size, bool silent) {
size_t header_size = (this->version == GameVersion::BB) ? 8 : 4;
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send(
header->command(this->version),
+3 -3
View File
@@ -15,7 +15,7 @@ struct Channel {
struct sockaddr_storage remote_addr;
bool is_virtual_connection;
GameVersion version;
Version version;
uint8_t language;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
@@ -39,7 +39,7 @@ struct Channel {
// Creates an unconnected channel
Channel(
GameVersion version,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
@@ -50,7 +50,7 @@ struct Channel {
// Creates a connected channel
Channel(
struct bufferevent* bev,
GameVersion version,
Version version,
uint8_t language,
on_command_received_t on_command_received,
on_error_t on_error,
+17 -20
View File
@@ -46,13 +46,13 @@ static void check_license_flags(shared_ptr<Client> c, uint32_t mask) {
}
}
static void check_version(shared_ptr<Client> c, GameVersion version) {
static void check_version(shared_ptr<Client> c, Version version) {
if (c->version() != version) {
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO.");
}
}
static void check_not_version(shared_ptr<Client> c, GameVersion version) {
static void check_not_version(shared_ptr<Client> c, Version version) {
if (c->version() == version) {
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO.");
}
@@ -65,7 +65,7 @@ static void check_is_game(shared_ptr<Lobby> l, bool is_game) {
}
static void check_is_ep3(shared_ptr<Client> c, bool is_ep3) {
if (c->config.check_flag(Client::Flag::IS_EPISODE_3) != is_ep3) {
if (::is_ep3(c->version()) != is_ep3) {
throw precondition_failed(is_ep3 ? "$C6This command can only\nbe used in Episode 3." : "$C6This command cannot\nbe used in Episode 3.");
}
}
@@ -282,7 +282,7 @@ static void server_command_qset_qclear(shared_ptr<Client> c, const std::string&
uint16_t flag_num = stoul(args, nullptr, 0);
if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
if (is_v1_or_v2(c->version())) {
G_SetQuestFlag_DC_PC_6x75 cmd = {{0x75, 0x02, 0x0000}, flag_num, should_set ? 0 : 1};
send_command_t(l, 0x60, 0x00, cmd);
} else {
@@ -355,7 +355,7 @@ static void proxy_command_qcall(shared_ptr<ProxyServer::LinkedSession> ses, cons
static void server_command_show_material_counts(shared_ptr<Client> c, const std::string&) {
auto p = c->game_data.character();
if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
if (is_v1_or_v2(c->version())) {
send_text_message_printf(c, "%hhu HP, %hhu TP",
p->get_material_usage(PSOBBCharacterFile::MaterialType::HP),
p->get_material_usage(PSOBBCharacterFile::MaterialType::TP));
@@ -429,8 +429,8 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
};
auto send_version_detect_or_send_call = [args, ses, send_call]() {
if (ses->version() == GameVersion::GC &&
ses->config.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) {
if (is_gc(ses->version()) &&
ses->config.specific_version == default_specific_version_for_version(ses->version(), -1)) {
auto s = ses->require_server_state();
send_function_call(
ses->client_channel,
@@ -475,7 +475,7 @@ static void server_command_persist(shared_ptr<Client> c, const std::string&) {
static void server_command_exit(shared_ptr<Client> c, const std::string&) {
auto l = c->require_lobby();
if (l->is_game()) {
if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (is_ep3(c->version())) {
c->channel.send(0xED, 0x00);
} else if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) {
G_UnusedHeader cmd = {0x73, 0x01, 0x0000};
@@ -496,7 +496,7 @@ static void server_command_exit(shared_ptr<Client> c, const std::string&) {
static void proxy_command_exit(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
if (ses->is_in_game) {
if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (is_ep3(ses->version())) {
ses->client_channel.send(0xED, 0x00);
} else if (ses->is_in_quest) {
G_UnusedHeader cmd = {0x73, 0x01, 0x0000};
@@ -595,10 +595,7 @@ static void proxy_command_lobby_event(shared_ptr<ProxyServer::LinkedSession> ses
send_text_message(ses->client_channel, "$C6No such lobby event.");
} else {
ses->config.override_lobby_event = new_event;
// This command is supported on all V3 versions except Ep1&2 Trial
if ((ses->version() == GameVersion::GC && !ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) ||
(ses->version() == GameVersion::XB) ||
(ses->version() == GameVersion::BB)) {
if (!is_v1_or_v2(ses->version())) {
ses->client_channel.send(0xDA, ses->config.override_lobby_event);
}
}
@@ -680,7 +677,7 @@ static void server_command_saverec(shared_ptr<Client> c, const std::string& args
}
static void server_command_playrec(shared_ptr<Client> c, const std::string& args) {
if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (!is_ep3(c->version())) {
send_text_message(c, "$C4This command can\nonly be used on\nEpisode 3");
return;
}
@@ -888,7 +885,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_is_game(l, false);
check_version(c, GameVersion::BB);
check_version(c, Version::BB_V4);
if (s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) {
send_text_message(l, "$C6Cheats are disabled\non this server");
@@ -988,7 +985,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
// TODO: implement this (and make sure the bank name is filesystem-safe)
/* static void server_command_change_bank(shared_ptr<Client> c, const std::string&) {
check_version(c, GameVersion::BB);
check_version(c, Version::BB_V4);
...
} */
@@ -997,7 +994,7 @@ static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::s
auto s = c->require_server_state();
auto l = c->require_lobby();
check_is_game(l, false);
check_not_version(c, GameVersion::BB);
check_not_version(c, Version::BB_V4);
vector<string> tokens = split(args, ' ');
if (tokens.size() != 3) {
@@ -1027,7 +1024,7 @@ static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::s
}
static void server_command_save(shared_ptr<Client> c, const std::string&) {
check_version(c, GameVersion::BB);
check_version(c, Version::BB_V4);
try {
c->game_data.save_character_file();
send_text_message(c, "Character data saved");
@@ -1373,7 +1370,7 @@ static void server_command_itemtable(shared_ptr<Client> c, const std::string&) {
check_is_leader(l, c);
if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE)) {
send_text_message(c, "Cannot switch item\ntables on this\nserver");
} else if (l->base_version == GameVersion::BB) {
} else if (l->base_version == Version::BB_V4) {
send_text_message(c, "Cannot use client\nitem table on BB");
} else if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
send_text_message(c, "Cannot use server\nitem tables if item\ntracking is off");
@@ -1405,7 +1402,7 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
auto s = ses->require_server_state();
check_proxy_cheats_allowed(s);
if (ses->version() == GameVersion::BB) {
if (ses->version() == Version::BB_V4) {
send_text_message(ses->client_channel, "$C6This command cannot\nbe used on the proxy\nserver in BB games");
return;
}
+27 -54
View File
@@ -21,34 +21,41 @@ const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
static atomic<uint64_t> next_id(1);
void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_version) {
void Client::Config::set_flags_for_version(Version version, int64_t sub_version) {
this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED);
this->set_flag(Flag::PROXY_CHAT_FILTER_ENABLED);
switch (sub_version) {
case -1: // Initial check (before sub_version recognition)
switch (version) {
case GameVersion::DC:
case Version::PC_PATCH:
case Version::BB_PATCH:
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
break;
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case GameVersion::GC:
break;
case GameVersion::XB:
// TODO: Do all versions of XB need this flag? US does, at least.
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case GameVersion::PC:
case Version::PC_V2:
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case GameVersion::PATCH:
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
break;
case GameVersion::BB:
case Version::XB_V3:
// TODO: Do all versions of XB need this flag? US does, at least.
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case Version::BB_V4:
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::SAVE_ENABLED);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
@@ -60,14 +67,12 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
case 0x20: // DCNTE, possibly also DCv1 JP
case 0x21: // DCv1 US
this->set_flag(Flag::IS_DC_V1);
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
// In the case of DCNTE, the IS_DC_TRIAL_EDITION flag is already set when
// we get here
break;
case 0x23: // DCv1 EU
this->set_flag(Flag::IS_DC_V1);
this->set_flag(Flag::NO_D6);
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
break;
@@ -82,7 +87,7 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.02, at least one version of XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, XB US
case 0x34: // GC Ep1&2 JP v1.03
// In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is
@@ -105,7 +110,6 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
break;
case 0x40: // GC Ep3 JP and Trial Edition
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::IS_EPISODE_3);
this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
// sub_version can't be used to tell JP final and Trial Edition apart; we
@@ -114,14 +118,12 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
break;
case 0x41: // GC Ep3 US
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::IS_EPISODE_3);
this->set_flag(Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::IS_EPISODE_3);
this->set_flag(Flag::NO_SEND_FUNCTION_CALL);
break;
default:
@@ -132,7 +134,7 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
Client::Client(
shared_ptr<Server> server,
struct bufferevent* bev,
GameVersion version,
Version version,
ServerBehavior server_behavior)
: server(server),
id(next_id++),
@@ -198,7 +200,7 @@ Client::~Client() {
}
void Client::reschedule_save_game_data_event() {
if (this->version() == GameVersion::BB) {
if (this->version() == Version::BB_V4) {
struct timeval tv = usecs_to_timeval(60000000); // 1 minute
event_add(this->save_game_data_event.get(), &tv);
}
@@ -211,39 +213,10 @@ void Client::reschedule_ping_and_timeout_events() {
event_add(this->idle_timeout_event.get(), &idle_tv);
}
QuestScriptVersion Client::quest_version() const {
switch (this->version()) {
case GameVersion::DC:
if (this->config.check_flag(Flag::IS_DC_TRIAL_EDITION)) {
return QuestScriptVersion::DC_NTE;
} else if (this->config.check_flag(Flag::IS_DC_V1)) {
return QuestScriptVersion::DC_V1;
} else {
return QuestScriptVersion::DC_V2;
}
case GameVersion::PC:
return QuestScriptVersion::PC_V2;
case GameVersion::GC:
if (this->config.check_flag(Flag::IS_GC_TRIAL_EDITION)) {
return QuestScriptVersion::GC_NTE;
} else if (this->config.check_flag(Flag::IS_EPISODE_3)) {
return QuestScriptVersion::GC_EP3;
} else {
return QuestScriptVersion::GC_V3;
}
case GameVersion::XB:
return QuestScriptVersion::XB_V3;
case GameVersion::BB:
return QuestScriptVersion::BB_V4;
default:
throw logic_error("client\'s game version does not have a quest version");
}
}
void Client::set_license(shared_ptr<License> l) {
this->license = l;
this->game_data.guild_card_number = this->license->serial_number;
if (this->version() == GameVersion::BB) {
if (this->version() == Version::BB_V4) {
this->game_data.bb_username = this->license->bb_username;
}
}
@@ -309,7 +282,7 @@ void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) {
}
void Client::save_game_data() {
if (this->version() != GameVersion::BB) {
if (this->version() != Version::BB_V4) {
throw logic_error("save_game_data called for non-BB client");
}
if (this->game_data.character(false)) {
@@ -322,7 +295,7 @@ void Client::dispatch_send_ping(evutil_socket_t, short, void* ctx) {
}
void Client::send_ping() {
if (this->version() != GameVersion::PATCH) {
if (!is_patch(this->version())) {
this->log.info("Sending ping command");
// The game doesn't use this timestamp; we only use it for debugging purposes
be_uint64_t timestamp = now();
+3 -11
View File
@@ -30,14 +30,7 @@ struct Client : public std::enable_shared_from_this<Client> {
// clang-format off
// Version-related flags
IS_DC_TRIAL_EDITION = 0x0000000000000001,
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
IS_DC_V1_PROTOTYPE = 0x0000000000000004,
IS_DC_V1 = 0x0000000000000008,
IS_GC_TRIAL_EDITION = 0x0000000000000010,
IS_EP3_TRIAL_EDITION = 0x0000000000000020,
IS_EPISODE_3 = 0x0000000000000040,
IS_BB_PATCH = 0x0000000000000080,
NO_D6_AFTER_LOBBY = 0x0000000000000100,
NO_D6 = 0x0000000000000200,
FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400,
@@ -114,7 +107,7 @@ struct Client : public std::enable_shared_from_this<Client> {
this->enabled_flags ^= static_cast<uint64_t>(flag);
}
void set_flags_for_version(GameVersion version, int64_t sub_version);
void set_flags_for_version(Version version, int64_t sub_version);
template <size_t Bytes>
void parse_from(const parray<uint8_t, Bytes>& data) {
@@ -220,20 +213,19 @@ struct Client : public std::enable_shared_from_this<Client> {
Client(
std::shared_ptr<Server> server,
struct bufferevent* bev,
GameVersion version,
Version version,
ServerBehavior server_behavior);
~Client();
void reschedule_save_game_data_event();
void reschedule_ping_and_timeout_events();
inline GameVersion version() const {
inline Version version() const {
return this->channel.version;
}
inline uint8_t language() const {
return this->channel.language;
}
QuestScriptVersion quest_version() const;
void set_license(std::shared_ptr<License> l);
+1
View File
@@ -4380,6 +4380,7 @@ struct G_Unknown_6x5C {
} __packed__;
// 6x5D: Drop meseta or stacked item
// On DC NTE, this command has the same format, but is subcommand 6x4F instead.
struct G_DropStackedItem_DC_6x5D {
G_ClientIDHeader header;
+5 -5
View File
@@ -242,8 +242,7 @@ void Server::send_6xB4x46() const {
this->send(cmd46);
}
string Server::prepare_6xB6x41_map_definition(
shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_trial) {
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_trial) {
auto vm = map->version(language);
const auto& compressed = vm->compressed(is_trial);
@@ -255,7 +254,7 @@ string Server::prepare_6xB6x41_map_definition(
return std::move(w.str());
}
void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) const {
void Server::send_commands_for_joining_spectator(Channel& ch) const {
bool should_send_state = true;
if (this->setup_phase == SetupPhase::REGISTRATION) {
// If registration is still in progress, we only need to send the map data
@@ -267,7 +266,8 @@ void Server::send_commands_for_joining_spectator(Channel& ch, bool is_trial) con
}
if (this->last_chosen_map) {
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, is_trial);
string data = this->prepare_6xB6x41_map_definition(
this->last_chosen_map, ch.language, (ch.version == Version::GC_EP3_TRIAL_EDITION));
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number);
ch.send(0x6C, 0x00, data);
}
@@ -2349,7 +2349,7 @@ void Server::send_6xB6x41_to_all_clients() const {
}
if (map_commands_by_language[c->language()].empty()) {
map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition(
this->last_chosen_map, c->language(), l->check_flag(Lobby::Flag::IS_EP3_TRIAL));
this->last_chosen_map, c->language(), (l->base_version == Version::GC_EP3_TRIAL_EDITION));
}
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number);
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]);
+1 -1
View File
@@ -112,7 +112,7 @@ public:
this->send(&cmd, cmd.header.size * 4);
}
void send(const void* data, size_t size) const;
void send_commands_for_joining_spectator(Channel& ch, bool is_trial) const;
void send_commands_for_joining_spectator(Channel& ch) const;
void force_battle_result(uint8_t surrendered_client_id, bool set_winner);
void force_destroy_field_character(uint8_t client_id, size_t set_index);
+5 -11
View File
@@ -701,10 +701,7 @@ void Tournament::send_all_state_updates() const {
// Note: The last check here is to make sure the client is still linked
// with this instance of the tournament - an intervening shell command
// `reload ep3` could have changed the client's linkage
if (c &&
c->config.check_flag(Client::Flag::IS_EPISODE_3) &&
!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) &&
(c->ep3_tournament_team.lock() == team)) {
if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) {
send_ep3_confirm_tournament_entry(c, this->shared_from_this());
}
}
@@ -715,10 +712,7 @@ void Tournament::send_all_state_updates_on_deletion() const {
for (const auto& team : this->teams) {
for (const auto& player : team->players) {
auto c = player.client.lock();
if (c &&
c->config.check_flag(Client::Flag::IS_EPISODE_3) &&
!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) &&
(c->ep3_tournament_team.lock() == team)) {
if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) {
send_ep3_confirm_tournament_entry(c, nullptr);
}
}
@@ -916,7 +910,7 @@ shared_ptr<Tournament::Team> TournamentIndex::team_for_serial_number(uint32_t se
}
void TournamentIndex::link_client(shared_ptr<Client> c) {
if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (!is_ep3(c->version())) {
return;
}
@@ -927,7 +921,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
if (player.serial_number == c->license->serial_number) {
c->ep3_tournament_team = team;
player.client = c;
if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) {
if (c->version() == Version::GC_EP3) {
send_ep3_confirm_tournament_entry(c, tourn);
}
return;
@@ -936,7 +930,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
throw logic_error("tournament team found for player, but player not found on team");
} else {
c->ep3_tournament_team.reset();
if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) {
if (c->version() == Version::GC_EP3) {
send_ep3_confirm_tournament_entry(c, nullptr);
}
}
+17 -7
View File
@@ -16,14 +16,14 @@ ItemCreator::ItemCreator(
shared_ptr<const WeaponRandomSet> weapon_random_set,
shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
shared_ptr<const ItemParameterTable> item_parameter_table,
GameVersion version,
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
uint8_t section_id,
uint32_t random_seed,
shared_ptr<const BattleRules> restrictions)
: log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_version(version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id)),
: log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id)),
version(version),
episode(episode),
mode(mode),
@@ -786,17 +786,27 @@ void ItemCreator::generate_unit_stars_tables() {
size_t star_base_index;
uint8_t num_units;
switch (this->version) {
case GameVersion::DC:
case GameVersion::PC:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_NTE:
throw logic_error("unknown parameters for version");
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
throw logic_error("ItemCreator cannot be created for Episode 3 games");
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
star_base_index = 0x1D1;
num_units = 0x44;
break;
case GameVersion::GC:
case GameVersion::XB:
case Version::GC_V3:
case Version::XB_V3:
star_base_index = 0x2AF;
num_units = 0x48;
break;
case GameVersion::BB:
case Version::BB_V4:
star_base_index = 0x37D;
num_units = 0x64;
break;
+3 -3
View File
@@ -19,7 +19,7 @@ public:
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
std::shared_ptr<const ItemParameterTable> item_parameter_table,
GameVersion version,
Version version,
Episode episode,
GameMode mode,
uint8_t difficulty,
@@ -50,7 +50,7 @@ public:
private:
PrefixedLogger log;
GameVersion version;
Version version;
Episode episode;
GameMode mode;
uint8_t difficulty;
@@ -77,7 +77,7 @@ private:
std::unordered_set<uint16_t> destroyed_boxes;
inline bool is_v3() const {
return (this->version != GameVersion::DC) && (this->version != GameVersion::PC);
return !is_v1_or_v2(this->version);
}
bool are_rare_drops_allowed() const;
+9 -11
View File
@@ -287,11 +287,10 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
}
}
void ItemData::decode_for_version(GameVersion from_version) {
bool is_v2 = (from_version == GameVersion::DC) || (from_version == GameVersion::PC);
void ItemData::decode_for_version(Version from_version) {
uint8_t encoded_v2_data = this->get_encoded_v2_data();
bool should_decode_v2_data = is_v2 && (encoded_v2_data != 0x00) && this->has_encoded_v2_data();
bool should_decode_v2_data = (is_v1(from_version) || is_v2(from_version)) &&
(encoded_v2_data != 0x00) && this->has_encoded_v2_data();
switch (this->data1[0]) {
case 0x00:
@@ -314,12 +313,12 @@ void ItemData::decode_for_version(GameVersion from_version) {
this->data1[1] = encoded_v2_data + 0x2B;
}
if (from_version == GameVersion::GC) {
if (is_big_endian(from_version)) {
// PSO GC erroneously byteswaps the data2d field, even though it's actually
// just four individual bytes, so we correct for that here.
this->data2d = bswap32(this->data2d);
} else if (from_version == GameVersion::DC || from_version == GameVersion::PC) {
} else if (is_v1(from_version) || is_v2(from_version)) {
// PSO PC encodes mags in a tediously annoying manner. The first four bytes are the same, but then...
// V2: pHHHHHHHHHHHHHHc pIIIIIIIIIIIIIIc JJJJJJJJJJJJJJJc KKKKKKKKKKKKKKKc QQQQQQQQ QQQQQQQQ YYYYYYYY pYYYYYYY
// V3: HHHHHHHHHHHHHHHH IIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJ KKKKKKKKKKKKKKKK YYYYYYYY QQQQQQQQ PPPPPPPP CCCCCCCC
@@ -369,9 +368,8 @@ void ItemData::decode_for_version(GameVersion from_version) {
}
}
void ItemData::encode_for_version(GameVersion to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
bool is_v2 = (to_version == GameVersion::DC) || (to_version == GameVersion::PC);
bool should_encode_v2_data = is_v2 && !this->has_encoded_v2_data();
void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
bool should_encode_v2_data = (is_v1(to_version) || is_v2(to_version)) && !this->has_encoded_v2_data();
switch (this->data1[0]) {
case 0x00:
@@ -410,9 +408,9 @@ void ItemData::encode_for_version(GameVersion to_version, shared_ptr<const ItemP
// This logic is the inverse of the corresponding logic in
// decode_for_version; see that function for a description of what's
// going on here.
if (to_version == GameVersion::GC) {
if (is_big_endian(to_version)) {
this->data2d = bswap32(this->data2d);
} else if (to_version == GameVersion::DC || to_version == GameVersion::PC) {
} else if (is_v1(to_version) || is_v2(to_version)) {
this->data1w[2] = (this->data1w[2] & 0x7FFE) | ((this->data2[2] << 14) & 0x8000) | (this->data2[3] & 1);
this->data1w[3] = (this->data1w[3] & 0x7FFE) | ((this->data2[2] << 13) & 0x8000) | ((this->data2[3] >> 1) & 1);
this->data1w[4] = (this->data1w[4] & 0xFFFE) | ((this->data2[3] >> 2) & 1);
+2 -2
View File
@@ -152,8 +152,8 @@ struct ItemData { // 0x14 bytes
uint8_t mag_photon_blast_for_slot(uint8_t slot) const;
bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const;
void add_mag_photon_blast(uint8_t pb_num);
void decode_for_version(GameVersion version);
void encode_for_version(GameVersion version, std::shared_ptr<const ItemParameterTable> item_parameter_table);
void decode_for_version(Version version);
void encode_for_version(Version version, std::shared_ptr<const ItemParameterTable> item_parameter_table);
uint8_t get_encoded_v2_data() const;
bool has_encoded_v2_data() const;
+15 -31
View File
@@ -106,7 +106,7 @@ const array<const char*, 0x11> name_for_s_rank_special = {
};
std::string ItemNameIndex::describe_item(
GameVersion version,
Version version,
const ItemData& item,
std::shared_ptr<const ItemParameterTable> item_parameter_table) const {
if (item.data1[0] == 0x04) {
@@ -171,20 +171,12 @@ std::string ItemNameIndex::describe_item(
try {
auto meta = this->primary_identifier_index.at(primary_identifier);
const string* name;
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
name = &meta->v2_name;
break;
case GameVersion::GC:
case GameVersion::XB:
name = &meta->v3_name;
break;
case GameVersion::BB:
name = &meta->v4_name;
break;
default:
throw logic_error("invalid game version");
if (is_v4(version)) {
name = &meta->v4_name;
} else if (is_v3(version)) {
name = &meta->v3_name;
} else {
name = &meta->v2_name;
}
if (name->empty()) {
throw out_of_range("item does not exist");
@@ -368,7 +360,7 @@ std::string ItemNameIndex::describe_item(
}
}
ItemData ItemNameIndex::parse_item_description(GameVersion version, const std::string& desc) const {
ItemData ItemNameIndex::parse_item_description(Version version, const std::string& desc) const {
try {
return this->parse_item_description_phase(version, desc, false);
} catch (const exception& e1) {
@@ -406,7 +398,7 @@ ItemData ItemNameIndex::parse_item_description(GameVersion version, const std::s
}
}
ItemData ItemNameIndex::parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const {
ItemData ItemNameIndex::parse_item_description_phase(Version version, const std::string& description, bool skip_special) const {
ItemData ret;
ret.data1d.clear(0);
ret.id = 0xFFFFFFFF;
@@ -468,20 +460,12 @@ ItemData ItemNameIndex::parse_item_description_phase(GameVersion version, const
}
const map<string, shared_ptr<ItemMetadata>>* name_index;
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
name_index = &this->v2_name_index;
break;
case GameVersion::GC:
case GameVersion::XB:
name_index = &this->v3_name_index;
break;
case GameVersion::BB:
name_index = &this->v4_name_index;
break;
default:
throw logic_error("invalid game version");
if (is_v4(version)) {
name_index = &this->v4_name_index;
} else if (is_v3(version)) {
name_index = &this->v3_name_index;
} else {
name_index = &this->v2_name_index;
}
auto name_it = name_index->lower_bound(desc);
+3 -3
View File
@@ -17,13 +17,13 @@ public:
ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names);
std::string describe_item(
GameVersion version,
Version version,
const ItemData& item,
std::shared_ptr<const ItemParameterTable> item_parameter_table = nullptr) const;
ItemData parse_item_description(GameVersion version, const std::string& description) const;
ItemData parse_item_description(Version version, const std::string& description) const;
private:
ItemData parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const;
ItemData parse_item_description_phase(Version version, const std::string& description, bool skip_special) const;
struct ItemMetadata {
uint32_t primary_identifier;
+7 -7
View File
@@ -14,7 +14,8 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
// used item. On GC and later versions, this does not happen, so we should
// delete the item here.
bool should_delete_item = (c->version() != GameVersion::DC) && (c->version() != GameVersion::PC);
bool is_v3_or_later = is_v3(c->version()) || is_v4(c->version());
bool should_delete_item = is_v3_or_later;
auto player = c->game_data.character();
auto& item = player->inventory.items[item_index];
@@ -43,7 +44,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
// be no way to disable this behavior, so there's no way for the server to
// get an accurate picture of what's actually in the player's inventory, so
// there's no way to know if we would be enforcing the correct grind limit.
if ((c->version() != GameVersion::DC) && (c->version() != GameVersion::PC)) {
if (is_v3_or_later) {
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
if (weapon.data.data1[3] >= weapon_def.max_grind) {
@@ -54,7 +55,6 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
auto p = c->game_data.character();
bool track_non_hp_tp_materials = (c->version() != GameVersion::DC) && (c->version() != GameVersion::PC);
using Type = PSOBBCharacterFile::MaterialType;
Type type;
@@ -83,7 +83,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
break;
case 6: // Hit Material (v1/v2) or Luck Material (v3/v4)
type = Type::LUCK;
if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) {
if (!is_v3_or_later) {
// Hit material doesn't exist on v3/v4, but we'll ignore type anyway
// in this case because track_non_hp_tp_materials is false
p->disp.stats.char_stats.ata += 2;
@@ -93,7 +93,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
break;
case 7: // Luck Material (v1/v2)
type = Type::LUCK;
if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) {
if (!is_v3_or_later) {
p->disp.stats.char_stats.lck += 2;
} else {
throw runtime_error("unknown material used");
@@ -102,7 +102,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
default:
throw runtime_error("unknown material used");
}
if (track_non_hp_tp_materials || (type == Type::HP) || (type == Type::TP)) {
if (is_v3_or_later || (type == Type::HP) || (type == Type::TP)) {
p->set_material_usage(type, p->get_material_usage(type) + 1);
}
@@ -256,7 +256,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
if (should_delete_item) {
// Allow overdrafting meseta if the client is not BB, since the server isn't
// informed when meseta is added or removed from the bank.
player->remove_item(item.data.id, 1, c->version() != GameVersion::BB);
player->remove_item(item.data.id, 1, !is_v4(c->version()));
}
}
+33 -19
View File
@@ -17,8 +17,8 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
min_level(0),
max_level(0xFFFFFFFF),
next_game_item_id(0x00810000),
base_version(GameVersion::GC),
allowed_versions(0xFFFF),
base_version(Version::GC_V3),
allowed_versions(0x0000),
section_id(0),
episode(Episode::NONE),
mode(GameMode::NORMAL),
@@ -56,19 +56,36 @@ void Lobby::create_item_creator() {
shared_ptr<const RareItemSet> rare_item_set;
shared_ptr<const CommonItemSet> common_item_set;
if (this->base_version == GameVersion::BB) {
common_item_set = s->common_item_set_v3;
rare_item_set = s->rare_item_sets.at("rare-table-v4");
} else if (this->base_version == GameVersion::GC || this->base_version == GameVersion::XB) {
common_item_set = s->common_item_set_v3;
rare_item_set = s->rare_item_sets.at("rare-table-v3");
} else if (!this->check_flag(Lobby::Flag::USE_DCV1_RARE_TABLE)) {
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v2");
} else {
// TODO: We should probably have a v1 common item set at some point too
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v1");
switch (this->base_version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
throw runtime_error("cannot create item creator for this base version");
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
// TODO: We should probably have a v1 common item set at some point too
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v1");
break;
case Version::DC_V2:
case Version::PC_V2:
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v2");
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
common_item_set = s->common_item_set_v3;
rare_item_set = s->rare_item_sets.at("rare-table-v3");
break;
case Version::BB_V4:
common_item_set = s->common_item_set_v3;
rare_item_set = s->rare_item_sets.at("rare-table-v4");
break;
default:
throw logic_error("invalid lobby base version");
}
this->item_creator.reset(new ItemCreator(
common_item_set,
@@ -95,7 +112,7 @@ void Lobby::create_ep3_server() {
this->log.info("Recreating Episode 3 server state");
}
auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr;
bool is_trial = this->check_flag(Lobby::Flag::IS_EP3_TRIAL);
bool is_trial = this->base_version == Version::GC_EP3_TRIAL_EDITION;
Episode3::Server::Options options = {
.card_index = is_trial ? s->ep3_card_index_trial : s->ep3_card_index,
.map_index = s->ep3_map_index,
@@ -355,9 +372,6 @@ uint32_t Lobby::generate_item_id(uint8_t client_id) {
}
void Lobby::on_item_id_generated_externally(uint32_t item_id) {
if (this->base_version == GameVersion::BB) {
throw logic_error("BB games cannot have externally-generated item IDs");
}
// Note: The client checks for the range (0x00010000, 0x02010000) here, but
// server-side item drop logic uses 0x00810000 as its base ID, so we restrict
// the range further here.
+4 -7
View File
@@ -37,17 +37,14 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also
SPECTATORS_FORBIDDEN = 0x00004000,
START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000,
IS_EP3_TRIAL = 0x00010000,
DROPS_ENABLED = 0x00020000,
CANNOT_CHANGE_DROPS_ENABLED = 0x00040000,
CANNOT_CHANGE_ITEM_TABLE = 0x00080000,
CANNOT_CHANGE_CHEAT_MODE = 0x00100000,
USE_DCV1_RARE_TABLE = 0x00200000,
// Flags used only for lobbies
PUBLIC = 0x01000000,
DEFAULT = 0x02000000,
V2_AND_LATER = 0x04000000, // Lobby does not appear on v1
IS_OVERFLOW = 0x08000000,
};
@@ -73,10 +70,10 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
parray<le_uint32_t, 0x20> variations;
// Game config
GameVersion base_version;
Version base_version;
// Bits in allowed_versions specify who is allowed to join this game. The
// bits are indexed as (1 << version), where version is a value from the
// QuestScriptVersion enum.
// Version enum.
uint16_t allowed_versions;
uint8_t section_id;
Episode episode;
@@ -164,10 +161,10 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
return this->episode == Episode::EP3;
}
[[nodiscard]] inline bool version_is_allowed(QuestScriptVersion v) const {
[[nodiscard]] inline bool version_is_allowed(Version v) const {
return this->allowed_versions & (1 << static_cast<size_t>(v));
}
inline void allow_version(QuestScriptVersion v) {
inline void allow_version(Version v) {
this->allowed_versions |= (1 << static_cast<size_t>(v));
}
+1006 -1266
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -85,7 +85,7 @@ string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const {
string item_name;
try {
auto item = ItemCreator::base_item_for_specialized_box(this->param4, this->param5, this->param6);
item_name = name_index ? name_index->describe_item(GameVersion::BB, item) : item.hex();
item_name = name_index ? name_index->describe_item(Version::BB_V4, item) : item.hex();
} catch (const exception& e) {
item_name = string_printf("(failed: %s)", e.what());
}
+128 -76
View File
@@ -17,40 +17,54 @@ PSOCommandHeader::PSOCommandHeader() {
this->bb.flag = 0;
}
uint16_t PSOCommandHeader::command(GameVersion version) const {
uint16_t PSOCommandHeader::command(Version version) const {
switch (version) {
case GameVersion::DC:
return this->dc.command;
case GameVersion::GC:
return this->gc.command;
case GameVersion::XB:
return this->xb.command;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
return this->pc.command;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.command;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->gc.command;
case Version::XB_V3:
return this->xb.command;
case Version::BB_V4:
return this->bb.command;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_command(GameVersion version, uint16_t command) {
void PSOCommandHeader::set_command(Version version, uint16_t command) {
switch (version) {
case GameVersion::DC:
this->dc.command = command;
break;
case GameVersion::GC:
this->gc.command = command;
break;
case GameVersion::XB:
this->xb.command = command;
break;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
this->pc.command = command;
break;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.command = command;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
this->gc.command = command;
break;
case Version::XB_V3:
this->xb.command = command;
break;
case Version::BB_V4:
this->bb.command = command;
break;
default:
@@ -58,40 +72,54 @@ void PSOCommandHeader::set_command(GameVersion version, uint16_t command) {
}
}
uint16_t PSOCommandHeader::size(GameVersion version) const {
uint16_t PSOCommandHeader::size(Version version) const {
switch (version) {
case GameVersion::DC:
return this->dc.size;
case GameVersion::GC:
return this->gc.size;
case GameVersion::XB:
return this->xb.size;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
return this->pc.size;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.size;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->gc.size;
case Version::XB_V3:
return this->xb.size;
case Version::BB_V4:
return this->bb.size;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_size(GameVersion version, uint32_t size) {
void PSOCommandHeader::set_size(Version version, uint32_t size) {
switch (version) {
case GameVersion::DC:
this->dc.size = size;
break;
case GameVersion::GC:
this->gc.size = size;
break;
case GameVersion::XB:
this->xb.size = size;
break;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
this->pc.size = size;
break;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.size = size;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
this->gc.size = size;
break;
case Version::XB_V3:
this->xb.size = size;
break;
case Version::BB_V4:
this->bb.size = size;
break;
default:
@@ -99,40 +127,54 @@ void PSOCommandHeader::set_size(GameVersion version, uint32_t size) {
}
}
uint32_t PSOCommandHeader::flag(GameVersion version) const {
uint32_t PSOCommandHeader::flag(Version version) const {
switch (version) {
case GameVersion::DC:
return this->dc.flag;
case GameVersion::GC:
return this->gc.flag;
case GameVersion::XB:
return this->xb.flag;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
return this->pc.flag;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc.flag;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->gc.flag;
case Version::XB_V3:
return this->xb.flag;
case Version::BB_V4:
return this->bb.flag;
default:
throw logic_error("unknown game version");
}
}
void PSOCommandHeader::set_flag(GameVersion version, uint32_t flag) {
void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
switch (version) {
case GameVersion::DC:
this->dc.flag = flag;
break;
case GameVersion::GC:
this->gc.flag = flag;
break;
case GameVersion::XB:
this->xb.flag = flag;
break;
case GameVersion::PC:
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2:
this->pc.flag = flag;
break;
case GameVersion::BB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
this->dc.flag = flag;
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
this->gc.flag = flag;
break;
case Version::XB_V3:
this->xb.flag = flag;
break;
case Version::BB_V4:
this->bb.flag = flag;
break;
default:
@@ -157,16 +199,22 @@ void check_size_v(size_t size, size_t min_size, size_t max_size) {
}
std::string prepend_command_header(
GameVersion version,
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
const std::string& data) {
StringWriter ret;
switch (version) {
case GameVersion::DC:
case GameVersion::GC:
case GameVersion::XB: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
PSOCommandHeaderDCV3 header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
@@ -178,8 +226,10 @@ std::string prepend_command_header(
ret.put(header);
break;
}
case GameVersion::PC:
case GameVersion::PATCH: {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::PC_V2: {
PSOCommandHeaderPC header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
@@ -191,7 +241,8 @@ std::string prepend_command_header(
ret.put(header);
break;
}
case GameVersion::BB: {
case Version::BB_V4: {
PSOCommandHeaderBB header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 3) & ~3;
@@ -203,6 +254,7 @@ std::string prepend_command_header(
ret.put(header);
break;
}
default:
throw logic_error("unimplemented game version in prepend_command_header");
}
+9 -9
View File
@@ -34,14 +34,14 @@ union PSOCommandHeader {
PSOCommandHeaderDCV3 xb;
PSOCommandHeaderBB bb;
uint16_t command(GameVersion version) const;
void set_command(GameVersion version, uint16_t command);
uint16_t size(GameVersion version) const;
void set_size(GameVersion version, uint32_t size);
uint32_t flag(GameVersion version) const;
void set_flag(GameVersion version, uint32_t flag);
static inline size_t header_size(GameVersion version) {
return (version == GameVersion::BB) ? 8 : 4;
uint16_t command(Version version) const;
void set_command(Version version, uint16_t command);
uint16_t size(Version version) const;
void set_size(Version version, uint32_t size);
uint32_t flag(Version version) const;
void set_flag(Version version, uint32_t flag);
static inline size_t header_size(Version version) {
return (version == Version::BB_V4) ? 8 : 4;
}
PSOCommandHeader();
@@ -123,7 +123,7 @@ T& check_size_t(void* data, size_t size) {
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
std::string prepend_command_header(
GameVersion version,
Version version,
bool encryption_enabled,
uint16_t cmd,
uint32_t flag,
+1 -1
View File
@@ -85,7 +85,7 @@ void ClientGameData::create_battle_overlay(shared_ptr<const BattleRules> rules,
}
}
void ClientGameData::create_challenge_overlay(GameVersion version, size_t template_index, shared_ptr<const LevelTable> level_table) {
void ClientGameData::create_challenge_overlay(Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
auto p = this->character(true, false);
const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index);
+1 -1
View File
@@ -56,7 +56,7 @@ public:
~ClientGameData();
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
void create_challenge_overlay(GameVersion version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
inline void delete_overlay() {
this->overlay_character_data.reset();
}
+9 -11
View File
@@ -90,7 +90,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptr<Clien
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000}};
const ClassMaxes* maxes;
if ((c->version() == GameVersion::PC) || (c->version() == GameVersion::DC)) {
if (is_v1_or_v2(c->version())) {
// V1/V2 have fewer classes, so we'll substitute some here
switch (this->visual.char_class) {
case 0: // HUmar
@@ -125,7 +125,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptr<Clien
this->visual.char_class = 0; // Invalid classes -> HUmar
}
this->visual.version = min<uint8_t>(this->visual.version, c->config.check_flag(Client::Flag::IS_DC_V1) ? 0 : 2);
this->visual.version = min<uint8_t>(this->visual.version, is_v1(c->version()) ? 0 : 2);
maxes = &v1_v2_class_maxes[this->visual.char_class];
} else {
@@ -152,7 +152,7 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_client(shared_ptr<Clien
}
void PlayerDispDataBB::enforce_lobby_join_limits_for_client(shared_ptr<Client> c) {
if (c->version() != GameVersion::BB) {
if (!is_v4(c->version())) {
throw logic_error("PlayerDispDataBB being sent to non-BB client");
}
this->play_time = 0;
@@ -660,7 +660,7 @@ void PlayerInventory::decode_from_client(shared_ptr<Client> c) {
}
void PlayerInventory::encode_for_client(shared_ptr<Client> c) {
if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) {
if (c->version() == Version::DC_NTE) {
// DC NTE has the item count as a 32-bit value here, whereas every other
// version uses a single byte. To stop DC NTE from crashing by trying to
// construct far more than 30 TItem objects, we clear the fields DC NTE
@@ -919,7 +919,7 @@ static PlayerInventoryItem make_template_item(bool equipped, uint64_t first_data
static PlayerInventoryItem v2_item(bool equipped, uint64_t first_data, uint64_t second_data) {
auto ret = make_template_item(equipped, first_data, second_data);
ret.data.decode_for_version(GameVersion::PC);
ret.data.decode_for_version(Version::PC_V2);
return ret;
}
@@ -927,7 +927,7 @@ static PlayerInventoryItem v3_item(bool equipped, uint64_t first_data, uint64_t
return make_template_item(equipped, first_data, second_data);
}
const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion version, uint32_t class_flags, size_t index) {
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index) {
// clang-format off
static const vector<ChallengeTemplateDefinition> v2_hunter_templates({
{0, {v2_item(true, 0x0001000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
@@ -1040,14 +1040,12 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion
});
// clang-format on
bool is_v2 = (version == GameVersion::DC) || (version == GameVersion::PC);
if ((class_flags & 0xE0) == 0x20) {
return is_v2 ? v2_hunter_templates.at(index) : v3_hunter_templates.at(index);
return is_v1_or_v2(version) ? v2_hunter_templates.at(index) : v3_hunter_templates.at(index);
} else if ((class_flags & 0xE0) == 0x40) {
return is_v2 ? v2_ranger_templates.at(index) : v3_ranger_templates.at(index);
return is_v1_or_v2(version) ? v2_ranger_templates.at(index) : v3_ranger_templates.at(index);
} else if ((class_flags & 0xE0) == 0x80) {
return is_v2 ? v2_force_templates.at(index) : v3_force_templates.at(index);
return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index);
} else {
throw runtime_error("invalid class flags on original player");
}
+1 -1
View File
@@ -621,7 +621,7 @@ struct ChallengeTemplateDefinition {
std::vector<TechLevel> tech_levels;
};
const ChallengeTemplateDefinition& get_challenge_template_definition(GameVersion version, uint32_t class_flags, size_t index);
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index);
struct SymbolChat {
// Bits: ----------------------DMSSSCCCFF
+412 -403
View File
File diff suppressed because it is too large Load Diff
+169 -134
View File
@@ -43,8 +43,7 @@ ProxyServer::ProxyServer(
state(state),
next_unlicensed_session_id(0xFF00000000000001) {}
void ProxyServer::listen(uint16_t port, GameVersion version,
const struct sockaddr_storage* default_destination) {
void ProxyServer::listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination) {
shared_ptr<ListeningSocket> socket_obj(new ListeningSocket(
this, port, version, default_destination));
auto l = this->listeners.emplace(port, socket_obj).first->second;
@@ -53,7 +52,7 @@ void ProxyServer::listen(uint16_t port, GameVersion version,
ProxyServer::ListeningSocket::ListeningSocket(
ProxyServer* server,
uint16_t port,
GameVersion version,
Version version,
const struct sockaddr_storage* default_destination)
: server(server),
log(string_printf("[ProxyServer:ListeningSocket:%hu] ", port), proxy_server_log.min_level),
@@ -83,8 +82,7 @@ ProxyServer::ListeningSocket::ListeningSocket(
this->default_destination.ss_family = 0;
}
this->log.info("Listening on TCP port %hu (%s) on fd %d",
this->port, name_for_version(this->version), static_cast<int>(this->fd));
this->log.info("Listening on TCP port %hu (%s) on fd %d", this->port, name_for_enum(this->version), static_cast<int>(this->fd));
}
void ProxyServer::ListeningSocket::dispatch_on_listen_accept(
@@ -98,10 +96,8 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error(
}
void ProxyServer::ListeningSocket::on_listen_accept(int fd) {
this->log.info("Client connected on fd %d (port %hu, version %s)",
fd, this->port, name_for_version(this->version));
auto* bev = bufferevent_socket_new(this->server->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
this->log.info("Client connected on fd %d (port %hu, version %s)", fd, this->port, name_for_enum(this->version));
auto* bev = bufferevent_socket_new(this->server->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
this->server->on_client_connect(bev, this->port, this->version,
(this->default_destination.ss_family == AF_INET) ? &this->default_destination : nullptr);
}
@@ -117,7 +113,7 @@ void ProxyServer::ListeningSocket::on_listen_error() {
void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port) {
// Look up the listening socket for the given port, and use that game version.
// We don't support default-destination proxying for virtual connections (yet)
GameVersion version;
Version version;
try {
version = this->listeners.at(server_port)->version;
} catch (const out_of_range&) {
@@ -136,12 +132,12 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port)
void ProxyServer::on_client_connect(
struct bufferevent* bev,
uint16_t listen_port,
GameVersion version,
Version version,
const struct sockaddr_storage* default_destination) {
// If a default destination exists for this client and the client is a patch
// client, create a linked session immediately and connect to the remote
// server. This creates a direct session.
if (default_destination && (version == GameVersion::PATCH)) {
if (default_destination && is_patch(version)) {
uint64_t session_id = this->next_unlicensed_session_id++;
if (this->next_unlicensed_session_id == 0) {
this->next_unlicensed_session_id = 0xFF00000000000001;
@@ -175,18 +171,24 @@ void ProxyServer::on_client_connect(
}
switch (version) {
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
throw logic_error("cannot create unlinked patch session");
case GameVersion::DC:
case GameVersion::PC:
case GameVersion::GC:
case GameVersion::XB: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
auto cmd = prepare_server_init_contents_console(
server_key, client_key, 0);
auto cmd = prepare_server_init_contents_console(server_key, client_key, 0);
ses->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
if (uses_v2_encryption(version)) {
ses->channel.crypt_out.reset(new PSOV2Encryption(server_key));
ses->channel.crypt_in.reset(new PSOV2Encryption(client_key));
} else {
@@ -195,7 +197,7 @@ void ProxyServer::on_client_connect(
}
break;
}
case GameVersion::BB: {
case Version::BB_V4: {
parray<uint8_t, 0x30> server_key;
parray<uint8_t, 0x30> client_key;
random_data(server_key.data(), server_key.bytes());
@@ -225,7 +227,7 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
shared_ptr<ProxyServer> server,
struct bufferevent* bev,
uint16_t local_port,
GameVersion version)
Version version)
: server(server),
log(string_printf("[ProxyServer:UnlinkedSession:%p] ", bev), proxy_server_log.min_level),
channel(
@@ -263,108 +265,139 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
bool should_close_unlinked_session = false;
try {
if (ses->version == GameVersion::DC) {
// We should only get a 93 or 9D while the session is unlinked; if we get
// anything else, disconnect
if (command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->hardware_id = cmd.hardware_id.decode();
ses->config.set_flag(Client::Flag::IS_DC_V1);
} else if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
} else {
throw runtime_error("command is not 93 or 9D");
}
} else if (ses->version == GameVersion::PC) {
// We should only get a 9D while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9D) {
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D));
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
} else if (ses->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
// anything else, disconnect
// TODO: GCTE will send 9D; we should presumably handle that too, sigh
if (command != 0x9E) {
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->config.parse_from(cmd.client_config);
} else if (ses->version == GameVersion::XB) {
// We should only get a 9E or 9F while the session is unlinked; if we get
// anything else, disconnect
if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_XB_9E>(data, sizeof(C_LoginExtended_XB_9E));
string xb_gamertag = cmd.serial_number.decode();
uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16);
uint64_t xb_account_id = cmd.netloc.account_id;
ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id);
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->xb_netloc = cmd.netloc;
ses->xb_9E_unknown_a1a = cmd.unknown_a1a;
ses->channel.send(0x9F, 0x00);
return;
} else if (command == 0x9F) {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
ses->config.parse_from(cmd.data);
} else {
throw runtime_error("command is not 9E or 9F");
}
} else if (ses->version == GameVersion::XB) {
throw runtime_error("xbox licenses are not implemented");
} else if (ses->version == GameVersion::BB) {
// We should only get a 93 while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x93) {
throw runtime_error("command is not 93");
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
try {
ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} catch (const LicenseIndex::missing_license&) {
if (!s->allow_unregistered_users) {
throw;
switch (ses->version) {
case Version::DC_NTE: {
// We should only get an 8B while the session is unlinked
if (command != 0x8B) {
throw runtime_error("command is not 8B");
}
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode();
l->bb_password = cmd.password.decode();
s->license_index->add(l);
l->save();
ses->license = l;
string l_str = l->str();
ses->log.info("Created license %s", l_str.c_str());
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data, sizeof(C_LoginExtended_DCNTE_8B));
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
// TODO: Parse cmd.hardware_id
ses->version = Version::DC_NTE;
break;
}
ses->login_command_bb = std::move(data);
} else {
throw logic_error("unsupported unlinked session version");
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
// We should only get a 93 or 9D while the session is unlinked
if (command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->hardware_id = cmd.hardware_id.decode();
ses->version = Version::DC_V1;
} else if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
} else {
throw runtime_error("command is not 93 or 9D");
}
break;
case Version::PC_V2: {
// We should only get a 9D while the session is unlinked
if (command != 0x9D) {
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D));
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
break;
}
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
// We should only get a 9D or 9E while the session is unlinked
if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->version = Version::GC_NTE;
ses->config.set_flags_for_version(ses->version, cmd.sub_version);
} else if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->config.parse_from(cmd.client_config);
if (cmd.sub_version >= 0x40) {
ses->version = Version::GC_EP3;
}
} else {
throw runtime_error("command is not 9D or 9E");
}
break;
case Version::XB_V3:
// We should only get a 9E or 9F while the session is unlinked
if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_XB_9E>(data, sizeof(C_LoginExtended_XB_9E));
string xb_gamertag = cmd.serial_number.decode();
uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16);
uint64_t xb_account_id = cmd.netloc.account_id;
ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id);
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->xb_netloc = cmd.netloc;
ses->xb_9E_unknown_a1a = cmd.unknown_a1a;
ses->channel.send(0x9F, 0x00);
return;
} else if (command == 0x9F) {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
ses->config.parse_from(cmd.data);
} else {
throw runtime_error("command is not 9E or 9F");
}
break;
case Version::BB_V4: {
// We should only get a 93 while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x93) {
throw runtime_error("command is not 93");
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
try {
ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} catch (const LicenseIndex::missing_license&) {
if (!s->allow_unregistered_users) {
throw;
}
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode();
l->bb_password = cmd.password.decode();
s->license_index->add(l);
l->save();
ses->license = l;
string l_str = l->str();
ses->log.info("Created license %s", l_str.c_str());
}
ses->login_command_bb = std::move(data);
break;
}
default:
throw runtime_error("unvalid unlinked session version");
}
} catch (const exception& e) {
ses->log.error("Failed to process command from unlinked client: %s", e.what());
should_close_unlinked_session = true;
@@ -410,7 +443,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
} else {
// Resume the linked session using the unlinked session
try {
if (ses->version == GameVersion::BB) {
if (ses->version == Version::BB_V4) {
linked_ses->resume(
std::move(ses->channel),
ses->detector_crypt,
@@ -455,7 +488,7 @@ ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
GameVersion version)
Version version)
: server(server),
id(id),
log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id), proxy_server_log.min_level),
@@ -501,7 +534,7 @@ ProxyServer::LinkedSession::LinkedSession(
ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
Version version,
shared_ptr<License> license,
const Client::Config& config)
: LinkedSession(server, license->serial_number, local_port, version) {
@@ -517,7 +550,7 @@ ProxyServer::LinkedSession::LinkedSession(
ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
Version version,
std::shared_ptr<License> license,
const struct sockaddr_storage& next_destination)
: LinkedSession(server, license->serial_number, local_port, version) {
@@ -529,7 +562,7 @@ ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
GameVersion version,
Version version,
const struct sockaddr_storage& destination)
: LinkedSession(server, id, local_port, version) {
this->next_destination = destination;
@@ -547,6 +580,11 @@ std::shared_ptr<ServerState> ProxyServer::LinkedSession::require_server_state()
return this->require_server()->state;
}
void ProxyServer::LinkedSession::set_version(Version v) {
this->client_channel.version = v;
this->server_channel.version = v;
}
void ProxyServer::LinkedSession::resume(
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
@@ -594,6 +632,7 @@ void ProxyServer::LinkedSession::resume_inner(
this,
string_printf("LinkedSession:%08" PRIX64 ":client", this->id));
this->server_channel.language = this->client_channel.language;
this->server_channel.version = this->client_channel.version;
this->detector_crypt = detector_crypt;
this->server_channel.disconnect();
@@ -663,10 +702,7 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) {
if (events & BEV_EVENT_CONNECTED) {
ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client");
if (is_server_stream && (ses->config.override_lobby_event != 0xFF) &&
(((ses->version() == GameVersion::GC) && !(ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) ||
(ses->version() == GameVersion::XB) ||
(ses->version() == GameVersion::BB))) {
if (is_server_stream && (ses->config.override_lobby_event != 0xFF) && (is_v3(ses->version()) || is_v4(ses->version()))) {
ses->client_channel.send(0xDA, ses->config.override_lobby_event);
}
}
@@ -703,7 +739,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
}
// On BB, do nothing - we can't return to the game server since the remote
// server likely sent different game data than what newserv would have sent
if (this->version() == GameVersion::BB) {
if (this->version() == Version::BB_V4) {
this->disconnect();
return;
}
@@ -728,7 +764,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
send_ship_info(this->client_channel, string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : ""));
// Restore newserv_client_config, so the login server gets the client flags
if (this->version() == GameVersion::GC || this->version() == GameVersion::XB) {
if (is_v3(this->version())) {
S_UpdateClientConfig_V3_04 update_client_config_cmd;
update_client_config_cmd.player_tag = 0x00010000;
update_client_config_cmd.guild_card_number = this->license->serial_number;
@@ -736,8 +772,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
}
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(this->version()));
string port_name = login_port_name_for_version(this->version());
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
// If the client is on a virtual connection, we can use any address
@@ -839,7 +874,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
shared_ptr<License> l,
uint16_t local_port,
GameVersion version,
Version version,
const Client::Config& config) {
shared_ptr<LinkedSession> session(new LinkedSession(this->shared_from_this(), local_port, version, l, config));
auto emplace_ret = this->id_to_session.emplace(session->id, session);
+13 -13
View File
@@ -26,8 +26,7 @@ public:
std::shared_ptr<ServerState> state);
virtual ~ProxyServer() = default;
void listen(uint16_t port, GameVersion version,
const struct sockaddr_storage* default_destination = nullptr);
void listen(uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
void connect_client(struct bufferevent* bev, uint16_t server_port);
@@ -116,35 +115,36 @@ public:
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
GameVersion version);
Version version);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
Version version,
std::shared_ptr<License> license,
const Client::Config& config);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
Version version,
std::shared_ptr<License> license,
const struct sockaddr_storage& next_destination);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint64_t id,
uint16_t local_port,
GameVersion version,
Version version,
const struct sockaddr_storage& next_destination);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
inline GameVersion version() const {
inline Version version() const {
return this->client_channel.version;
}
inline uint8_t language() const {
return this->client_channel.language;
}
void set_version(Version v);
void resume(
Channel&& client_channel,
@@ -182,7 +182,7 @@ public:
std::shared_ptr<LinkedSession> create_licensed_session(
std::shared_ptr<License> l,
uint16_t local_port,
GameVersion version,
Version version,
const Client::Config& config);
void delete_session(uint64_t id);
void delete_session(struct bufferevent* bev);
@@ -197,13 +197,13 @@ private:
uint16_t port;
scoped_fd fd;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
GameVersion version;
Version version;
struct sockaddr_storage default_destination;
ListeningSocket(
ProxyServer* server,
uint16_t port,
GameVersion version,
Version version,
const struct sockaddr_storage* default_destination);
static void dispatch_on_listen_accept(struct evconnlistener* listener,
@@ -219,7 +219,7 @@ private:
PrefixedLogger log;
Channel channel;
uint16_t local_port;
GameVersion version;
Version version;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
@@ -237,7 +237,7 @@ private:
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
UnlinkedSession(std::shared_ptr<ProxyServer> server, struct bufferevent* bev, uint16_t port, GameVersion version);
UnlinkedSession(std::shared_ptr<ProxyServer> server, struct bufferevent* bev, uint16_t port, Version version);
std::shared_ptr<ProxyServer> require_server() const;
std::shared_ptr<ServerState> require_server_state() const;
@@ -263,6 +263,6 @@ private:
void on_client_connect(
struct bufferevent* bev,
uint16_t listen_port,
GameVersion version,
Version version,
const struct sockaddr_storage* default_destination);
};
+77 -55
View File
@@ -207,7 +207,7 @@ struct PSODownloadQuestHeader {
VersionedQuest::VersionedQuest(
uint32_t quest_number,
uint32_t category_id,
QuestScriptVersion version,
Version version,
uint8_t language,
std::shared_ptr<const std::string> bin_contents,
std::shared_ptr<const std::string> dat_contents,
@@ -230,9 +230,23 @@ VersionedQuest::VersionedQuest(
auto bin_decompressed = prs_decompress(*this->bin_contents);
switch (this->version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2: {
case Version::DC_NTE: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderDCNTE)) {
throw invalid_argument("file is too small for header");
}
auto* header = reinterpret_cast<const PSOQuestHeaderDCNTE*>(bin_decompressed.data());
this->joinable = false;
this->episode = Episode::EP1;
if (this->quest_number == 0xFFFFFFFF) {
this->quest_number = fnv1a32(header, sizeof(header)) & 0xFFFF;
}
this->name = header->name.decode(this->language);
break;
}
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderDC)) {
throw invalid_argument("file is too small for header");
}
@@ -248,7 +262,7 @@ VersionedQuest::VersionedQuest(
break;
}
case QuestScriptVersion::PC_V2: {
case Version::PC_V2: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderPC)) {
throw invalid_argument("file is too small for header");
}
@@ -264,14 +278,14 @@ VersionedQuest::VersionedQuest(
break;
}
case QuestScriptVersion::GC_EP3: {
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3: {
// Note: This codepath handles Episode 3 download quests, which are not
// the same as Episode 3 quest scripts. The latter are only used offline
// in story mode, but can be disassembled with disassemble_quest_script.
// It's unfortunate that the QuestScriptVersion::GC_EP3 value is used
// here for Episode 3 download quests (maps) and there for offline story
// mode scripts, but it's probably not worth refactoring this logic, at
// least right now.
// It's unfortunate that Version::GC_EP3 is used here for Episode 3
// download quests (maps) and there for offline story mode scripts, but
// it's probably not worth refactoring this logic, at least right now.
if (bin_decompressed.size() != sizeof(Episode3::MapDefinition)) {
throw invalid_argument("file is incorrect size");
}
@@ -287,9 +301,9 @@ VersionedQuest::VersionedQuest(
break;
}
case QuestScriptVersion::XB_V3:
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3: {
case Version::XB_V3:
case Version::GC_NTE:
case Version::GC_V3: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderGC)) {
throw invalid_argument("file is too small for header");
}
@@ -305,7 +319,7 @@ VersionedQuest::VersionedQuest(
break;
}
case QuestScriptVersion::BB_V4: {
case Version::BB_V4: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderBB)) {
throw invalid_argument("file is too small for header");
}
@@ -372,8 +386,8 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version);
}
uint16_t Quest::versions_key(QuestScriptVersion v, uint8_t language) {
return (static_cast<uint16_t>(v) << 8) | language;
uint32_t Quest::versions_key(Version v, uint8_t language) {
return (static_cast<uint32_t>(v) << 8) | language;
}
void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
@@ -402,17 +416,17 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
}
bool Quest::has_version(QuestScriptVersion v, uint8_t language) const {
bool Quest::has_version(Version v, uint8_t language) const {
return this->versions.count(this->versions_key(v, language));
}
bool Quest::has_version_any_language(QuestScriptVersion v) const {
uint16_t k = this->versions_key(v, 0);
bool Quest::has_version_any_language(Version v) const {
uint32_t k = this->versions_key(v, 0);
auto it = this->versions.lower_bound(k);
return ((it != this->versions.end()) && ((it->first & 0xFF00) == k));
}
shared_ptr<const VersionedQuest> Quest::version(QuestScriptVersion v, uint8_t language) const {
shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) const {
// Return the requested version, if it exists
try {
return this->versions.at(this->versions_key(v, language));
@@ -563,16 +577,18 @@ QuestIndex::QuestIndex(
uint32_t quest_number = strtoull(quest_number_token.c_str() + 1, nullptr, 10);
// Get the version from the second token
static const unordered_map<string, QuestScriptVersion> name_to_version({
{"dn", QuestScriptVersion::DC_NTE},
{"d1", QuestScriptVersion::DC_V1},
{"dc", QuestScriptVersion::DC_V2},
{"pc", QuestScriptVersion::PC_V2},
{"gcn", QuestScriptVersion::GC_NTE},
{"gc", QuestScriptVersion::GC_V3},
{"gc3", QuestScriptVersion::GC_EP3},
{"xb", QuestScriptVersion::XB_V3},
{"bb", QuestScriptVersion::BB_V4},
static const unordered_map<string, Version> name_to_version({
{"dn", Version::DC_NTE},
{"dp", Version::DC_V1_12_2000_PROTOTYPE},
{"d1", Version::DC_V1},
{"dc", Version::DC_V2},
{"pc", Version::PC_V2},
{"gcn", Version::GC_NTE},
{"gc", Version::GC_V3},
{"gc3t", Version::GC_EP3_TRIAL_EDITION},
{"gc3", Version::GC_EP3},
{"xb", Version::XB_V3},
{"bb", Version::BB_V4},
});
auto version = name_to_version.at(version_token);
@@ -587,7 +603,7 @@ QuestIndex::QuestIndex(
string pvr_filename;
shared_ptr<const string> dat_contents;
shared_ptr<const string> pvr_contents;
if (version != QuestScriptVersion::GC_EP3) {
if (!::is_ep3(version)) {
// Look for dat and pvr files with the same basename as the bin file; if
// not found, look for them without the language suffix
try {
@@ -703,7 +719,7 @@ shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
}
}
vector<shared_ptr<const Quest>> QuestIndex::filter(Episode episode, uint32_t category_id, QuestScriptVersion version) const {
vector<shared_ptr<const Quest>> QuestIndex::filter(Episode episode, uint32_t category_id, Version version) const {
vector<shared_ptr<const Quest>> ret;
for (auto it : this->quests_by_number) {
if (((episode == Episode::NONE) || (it.second->episode == episode)) &&
@@ -754,7 +770,7 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(uint8_t overrid
// This function should not be used for Episode 3 quests (they should be sent
// to the client as-is, without any encryption or other preprocessing)
if (this->episode == Episode::EP3 || this->version == QuestScriptVersion::GC_EP3) {
if (this->episode == Episode::EP3 || is_ep3(this->version)) {
throw logic_error("Episode 3 quests cannot be converted to download quests");
}
@@ -762,9 +778,16 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(uint8_t overrid
void* data_ptr = decompressed_bin.data();
switch (this->version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
case Version::DC_NTE:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDCNTE)) {
throw runtime_error("bin file is too small for header");
}
// There's no known language field in this version, so we don't write
// anything here
break;
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) {
throw runtime_error("bin file is too small for header");
}
@@ -772,7 +795,7 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(uint8_t overrid
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->language = override_language;
}
break;
case QuestScriptVersion::PC_V2:
case Version::PC_V2:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) {
throw runtime_error("bin file is too small for header");
}
@@ -780,9 +803,9 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(uint8_t overrid
reinterpret_cast<PSOQuestHeaderPC*>(data_ptr)->language = override_language;
}
break;
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::XB_V3:
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) {
throw runtime_error("bin file is too small for header");
}
@@ -790,10 +813,8 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(uint8_t overrid
reinterpret_cast<PSOQuestHeaderGC*>(data_ptr)->language = override_language;
}
break;
case QuestScriptVersion::BB_V4:
case Version::BB_V4:
throw invalid_argument("PSOBB does not support download quests");
case QuestScriptVersion::GC_EP3:
throw logic_error("Episode 3 quests cannot be converted to download quests");
default:
throw invalid_argument("unknown game version");
}
@@ -1094,8 +1115,7 @@ unordered_map<string, string> decode_qst_data(const string& data) {
}
template <typename HeaderT>
void add_command_header(
StringWriter& w, uint8_t command, uint8_t flag, uint16_t size) {
void add_command_header(StringWriter& w, uint8_t command, uint8_t flag, uint16_t size) {
HeaderT header;
header.command = command;
header.flag = flag;
@@ -1172,16 +1192,17 @@ string encode_qst_file(
const string& name,
uint32_t quest_number,
const string& xb_filename,
QuestScriptVersion version,
Version version,
bool is_dlq_encoded) {
StringWriter w;
// Some tools expect both open file commands at the beginning, hence this
// unfortunate abstraction-breaking.
switch (version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
case Version::DC_NTE: // DC NTE doesn't support quests, but we support encoding QST files anyway
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
@@ -1189,7 +1210,7 @@ string encode_qst_file(
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
}
break;
case QuestScriptVersion::PC_V2:
case Version::PC_V2:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
@@ -1197,9 +1218,10 @@ string encode_qst_file(
add_write_file_commands_t<PSOCommandHeaderPC>(w, it.first, *it.second, is_dlq_encoded, false);
}
break;
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::GC_EP3:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
@@ -1207,7 +1229,7 @@ string encode_qst_file(
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
}
break;
case QuestScriptVersion::XB_V3:
case Version::XB_V3:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
@@ -1215,7 +1237,7 @@ string encode_qst_file(
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, it.first, *it.second, is_dlq_encoded, false);
}
break;
case QuestScriptVersion::BB_V4:
case Version::BB_V4:
for (const auto& it : files) {
add_open_file_command_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, it.first, xb_filename, quest_number, it.second->size(), is_dlq_encoded);
}
+9 -9
View File
@@ -58,7 +58,7 @@ struct VersionedQuest {
Episode episode;
bool joinable;
std::string name;
QuestScriptVersion version;
Version version;
uint8_t language;
bool is_dlq_encoded;
std::string short_description;
@@ -72,7 +72,7 @@ struct VersionedQuest {
VersionedQuest(
uint32_t quest_number,
uint32_t category_id,
QuestScriptVersion version,
Version version,
uint8_t language,
std::shared_ptr<const std::string> bin_contents,
std::shared_ptr<const std::string> dat_contents,
@@ -98,11 +98,11 @@ public:
Quest& operator=(Quest&&) = default;
void add_version(std::shared_ptr<const VersionedQuest> vq);
bool has_version(QuestScriptVersion v, uint8_t language) const;
bool has_version_any_language(QuestScriptVersion v) const;
std::shared_ptr<const VersionedQuest> version(QuestScriptVersion v, uint8_t language) const;
bool has_version(Version v, uint8_t language) const;
bool has_version_any_language(Version v) const;
std::shared_ptr<const VersionedQuest> version(Version v, uint8_t language) const;
static uint16_t versions_key(QuestScriptVersion v, uint8_t language);
static uint32_t versions_key(Version v, uint8_t language);
uint32_t quest_number;
uint32_t category_id;
@@ -111,7 +111,7 @@ public:
std::string name;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
std::map<uint16_t, std::shared_ptr<const VersionedQuest>> versions;
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
};
struct QuestIndex {
@@ -123,7 +123,7 @@ struct QuestIndex {
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
std::vector<std::shared_ptr<const Quest>> filter(Episode episode, uint32_t category_id, QuestScriptVersion version) const;
std::vector<std::shared_ptr<const Quest>> filter(Episode episode, uint32_t category_id, Version version) const;
};
std::string encode_download_quest_data(
@@ -149,5 +149,5 @@ std::string encode_qst_file(
const std::string& name,
uint32_t quest_number,
const std::string& xb_filename,
QuestScriptVersion version,
Version version,
bool is_dlq_encoded);
+34 -50
View File
@@ -23,32 +23,6 @@ using AttackData = BattleParamsIndex::AttackData;
using ResistData = BattleParamsIndex::ResistData;
using MovementData = BattleParamsIndex::MovementData;
template <>
const char* name_for_enum<QuestScriptVersion>(QuestScriptVersion v) {
switch (v) {
case QuestScriptVersion::DC_NTE:
return "DC_NTE";
case QuestScriptVersion::DC_V1:
return "DC_V1";
case QuestScriptVersion::DC_V2:
return "DC_V2";
case QuestScriptVersion::PC_V2:
return "PC_V2";
case QuestScriptVersion::GC_NTE:
return "GC_NTE";
case QuestScriptVersion::GC_V3:
return "GC_V3";
case QuestScriptVersion::XB_V3:
return "XB_V3";
case QuestScriptVersion::GC_EP3:
return "GC_EP3";
case QuestScriptVersion::BB_V4:
return "BB_V4";
default:
return "__UNKNOWN__";
}
}
// bit_cast isn't in the standard place on macOS (it is apparently implicitly
// included by resource_dasm, but newserv can be built without resource_dasm)
// and I'm too lazy to go find the right header to include
@@ -152,12 +126,12 @@ struct QuestScriptOpcodeDefinition {
flags(flags) {}
};
constexpr uint16_t v_flag(QuestScriptVersion v) {
constexpr uint16_t v_flag(Version v) {
return (1 << static_cast<uint16_t>(v));
}
using Arg = QuestScriptOpcodeDefinition::Argument;
using V = QuestScriptVersion;
using V = Version;
static constexpr uint16_t F_DC_NTE = v_flag(V::DC_NTE);
static constexpr uint16_t F_DC_V1 = v_flag(V::DC_V1);
@@ -801,10 +775,10 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
};
static const unordered_map<uint16_t, const QuestScriptOpcodeDefinition*>&
opcodes_for_version(QuestScriptVersion v) {
opcodes_for_version(Version v) {
static array<
unordered_map<uint16_t, const QuestScriptOpcodeDefinition*>,
static_cast<size_t>(QuestScriptVersion::BB_V4) + 1>
static_cast<size_t>(Version::BB_V4) + 1>
indexes;
auto& index = indexes.at(static_cast<size_t>(v));
@@ -823,7 +797,7 @@ opcodes_for_version(QuestScriptVersion v) {
return index;
}
std::string disassemble_quest_script(const void* data, size_t size, QuestScriptVersion version, uint8_t language) {
std::string disassemble_quest_script(const void* data, size_t size, Version version, uint8_t language) {
StringReader r(data, size);
deque<string> lines;
@@ -831,9 +805,16 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
size_t code_offset = 0;
size_t function_table_offset = 0;
switch (version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2: {
case Version::DC_NTE: {
const auto& header = r.get<PSOQuestHeaderDCNTE>();
code_offset = header.code_offset;
function_table_offset = header.function_table_offset;
lines.emplace_back(".name " + JSON(header.name.decode(0)).serialize());
break;
}
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2: {
const auto& header = r.get<PSOQuestHeaderDC>();
code_offset = header.code_offset;
function_table_offset = header.function_table_offset;
@@ -844,7 +825,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::PC_V2: {
case Version::PC_V2: {
use_wstrs = true;
const auto& header = r.get<PSOQuestHeaderPC>();
code_offset = header.code_offset;
@@ -856,10 +837,11 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::GC_EP3:
case QuestScriptVersion::XB_V3: {
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
const auto& header = r.get<PSOQuestHeaderGC>();
code_offset = header.code_offset;
function_table_offset = header.function_table_offset;
@@ -871,7 +853,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
lines.emplace_back(".long_desc " + JSON(header.long_description.decode(language)).serialize());
break;
}
case QuestScriptVersion::BB_V4: {
case Version::BB_V4: {
use_wstrs = true;
const auto& header = r.get<PSOQuestHeaderBB>();
code_offset = header.code_offset;
@@ -1484,7 +1466,7 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
return join(lines, "\n");
}
Episode find_quest_episode_from_script(const void* data, size_t size, QuestScriptVersion version) {
Episode find_quest_episode_from_script(const void* data, size_t size, Version version) {
StringReader r(data, size);
bool use_wstrs = false;
@@ -1492,22 +1474,24 @@ Episode find_quest_episode_from_script(const void* data, size_t size, QuestScrip
size_t function_table_offset = 0;
Episode header_episode = Episode::NONE;
switch (version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
case QuestScriptVersion::PC_V2:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
return Episode::EP1;
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::GC_EP3:
case QuestScriptVersion::XB_V3: {
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
const auto& header = r.get<PSOQuestHeaderGC>();
code_offset = header.code_offset;
function_table_offset = header.function_table_offset;
header_episode = episode_for_quest_episode_number(header.episode);
break;
}
case QuestScriptVersion::BB_V4: {
case Version::BB_V4: {
use_wstrs = true;
const auto& header = r.get<PSOQuestHeaderBB>();
code_offset = header.code_offset;
+10 -18
View File
@@ -9,21 +9,14 @@
#include "Text.hh"
#include "Version.hh"
enum class QuestScriptVersion {
DC_NTE = 0,
DC_V1 = 1,
DC_V2 = 2,
PC_V2 = 3,
GC_NTE = 4,
GC_V3 = 5,
XB_V3 = 6,
GC_EP3 = 7,
BB_V4 = 8,
UNKNOWN = 15,
};
template <>
const char* name_for_enum<QuestScriptVersion>(QuestScriptVersion v);
struct PSOQuestHeaderDCNTE {
/* 0000 */ le_uint32_t code_offset;
/* 0004 */ le_uint32_t function_table_offset;
/* 0008 */ le_uint32_t size;
/* 000C */ le_uint32_t unused;
/* 0010 */ pstring<TextEncoding::SJIS, 0x10> name;
/* 0020 */
} __attribute__((packed));
struct PSOQuestHeaderDC { // Same format for DC v1 and v2
/* 0000 */ le_uint32_t code_offset;
@@ -89,7 +82,6 @@ struct PSOQuestHeaderBB {
Episode episode_for_quest_episode_number(uint8_t episode_number);
std::string disassemble_quest_script(
const void* data, size_t size, QuestScriptVersion version, uint8_t language);
std::string disassemble_quest_script(const void* data, size_t size, Version version, uint8_t language);
Episode find_quest_episode_from_script(const void* data, size_t size, QuestScriptVersion version);
Episode find_quest_episode_from_script(const void* data, size_t size, Version version);
+5 -5
View File
@@ -17,7 +17,7 @@ string RareItemSet::ExpandedDrop::str() const {
this->probability, frac.first, frac.second, this->item_code[0], this->item_code[1], this->item_code[2]);
}
string RareItemSet::ExpandedDrop::str(GameVersion version, shared_ptr<const ItemNameIndex> name_index) const {
string RareItemSet::ExpandedDrop::str(Version version, shared_ptr<const ItemNameIndex> name_index) const {
ItemData item;
item.data1[0] = this->item_code[0];
item.data1[1] = this->item_code[1];
@@ -296,7 +296,7 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
}
}
RareItemSet::RareItemSet(const JSON& json, GameVersion version, shared_ptr<const ItemNameIndex> name_index) {
RareItemSet::RareItemSet(const JSON& json, Version version, shared_ptr<const ItemNameIndex> name_index) {
for (const auto& mode_it : json.as_dict()) {
static const unordered_map<string, GameMode> mode_keys(
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
@@ -420,7 +420,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
return GSLArchive::generate(files, big_endian);
}
std::string RareItemSet::serialize_json(GameVersion version, shared_ptr<const ItemNameIndex> name_index) const {
std::string RareItemSet::serialize_json(Version version, shared_ptr<const ItemNameIndex> name_index) const {
auto modes_dict = JSON::dict();
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
for (const auto& mode : modes) {
@@ -505,7 +505,7 @@ std::string RareItemSet::serialize_json(GameVersion version, shared_ptr<const It
void RareItemSet::print_collection(
FILE* stream,
GameVersion version,
Version version,
GameMode mode,
Episode episode,
uint8_t difficulty,
@@ -553,7 +553,7 @@ void RareItemSet::print_collection(
}
void RareItemSet::print_all_collections(
FILE* stream, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index) const {
FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index) const {
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
for (GameMode mode : modes) {
+5 -5
View File
@@ -22,14 +22,14 @@ public:
parray<uint8_t, 3> item_code;
std::string str() const;
std::string str(GameVersion version, std::shared_ptr<const ItemNameIndex> name_index) const;
std::string str(Version version, std::shared_ptr<const ItemNameIndex> name_index) const;
};
RareItemSet();
RareItemSet(const AFSArchive& afs, bool is_v1);
RareItemSet(const GSLArchive& gsl, bool is_big_endian);
RareItemSet(const std::string& rel, bool is_big_endian);
RareItemSet(const JSON& json, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
RareItemSet(const JSON& json, Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
~RareItemSet() = default;
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
@@ -37,17 +37,17 @@ public:
std::string serialize_afs() const;
std::string serialize_gsl(bool big_endian) const;
std::string serialize_json(GameVersion version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
std::string serialize_json(Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_collection(
FILE* stream,
GameVersion version,
Version version,
GameMode mode,
Episode episode,
uint8_t difficulty,
uint8_t section_id,
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_all_collections(FILE* stream, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
void print_all_collections(FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
protected:
struct SpecCollection {
+539 -491
View File
File diff suppressed because it is too large Load Diff
+92 -85
View File
@@ -57,7 +57,7 @@ static void forward_subcommand(
// If the command is an Ep3-only command, make sure an Ep3 client sent it
bool command_is_ep3 = (command & 0xF0) == 0xC0;
if (command_is_ep3 && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (command_is_ep3 && !is_ep3(c->version())) {
throw runtime_error("Episode 3 command sent by non-Episode 3 client");
}
@@ -70,7 +70,7 @@ static void forward_subcommand(
if (!target) {
return;
}
if (target->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) {
if (target->version() == Version::DC_NTE) {
if (dc_nte_subcommand) {
string nte_data(reinterpret_cast<const char*>(data), size);
nte_data[0] = dc_nte_subcommand;
@@ -85,7 +85,7 @@ static void forward_subcommand(
} else {
if (command_is_ep3) {
for (auto& target : l->clients) {
if (!target || (target == c) || !target->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (!target || (target == c) || !is_ep3(target->version())) {
continue;
}
send_command(target, command, flag, data, size);
@@ -97,7 +97,7 @@ static void forward_subcommand(
if (!lc || (lc == c)) {
continue;
}
if (lc->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) {
if (lc->version() == Version::DC_NTE) {
if (dc_nte_subcommand) {
if (nte_data.empty()) {
nte_data.assign(reinterpret_cast<const char*>(data), size);
@@ -121,7 +121,7 @@ static void forward_subcommand(
watcher_subcommands.count(subcommand)) {
for (const auto& watcher_lobby : l->watcher_lobbies) {
for (auto& target : watcher_lobby->clients) {
if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (target && is_ep3(target->version())) {
send_command(target, command, flag, data, size);
}
}
@@ -317,7 +317,7 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
// We need to byteswap mags' data2 fields if exactly one of the sender and
// recipient is PSO GC
if ((c->version() == GameVersion::GC) == (target->version() == GameVersion::GC)) {
if (is_big_endian(c->version()) == is_big_endian(target->version())) {
send_or_enqueue_joining_player_command(target, command, flag, data, size);
} else {
@@ -373,8 +373,8 @@ static void on_sync_joining_player_disp_and_inventory(
}
// This command's format is different on BB and non-BB
bool sender_is_bb = (c->version() == GameVersion::BB);
bool target_is_bb = (target->version() == GameVersion::BB);
bool sender_is_bb = (c->version() == Version::BB_V4);
bool target_is_bb = (target->version() == Version::BB_V4);
if (sender_is_bb != target_is_bb) {
// TODO: Figure out the BB 6x70 format and implement this
throw runtime_error("6x70 command cannot be translated across BB boundary");
@@ -382,8 +382,8 @@ static void on_sync_joining_player_disp_and_inventory(
// We need to byteswap mags' data2 fields if exactly one of the sender and
// recipient are PSO GC
bool sender_is_gc = (c->version() == GameVersion::GC);
bool target_is_gc = (target->version() == GameVersion::GC);
bool sender_is_gc = is_gc(c->version());
bool target_is_gc = is_gc(target->version());
if (target_is_gc == sender_is_gc) {
send_or_enqueue_joining_player_command(target, command, flag, data, size);
@@ -398,8 +398,8 @@ static void on_sync_joining_player_disp_and_inventory(
out_cmd.xb_user_id_low = c->license->serial_number;
}
for (size_t z = 0; z < out_cmd.inventory.num_items; z++) {
out_cmd.inventory.items[z].data.decode_for_version(GameVersion::GC);
out_cmd.inventory.items[z].data.encode_for_version(GameVersion::XB, s->item_parameter_table_for_version(GameVersion::XB));
out_cmd.inventory.items[z].data.decode_for_version(c->version());
out_cmd.inventory.items[z].data.encode_for_version(target->version(), s->item_parameter_table_for_version(target->version()));
}
send_or_enqueue_joining_player_command(target, command, flag, out_cmd);
@@ -410,8 +410,8 @@ static void on_sync_joining_player_disp_and_inventory(
"GC 6x70 command is larger than XB 6x70 command");
auto out_cmd = check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size);
for (size_t z = 0; z < out_cmd.inventory.num_items; z++) {
out_cmd.inventory.items[z].data.decode_for_version(GameVersion::XB);
out_cmd.inventory.items[z].data.encode_for_version(GameVersion::GC, s->item_parameter_table_for_version(GameVersion::GC));
out_cmd.inventory.items[z].data.decode_for_version(c->version());
out_cmd.inventory.items[z].data.encode_for_version(target->version(), s->item_parameter_table_for_version(target->version()));
}
send_or_enqueue_joining_player_command(target, command, flag, out_cmd);
}
@@ -460,7 +460,7 @@ static void on_ep3_sound_chat(shared_ptr<Client> c, uint8_t command, uint8_t fla
// forwarded from spectator teams to the primary team. The client only uses
// this behavior for the 6xBE command (sound chat), and newserv enforces that
// only that command is sent via CB.
if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (!is_ep3(c->version())) {
throw runtime_error("non-Episode 3 client sent sound chat command");
}
@@ -469,7 +469,7 @@ static void on_ep3_sound_chat(shared_ptr<Client> c, uint8_t command, uint8_t fla
auto watched_lobby = l->watched_lobby.lock();
if (watched_lobby) {
for (auto& target : watched_lobby->clients) {
if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (target && is_ep3(target->version())) {
send_command(target, command, flag, data, size);
}
}
@@ -521,27 +521,33 @@ static void on_send_guild_card(shared_ptr<Client> c, uint8_t command, uint8_t fl
}
switch (c->version()) {
case GameVersion::DC: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2: {
const auto& cmd = check_size_t<G_SendGuildCard_DC_6x06>(data, size);
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::PC: {
case Version::PC_V2: {
const auto& cmd = check_size_t<G_SendGuildCard_PC_6x06>(data, size);
c->game_data.character(true, false)->guild_card.description = cmd.guild_card.description;
break;
}
case GameVersion::GC: {
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3: {
const auto& cmd = check_size_t<G_SendGuildCard_GC_6x06>(data, size);
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::XB: {
case Version::XB_V3: {
const auto& cmd = check_size_t<G_SendGuildCard_XB_6x06>(data, size);
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::BB:
case Version::BB_V4:
// Nothing to do... the command is blank; the server generates the guild
// card to be sent
break;
@@ -591,18 +597,18 @@ static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, con
// In non-Ep3 lobbies, Ep3 uses the Ep1&2 word select table.
bool is_non_ep3_lobby = (l->episode != Episode::EP3);
QuestScriptVersion from_version = c->quest_version();
if (is_non_ep3_lobby && (from_version == QuestScriptVersion::GC_EP3)) {
from_version = QuestScriptVersion::GC_V3;
Version from_version = c->version();
if (is_non_ep3_lobby && is_ep3(from_version)) {
from_version = Version::GC_V3;
}
for (const auto& lc : target_clients) {
try {
QuestScriptVersion lc_version = lc->quest_version();
if (is_non_ep3_lobby && (lc_version == QuestScriptVersion::GC_EP3)) {
lc_version = QuestScriptVersion::GC_V3;
Version lc_version = lc->version();
if (is_non_ep3_lobby && is_ep3(lc_version)) {
lc_version = Version::GC_V3;
}
if (lc->version() == GameVersion::GC) {
if (is_big_endian(lc->version())) {
G_WordSelect_6x74<true> out_cmd = {
cmd.subcommand, cmd.size, cmd.client_id.load(),
s->word_select_table->translate(cmd.message, from_version, lc_version)};
@@ -624,7 +630,7 @@ static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, con
}
static void on_word_select(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
if (c->version() == GameVersion::GC) {
if (is_big_endian(c->version())) {
on_word_select_t<true>(c, command, flag, data, size);
} else {
on_word_select_t<false>(c, command, flag, data, size);
@@ -646,7 +652,7 @@ static void on_set_player_visible(shared_ptr<Client> c, uint8_t command, uint8_t
forward_subcommand(c, command, flag, data, size, 0x1F);
auto l = c->require_lobby();
if (!l->is_game() && !c->config.check_flag(Client::Flag::IS_DC_V1)) {
if (!l->is_game() && !is_v1(c->version())) {
send_arrow_update(l);
}
if (!l->is_game() && l->check_flag(Lobby::Flag::IS_OVERFLOW)) {
@@ -821,7 +827,7 @@ static void on_player_drop_item(shared_ptr<Client> c, uint8_t command, uint8_t f
auto l = c->require_lobby();
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto p = c->game_data.character();
auto item = p->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB);
auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4);
l->add_item(item, cmd.floor, cmd.x, cmd.z);
auto s = c->require_server_state();
@@ -871,7 +877,7 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
if ((cmd.header.client_id != c->lobby_client_id)) {
return;
}
if (c->version() == GameVersion::BB) {
if (c->version() == Version::BB_V4) {
// BB should never send this command - inventory items should only be
// created by the server in response to shop buy / bank withdraw / etc. reqs
return;
@@ -917,7 +923,7 @@ static void on_drop_partial_stack_t(shared_ptr<Client> c, uint8_t command, uint8
if (!l->is_game()) {
return;
}
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
return;
}
@@ -956,7 +962,7 @@ static void on_drop_partial_stack(shared_ptr<Client> c, uint8_t command, uint8_t
static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_SplitStackedItem_BB_6xC3>(data, size);
if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) {
@@ -968,7 +974,7 @@ static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint
}
auto p = c->game_data.character();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
// If a stack was split, the original item still exists, so the dropped item
// needs a new ID. remove_item signals this by returning an item with an ID
@@ -1011,7 +1017,7 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) {
return;
}
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
return;
}
@@ -1031,7 +1037,7 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5BUY %08" PRIX32 "\n%s", item.id.load(), name.c_str());
}
p->remove_meseta(price, c->version() != GameVersion::BB);
p->remove_meseta(price, c->version() != Version::BB_V4);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
@@ -1053,7 +1059,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
if (!l->is_game() || (c->lobby_client_id != l->leader_id)) {
return;
}
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
return;
}
@@ -1103,7 +1109,7 @@ static void on_pick_up_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
if (!l->is_game()) {
return;
}
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
// BB clients should never send this; only the server should send this
return;
}
@@ -1178,7 +1184,7 @@ static void on_pick_up_item_request(shared_ptr<Client> c, uint8_t command, uint8
l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but the item does not exist; dropping command",
cmd.header.client_id.load(), cmd.item_id.load());
} else if (l->base_version == GameVersion::BB) {
} else if (l->base_version == Version::BB_V4) {
// This is handled by the server on BB, and by the leader on other versions.
if (!item_tracking_enabled) {
throw logic_error("item tracking not enabled in BB game");
@@ -1217,7 +1223,7 @@ static void on_equip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag, c
auto p = c->game_data.character();
p->inventory.equip_item_id(cmd.item_id, slot);
c->log.info("Equipped item %08" PRIX32, cmd.item_id.load());
} else if (l->base_version == GameVersion::BB) {
} else if (l->base_version == Version::BB_V4) {
throw logic_error("item tracking not enabled in BB game");
}
@@ -1236,7 +1242,7 @@ static void on_unequip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
auto p = c->game_data.character();
p->inventory.unequip_item_id(cmd.item_id);
c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load());
} else if (l->base_version == GameVersion::BB) {
} else if (l->base_version == Version::BB_V4) {
throw logic_error("item tracking not enabled in BB game");
}
@@ -1316,7 +1322,7 @@ static void on_feed_mag(
// a 6x29 immediately after to destroy the fed item. So on BB, we should
// remove the fed item here, but on other versions, we allow the following
// 6x29 command to do that.
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
p->remove_item(cmd.fed_item_id, 1, false);
}
@@ -1344,7 +1350,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Client> c, uint8_t com
} else {
const auto& cmd = check_size_t<G_ShopContentsRequest_BB_6xB5>(data, size);
if ((l->base_version == GameVersion::BB) && l->is_game()) {
if ((l->base_version == Version::BB_V4) && l->is_game()) {
if (!l->item_creator) {
throw logic_error("item creator missing from BB game");
}
@@ -1376,9 +1382,9 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Client> c, uint8_t com
static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
if ((l->base_version == GameVersion::BB) && l->is_game()) {
if ((l->base_version == Version::BB_V4) && l->is_game()) {
send_bank(c);
} else if ((l->base_version == GameVersion::GC) && l->is_ep3()) {
} else if (l->is_ep3()) {
forward_subcommand(c, command, flag, data, size);
}
}
@@ -1386,7 +1392,7 @@ static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr<Client> c, uint
static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_BankAction_BB_6xBD>(data, size);
if (!l->is_game()) {
@@ -1414,11 +1420,11 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
}
} else { // Deposit item
auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != GameVersion::BB);
auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != Version::BB_V4);
p->bank.add_item(item);
send_destroy_item(c, cmd.item_id, cmd.item_amount);
string name = s->item_name_index->describe_item(GameVersion::BB, item);
string name = s->item_name_index->describe_item(Version::BB_V4, item);
l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank",
c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str());
c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index);
@@ -1445,21 +1451,21 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
p->add_item(item);
send_create_inventory_item(c, item);
string name = s->item_name_index->describe_item(GameVersion::BB, item);
string name = s->item_name_index->describe_item(Version::BB_V4, item);
l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank",
c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str());
c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index);
}
}
} else if ((c->version() == GameVersion::GC) && c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
} else if (is_ep3(c->version())) {
forward_subcommand(c, command, flag, data, size);
}
}
static void on_sort_inventory_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_SortInventory_BB_6xC4>(data, size);
if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
@@ -1661,7 +1667,8 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
}
uint16_t flag_index, difficulty, action;
if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) {
// TODO: Which format does GC NTE use?
if (is_v1_or_v2(c->version())) {
const auto& cmd = check_size_t<G_SetQuestFlag_DC_PC_6x75>(data, size);
flag_index = cmd.flag;
action = cmd.action;
@@ -1692,7 +1699,7 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
forward_subcommand(c, command, flag, data, size);
if (c->version() == GameVersion::GC) {
if (is_v3(c->version())) {
bool should_send_boss_drop_req = false;
bool is_ep2 = (l->episode == Episode::EP2);
if ((l->episode == Episode::EP1) && (c->floor == 0x0E)) {
@@ -1742,10 +1749,10 @@ static void on_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t, co
G_DragonBossActions_GC_6x12 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id},
cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()}};
bool sender_is_gc = (c->version() == GameVersion::GC);
bool sender_is_gc = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
if ((lc->version() == GameVersion::GC) == sender_is_gc) {
if (is_big_endian(lc->version()) == sender_is_gc) {
send_command_t(lc, 0x60, 0x00, cmd);
} else {
send_command_t(lc, 0x60, 0x00, sw_cmd);
@@ -1773,10 +1780,10 @@ static void on_gol_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t
cmd.z.load(),
cmd.unknown_a5,
0}};
bool sender_is_gc = (c->version() == GameVersion::GC);
bool sender_is_gc = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
if ((lc->version() == GameVersion::GC) == sender_is_gc) {
if (is_big_endian(lc->version()) == sender_is_gc) {
send_command_t(lc, 0x60, 0x00, cmd);
} else {
send_command_t(lc, 0x60, 0x00, sw_cmd);
@@ -1796,7 +1803,7 @@ static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, const v
return;
}
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
if (c->lobby_client_id > 3) {
throw logic_error("client ID is above 3");
}
@@ -1816,10 +1823,10 @@ static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, const v
}
G_EnemyHitByPlayer_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.remaining_hp, cmd.flags.load()}};
bool sender_is_gc = (c->version() == GameVersion::GC);
bool sender_is_gc = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
if ((lc->version() == GameVersion::GC) == sender_is_gc) {
if (is_big_endian(lc->version()) == sender_is_gc) {
send_command_t(lc, 0x60, 0x00, cmd);
} else {
send_command_t(lc, 0x60, 0x00, sw_cmd);
@@ -1830,7 +1837,7 @@ static void on_enemy_hit(shared_ptr<Client> c, uint8_t command, uint8_t, const v
static void on_charge_attack_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version != GameVersion::BB) {
if (l->base_version != Version::BB_V4) {
throw runtime_error("BB-only command sent in non-BB game");
}
@@ -1893,7 +1900,7 @@ static void on_steal_exp_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->base_version != GameVersion::BB) {
if (l->base_version != Version::BB_V4) {
throw runtime_error("BB-only command sent in non-BB game");
}
if (!l->map) {
@@ -1942,7 +1949,7 @@ static void on_enemy_killed_bb(shared_ptr<Client> c, uint8_t, uint8_t, const voi
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->base_version != GameVersion::BB) {
if (l->base_version != Version::BB_V4) {
throw runtime_error("BB-only command sent in non-BB game");
}
@@ -2082,7 +2089,7 @@ static void on_destroy_inventory_item(shared_ptr<Client> c, uint8_t command, uin
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto s = c->require_server_state();
auto p = c->game_data.character();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)",
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
@@ -2121,7 +2128,7 @@ static void on_destroy_ground_item(shared_ptr<Client> c, uint8_t command, uint8_
static void on_identify_item_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_IdentifyItemRequest_6xB8>(data, size);
if (!l->is_game()) {
return;
@@ -2157,7 +2164,7 @@ static void on_identify_item_bb(shared_ptr<Client> c, uint8_t command, uint8_t f
static void on_accept_identify_item_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_AcceptItemIdentification_BB_6xBA>(data, size);
if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
@@ -2182,7 +2189,7 @@ static void on_accept_identify_item_bb(shared_ptr<Client> c, uint8_t command, ui
static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_SellItemAtShop_BB_6xC0>(data, size);
if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
@@ -2191,7 +2198,7 @@ static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8
auto s = c->require_server_state();
auto p = c->game_data.character();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price);
@@ -2211,7 +2218,7 @@ static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8
static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->base_version == GameVersion::BB) {
if (l->base_version == Version::BB_V4) {
const auto& cmd = check_size_t<G_BuyShopItem_BB_6xB7>(data, size);
if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
throw logic_error("item tracking not enabled in BB game");
@@ -2249,7 +2256,7 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const vo
static void on_medical_center_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*, size_t) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB)) {
if (l->is_game() && (l->base_version == Version::BB_V4)) {
c->game_data.character()->remove_meseta(10, false);
}
}
@@ -2258,7 +2265,7 @@ static void on_battle_restart_bb(shared_ptr<Client> c, uint8_t, uint8_t, const v
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->is_game() &&
(l->base_version == GameVersion::BB) &&
(l->base_version == Version::BB_V4) &&
(l->mode == GameMode::BATTLE) &&
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) &&
l->leader_id == c->lobby_client_id) {
@@ -2283,7 +2290,7 @@ static void on_battle_restart_bb(shared_ptr<Client> c, uint8_t, uint8_t, const v
static void on_battle_level_up_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() &&
(l->base_version == GameVersion::BB) &&
(l->base_version == Version::BB_V4) &&
(l->mode == GameMode::BATTLE) &&
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_BattleModeLevelUp_BB_6xD0>(data, size);
@@ -2303,7 +2310,7 @@ static void on_battle_level_up_bb(shared_ptr<Client> c, uint8_t, uint8_t, const
static void on_request_challenge_grave_recovery_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() &&
(l->base_version == GameVersion::BB) &&
(l->base_version == Version::BB_V4) &&
(l->mode == GameMode::CHALLENGE) &&
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1>(data, size);
@@ -2325,7 +2332,7 @@ static void on_request_challenge_grave_recovery_item_bb(shared_ptr<Client> c, ui
static void on_quest_exchange_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() &&
(l->base_version == GameVersion::BB) &&
(l->base_version == Version::BB_V4) &&
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_ExchangeItemInQuest_BB_6xD5>(data, size);
@@ -2354,7 +2361,7 @@ static void on_quest_exchange_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, co
static void on_wrap_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB)) {
if (l->is_game() && (l->base_version == Version::BB_V4)) {
const auto& cmd = check_size_t<G_WrapItem_BB_6xD6>(data, size);
auto p = c->game_data.character();
@@ -2368,7 +2375,7 @@ static void on_wrap_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
static void on_photon_drop_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB)) {
if (l->is_game() && (l->base_version == Version::BB_V4)) {
const auto& cmd = check_size_t<G_PaganiniPhotonDropExchange_BB_6xD7>(data, size);
try {
@@ -2397,7 +2404,7 @@ static void on_photon_drop_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, c
static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto s = c->require_server_state();
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_ExchangeSecretLotteryTicket_BB_6xDE>(data, size);
if (s->secret_lottery_results.empty()) {
@@ -2451,7 +2458,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
static void on_photon_crystal_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(data, size);
auto p = c->game_data.character();
size_t index = p->inventory.find_item_by_primary_identifier(0x031002);
@@ -2463,7 +2470,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t
static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_RequestItemDropFromQuest_BB_6xE0>(data, size);
auto s = c->require_server_state();
@@ -2493,7 +2500,7 @@ static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_ExchangePhotonTickets_BB_6xE1>(data, size);
auto s = c->require_server_state();
auto p = c->game_data.character();
@@ -2532,7 +2539,7 @@ static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
static void on_momoka_item_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_MomokaItemExchange_BB_6xD9>(data, size);
auto p = c->game_data.character();
try {
@@ -2561,7 +2568,7 @@ static void on_momoka_item_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, c
static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
auto l = c->require_lobby();
if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
const auto& cmd = check_size_t<G_UpgradeWeaponAttribute_BB_6xDA>(data, size);
auto p = c->game_data.character();
try {
@@ -2668,7 +2675,7 @@ static void handle_subcommand_dc_nte(shared_ptr<Client> c, uint8_t command, uint
continue;
}
if (lc->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) {
if (lc->version() == Version::DC_NTE) {
send_command(lc, command, flag, data, size);
} else if (non_nte_subcommand != 0x00) {
if (non_nte_data.empty()) {
@@ -2970,7 +2977,7 @@ void on_subcommand_multi(shared_ptr<Client> c, uint8_t command, uint8_t flag, co
}
const void* data = r.getv(size);
if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) {
if (c->version() == Version::DC_NTE) {
handle_subcommand_dc_nte(c, command, flag, data, size);
} else {
auto fn = subcommand_handlers[header.subcommand];
+81 -59
View File
@@ -39,7 +39,7 @@ string ReplaySession::Event::str() const {
}
ReplaySession::Client::Client(
ReplaySession* session, uint64_t id, uint16_t port, GameVersion version)
ReplaySession* session, uint64_t id, uint16_t port, Version version)
: id(id),
port(port),
version(version),
@@ -53,7 +53,7 @@ ReplaySession::Client::Client(
string ReplaySession::Client::str() const {
return string_printf("Client[%" PRIu64 ", T-%hu, %s]",
this->id, this->port, name_for_version(this->version));
this->id, this->port, name_for_enum(this->version));
}
shared_ptr<ReplaySession::Event> ReplaySession::create_event(
@@ -107,18 +107,20 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
}
};
const void* cmd_data = ev->data.data() + ((version == GameVersion::BB) ? 8 : 4);
size_t cmd_size = ev->data.size() - ((version == GameVersion::BB) ? 8 : 4);
const void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
size_t cmd_size = ev->data.size() - ((version == Version::BB_V4) ? 8 : 4);
switch (version) {
case GameVersion::PATCH: {
case Version::PC_PATCH:
case Version::BB_PATCH: {
const auto& header = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF);
if (header.command == 0x04) {
check_either(check_size_t<C_Login_Patch_04>(cmd_data, cmd_size).password.decode());
}
break;
}
case GameVersion::PC: {
case Version::PC_V2: {
const auto& header = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF);
if (header.command == 0x03) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2.decode());
@@ -142,9 +144,16 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
}
break;
}
case GameVersion::DC:
case GameVersion::GC:
case GameVersion::XB: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
const auto& header = check_size_t<PSOCommandHeaderDCV3>(ev->data, 0xFFFF);
if (header.command == 0x03) {
check_ak(check_size_t<C_LegacyLogin_PC_V3_03>(cmd_data, cmd_size).access_key2.decode());
@@ -170,7 +179,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
} else if (header.command == 0x9E) {
if (version == GameVersion::GC) {
if (is_gc(version)) {
const auto& cmd = check_size_t<C_Login_GC_9E>(cmd_data, cmd_size, sizeof(C_LoginExtended_GC_9E));
check_ak(cmd.access_key.decode());
check_ak(cmd.access_key2.decode());
@@ -187,7 +196,8 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
}
break;
}
case GameVersion::BB: {
case Version::BB_V4: {
const auto& header = check_size_t<PSOCommandHeaderBB>(ev->data, 0xFFFF);
if (header.command == 0x04) {
check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password.decode());
@@ -202,6 +212,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
}
break;
}
default:
throw logic_error("invalid game version");
}
@@ -210,13 +221,14 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
auto version = this->clients.at(ev->client_id)->version;
void* cmd_data = ev->data.data() + ((version == GameVersion::BB) ? 8 : 4);
size_t cmd_size = ev->data.size() - ((version == GameVersion::BB) ? 8 : 4);
void* mask_data = ev->mask.data() + ((version == GameVersion::BB) ? 8 : 4);
size_t mask_size = ev->mask.size() - ((version == GameVersion::BB) ? 8 : 4);
void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
size_t cmd_size = ev->data.size() - ((version == Version::BB_V4) ? 8 : 4);
void* mask_data = ev->mask.data() + ((version == Version::BB_V4) ? 8 : 4);
size_t mask_size = ev->mask.size() - ((version == Version::BB_V4) ? 8 : 4);
switch (version) {
case GameVersion::PATCH: {
case Version::PC_PATCH:
case Version::BB_PATCH: {
const auto& header = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF);
if (header.command == 0x02) {
auto& cmd_mask = check_size_t<S_ServerInit_Patch_02>(mask_data, mask_size);
@@ -225,12 +237,18 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
break;
}
case GameVersion::DC:
case GameVersion::PC:
case GameVersion::GC:
case GameVersion::XB: {
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3: {
uint8_t command;
if (version == GameVersion::PC) {
if (version == Version::PC_V2) {
command = check_size_t<PSOCommandHeaderPC>(ev->data, 0xFFFF).command;
} else { // V3
command = check_size_t<PSOCommandHeaderDCV3>(ev->data, 0xFFFF).command;
@@ -257,27 +275,24 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
break;
case 0x41:
if (version == GameVersion::PC) {
if (version == Version::PC_V2) {
auto& mask = check_size_t<S_GuildCardSearchResult_PC_41>(mask_data, mask_size);
mask.reconnect_command.address = 0;
} else if (version == GameVersion::BB) {
auto& mask = check_size_t<S_GuildCardSearchResult_BB_41>(mask_data, mask_size);
mask.reconnect_command.address = 0;
} else { // V3
auto& mask = check_size_t<S_GuildCardSearchResult_DC_V3_41>(mask_data, mask_size);
mask.reconnect_command.address = 0;
}
break;
case 0x64:
if (version == GameVersion::PC) {
if (version == Version::PC_V2) {
auto& mask = check_size_t<S_JoinGame_PC_64>(mask_data, mask_size);
mask.variations.clear(0);
mask.rare_seed = 0;
} else if (version == GameVersion::XB) {
} else if (version == Version::XB_V3) {
auto& mask = check_size_t<S_JoinGame_XB_64>(mask_data, mask_size);
mask.variations.clear(0);
mask.rare_seed = 0;
} else if (version == GameVersion::DC || version == GameVersion::GC) {
} else if (version != Version::DC_NTE && version != Version::DC_V1_12_2000_PROTOTYPE) {
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64));
mask.variations.clear(0);
mask.rare_seed = 0;
@@ -290,45 +305,45 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
break;
case 0xEB:
if (version != GameVersion::GC) {
if (!is_gc(version)) {
break;
}
[[fallthrough]];
case 0x65:
case 0x67:
case 0x68:
if ((version == GameVersion::DC) || (version == GameVersion::GC)) {
for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) +
offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::XB) {
for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) +
offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::PC) {
if (version == Version::PC_V2) {
for (size_t offset = offsetof(S_JoinLobby_PC_65_67_68, entries) +
offsetof(S_JoinLobby_PC_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_PC_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::BB) {
for (size_t offset = offsetof(S_JoinLobby_BB_65_67_68, entries) +
offsetof(S_JoinLobby_BB_65_67_68::Entry, disp.visual.name_color_checksum);
} else if (version == Version::XB_V3) {
for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) +
offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_BB_65_67_68::Entry)) {
offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == Version::DC_NTE) {
for (size_t offset = offsetof(S_JoinLobby_DCNTE_65_67_68, entries) +
offsetof(S_JoinLobby_DCNTE_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_DCNTE_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else {
for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) +
offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
}
break;
case 0xE8:
if (version == GameVersion::GC) {
if (is_gc(version)) {
auto& mask = check_size_t<S_JoinSpectatorTeam_GC_Ep3_E8>(mask_data, mask_size);
mask.rare_seed = 0;
for (size_t z = 0; z < 4; z++) {
@@ -354,7 +369,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
break;
case 0x6C:
if (version == GameVersion::GC && mask_size >= 0x14) {
if (is_gc(version) && mask_size >= 0x14) {
const auto& cmd = check_size_t<G_MapList_GC_Ep3_6xB6x40>(cmd_data, cmd_size, 0xFFFF);
if ((cmd.header.header.basic_header.subcommand == 0xB6) &&
(cmd.header.subsubcommand == 0x40)) {
@@ -372,7 +387,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
break;
}
case GameVersion::BB: {
case Version::BB_V4: {
uint16_t command = check_size_t<PSOCommandHeaderBB>(ev->data, 0xFFFF).command;
switch (command) {
case 0x0003: {
@@ -481,7 +496,7 @@ ReplaySession::ReplaySession(
this,
stoull(tokens[8].substr(2), nullptr, 16),
stoul(listen_tokens[1], nullptr, 10),
version_for_name(listen_tokens[2].c_str())));
enum_for_name<Version>(listen_tokens[2].c_str())));
if (!this->clients.emplace(c->id, c).second) {
throw runtime_error(string_printf("(ev-line %zu) duplicate client ID in input log", line_num));
}
@@ -701,20 +716,27 @@ void ReplaySession::on_command_received(
// If the command is an encryption init, set up encryption on the channel
switch (c->version) {
case GameVersion::PATCH:
case Version::PC_PATCH:
case Version::BB_PATCH:
if (command == 0x02) {
auto& cmd = check_size_t<S_ServerInit_Patch_02>(data);
c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
}
break;
case GameVersion::DC:
case GameVersion::PC:
case GameVersion::GC:
case GameVersion::XB:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3:
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) {
if (is_v1_or_v2(c->version)) {
c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
} else { // V3
@@ -723,7 +745,7 @@ void ReplaySession::on_command_received(
}
}
break;
case GameVersion::BB:
case Version::BB_V4:
if (command == 0x03 || command == 0x9B) {
auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 0xFFFF);
// TODO: At some point it may matter which BB private key file we use.
+2 -2
View File
@@ -53,12 +53,12 @@ private:
struct Client {
uint64_t id;
uint16_t port;
GameVersion version;
Version version;
Channel channel;
std::deque<std::shared_ptr<Event>> receive_events;
std::shared_ptr<Event> disconnect_event;
Client(ReplaySession* session, uint64_t id, uint16_t port, GameVersion version);
Client(ReplaySession* session, uint64_t id, uint16_t port, Version version);
std::string str() const;
};
+1 -1
View File
@@ -562,7 +562,7 @@ void PSOBBCharacterFile::clear_all_material_usage() {
}
}
void PSOBBCharacterFile::print_inventory(FILE* stream, GameVersion version, shared_ptr<const ItemNameIndex> name_index) const {
void PSOBBCharacterFile::print_inventory(FILE* stream, Version version, shared_ptr<const ItemNameIndex> name_index) const {
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load());
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
for (size_t x = 0; x < this->inventory.num_items; x++) {
+1 -1
View File
@@ -246,7 +246,7 @@ struct PSOBBCharacterFile {
void set_material_usage(MaterialType which, uint8_t usage);
void clear_all_material_usage();
void print_inventory(FILE* stream, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index) const;
void print_inventory(FILE* stream, Version version, std::shared_ptr<const ItemNameIndex> name_index) const;
} __attribute__((packed));
struct PSOBBGuildCardFile {
+295 -225
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -187,8 +187,7 @@ void send_text_message(std::shared_ptr<Lobby> l, const std::string& text);
void send_text_message(std::shared_ptr<ServerState> s, const std::string& text);
std::string prepare_chat_data(
GameVersion version,
bool is_nte,
Version version,
uint8_t language,
uint8_t from_client_id,
const std::string& from_name,
+8 -8
View File
@@ -130,7 +130,7 @@ void Server::on_listen_accept(
void Server::connect_client(
struct bufferevent* bev, uint32_t address, uint16_t client_port,
uint16_t server_port, GameVersion version, ServerBehavior initial_state) {
uint16_t server_port, Version version, ServerBehavior initial_state) {
shared_ptr<Client> c(new Client(this->shared_from_this(), bev, version, initial_state));
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
@@ -141,8 +141,8 @@ void Server::connect_client(
c->id,
bev,
server_port,
name_for_version(version),
name_for_server_behavior(initial_state));
name_for_enum(version),
name_for_enum(initial_state));
this->state->channel_to_client.emplace(&c->channel, c);
@@ -221,7 +221,7 @@ Server::Server(
void Server::listen(
const std::string& addr_str,
const string& socket_path,
GameVersion version,
Version version,
ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
server_log.info("Listening on Unix socket %s on fd %d as %s",
@@ -233,7 +233,7 @@ void Server::listen(
const std::string& addr_str,
const string& addr,
int port,
GameVersion version,
Version version,
ServerBehavior behavior) {
if (port == 0) {
this->listen(addr_str, addr, version, behavior);
@@ -246,13 +246,13 @@ void Server::listen(
}
}
void Server::listen(const std::string& addr_str, int port, GameVersion version, ServerBehavior behavior) {
void Server::listen(const std::string& addr_str, int port, Version version, ServerBehavior behavior) {
this->listen(addr_str, "", port, version, behavior);
}
Server::ListeningSocket::ListeningSocket(
Server* s, const std::string& addr_str,
int fd, GameVersion version, ServerBehavior behavior)
int fd, Version version, ServerBehavior behavior)
: addr_str(addr_str),
fd(fd),
version(version),
@@ -269,7 +269,7 @@ Server::ListeningSocket::ListeningSocket(
void Server::add_socket(
const std::string& addr_str,
int fd,
GameVersion version,
Version version,
ServerBehavior behavior) {
this->listening_sockets.emplace(
piecewise_construct, forward_as_tuple(fd),
+7 -7
View File
@@ -18,14 +18,14 @@ public:
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
virtual ~Server() = default;
void listen(const std::string& addr_str, const std::string& socket_path, GameVersion version, ServerBehavior initial_state);
void listen(const std::string& addr_str, const std::string& addr, int port, GameVersion version, ServerBehavior initial_state);
void listen(const std::string& addr_str, int port, GameVersion version, ServerBehavior initial_state);
void add_socket(const std::string& addr_str, int fd, GameVersion version, ServerBehavior initial_state);
void listen(const std::string& addr_str, const std::string& socket_path, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, const std::string& addr, int port, Version version, ServerBehavior initial_state);
void listen(const std::string& addr_str, int port, Version version, ServerBehavior initial_state);
void add_socket(const std::string& addr_str, int fd, Version version, ServerBehavior initial_state);
void connect_client(struct bufferevent* bev, uint32_t address,
uint16_t client_port, uint16_t server_port,
GameVersion version, ServerBehavior initial_state);
Version version, ServerBehavior initial_state);
void connect_client(std::shared_ptr<Client> c, Channel&& ch);
void disconnect_client(std::shared_ptr<Client> c);
@@ -45,7 +45,7 @@ private:
struct ListeningSocket {
std::string addr_str;
int fd;
GameVersion version;
Version version;
ServerBehavior behavior;
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
@@ -53,7 +53,7 @@ private:
Server* s,
const std::string& name,
int fd,
GameVersion version,
Version version,
ServerBehavior behavior);
};
std::unordered_map<int, ListeningSocket> listening_sockets;
+4 -7
View File
@@ -670,7 +670,7 @@ Proxy session commands:\n\
auto ses = this->get_proxy_session(session_name);
bool is_dchat = (command_name == "dchat");
if (!is_dchat && (ses->version() == GameVersion::PC || ses->version() == GameVersion::BB)) {
if (!is_dchat && uses_utf16(ses->version())) {
send_chat_message_from_client(ses->server_channel, command_args, 0);
} else {
string data(8, '\0');
@@ -688,8 +688,7 @@ Proxy session commands:\n\
} else if ((command_name == "wc") || (command_name == "wchat")) {
auto ses = this->get_proxy_session(session_name);
if ((ses->version() != GameVersion::GC) ||
!ses->config.check_flag(Client::Flag::IS_EPISODE_3)) {
if (!is_ep3(ses->version())) {
throw runtime_error("wchat can only be used on Episode 3");
}
string data(8, '\0');
@@ -746,9 +745,7 @@ Proxy session commands:\n\
ses->config.override_lobby_event = 0xFF;
} else {
ses->config.override_lobby_event = event_for_name(command_args);
if ((ses->version() != GameVersion::DC) &&
(ses->version() != GameVersion::PC) &&
!((ses->version() == GameVersion::GC) && ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) {
if (!is_v1_or_v2(ses->version())) {
ses->client_channel.send(0xDA, ses->config.override_lobby_event);
}
}
@@ -796,7 +793,7 @@ Proxy session commands:\n\
} else if ((command_name == "create-item") || (command_name == "set-next-item")) {
auto ses = this->get_proxy_session(session_name);
if (ses->version() == GameVersion::BB) {
if (ses->version() == Version::BB_V4) {
throw runtime_error("proxy session is BB");
}
if (!ses->is_in_game) {
+81 -58
View File
@@ -17,7 +17,7 @@
using namespace std;
ServerState::ServerState(const char* config_filename, bool is_replay)
ServerState::ServerState(const string& config_filename, bool is_replay)
: config_filename(config_filename),
is_replay(is_replay),
dns_server_port(0),
@@ -56,28 +56,38 @@ void ServerState::init() {
for (size_t x = 0; x < 20; x++) {
auto lobby_name = string_printf("LOBBY%zu", x + 1);
bool v2_and_later_only = (x > 9);
bool is_ep3_only = (x > 14);
bool allow_v1 = (x <= 9);
bool allow_non_ep3 = (x <= 14);
shared_ptr<Lobby> l = this->create_lobby();
l->set_flag(Lobby::Flag::PUBLIC);
l->set_flag(Lobby::Flag::DEFAULT);
l->set_flag(Lobby::Flag::PERSISTENT);
if (v2_and_later_only) {
l->set_flag(Lobby::Flag::V2_AND_LATER);
if (allow_non_ep3) {
if (allow_v1) {
l->allow_version(Version::DC_NTE);
l->allow_version(Version::DC_V1_12_2000_PROTOTYPE);
l->allow_version(Version::DC_V1);
}
l->allow_version(Version::DC_V2);
l->allow_version(Version::PC_V2);
l->allow_version(Version::GC_NTE);
l->allow_version(Version::GC_V3);
l->allow_version(Version::XB_V3);
l->allow_version(Version::BB_V4);
}
l->allow_version(Version::GC_EP3_TRIAL_EDITION);
l->allow_version(Version::GC_EP3);
l->block = x + 1;
l->name = lobby_name;
l->max_clients = 12;
if (is_ep3_only) {
if (!allow_non_ep3) {
l->episode = Episode::EP3;
}
if (!v2_and_later_only) {
this->public_lobby_search_order_v1.emplace_back(l);
}
if (!is_ep3_only) {
this->public_lobby_search_order_non_v1.emplace_back(l);
if (allow_non_ep3) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
@@ -86,9 +96,8 @@ void ServerState::init() {
// Annoyingly, the CARD lobbies should be searched first, but are sent at the
// end of the lobby list command, so we have to change the search order
// manually here.
this->public_lobby_search_order_ep3 = this->public_lobby_search_order_non_v1;
this->public_lobby_search_order_ep3.insert(
this->public_lobby_search_order_ep3.begin(),
this->public_lobby_search_order.insert(
this->public_lobby_search_order.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
@@ -120,7 +129,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
if (l &&
!l->is_game() &&
l->check_flag(Lobby::Flag::PUBLIC) &&
(c->config.check_flag(Client::Flag::IS_EPISODE_3) || (l->episode != Episode::EP3))) {
l->version_is_allowed(c->version())) {
l->add_client(c);
added_to_lobby = l;
}
@@ -129,17 +138,16 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
}
if (!added_to_lobby.get()) {
const auto* search_order = &this->public_lobby_search_order_non_v1;
if (c->config.check_flag(Client::Flag::IS_DC_V1)) {
search_order = &this->public_lobby_search_order_v1;
} else if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
search_order = &this->public_lobby_search_order_ep3;
}
for (const auto& l : *search_order) {
for (const auto& l : this->public_lobby_search_order) {
try {
l->add_client(c);
added_to_lobby = l;
break;
if (l &&
!l->is_game() &&
l->check_flag(Lobby::Flag::PUBLIC) &&
l->version_is_allowed(c->version())) {
l->add_client(c);
added_to_lobby = l;
break;
}
} catch (const out_of_range&) {
}
}
@@ -153,6 +161,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
added_to_lobby->name = "Overflow";
added_to_lobby->max_clients = 12;
added_to_lobby->event = this->pre_lobby_event;
added_to_lobby->allow_version(c->version());
added_to_lobby->add_client(c);
}
@@ -325,61 +334,79 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) cons
}
}
std::shared_ptr<const Menu> ServerState::information_menu_for_version(GameVersion version) const {
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
std::shared_ptr<const Menu> ServerState::information_menu_for_version(Version version) const {
if (is_v1_or_v2(version)) {
return this->information_menu_v2;
} else if ((version == GameVersion::GC) || (version == GameVersion::XB)) {
} else if (is_v3(version)) {
return this->information_menu_v3;
}
throw out_of_range("no information menu exists for this version");
}
shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(GameVersion version) const {
shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(Version version) const {
switch (version) {
case GameVersion::DC:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->proxy_destinations_menu_dc;
case GameVersion::PC:
case Version::PC_V2:
return this->proxy_destinations_menu_pc;
case GameVersion::GC:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->proxy_destinations_menu_gc;
case GameVersion::XB:
case Version::XB_V3:
return this->proxy_destinations_menu_xb;
default:
throw out_of_range("no proxy destinations menu exists for this version");
}
}
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(GameVersion version) const {
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(Version version) const {
switch (version) {
case GameVersion::DC:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->proxy_destinations_dc;
case GameVersion::PC:
case Version::PC_V2:
return this->proxy_destinations_pc;
case GameVersion::GC:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->proxy_destinations_gc;
case GameVersion::XB:
case Version::XB_V3:
return this->proxy_destinations_xb;
default:
throw out_of_range("no proxy destinations menu exists for this version");
}
}
std::shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_version(GameVersion version) const {
std::shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_version(Version version) const {
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::PC_V2:
return this->item_parameter_table_v2;
case GameVersion::GC:
case GameVersion::XB:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
case Version::XB_V3:
return this->item_parameter_table_v3;
case GameVersion::BB:
case Version::BB_V4:
return this->item_parameter_table_v4;
default:
throw out_of_range("no item parameter table exists for this version");
}
}
std::string ServerState::describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const {
std::string ServerState::describe_item(Version version, const ItemData& item, bool include_color_codes) const {
return this->item_name_index->describe_item(
version,
item,
@@ -499,8 +526,8 @@ static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
PortConfiguration& pc = ret.emplace_back();
pc.name = item_json_it.first;
pc.port = item_list->at(0).as_int();
pc.version = version_for_name(item_list->at(1).as_string().c_str());
pc.behavior = server_behavior_for_name(item_list->at(2).as_string().c_str());
pc.version = enum_for_name<Version>(item_list->at(1).as_string().c_str());
pc.behavior = enum_for_name<ServerBehavior>(item_list->at(2).as_string().c_str());
}
return ret;
}
@@ -846,7 +873,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->proxy_destination_patch = parse_netloc(netloc_str);
config_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) {
if (is_patch(it.second->version)) {
it.second->behavior = ServerBehavior::PROXY_SERVER;
}
}
@@ -859,7 +886,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->proxy_destination_bb = parse_netloc(netloc_str);
config_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) {
if (it.second->version == Version::BB_V4) {
it.second->behavior = ServerBehavior::PROXY_SERVER;
}
}
@@ -957,13 +984,13 @@ void ServerState::load_item_tables() {
if (ends_with(filename, "-v2.json")) {
config_log.info("Loading v2 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::PC, this->item_name_index));
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::PC_V2, this->item_name_index));
} else if (ends_with(filename, "-v3.json")) {
config_log.info("Loading v3 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::GC, this->item_name_index));
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::GC_V3, this->item_name_index));
} else if (ends_with(filename, "-v4.json")) {
config_log.info("Loading v4 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), GameVersion::BB, this->item_name_index));
this->rare_item_sets.emplace(basename, new RareItemSet(JSON::parse(load_file(path)), Version::BB_V4, this->item_name_index));
} else if (ends_with(filename, ".afs")) {
config_log.info("Loading AFS rare item table %s", filename.c_str());
@@ -1121,13 +1148,9 @@ void ServerState::load_dol_files() {
}
shared_ptr<const vector<string>> ServerState::information_contents_for_client(shared_ptr<const Client> c) const {
return ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC))
? this->information_contents_v2
: this->information_contents_v3;
return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3;
}
shared_ptr<const QuestIndex> ServerState::quest_index_for_client(shared_ptr<const Client> c) const {
return c->config.check_flag(Client::Flag::IS_EPISODE_3)
? this->ep3_download_quest_index
: this->default_quest_index;
return is_ep3(c->version()) ? this->ep3_download_quest_index : this->default_quest_index;
}
+8 -10
View File
@@ -32,7 +32,7 @@ class Server;
struct PortConfiguration {
std::string name;
uint16_t port;
GameVersion version;
Version version;
ServerBehavior behavior;
};
@@ -164,9 +164,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_v1;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_non_v1;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_ep3;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
std::atomic<int32_t> next_lobby_id;
uint8_t pre_lobby_event;
int32_t ep3_menu_song;
@@ -181,7 +179,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
ServerState(const char* config_filename, bool is_replay);
ServerState(const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
@@ -212,12 +210,12 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
std::shared_ptr<const Menu> information_menu_for_version(GameVersion version) const;
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(GameVersion version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version) const;
std::shared_ptr<const Menu> information_menu_for_version(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_version(GameVersion version) const;
std::string describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_version(Version version) const;
std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const;
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
std::shared_ptr<const QuestIndex> quest_index_for_client(std::shared_ptr<const Client> c) const;
+187 -71
View File
@@ -8,52 +8,154 @@
using namespace std;
const vector<string> version_to_login_port_name = {
"bb-patch", "console-login", "pc-login", "console-login", "xb-login", "bb-init"};
const vector<string> version_to_lobby_port_name = {
"bb-patch", "console-lobby", "pc-lobby", "console-lobby", "xb-lobby", "bb-lobby"};
const vector<string> version_to_proxy_port_name = {
"", "dc-proxy", "pc-proxy", "gc-proxy", "xb-proxy", "bb-proxy"};
const char* name_for_version(GameVersion version) {
switch (version) {
case GameVersion::GC:
return "GC";
case GameVersion::XB:
return "XB";
case GameVersion::PC:
return "PC";
case GameVersion::BB:
return "BB";
case GameVersion::DC:
return "DC";
case GameVersion::PATCH:
return "Patch";
const char* login_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return "console-login";
case Version::PC_V2:
return "pc-login";
case Version::XB_V3:
return "xb-login";
case Version::BB_V4:
return "bb-init";
default:
return "Unknown";
throw runtime_error("unknown version");
}
}
GameVersion version_for_name(const char* name) {
if (!strcasecmp(name, "DC") || !strcasecmp(name, "DreamCast")) {
return GameVersion::DC;
} else if (!strcasecmp(name, "PC")) {
return GameVersion::PC;
} else if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) {
return GameVersion::GC;
} else if (!strcasecmp(name, "XB") || !strcasecmp(name, "Xbox")) {
return GameVersion::XB;
} else if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") ||
!strcasecmp(name, "Blue Burst")) {
return GameVersion::BB;
} else if (!strcasecmp(name, "Patch")) {
return GameVersion::PATCH;
const char* lobby_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return "console-lobby";
case Version::PC_V2:
return "pc-lobby";
case Version::XB_V3:
return "xb-lobby";
case Version::BB_V4:
return "bb-lobby";
default:
throw runtime_error("unknown version");
}
}
const char* proxy_port_name_for_version(Version v) {
switch (v) {
case Version::PC_PATCH:
return "pc-patch";
case Version::BB_PATCH:
return "bb-patch";
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return "dc-proxy";
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return "gc-proxy";
case Version::PC_V2:
return "pc-proxy";
case Version::XB_V3:
return "xb-proxy";
case Version::BB_V4:
return "bb-proxy";
default:
throw runtime_error("unknown version");
}
}
template <>
const char* name_for_enum<Version>(Version v) {
switch (v) {
case Version::PC_PATCH:
return "PC_PATCH";
case Version::BB_PATCH:
return "BB_PATCH";
case Version::DC_NTE:
return "DC_NTE";
case Version::DC_V1_12_2000_PROTOTYPE:
return "DC_V1_12_2000_PROTOTYPE";
case Version::DC_V1:
return "DC_V1";
case Version::DC_V2:
return "DC_V2";
case Version::PC_V2:
return "PC_V2";
case Version::GC_NTE:
return "GC_NTE";
case Version::GC_V3:
return "GC_V3";
case Version::GC_EP3_TRIAL_EDITION:
return "GC_EP3_TRIAL_EDITION";
case Version::GC_EP3:
return "GC_EP3";
case Version::XB_V3:
return "XB_V3";
case Version::BB_V4:
return "BB_V4";
default:
throw runtime_error("unknown version");
}
}
template <>
Version enum_for_name<Version>(const char* name) {
if (!strcmp(name, "PC_PATCH") || !strcasecmp(name, "patch")) {
return Version::PC_PATCH;
} else if (!strcmp(name, "BB_PATCH")) {
return Version::BB_PATCH;
} else if (!strcmp(name, "DC_NTE")) {
return Version::DC_NTE;
} else if (!strcmp(name, "DC_V1_12_2000_PROTOTYPE")) {
return Version::DC_V1_12_2000_PROTOTYPE;
} else if (!strcmp(name, "DC_V1")) {
return Version::DC_V1;
} else if (!strcmp(name, "DC_V2") || !strcasecmp(name, "dc")) {
return Version::DC_V2;
} else if (!strcmp(name, "PC_V2") || !strcasecmp(name, "pc")) {
return Version::PC_V2;
} else if (!strcmp(name, "GC_NTE")) {
return Version::GC_NTE;
} else if (!strcmp(name, "GC_V3") || !strcasecmp(name, "gc")) {
return Version::GC_V3;
} else if (!strcmp(name, "GC_EP3_TRIAL_EDITION")) {
return Version::GC_EP3_TRIAL_EDITION;
} else if (!strcmp(name, "GC_EP3")) {
return Version::GC_EP3;
} else if (!strcmp(name, "XB_V3") || !strcasecmp(name, "xb")) {
return Version::XB_V3;
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
return Version::BB_V4;
} else {
throw invalid_argument("incorrect version name");
}
}
const char* name_for_server_behavior(ServerBehavior behavior) {
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior) {
switch (behavior) {
case ServerBehavior::PC_CONSOLE_DETECT:
return "pc_console_detect";
@@ -69,12 +171,12 @@ const char* name_for_server_behavior(ServerBehavior behavior) {
return "patch_server_bb";
case ServerBehavior::PROXY_SERVER:
return "proxy_server";
default:
throw logic_error("invalid server behavior");
}
throw logic_error("invalid server behavior");
}
ServerBehavior server_behavior_for_name(const char* name) {
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name) {
if (!strcasecmp(name, "pc_console_detect")) {
return ServerBehavior::PC_CONSOLE_DETECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login")) {
@@ -94,36 +196,50 @@ ServerBehavior server_behavior_for_name(const char* name) {
}
}
uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version) {
uint32_t base_specific_version = (static_cast<uint32_t>(version) + '0') << 24;
if (version == GameVersion::GC) {
// For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetect call.
switch (sub_version) {
case 0x36: // GC Ep1&2 US v1.02 (Plus)
return 0x334F4532; // 3OE2
case 0x39: // GC Ep1&2 JP v1.05 (Plus)
return 0x334F4A35; // 3OJ5
case 0x41: // GC Ep3 US
return 0x33534530; // 3SE0
case 0x43: // GC Ep3 EU
return 0x33535030; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
case 0x34: // GC Ep1&2 JP v1.03
case 0x35: // GC Ep1&2 JP v1.04 (Plus)
case 0x40: // GC Ep3 trial
case 0x42: // GC Ep3 JP
default:
return base_specific_version;
}
} else {
return base_specific_version;
uint32_t default_specific_version_for_version(Version version, int64_t sub_version) {
// For versions that don't support send_function_call by default, we need
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetect call.
switch (version) {
case Version::GC_NTE:
return 0x334F4A54; // 3OJT
case Version::GC_V3:
switch (sub_version) {
case 0x32: // GC Ep1&2 EU 50Hz
case 0x33: // GC Ep1&2 EU 60Hz
return 0x334F5030; // 3OP0
case 0x36: // GC Ep1&2 US v1.02 (Plus)
return 0x334F4532; // 3OE2
case 0x39: // GC Ep1&2 JP v1.05 (Plus)
return 0x334F4A35; // 3OJ5
case 0x34: // GC Ep1&2 JP v1.03
return 0x334F4A33; // 3OJ3
case 0x35: // GC Ep1&2 JP v1.04 (Plus)
return 0x334F4A34; // 3OJ4
case -1: // Initial check (before sub_version recognition)
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 Trial Edition, GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, XB US
default:
return 0x33000000;
}
throw logic_error("this should be impossible");
case Version::GC_EP3_TRIAL_EDITION:
return 0x33534A54; // 3SJT
case Version::GC_EP3:
switch (sub_version) {
case 0x41: // GC Ep3 US
return 0x33534530; // 3SE0
case 0x42: // GC Ep3 EU 50Hz
case 0x43: // GC Ep3 EU 60Hz
return 0x33535030; // 3SP0
case -1: // Initial check (before sub_version recognition)
case 0x40: // GC Ep3 trial and GC Ep3 JP
default:
return 0x33000000;
}
default:
return 0x00000000;
}
}
+100 -17
View File
@@ -2,18 +2,104 @@
#include <inttypes.h>
#include <phosg/Types.hh>
#include <string>
#include <vector>
enum class GameVersion {
PATCH = 0,
DC = 1,
PC = 2,
GC = 3,
XB = 4,
BB = 5,
enum class Version {
PC_PATCH = 0,
BB_PATCH = 1,
DC_NTE = 2,
DC_V1_12_2000_PROTOTYPE = 3,
DC_V1 = 4,
DC_V2 = 5,
PC_V2 = 6,
GC_NTE = 7,
GC_V3 = 8,
GC_EP3_TRIAL_EDITION = 9,
GC_EP3 = 10,
XB_V3 = 11,
BB_V4 = 12,
UNKNOWN = 15,
};
template <>
const char* name_for_enum<Version>(Version v);
template <>
Version enum_for_name<Version>(const char* name);
inline bool is_patch(Version version) {
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
}
inline bool is_v1(Version version) {
return (version == Version::DC_NTE) || (version == Version::DC_V1_12_2000_PROTOTYPE) || (version == Version::DC_V1);
}
inline bool is_v2(Version version) {
return (version == Version::DC_V2) || (version == Version::PC_V2) || (version == Version::GC_NTE);
}
inline bool is_v1_or_v2(Version version) {
return is_v1(version) || is_v2(version);
}
inline bool is_v3(Version version) {
return (version == Version::GC_V3) ||
(version == Version::GC_EP3_TRIAL_EDITION) ||
(version == Version::GC_EP3) ||
(version == Version::XB_V3);
}
inline bool is_v4(Version version) {
return (version == Version::BB_V4);
}
inline bool is_ep3(Version version) {
return (version == Version::GC_EP3_TRIAL_EDITION) || (version == Version::GC_EP3);
}
inline bool is_dc(Version version) {
return (version == Version::DC_NTE) ||
(version == Version::DC_V1_12_2000_PROTOTYPE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2);
}
inline bool is_gc(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_TRIAL_EDITION) ||
(version == Version::GC_EP3);
}
inline bool is_big_endian(Version version) {
return (version == Version::GC_NTE) ||
(version == Version::GC_V3) ||
(version == Version::GC_EP3_TRIAL_EDITION) ||
(version == Version::GC_EP3);
}
inline bool uses_v2_encryption(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::DC_NTE) ||
(version == Version::DC_V1) ||
(version == Version::DC_V2) ||
(version == Version::PC_V2) ||
(version == Version::GC_NTE);
}
inline bool uses_v3_encryption(Version version) {
return (version == Version::GC_V3) ||
(version == Version::XB_V3) ||
(version == Version::GC_EP3);
}
inline bool uses_v4_encryption(Version version) {
return (version == Version::BB_V4);
}
inline bool uses_utf16(Version version) {
return (version == Version::PC_PATCH) ||
(version == Version::BB_PATCH) ||
(version == Version::PC_V2) ||
(version == Version::BB_V4);
}
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
enum class ServerBehavior {
PC_CONSOLE_DETECT = 0,
LOGIN_SERVER,
@@ -24,14 +110,11 @@ enum class ServerBehavior {
PROXY_SERVER,
};
extern const std::vector<std::string> version_to_login_port_name;
extern const std::vector<std::string> version_to_lobby_port_name;
extern const std::vector<std::string> version_to_proxy_port_name;
const char* login_port_name_for_version(Version v);
const char* lobby_port_name_for_version(Version v);
const char* proxy_port_name_for_version(Version v);
const char* name_for_version(GameVersion version);
GameVersion version_for_name(const char* name);
const char* name_for_server_behavior(ServerBehavior behavior);
ServerBehavior server_behavior_for_name(const char* name);
uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version);
template <>
const char* name_for_enum<ServerBehavior>(ServerBehavior behavior);
template <>
ServerBehavior enum_for_name<ServerBehavior>(const char* name);
+29 -25
View File
@@ -44,23 +44,25 @@ WordSelectTable::WordSelectTable(const JSON& json) {
}
}
uint16_t WordSelectTable::Token::value_for_version(QuestScriptVersion version) const {
uint16_t WordSelectTable::Token::value_for_version(Version version) const {
switch (version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
return this->dc_value;
case QuestScriptVersion::PC_V2:
case Version::PC_V2:
return this->pc_value;
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
// but this may not be true
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::XB_V3:
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
// but this may not be true
return this->gc_value;
case QuestScriptVersion::GC_EP3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
return this->ep3_value;
case QuestScriptVersion::BB_V4:
case Version::BB_V4:
return this->bb_value;
default:
throw logic_error("invalid word select version");
@@ -69,29 +71,31 @@ uint16_t WordSelectTable::Token::value_for_version(QuestScriptVersion version) c
WordSelectMessage WordSelectTable::translate(
const WordSelectMessage& msg,
QuestScriptVersion from_version,
QuestScriptVersion to_version) const {
Version from_version,
Version to_version) const {
const std::vector<size_t>* index;
switch (from_version) {
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
case Version::DC_NTE:
case Version::DC_V1_12_2000_PROTOTYPE:
case Version::DC_V1:
case Version::DC_V2:
index = &this->dc_index;
break;
case QuestScriptVersion::PC_V2:
case Version::PC_V2:
index = &this->pc_index;
break;
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
// but this may not be true
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
case QuestScriptVersion::XB_V3:
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
// TODO: Which index does GC_NTE use? Here we presume it's the same as GC,
// but this may not be true
index = &this->gc_index;
break;
case QuestScriptVersion::GC_EP3:
case Version::GC_EP3_TRIAL_EDITION:
case Version::GC_EP3:
index = &this->ep3_index;
break;
case QuestScriptVersion::BB_V4:
case Version::BB_V4:
index = &this->bb_index;
break;
default:
+3 -3
View File
@@ -15,8 +15,8 @@ public:
WordSelectMessage translate(
const WordSelectMessage& msg,
QuestScriptVersion from_version,
QuestScriptVersion to_version) const;
Version from_version,
Version to_version) const;
private:
struct Token {
@@ -26,7 +26,7 @@ private:
uint16_t ep3_value;
uint16_t bb_value;
uint16_t value_for_version(QuestScriptVersion version) const;
uint16_t value_for_version(Version version) const;
};
std::vector<size_t> dc_index;
std::vector<size_t> pc_index;
+7 -7
View File
@@ -58,7 +58,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (version=GC command=9
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -2619,7 +2619,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=CC f
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00)
0000 | 99 00 04 00 |
@@ -2627,7 +2627,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC co
0000 | D6 00 04 00 |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06)
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
@@ -2651,7 +2651,7 @@ I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC comma
0000 | 97 01 04 00 |
I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:35 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00)
0000 | B1 00 04 00 |
@@ -2702,11 +2702,11 @@ I 16332 2023-09-17 10:14:37 - [Commands] Received from C-2 (version=GC command=9
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2
00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
00D0 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=83 flag=14)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -3584,7 +3584,7 @@ I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC comma
0440 | FF FF FF FF FF FF FF FF FF FF FF FF |
I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16332 2023-09-17 10:14:39 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ?
+14 -14
View File
@@ -58,7 +58,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (version=GC command=9
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -2619,7 +2619,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=CC f
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00)
0000 | 99 00 04 00 |
@@ -2627,7 +2627,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC co
0000 | D6 00 04 00 |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06)
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
@@ -2651,7 +2651,7 @@ I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC comma
0000 | 97 01 04 00 |
I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:49 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00)
0000 | B1 00 04 00 |
@@ -2702,11 +2702,11 @@ I 17097 2023-09-19 21:52:50 - [Commands] Received from C-2 (version=GC command=9
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2
00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
00D0 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -3584,7 +3584,7 @@ I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC comma
0440 | FF FF FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0010 | 30 45 53 33 00 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:52:52 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ?
@@ -5869,7 +5869,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (version=GC command=9
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A1 00 42 60 00 00 00 00 00 00 00 | 3@ `
0010 | 00 00 00 33 00 A1 00 42 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -8430,7 +8430,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=CC f
0500 | 00 00 00 00 00 00 00 00 00 00 00 00 |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A1 00 4A 60 00 00 00 00 00 00 00 | 3@ `
0010 | 30 4A 53 33 00 A1 00 4A 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=99 flag=00)
0000 | 99 00 04 00 |
@@ -8438,7 +8438,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC co
0000 | D6 00 04 00 |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A1 00 48 60 00 00 00 00 00 00 00 | 3@ `
0010 | 30 4A 53 33 00 A1 00 48 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=07 flag=08)
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
@@ -8462,7 +8462,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC comma
0000 | 97 01 04 00 |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0010 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=B1 flag=00)
0000 | B1 00 04 00 |
@@ -8513,11 +8513,11 @@ I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (version=GC command=9
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , """"2
00D0 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
00D0 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0010 | 30 4A 53 33 00 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -9395,7 +9395,7 @@ I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC comma
0440 | FF FF FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2
0010 | 00 00 00 33 40 A3 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0010 | 30 4A 53 33 00 A3 00 4C 60 00 00 00 00 00 00 00 | 3@ `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (Tali) (version=GC command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ?
+8 -8
View File
@@ -58,7 +58,7 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 01 00 42 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 01 00 42 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
@@ -75,7 +75,7 @@ I 49108 2023-05-26 16:18:02 - [Commands] Received from C-1 (version=GC command=D
0000 | D6 00 04 00 |
I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 01 00 40 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 01 00 40 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=07 flag=08)
0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
@@ -97,7 +97,7 @@ I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=97 f
0000 | 97 01 04 00 |
I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:06 - [Commands] Received from C-1 (version=GC command=B1 flag=00)
0000 | B1 00 04 00 |
@@ -148,11 +148,11 @@ I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (version=GC command=9
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2
00D0 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
00D0 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=83 flag=0F)
0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
@@ -365,7 +365,7 @@ I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC comma
0440 | 0D FF 05 03 01 01 00 04 00 FF FF FF |
I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 FF FF | ` ?
@@ -10122,7 +10122,7 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9
00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess
00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2
00D0 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
00D0 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF 00 00 00 00 |
00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
@@ -10132,7 +10132,7 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9
0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2
0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
0010 | 30 50 4F 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 `
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=07 flag=06)
0000 | 07 05 AC 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
+6 -6
View File
@@ -52,7 +52,7 @@ I 16496 2023-11-08 01:54:08 - [Commands] Received from C-1 (version=XB command=9
0020 | 00 00 00 00 |
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
0010 | 00 00 00 34 00 81 00 42 60 00 00 00 00 00 00 00 | 4 B`
0010 | 00 00 00 00 00 81 00 42 60 00 00 00 00 00 00 00 | 4 B`
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=D5 flag=00)
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
@@ -69,7 +69,7 @@ I 16496 2023-11-08 01:54:13 - [Commands] Received from C-1 (version=XB command=D
0000 | D6 00 04 00 |
I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
0010 | 00 00 00 34 00 81 00 40 60 00 00 00 00 00 00 00 | 4 @`
0010 | 00 00 00 00 00 81 00 40 60 00 00 00 00 00 00 00 | 4 @`
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16496 2023-11-08 01:54:13 - [Commands] Sending to C-1 (version=XB command=07 flag=05)
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
@@ -87,7 +87,7 @@ I 16496 2023-11-08 01:54:15 - [Commands] Sending to C-1 (version=XB command=97 f
0000 | 97 01 04 00 |
I 16496 2023-11-08 01:54:15 - [Commands] Sending to C-1 (version=XB command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
0010 | 00 00 00 34 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D`
0010 | 00 00 00 00 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D`
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16496 2023-11-08 01:54:16 - [Commands] Received from C-1 (version=XB command=B1 flag=00)
0000 | B1 00 04 00 |
@@ -149,12 +149,12 @@ I 16496 2023-11-08 01:54:17 - [Commands] Received from C-2 (version=XB command=9
I 16496 2023-11-08 01:54:17 - [Commands] Sending to C-2 (version=XB command=9F flag=00)
0000 | 9F 00 04 00 |
I 16496 2023-11-08 01:54:18 - [Commands] Received from C-2 (version=XB command=9F flag=00)
0000 | 9F 00 24 00 32 AC 99 83 00 00 00 34 00 81 00 44 | $ 2 4 D
0000 | 9F 00 24 00 32 AC 99 83 00 00 00 00 00 81 00 44 | $ 2 4 D
0010 | 60 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF | `
0020 | 80 FF FF FF |
I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (version=XB command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
0010 | 00 00 00 34 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D`
0010 | 00 00 00 00 00 81 00 44 60 00 00 00 00 00 00 00 | 4 D`
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (version=XB command=83 flag=0F)
0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
@@ -371,7 +371,7 @@ I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (Tali) (version=XB comma
0480 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
I 16496 2023-11-08 01:54:18 - [Commands] Sending to C-2 (Tali) (version=XB command=04 flag=00)
0000 | 04 00 2C 00 00 00 01 00 E2 D4 45 39 32 AC 99 83 | , E92
0010 | 00 00 00 34 00 83 00 44 60 00 00 00 00 00 00 00 | 4 D`
0010 | 00 00 00 00 00 83 00 44 60 00 00 00 00 00 00 00 | 4 D`
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
I 16496 2023-11-08 01:54:19 - [Commands] Received from C-2 (Tali) (version=XB command=60 flag=00)
0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 FF FF | ` ?
+1 -1
View File
@@ -11,7 +11,7 @@ fi
BASENAME="card-defs-test-$SCHEME"
echo "... decompress"
echo "... decompress-prs"
$EXECUTABLE decompress-prs system/ep3/card-definitions.mnr $BASENAME.mnrd
echo "... compress with level=-1 (no compression)"
+4 -4
View File
@@ -10,17 +10,17 @@ fi
DIR="tests/saves-gci"
echo "... decrypt Ep1&2 charfile"
$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_CHARACTER.gci --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci
$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_CHARACTER.gci $DIR/8P-GPOJ-PSO_CHARACTER.gcid --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci
diff $DIR/8P-GPOJ-PSO_CHARACTER-expected.gcid $DIR/8P-GPOJ-PSO_CHARACTER.gcid
echo "... decrypt Ep1&2 guildfile"
$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_GUILDCARD.gci --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci
$EXECUTABLE decrypt-gci-save $DIR/8P-GPOJ-PSO_GUILDCARD.gci $DIR/8P-GPOJ-PSO_GUILDCARD.gcid --sys=$DIR/8P-GPOJ-PSO_SYSTEM.gci
diff $DIR/8P-GPOJ-PSO_GUILDCARD-expected.gcid $DIR/8P-GPOJ-PSO_GUILDCARD.gcid
echo "... decrypt Ep3 charfile"
$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_CHARACTER.gci --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci
$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_CHARACTER.gci $DIR/8P-GPSJ-PSO3_CHARACTER.gcid --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci
diff $DIR/8P-GPSJ-PSO3_CHARACTER-expected.gcid $DIR/8P-GPSJ-PSO3_CHARACTER.gcid
echo "... decrypt Ep3 guildfile"
$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_GUILDCARD.gci --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci
$EXECUTABLE decrypt-gci-save $DIR/8P-GPSJ-PSO3_GUILDCARD.gci $DIR/8P-GPSJ-PSO3_GUILDCARD.gcid --sys=$DIR/8P-GPSJ-PSO3_SYSTEM.gci
diff $DIR/8P-GPSJ-PSO3_GUILDCARD-expected.gcid $DIR/8P-GPSJ-PSO3_GUILDCARD.gcid
echo "... clean up"