add version/area flags to object/enemy defs

This commit is contained in:
Martin Michelsen
2025-04-04 00:39:57 -07:00
parent 822c0e0670
commit 61570a2563
7 changed files with 3221 additions and 3117 deletions
+118 -50
View File
@@ -4,6 +4,7 @@
#include <future>
#include <phosg/Filesystem.hh>
#include <phosg/Strings.hh>
#include <resource_file/Emulators/X86Emulator.hh>
#include <resource_file/ExecutableFormats/DOLFile.hh>
#include <resource_file/ExecutableFormats/PEFile.hh>
#include <resource_file/ExecutableFormats/XBEFile.hh>
@@ -213,6 +214,32 @@ public:
}
}
struct ParseDATConstructorTableSpec {
string src_name;
uint32_t index_addr;
size_t num_areas;
bool has_names;
vector<uint32_t> x86_constructor_calls;
ParseDATConstructorTableSpec(const phosg::JSON& json) {
this->src_name = json.at("SourceName").as_string();
this->index_addr = json.at("IndexAddress").as_int();
this->num_areas = json.at("AreaCount").as_int();
this->has_names = json.at("HasNames").as_bool();
for (const auto& z : json.at("X86ConstructorCalls").as_list()) {
this->x86_constructor_calls.emplace_back(z->as_int());
}
}
static vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
vector<ParseDATConstructorTableSpec> ret;
for (const auto& z : json.as_list()) {
ret.emplace_back(*z);
}
return ret;
}
};
template <bool BE>
struct DATConstructorTableEntry {
static constexpr bool IsBE = BE;
@@ -240,20 +267,54 @@ public:
template <typename EntryT>
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>>
parse_dat_constructor_table_t(
shared_ptr<const ResourceDASM::MemoryContext>& mem, uint32_t address, size_t num_areas) {
shared_ptr<const ResourceDASM::MemoryContext>& mem,
const ParseDATConstructorTableSpec& spec) {
if (!mem) {
throw runtime_error("no file selected");
}
// On some of the x86 builds of the game (PCv2 and Xbox), the constructor
// tables aren't entirely static in the data sections - some parts are
// written during static initialization instead. To handle this, we make a
// copy of the immutable MemoryContext and run the static initialization
// functions using resource_dasm's emulator before parsing the constructor
// table.
shared_ptr<const ResourceDASM::MemoryContext> effective_mem = mem;
if (!spec.x86_constructor_calls.empty()) {
auto constructed_mem = make_shared<ResourceDASM::MemoryContext>(mem->duplicate());
uint32_t esp = constructed_mem->allocate(0x1000) + 0x1000;
for (uint32_t constructor_addr : spec.x86_constructor_calls) {
ResourceDASM::X86Emulator emu(constructed_mem);
// Uncomment for debugging
// auto debugger = make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
// debugger->bind(emu);
// debugger->state.mode = ResourceDASM::DebuggerMode::TRACE;
auto& regs = emu.registers();
regs.eip = constructor_addr;
regs.esp().u = esp - 4;
constructed_mem->write_u32l(esp - 4, 0xFFFFFFFF); // Return addr
try {
emu.execute();
} catch (const out_of_range&) {
if (regs.eip != 0xFFFFFFFF) {
throw;
}
}
}
effective_mem = constructed_mem;
}
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
auto index_r = mem->reader(address, num_areas * sizeof(uint32_t));
for (size_t area = 0; area < num_areas; area++) {
auto index_r = effective_mem->reader(spec.index_addr, spec.num_areas * sizeof(uint32_t));
for (size_t area = 0; area < spec.num_areas; area++) {
uint32_t entries_addr = EntryT::IsBE ? index_r.get_u32b() : index_r.get_u32l();
if (!entries_addr) {
continue;
}
auto entries_r = mem->reader(entries_addr, 0x4000); // 0x4000 is probably enough
auto entries_r = effective_mem->reader(entries_addr, 0x4000); // 0x4000 is probably enough
while (!entries_r.eof()) {
const auto& entry = entries_r.get<EntryT>();
if (entry.type == 0xFFFF) {
@@ -274,17 +335,25 @@ public:
return table;
}
void parse_dat_constructor_table(uint32_t index_addr, size_t num_areas, bool has_names) {
static uint64_t area_mask_for_ranges(const vector<pair<size_t, size_t>>& ranges) {
uint64_t ret = 0;
for (const auto& [start, end] : ranges) {
for (size_t z = start; z <= end; z++) {
ret |= static_cast<uint64_t>(1ULL << z);
}
}
return ret;
}
void parse_dat_constructor_table(const ParseDATConstructorTableSpec& spec) {
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
if (this->ppc_mems.count(this->src_mem)) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(
this->src_mem, index_addr, num_areas);
} else if (!has_names) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<false>>(
this->src_mem, index_addr, num_areas);
auto spec_mem = this->mems.at(spec.src_name);
if (this->ppc_mems.count(spec_mem)) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
} else if (!spec.has_names) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<false>>(spec_mem, spec);
} else {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntryWithName<false>>(
this->src_mem, index_addr, num_areas);
table = this->parse_dat_constructor_table_t<DATConstructorTableEntryWithName<false>>(spec_mem, spec);
}
for (const auto& [type, constructor_to_area_ranges] : table) {
@@ -306,24 +375,26 @@ public:
}
}
void parse_dat_constructor_table_multi(const vector<tuple<string, uint32_t, size_t, bool>>& specs, bool is_enemies) {
void parse_dat_constructor_table_multi(
const vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
map<string, map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>>> all_tables;
for (const auto& [src_name, index_addr, num_areas, has_names] : specs) {
auto src_mem = this->mems.at(src_name);
for (const auto& spec : specs) {
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
if (this->ppc_mems.count(src_mem)) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(src_mem, index_addr, num_areas);
} else if (!has_names) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<false>>(src_mem, index_addr, num_areas);
auto spec_mem = this->mems.at(spec.src_name);
if (this->ppc_mems.count(spec_mem)) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
} else if (!spec.has_names) {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<false>>(spec_mem, spec);
} else {
table = this->parse_dat_constructor_table_t<DATConstructorTableEntryWithName<false>>(src_mem, index_addr, num_areas);
table = this->parse_dat_constructor_table_t<DATConstructorTableEntryWithName<false>>(spec_mem, spec);
}
all_tables.emplace(src_name, std::move(table));
all_tables.emplace(spec.src_name, std::move(table));
}
map<string, size_t> version_widths;
map<uint32_t, map<string, string>> formatted_cells_for_type;
for (const auto& [src_name, table] : all_tables) {
for (const auto& spec : specs) {
const auto& table = all_tables.at(spec.src_name);
size_t max_width = 0;
for (const auto& [type, constructor_to_area_ranges] : table) {
@@ -333,42 +404,47 @@ public:
cell_data.push_back(' ');
}
cell_data += phosg::string_printf("%08" PRIX32, constructor);
bool is_first = true;
for (const auto& [start, end] : area_ranges) {
cell_data.push_back(is_first ? ':' : ',');
if (start == end) {
cell_data += phosg::string_printf("%02zX", start);
} else {
cell_data += phosg::string_printf("%02zX-%02zX", start, end);
if (print_area_masks) {
cell_data += phosg::string_printf(":%016" PRIX64, this->area_mask_for_ranges(area_ranges));
} else {
bool is_first = true;
for (const auto& [start, end] : area_ranges) {
cell_data.push_back(is_first ? ':' : ',');
if (start == end) {
cell_data += phosg::string_printf("%02zX", start);
} else {
cell_data += phosg::string_printf("%02zX-%02zX", start, end);
}
is_first = false;
}
is_first = false;
}
}
max_width = max<size_t>(max_width, cell_data.size());
formatted_cells_for_type[type][src_name] = std::move(cell_data);
formatted_cells_for_type[type][spec.src_name] = std::move(cell_data);
}
version_widths[src_name] = max_width;
version_widths[spec.src_name] = max_width;
}
vector<string> formatted_lines;
string header_line = "TYPE =>";
for (const auto& [version, width] : version_widths) {
for (const auto& spec : specs) {
size_t width = version_widths.at(spec.src_name);
header_line.push_back(' ');
header_line += version;
if (width > version.size()) {
header_line.resize(header_line.size() + (width - version.size()), '-');
header_line += spec.src_name;
if (width > spec.src_name.size()) {
header_line.resize(header_line.size() + (width - spec.src_name.size()), '-');
}
}
header_line += " NAME";
for (const auto& [type, formatted_cells] : formatted_cells_for_type) {
string line = phosg::string_printf("%04" PRIX32 " =>", type);
for (const auto& [src_name, width] : version_widths) {
for (const auto& spec : specs) {
size_t width = version_widths.at(spec.src_name);
try {
const auto& cell_data = formatted_cells.at(src_name);
const auto& cell_data = formatted_cells.at(spec.src_name);
line.push_back(' ');
line += cell_data;
size_t width = version_widths[src_name];
if (width > cell_data.size()) {
line.resize(line.size() + (width - cell_data.size()), ' ');
}
@@ -763,16 +839,8 @@ public:
} else if ((tokens[0] == "parse-dat-object-constructor-tables") ||
(tokens[0] == "parse-dat-enemy-constructor-tables")) {
bool is_enemies = (tokens[0] == "parse-dat-enemy-constructor-tables");
vector<tuple<string, uint32_t, size_t, bool>> specs;
for (size_t z = 1; z < tokens.size(); z++) {
auto subtokens = phosg::split(tokens[z], ':');
specs.emplace_back(make_tuple(
subtokens.at(0),
stoul(subtokens.at(1), nullptr, 16),
stoul(subtokens.at(2), nullptr, 16),
(subtokens.size() > 3 && subtokens.at(3) == "names")));
}
this->parse_dat_constructor_table_multi(specs, is_enemies);
auto specs = ParseDATConstructorTableSpec::from_json_list(phosg::JSON::parse(phosg::load_file(tokens.at(1))));
this->parse_dat_constructor_table_multi(specs, is_enemies, true);
} else if (!tokens[0].empty()) {
throw runtime_error("unknown command");
}
+7 -6
View File
@@ -2846,20 +2846,21 @@ static void whatobj_whatene_fn(const ServerArgs& a, bool include_objs, bool incl
// set, and print the object if not.
if (nearest_ene) {
const auto* set_entry = nearest_ene->super_ene->version(a.c->version()).set_entry;
string type_name = MapFile::name_for_enemy_type(set_entry->base_type, a.c->version(), area);
send_text_message_printf(a.c, "$C5E-%03zX\n$C6%s\n$C2%s\n$C7X:%.2f Z:%.2f",
nearest_ene->e_id, phosg::name_for_enum(nearest_ene->type(a.c->version(), l->episode, l->event)),
MapFile::name_for_enemy_type(set_entry->base_type),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = set_entry->str();
type_name.c_str(), nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = set_entry->str(a.c->version(), area);
a.c->log.info("Enemy found via $whatobj: E-%03zX %s at x=%g y=%g z=%g",
nearest_ene->e_id, set_str.c_str(),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load());
} else if (nearest_obj) {
const auto* set_entry = nearest_obj->super_obj->version(a.c->version()).set_entry;
auto type_name = nearest_obj->type_name(a.c->version());
send_text_message_printf(a.c, "$C5K-%03zX\n$C6%s\n$C7X:%.2f Z:%.2f",
nearest_obj->k_id, nearest_obj->type_name(a.c->version()),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = nearest_obj->super_obj->version(a.c->version()).set_entry->str();
nearest_obj->k_id, type_name.c_str(), nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = set_entry->str(a.c->version(), area);
a.c->log.info("Object found via $whatobj: K-%03zX %s at x=%g y=%g z=%g",
nearest_obj->k_id, set_str.c_str(),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load());
+4 -4
View File
@@ -1540,7 +1540,7 @@ Action a_disassemble_quest_map(
*data = prs_decompress(*data);
}
bool reassembly = args.get<bool>("reassembly");
string result = MapFile(data).disassemble(reassembly);
string result = MapFile(data).disassemble(reassembly, get_cli_version(args, Version::UNKNOWN));
write_output_data(args, result.data(), result.size(), "txt");
});
Action a_disassemble_free_map(
@@ -1571,11 +1571,11 @@ Action a_disassemble_free_map(
bool reassembly = args.get<bool>("reassembly");
string result;
if (is_objects) {
result = MapFile(floor, data, nullptr, nullptr).disassemble(reassembly);
result = MapFile(floor, data, nullptr, nullptr).disassemble(reassembly, get_cli_version(args, Version::UNKNOWN));
} else if (is_enemies) {
result = MapFile(floor, nullptr, data, nullptr).disassemble(reassembly);
result = MapFile(floor, nullptr, data, nullptr).disassemble(reassembly, get_cli_version(args, Version::UNKNOWN));
} else if (is_events) {
result = MapFile(floor, nullptr, nullptr, data).disassemble(reassembly);
result = MapFile(floor, nullptr, nullptr, data).disassemble(reassembly, get_cli_version(args, Version::UNKNOWN));
} else {
throw logic_error("unhandled input type");
}
+2626 -2598
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -142,8 +142,8 @@ private:
class MapFile : public std::enable_shared_from_this<MapFile> {
public:
static const char* name_for_object_type(uint16_t type);
static const char* name_for_enemy_type(uint16_t type);
static std::string name_for_object_type(uint16_t type, Version version = Version::UNKNOWN, uint8_t area = 0xFF);
static std::string name_for_enemy_type(uint16_t type, Version version = Version::UNKNOWN, uint8_t area = 0xFF);
struct SectionHeader { // Only used for quest DAT files
enum class Type {
@@ -192,7 +192,7 @@ public:
/* 44 */
uint64_t semantic_hash(uint8_t floor) const;
std::string str() const;
std::string str(Version version = Version::UNKNOWN, uint8_t area = 0xFF) const;
} __packed_ws__(ObjectSetEntry, 0x44);
struct EnemySetEntry { // Section type 2 (ENEMY_SETS)
@@ -219,7 +219,7 @@ public:
/* 48 */
uint64_t semantic_hash(uint8_t floor) const;
std::string str() const;
std::string str(Version version = Version::UNKNOWN, uint8_t area = 0xFF) const;
} __packed_ws__(EnemySetEntry, 0x48);
struct EventsSectionHeader { // Section type 3 (EVENTS)
@@ -424,7 +424,7 @@ public:
size_t count_events() const;
static std::string disassemble_action_stream(const void* data, size_t size);
std::string disassemble(bool reassembly = false) const;
std::string disassemble(bool reassembly = false, Version version = Version::UNKNOWN) const;
protected:
void link_data(std::shared_ptr<const std::string> data);
@@ -701,10 +701,11 @@ public:
this->item_drop_checked = false;
}
inline const char* type_name(Version v) const {
return this->super_obj
? MapFile::name_for_object_type(this->super_obj->version(v).set_entry->base_type)
: "<DYNAMIC>";
inline std::string type_name(Version v, uint8_t area = 0xFF) const {
if (!this->super_obj) {
return "<DYNAMIC>";
}
return MapFile::name_for_object_type(this->super_obj->version(v).set_entry->base_type, v, area);
}
};
+9 -5
View File
@@ -1753,7 +1753,11 @@ static void on_switch_state_changed(shared_ptr<Client> c, uint8_t command, uint8
if (cmd.header.entity_id != 0xFFFF && c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
const auto& obj_st = l->map_state->object_state_for_index(
c->version(), cmd.switch_flag_floor, cmd.header.entity_id - 0x4000);
send_text_message_printf(c, "$C5K-%03zX A %s", obj_st->k_id, obj_st->type_name(c->version()));
auto s = c->require_server_state();
auto sdt = s->set_data_table(c->version(), l->episode, l->mode, l->difficulty);
uint8_t area = sdt->default_area_for_floor(l->episode, c->floor);
auto type_name = obj_st->type_name(c->version(), area);
send_text_message_printf(c, "$C5K-%03zX A %s", obj_st->k_id, type_name.c_str());
}
// Apparently sometimes 6x05 is sent with an invalid switch flag number. The
@@ -2778,10 +2782,11 @@ DropReconcileResult reconcile_drop_request_with_map(
if (!set_entry) {
throw std::runtime_error("object set entry is missing");
}
string type_name = MapFile::name_for_object_type(set_entry->base_type, client_channel.version);
log.info("Drop check for K-%03zX %c %s",
res.obj_st->k_id,
res.ignore_def ? 'G' : 'S',
MapFile::name_for_object_type(set_entry->base_type));
type_name.c_str());
if (cmd.floor != res.obj_st->super_obj->floor) {
log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX",
cmd.floor, res.obj_st->super_obj->floor);
@@ -2800,10 +2805,9 @@ DropReconcileResult reconcile_drop_request_with_map(
res.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", set_entry->param1.load());
}
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
string type_name = MapFile::name_for_object_type(set_entry->base_type, client_channel.version);
send_text_message_printf(client_channel, "$C5K-%03zX %c %s",
res.obj_st->k_id,
res.ignore_def ? 'G' : 'S',
MapFile::name_for_object_type(set_entry->base_type));
res.obj_st->k_id, res.ignore_def ? 'G' : 'S', type_name.c_str());
}
}