make client functions parameterizable by version
This commit is contained in:
+14
-1
@@ -6364,7 +6364,7 @@ struct G_SetMesetaSlotPrizeResult_BB_6xE3 {
|
||||
ItemData item;
|
||||
} __packed_ws__(G_SetMesetaSlotPrizeResult_BB_6xE3, 0x18);
|
||||
|
||||
// 6xE4: Invalid subcommand
|
||||
// 6xE4: Invalid subcommand (but used as an extension; see end of this file)
|
||||
// 6xE5: Invalid subcommand
|
||||
// 6xE6: Invalid subcommand
|
||||
// 6xE7: Invalid subcommand
|
||||
@@ -7430,3 +7430,16 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 {
|
||||
// DC v2: PSODCV2CharacterFile
|
||||
// GC v3: PSOGCCharacterFile::Character
|
||||
// XB v3: PSOXBCharacterFileCharacter
|
||||
|
||||
// 6xE4: Increment enemy damage threshold
|
||||
// This command increments or decrements the minimum amount of damage an enemy
|
||||
// has sustained. This threshold is used to mitigate the effects of damage
|
||||
// cancellation due to the original game's 6x0A implementation.
|
||||
|
||||
struct G_IncrementEnemyDamageThreshold_Extension_6xE4 {
|
||||
G_EntityIDHeader header = {0xE4, sizeof(G_IncrementEnemyDamageThreshold_Extension_6xE4) / 4, 0x0000};
|
||||
le_int16_t hit_amount = 0;
|
||||
le_uint16_t total_damage_before_hit = 0;
|
||||
le_uint16_t current_hp_before_hit = 0;
|
||||
le_uint16_t max_hp = 0;
|
||||
} __packed_ws__(G_IncrementEnemyDamageThreshold_Extension_6xE4, 0x0C);
|
||||
|
||||
+155
-81
@@ -96,11 +96,9 @@ string CompiledFunctionCode::generate_client_command(
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
if (this->arch == Architecture::POWERPC) {
|
||||
return this->generate_client_command_t<true>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
return this->generate_client_command_t<true>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<false>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
}
|
||||
@@ -110,18 +108,12 @@ bool CompiledFunctionCode::is_big_endian() const {
|
||||
return this->arch == Architecture::POWERPC;
|
||||
}
|
||||
|
||||
shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
static vector<shared_ptr<CompiledFunctionCode>> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const string& function_directory,
|
||||
const string& system_directory,
|
||||
const string& name,
|
||||
const string& text) {
|
||||
auto ret = make_shared<CompiledFunctionCode>();
|
||||
ret->arch = arch;
|
||||
ret->short_name = name;
|
||||
ret->index = 0;
|
||||
ret->hide_from_patches_menu = false;
|
||||
|
||||
unordered_set<string> get_include_stack;
|
||||
function<string(const string&)> get_include = [&](const string& name) -> string {
|
||||
const char* arch_name_token;
|
||||
@@ -177,56 +169,135 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
throw runtime_error("data not found for include: " + name + " (from " + asm_filename + " or " + bin_filename + ")");
|
||||
};
|
||||
|
||||
ResourceDASM::EmulatorBase::AssembleResult assembled;
|
||||
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
|
||||
assembled = ResourceDASM::PPC32Emulator::assemble(text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::X86) {
|
||||
assembled = ResourceDASM::X86Emulator::assemble(text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::SH4) {
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(text, get_include);
|
||||
} else {
|
||||
throw runtime_error("invalid architecture");
|
||||
}
|
||||
ret->code = std::move(assembled.code);
|
||||
ret->label_offsets = std::move(assembled.label_offsets);
|
||||
for (const auto& it : assembled.metadata_keys) {
|
||||
if (it.first == "hide_from_patches_menu") {
|
||||
ret->hide_from_patches_menu = true;
|
||||
} else if (it.first == "index") {
|
||||
if (it.second.size() != 1) {
|
||||
throw runtime_error("invalid index value in .meta directive");
|
||||
// Handle VERS tokens
|
||||
vector<uint32_t> specific_versions;
|
||||
auto lines = phosg::split(text, '\n');
|
||||
for (auto& line : lines) {
|
||||
if (line.starts_with(".versions ")) {
|
||||
if (!specific_versions.empty()) {
|
||||
throw std::runtime_error("multiple .versions directives in file");
|
||||
}
|
||||
ret->index = it.second[0];
|
||||
} else if (it.first == "name") {
|
||||
ret->long_name = it.second;
|
||||
} else if (it.first == "description") {
|
||||
ret->description = it.second;
|
||||
} else {
|
||||
throw runtime_error("unknown metadata key: " + it.first);
|
||||
for (auto& vers_token : phosg::split(line.substr(10), ' ')) {
|
||||
phosg::strip_whitespace(vers_token);
|
||||
if (vers_token.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (vers_token.size() != 4) {
|
||||
throw std::runtime_error("invalid token in .version directive: " + vers_token);
|
||||
}
|
||||
specific_versions.emplace_back(*reinterpret_cast<const be_uint32_t*>(vers_token.data()));
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
// Preprocess <VERS> tokens in the text if a .versions directive was given
|
||||
vector<string> version_texts;
|
||||
if (specific_versions.empty()) {
|
||||
specific_versions.emplace_back(0);
|
||||
version_texts.emplace_back(text);
|
||||
|
||||
} else {
|
||||
vector<deque<string>> version_lines;
|
||||
version_lines.resize(specific_versions.size());
|
||||
|
||||
size_t line_num = 1;
|
||||
for (const auto& line : lines) {
|
||||
size_t vers_offset = line.find("<VERS ");
|
||||
if (vers_offset == string::npos) {
|
||||
for (auto& lines : version_lines) {
|
||||
lines.emplace_back(line);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (size_t vers_index = 0; vers_index < specific_versions.size(); vers_index++) {
|
||||
string version_line = line;
|
||||
size_t vers_offset = line.find("<VERS ");
|
||||
while (vers_offset != string::npos) {
|
||||
size_t end_offset = version_line.find('>', vers_offset + 6);
|
||||
if (end_offset == string::npos) {
|
||||
throw runtime_error(std::format("(line {}) unterminated <VERS> replacement", line_num));
|
||||
}
|
||||
auto tokens = phosg::split(version_line.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
||||
if (tokens.size() != specific_versions.size()) {
|
||||
throw runtime_error(std::format("(line {}) invalid <VERS> replacement", line_num));
|
||||
}
|
||||
version_line = version_line.substr(0, vers_offset) + tokens.at(vers_index) + version_line.substr(end_offset + 1);
|
||||
vers_offset = version_line.find("<VERS ");
|
||||
}
|
||||
version_lines[vers_index].emplace_back(version_line);
|
||||
}
|
||||
}
|
||||
line_num++;
|
||||
}
|
||||
|
||||
for (const auto& lines : version_lines) {
|
||||
version_texts.emplace_back(phosg::join(lines, "\n"));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ret->entrypoint_offset_offset = ret->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
vector<shared_ptr<CompiledFunctionCode>> ret;
|
||||
for (size_t vers_index = 0; vers_index < specific_versions.size(); vers_index++) {
|
||||
uint32_t specific_version = specific_versions[vers_index];
|
||||
const auto& version_text = version_texts.at(vers_index);
|
||||
|
||||
uint32_t prev_index = 0;
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
try {
|
||||
ResourceDASM::EmulatorBase::AssembleResult assembled;
|
||||
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
|
||||
assembled = ResourceDASM::PPC32Emulator::assemble(version_text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::X86) {
|
||||
assembled = ResourceDASM::X86Emulator::assemble(version_text, get_include);
|
||||
} else if (arch == CompiledFunctionCode::Architecture::SH4) {
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(version_text, get_include);
|
||||
} else {
|
||||
throw runtime_error("invalid architecture");
|
||||
}
|
||||
|
||||
auto compiled = ret.emplace_back(make_shared<CompiledFunctionCode>());
|
||||
compiled->arch = arch;
|
||||
compiled->short_name = name;
|
||||
compiled->specific_version = specific_version;
|
||||
compiled->code = std::move(assembled.code);
|
||||
compiled->label_offsets = std::move(assembled.label_offsets);
|
||||
for (const auto& it : assembled.metadata_keys) {
|
||||
if (it.first == "hide_from_patches_menu") {
|
||||
compiled->hide_from_patches_menu = true;
|
||||
} else if (it.first == "name") {
|
||||
compiled->long_name = it.second;
|
||||
} else if (it.first == "description") {
|
||||
compiled->description = it.second;
|
||||
} else {
|
||||
throw runtime_error("unknown metadata key: " + it.first);
|
||||
}
|
||||
}
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : compiled->label_offsets) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
compiled->entrypoint_offset_offset = compiled->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
|
||||
uint32_t prev_index = 0;
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
}
|
||||
compiled->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
string version_str = specific_version ? (" (" + str_for_specific_version(specific_version) + ")") : "";
|
||||
function_compiler_log.warning_f("Failed to compile function {}{}: {}", name, version_str, e.what());
|
||||
}
|
||||
ret->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -239,21 +310,16 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
string subdir_name = item.path().filename().string();
|
||||
string subdir_path = directory.ends_with("/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
if (!std::filesystem::is_directory(subdir_path)) {
|
||||
function_compiler_log.warning_f("Skipping {} (not a directory)", subdir_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& item : std::filesystem::directory_iterator(subdir_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
auto add_file = [&](string filename) -> void {
|
||||
try {
|
||||
if (!filename.ends_with(".s")) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
if (name.ends_with(".inc")) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_patch = name.ends_with(".patch");
|
||||
@@ -299,33 +365,41 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
|
||||
string path = subdir_path + "/" + filename;
|
||||
string text = phosg::load_file(path);
|
||||
auto code = compile_function_code(arch, subdir_path, system_dir_path, name, text);
|
||||
if (code->index != 0) {
|
||||
if (!this->index_to_function.emplace(code->index, code).second) {
|
||||
throw runtime_error(std::format(
|
||||
"duplicate function index: {:08X}", code->index));
|
||||
for (auto code : compile_function_code(arch, subdir_path, system_dir_path, name, text)) {
|
||||
if (code->specific_version == 0) {
|
||||
code->specific_version = specific_version;
|
||||
}
|
||||
code->source_path = path;
|
||||
code->short_name = short_name;
|
||||
this->name_to_function.emplace(name, code);
|
||||
if (is_patch) {
|
||||
code->menu_item_id = next_menu_item_id++;
|
||||
this->menu_item_id_and_specific_version_to_patch_function.emplace(
|
||||
static_cast<uint64_t>(code->menu_item_id) << 32 | code->specific_version, code);
|
||||
this->name_and_specific_version_to_patch_function.emplace(
|
||||
std::format("{}-{:08X}", code->short_name, code->specific_version), code);
|
||||
}
|
||||
}
|
||||
code->specific_version = specific_version;
|
||||
code->source_path = path;
|
||||
code->short_name = short_name;
|
||||
this->name_to_function.emplace(name, code);
|
||||
if (is_patch) {
|
||||
code->menu_item_id = next_menu_item_id++;
|
||||
this->menu_item_id_and_specific_version_to_patch_function.emplace(
|
||||
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
|
||||
this->name_and_specific_version_to_patch_function.emplace(
|
||||
std::format("{}-{:08X}", short_name, specific_version), code);
|
||||
}
|
||||
|
||||
string index_prefix = code->index ? std::format("{:02X} => ", code->index) : "";
|
||||
string patch_prefix = is_patch ? std::format("[{:08X}/{:08X}] ", code->menu_item_id, code->specific_version) : "";
|
||||
function_compiler_log.debug_f("Compiled function {}{}{} ({})",
|
||||
index_prefix, patch_prefix, name, name_for_architecture(code->arch));
|
||||
string patch_prefix = is_patch ? std::format("[{:08X}] ", code->menu_item_id) : "";
|
||||
function_compiler_log.debug_f("Compiled function {}{} ({}; {})",
|
||||
patch_prefix, name, str_for_specific_version(code->specific_version), name_for_architecture(code->arch));
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
function_compiler_log.warning_f("Failed to compile function {}: {}", filename, e.what());
|
||||
}
|
||||
};
|
||||
|
||||
if (std::filesystem::is_regular_file(subdir_path)) {
|
||||
add_file(subdir_path);
|
||||
} else if (std::filesystem::is_directory(subdir_path)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(subdir_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
add_file(filename);
|
||||
}
|
||||
} else {
|
||||
function_compiler_log.warning_f("Skipping {} (unknown file type)", subdir_name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-11
@@ -25,15 +25,14 @@ struct CompiledFunctionCode {
|
||||
std::string code;
|
||||
std::vector<uint16_t> relocation_deltas;
|
||||
std::unordered_map<std::string, uint32_t> label_offsets;
|
||||
uint32_t entrypoint_offset_offset;
|
||||
uint32_t entrypoint_offset_offset = 0;
|
||||
std::string source_path; // Path to source file from newserv root
|
||||
std::string short_name; // Based on filename
|
||||
std::string long_name; // From .meta name directive
|
||||
std::string description; // From .meta description directive
|
||||
uint8_t index; // 0 = unused (not registered in index_to_function)
|
||||
uint32_t menu_item_id;
|
||||
bool hide_from_patches_menu;
|
||||
uint32_t specific_version;
|
||||
uint32_t menu_item_id = 0;
|
||||
bool hide_from_patches_menu = false;
|
||||
uint32_t specific_version = 0; // 0 = not a client-selectable patch
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
@@ -52,12 +51,6 @@ struct CompiledFunctionCode {
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch);
|
||||
|
||||
std::shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
CompiledFunctionCode::Architecture arch,
|
||||
const std::string& directory,
|
||||
const std::string& name,
|
||||
const std::string& text);
|
||||
|
||||
struct FunctionCodeIndex {
|
||||
FunctionCodeIndex() = default;
|
||||
explicit FunctionCodeIndex(const std::string& directory);
|
||||
|
||||
+3
-2
@@ -1660,9 +1660,10 @@ Action a_assemble_all_patches(
|
||||
phosg::StringWriter w;
|
||||
string data = prepare_send_function_call_data(
|
||||
code, {}, nullptr, 0, checksum_addr, checksum_size, override_start_addr, encrypted);
|
||||
w.put(PSOCommandHeaderDCV3{.command = 0xB2, .flag = code->index, .size = data.size() + 4});
|
||||
w.put(PSOCommandHeaderDCV3{.command = 0xB2, .flag = 0x00, .size = data.size() + 4});
|
||||
w.write(data);
|
||||
string out_path = code->source_path + (encrypted ? ".enc.bin" : ".std.bin");
|
||||
string out_path = std::format("{}.{}.{}.bin",
|
||||
code->source_path, str_for_specific_version(code->specific_version), (encrypted ? "enc" : "std"));
|
||||
phosg::save_file(out_path, w.str());
|
||||
phosg::fwrite_fmt(stderr, "... {}\n", out_path);
|
||||
}
|
||||
|
||||
@@ -5408,7 +5408,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xE1 */ {NONE, NONE, 0xE1, on_quest_F95F_result_bb},
|
||||
/* 6xE2 */ {NONE, NONE, 0xE2, on_quest_F960_result_bb},
|
||||
/* 6xE3 */ {NONE, NONE, 0xE3, on_invalid},
|
||||
/* 6xE4 */ {NONE, NONE, 0xE4, on_invalid},
|
||||
/* 6xE4 */ {NONE, NONE, 0xE4, forward_subcommand_with_entity_id_transcode_t<G_IncrementEnemyDamageThreshold_Extension_6xE4>}, // Extended subcommand; see CommandFormats.hh
|
||||
/* 6xE5 */ {NONE, NONE, 0xE5, on_invalid},
|
||||
/* 6xE6 */ {NONE, NONE, 0xE6, on_invalid},
|
||||
/* 6xE7 */ {NONE, NONE, 0xE7, on_invalid},
|
||||
|
||||
+1
-1
@@ -496,7 +496,7 @@ void send_function_call(
|
||||
code, label_writes, suffix_data, suffix_size, checksum_addr, checksum_size, override_relocations_offset,
|
||||
Client::check_flag(client_enabled_flags, Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL));
|
||||
|
||||
ch->send(0xB2, code ? code->index : 0x00, data);
|
||||
ch->send(0xB2, 0x00, data);
|
||||
}
|
||||
|
||||
asio::awaitable<bool> send_protected_command(
|
||||
|
||||
+14
-25
@@ -228,38 +228,27 @@ bool specific_version_is_pc_v2(uint32_t specific_version) {
|
||||
}
|
||||
|
||||
bool specific_version_is_gc(uint32_t specific_version) {
|
||||
// GC specific_versions are 3GRV, where G is [OS], R is [JEP], V is [0-9T]
|
||||
if ((specific_version & 0xFF000000) != 0x33000000) {
|
||||
return false;
|
||||
}
|
||||
char game = specific_version >> 16;
|
||||
if ((game != 'O') && (game != 'S')) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return (isdigit(revision) || (revision == 'T'));
|
||||
// GC specific_versions are 3___
|
||||
return ((specific_version & 0xFF000000) == 0x33000000);
|
||||
}
|
||||
|
||||
bool specific_version_is_xb(uint32_t specific_version) {
|
||||
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
|
||||
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
|
||||
return false;
|
||||
}
|
||||
char region = specific_version >> 8;
|
||||
if ((region != 'J') && (region != 'E') && (region != 'P')) {
|
||||
return false;
|
||||
}
|
||||
char revision = specific_version;
|
||||
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
|
||||
// XB specific_versions are 4O__
|
||||
return ((specific_version & 0xFF000000) == 0x34000000);
|
||||
}
|
||||
|
||||
bool specific_version_is_bb(uint32_t specific_version) {
|
||||
// BB specific_versions are 5XXX, where X is an encoding of the revision number
|
||||
return (specific_version & 0xFF000000) == 0x35000000;
|
||||
return ((specific_version & 0xFF000000) == 0x35000000);
|
||||
}
|
||||
|
||||
string str_for_specific_version(uint32_t specific_version) {
|
||||
string ret;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
char ch = specific_version >> (24 - (z << 3));
|
||||
ret.push_back(isalnum(ch) ? ch : '_');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char* file_path_token_for_version(Version version) {
|
||||
|
||||
@@ -218,6 +218,8 @@ bool specific_version_is_gc(uint32_t specific_version);
|
||||
bool specific_version_is_xb(uint32_t specific_version);
|
||||
bool specific_version_is_bb(uint32_t specific_version);
|
||||
|
||||
std::string str_for_specific_version(uint32_t specific_version);
|
||||
|
||||
enum class ServerBehavior {
|
||||
PC_CONSOLE_DETECT = 0,
|
||||
GAME_SERVER,
|
||||
|
||||
Reference in New Issue
Block a user