add version/area flags to object/enemy defs
This commit is contained in:
+118
-50
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+10
-9
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user