diff --git a/CMakeLists.txt b/CMakeLists.txt index 88e3c5a3..64075b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ add_executable(newserv src/ProxyCommands.cc src/ProxyServer.cc src/PSOEncryption.cc + src/PSOGCObjectGraph.cc src/PSOProtocol.cc src/Quest.cc src/RareItemSet.cc diff --git a/src/Main.cc b/src/Main.cc index bbd68473..8fe89291 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -19,6 +19,7 @@ #include "Loggers.hh" #include "NetworkAddresses.hh" #include "ProxyServer.hh" +#include "PSOGCObjectGraph.hh" #include "ReplaySession.hh" #include "SendCommands.hh" #include "Server.hh" @@ -326,6 +327,7 @@ enum class Behavior { DECODE_SJIS, EXTRACT_GSL, SHOW_EP3_DATA, + PARSE_OBJECT_GRAPH, REPLAY_LOG, CAT_CLIENT, }; @@ -340,6 +342,7 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::DECODE_QUEST_FILE) || (b == Behavior::DECODE_SJIS) || (b == Behavior::EXTRACT_GSL) || + (b == Behavior::PARSE_OBJECT_GRAPH) || (b == Behavior::REPLAY_LOG); } @@ -377,6 +380,7 @@ int main(int argc, char** argv) { const char* output_filename = nullptr; const char* replay_required_access_key = ""; const char* replay_required_password = ""; + uint32_t root_object_address = 0; struct sockaddr_storage cat_client_remote; for (int x = 1; x < argc; x++) { if (!strcmp(argv[x], "--help")) { @@ -442,6 +446,8 @@ int main(int argc, char** argv) { skip_big_endian = true; } else if (!strcmp(argv[x], "--show-ep3-data")) { behavior = Behavior::SHOW_EP3_DATA; + } else if (!strcmp(argv[x], "--parse-object-graph")) { + behavior = Behavior::PARSE_OBJECT_GRAPH; } else if (!strcmp(argv[x], "--replay-log")) { behavior = Behavior::REPLAY_LOG; } else if (!strcmp(argv[x], "--extract-gsl")) { @@ -450,6 +456,8 @@ int main(int argc, char** argv) { replay_required_password = &argv[x][19]; } else if (!strncmp(argv[x], "--require-access-key=", 21)) { replay_required_access_key = &argv[x][21]; + } else if (!strncmp(argv[x], "--root-addr=", 12)) { + root_object_address = strtoul(&argv[x][12], nullptr, 16); } else if (!strncmp(argv[x], "--config=", 9)) { config_filename = &argv[x][9]; } else if (!strcmp(argv[x], "-") || argv[x][0] != '-') { @@ -742,6 +750,13 @@ int main(int argc, char** argv) { break; } + case Behavior::PARSE_OBJECT_GRAPH: { + string data = read_input_data(); + PSOGCObjectGraph g(data, root_object_address); + g.print(stdout); + break; + } + case Behavior::REPLAY_LOG: case Behavior::RUN_SERVER: { signal(SIGPIPE, SIG_IGN); diff --git a/src/PSOGCObjectGraph.cc b/src/PSOGCObjectGraph.cc new file mode 100644 index 00000000..75afcd96 --- /dev/null +++ b/src/PSOGCObjectGraph.cc @@ -0,0 +1,101 @@ +#include "PSOGCObjectGraph.hh" + +#include "Text.hh" + +using namespace std; + + + +struct TObjectVTable { + be_uint32_t unused_a1; + be_uint32_t unused_a2; + be_uint32_t destroy; + be_uint32_t update; + be_uint32_t render; + be_uint32_t render_shadow; +} __attribute__((packed)); + +struct TObject { + be_uint32_t type_name_addr; + be_uint16_t flags; + parray unused; + be_uint32_t prev_sibling_addr; + be_uint32_t next_sibling_addr; + be_uint32_t parent_addr; + be_uint32_t children_head_addr; + be_uint32_t vtable_addr; +} __attribute__((packed)); + +PSOGCObjectGraph::PSOGCObjectGraph( + const string& memory_data, uint32_t root_address) { + StringReader r(memory_data); + this->root = this->parse_object_memo(r, root_address); +} + +shared_ptr PSOGCObjectGraph::parse_vtable_memo( + StringReader& r, uint32_t addr) { + try { + return this->all_vtables.at(addr); + } catch (const out_of_range&) { } + + const auto& vt = r.pget(addr & 0x01FFFFFF); + auto ret = this->all_vtables.emplace(addr, new VTable()).first->second; + ret->address = addr; + ret->destroy_addr = vt.destroy; + ret->update_addr = vt.update; + ret->render_addr = vt.render; + ret->render_shadow_addr = vt.render_shadow; + return ret; +} + +shared_ptr PSOGCObjectGraph::parse_object_memo( + StringReader& r, uint32_t addr) { + try { + return this->all_objects.at(addr); + } catch (const out_of_range&) { } + + const auto& obj = r.pget(addr & 0x01FFFFFF); + string type_name = r.pget_cstr(obj.type_name_addr & 0x01FFFFFF); + + auto ret = this->all_objects.emplace(addr, new Object()).first->second; + ret->address = addr; + ret->flags = obj.flags; + ret->type_name = move(type_name); + ret->vtable = this->parse_vtable_memo(r, obj.vtable_addr); + if (obj.parent_addr) { + ret->parent = this->parse_object_memo(r, obj.parent_addr); + } + if (obj.children_head_addr) { + uint32_t child_addr = obj.children_head_addr; + while (child_addr) { + ret->children.emplace_back(this->parse_object_memo(r, child_addr)); + child_addr = r.pget(child_addr & 0x01FFFFFF).next_sibling_addr; + } + } + return ret; +} + +void PSOGCObjectGraph::print(FILE* stream) const { + this->root->print(stream); +} + + + +void PSOGCObjectGraph::Object::print(FILE* stream, size_t indent_level) const { + for (size_t z = 0; z < indent_level; z++) { + fputc(' ', stream); + fputc(' ', stream); + } + fprintf(stream, "%s +%04hX @ %08" PRIX32 " (VT %08" PRIX32 ": destroy=%08" PRIX32 " update=%08" PRIX32 " render=%08" PRIX32 " render_shadow=%08" PRIX32 ")\n", + this->type_name.c_str(), + this->flags, + this->address, + this->vtable->address, + this->vtable->destroy_addr, + this->vtable->update_addr, + this->vtable->render_addr, + this->vtable->render_shadow_addr); + for (const auto& child : this->children) { + child->print(stream, indent_level + 1); + } +} diff --git a/src/PSOGCObjectGraph.hh b/src/PSOGCObjectGraph.hh new file mode 100644 index 00000000..12b16194 --- /dev/null +++ b/src/PSOGCObjectGraph.hh @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + + + +struct PSOGCObjectGraph { + PSOGCObjectGraph(const std::string& memory_data, uint32_t root_address); + + void print(FILE* stream) const; + + struct VTable { + uint32_t address; + uint32_t destroy_addr; + uint32_t update_addr; + uint32_t render_addr; + uint32_t render_shadow_addr; + }; + + struct Object { + uint32_t address; + uint16_t flags; + std::string type_name; + std::shared_ptr vtable; + std::shared_ptr parent; + std::vector> children; + + void print(FILE* stream, size_t indent_level = 0) const; + }; + + std::shared_ptr root; + std::unordered_map> all_objects; + std::unordered_map> all_vtables; + + std::shared_ptr parse_object_memo(StringReader& r, uint32_t addr); + std::shared_ptr parse_vtable_memo(StringReader& r, uint32_t addr); +};