From 6a606d72229351af0ba69f8423ceb5e1dfbf91cc Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 31 Oct 2021 13:12:50 -0400 Subject: [PATCH] Start a debug tool for looking through level data (#954) * temp * temp * level inspection tool * docs * windows fix --- common/CMakeLists.txt | 4 + {game/graphics => common}/dma/dma.cpp | 30 +- {game/graphics => common}/dma/dma.h | 3 + .../graphics => common}/dma/dma_chain_read.h | 2 +- {game/graphics => common}/dma/dma_copy.cpp | 2 +- {game/graphics => common}/dma/dma_copy.h | 2 +- {game/graphics => common}/dma/gs.cpp | 0 {game/graphics => common}/dma/gs.h | 2 +- docs/markdown/_sidebar.md | 3 +- docs/markdown/drawable_and_tfrag.md | 38 + game/CMakeLists.txt | 3 - game/graphics/opengl_renderer/AdgifHandler.h | 2 +- .../graphics/opengl_renderer/BucketRenderer.h | 2 +- .../opengl_renderer/DirectRenderer.cpp | 2 +- .../graphics/opengl_renderer/DirectRenderer.h | 2 +- .../graphics/opengl_renderer/OpenGLRenderer.h | 2 +- .../graphics/opengl_renderer/SpriteRenderer.h | 2 +- game/graphics/opengl_renderer/debug_gui.h | 2 +- game/graphics/pipelines/opengl.cpp | 2 +- game/kernel/kmachine.cpp | 4 +- tools/CMakeLists.txt | 2 + tools/level_tools/BspHeader.cpp | 1085 +++++++++++++++++ tools/level_tools/BspHeader.h | 412 +++++++ tools/level_tools/CMakeLists.txt | 7 + tools/level_tools/Error.h | 11 + tools/level_tools/goal_data_reader.cpp | 281 +++++ tools/level_tools/goal_data_reader.h | 72 ++ tools/level_tools/level_dump/main.cpp | 93 ++ 28 files changed, 2053 insertions(+), 19 deletions(-) rename {game/graphics => common}/dma/dma.cpp (66%) rename {game/graphics => common}/dma/dma.h (96%) rename {game/graphics => common}/dma/dma_chain_read.h (98%) rename {game/graphics => common}/dma/dma_copy.cpp (99%) rename {game/graphics => common}/dma/dma_copy.h (96%) rename {game/graphics => common}/dma/gs.cpp (100%) rename {game/graphics => common}/dma/gs.h (99%) create mode 100644 docs/markdown/drawable_and_tfrag.md create mode 100644 tools/level_tools/BspHeader.cpp create mode 100644 tools/level_tools/BspHeader.h create mode 100644 tools/level_tools/CMakeLists.txt create mode 100644 tools/level_tools/Error.h create mode 100644 tools/level_tools/goal_data_reader.cpp create mode 100644 tools/level_tools/goal_data_reader.h create mode 100644 tools/level_tools/level_dump/main.cpp diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 55bcffe62b..a078ef2c17 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -3,6 +3,10 @@ add_library(common audio/audio_formats.cpp cross_os_debug/xdbg.cpp cross_sockets/xsocket.cpp + dma/dma.cpp + dma/dma_copy.cpp + dma/gs.cpp + goos/Interpreter.cpp goos/Object.cpp goos/ParseHelpers.cpp diff --git a/game/graphics/dma/dma.cpp b/common/dma/dma.cpp similarity index 66% rename from game/graphics/dma/dma.cpp rename to common/dma/dma.cpp index a87a75447d..18bae1a10d 100644 --- a/game/graphics/dma/dma.cpp +++ b/common/dma/dma.cpp @@ -35,7 +35,7 @@ std::string VifCode::print() { result = "ITOP"; break; case Kind::STMOD: - result = "STMOD"; + result = fmt::format("STMOD 0b{:b}", immediate); break; case Kind::MSK3PATH: result = "MSK3PATH"; @@ -79,6 +79,34 @@ std::string VifCode::print() { case Kind::DIRECTHL: result = "DIRECTHL"; break; + case Kind::UNPACK_V4_8: { + VifCodeUnpack up(*this); + result = fmt::format("UNPACK-V4-8: {} addr: {} us: {} tops: {}", num, up.addr_qw, + up.is_unsigned, up.use_tops_flag); + break; + } + + case Kind::UNPACK_V4_16: { + VifCodeUnpack up(*this); + result = fmt::format("UNPACK-V4-16: {} addr: {} us: {} tops: {}", num, up.addr_qw, + up.is_unsigned, up.use_tops_flag); + break; + } + + case Kind::UNPACK_V4_32: { + VifCodeUnpack up(*this); + result = fmt::format("UNPACK-V4-32: {} addr: {} us: {} tops: {}", num, up.addr_qw, + up.is_unsigned, up.use_tops_flag); + break; + } + + case Kind::UNPACK_V3_32: { + VifCodeUnpack up(*this); + result = fmt::format("UNPACK-V3-32: {} addr: {} us: {} tops: {}", num, up.addr_qw, + up.is_unsigned, up.use_tops_flag); + break; + } + default: fmt::print("Unhandled vif code {}", (int)kind); result = "???"; diff --git a/game/graphics/dma/dma.h b/common/dma/dma.h similarity index 96% rename from game/graphics/dma/dma.h rename to common/dma/dma.h index 7517279fb2..021c955920 100644 --- a/game/graphics/dma/dma.h +++ b/common/dma/dma.h @@ -71,6 +71,9 @@ struct VifCode { DIRECTHL = 0b1010001, UNPACK_MASK = 0b1100000, // unpack is a bunch of commands. UNPACK_V4_32 = 0b1101100, + UNPACK_V4_16 = 0b1101101, + UNPACK_V3_32 = 0b1101000, + UNPACK_V4_8 = 0b1101110, }; VifCode(u32 value) { diff --git a/game/graphics/dma/dma_chain_read.h b/common/dma/dma_chain_read.h similarity index 98% rename from game/graphics/dma/dma_chain_read.h rename to common/dma/dma_chain_read.h index f472f57266..8dd8a6ec27 100644 --- a/game/graphics/dma/dma_chain_read.h +++ b/common/dma/dma_chain_read.h @@ -1,7 +1,7 @@ #pragma once #include -#include "game/graphics/dma/dma.h" +#include "common/dma/dma.h" #include "common/util/assert.h" /*! diff --git a/game/graphics/dma/dma_copy.cpp b/common/dma/dma_copy.cpp similarity index 99% rename from game/graphics/dma/dma_copy.cpp rename to common/dma/dma_copy.cpp index 6e3dfda236..0636c5dc00 100644 --- a/game/graphics/dma/dma_copy.cpp +++ b/common/dma/dma_copy.cpp @@ -1,6 +1,6 @@ #include "common/goal_constants.h" -#include "game/graphics/dma/dma_chain_read.h" +#include "common/dma/dma_chain_read.h" #include "dma_copy.h" #include "third-party/fmt/core.h" #include "common/util/Timer.h" diff --git a/game/graphics/dma/dma_copy.h b/common/dma/dma_copy.h similarity index 96% rename from game/graphics/dma/dma_copy.h rename to common/dma/dma_copy.h index e0dd12851f..781ff3e72e 100644 --- a/game/graphics/dma/dma_copy.h +++ b/common/dma/dma_copy.h @@ -3,7 +3,7 @@ #include #include "common/common_types.h" -#include "game/graphics/dma/dma_chain_read.h" +#include "common/dma/dma_chain_read.h" #include "common/util/Serializer.h" struct DmaData { diff --git a/game/graphics/dma/gs.cpp b/common/dma/gs.cpp similarity index 100% rename from game/graphics/dma/gs.cpp rename to common/dma/gs.cpp diff --git a/game/graphics/dma/gs.h b/common/dma/gs.h similarity index 99% rename from game/graphics/dma/gs.h rename to common/dma/gs.h index 62604e0517..260683d943 100644 --- a/game/graphics/dma/gs.h +++ b/common/dma/gs.h @@ -1,6 +1,6 @@ #pragma once -#include "game/graphics/dma/dma.h" +#include "common/dma/dma.h" /*! * @file gs.h diff --git a/docs/markdown/_sidebar.md b/docs/markdown/_sidebar.md index 34e44fd430..28aa98c0b1 100644 --- a/docs/markdown/_sidebar.md +++ b/docs/markdown/_sidebar.md @@ -22,4 +22,5 @@ - [Register Handling](/registers.md) - PC Port Documentation - - [Graphics](/graphics.md) \ No newline at end of file + - [Graphics](/graphics.md) + - [Drawable and TFRAG](/drawable_and_tfrag.md) \ No newline at end of file diff --git a/docs/markdown/drawable_and_tfrag.md b/docs/markdown/drawable_and_tfrag.md new file mode 100644 index 0000000000..ec4558456e --- /dev/null +++ b/docs/markdown/drawable_and_tfrag.md @@ -0,0 +1,38 @@ +## Drawable Trees +At the highest level is the level file itself, which is a `bsp-header`. It contains a `drawable-tree-array`, which contains a small number of `drawable-tree`s. (~1 to 15?). + +Each of these `drawable-tree`s is a different kind, distinguished by its type. Different types of trees go to different renderers. For example, there is a `drawable-tree-lowres-tfrag`, a `drawable-tree-instance-tie`, and even a `drawable-tree-actor`. It is possible to have multiple trees of the same type, as some trees seem to have a maximum size - for example a `drawable-tree-instance-tie` can have only 43 different prototypes, so it is common to see a few of these. These trees are the thing passed to the appropriate renderers. All the trees for all the levels get put in the `background-work` structure, which is then read by the renderers. + +The `drawable-tree-tfrag` contains all the "normal" tfrag data. There are other trees like `trans`, `dirt`, `ice`, `lowres`, and `lowres-trans`, which are only present if the level actually has this type of geometry. As far as I can tell, the special ones follow the same format, but I haven't checked this in detail. + +The `drawable-tree-tfrag` contains a small number (1 to 6?) of `drawable-inline-array`. They all refer to the same set of `tfragment`s (the actual things to draw), but have a different tree structure. + +The `drawable-inline-array` contains `draw-node`s. Each `draw-node` contains between 0 and 8 children. The children are represented by a pointer to an inline array of children. The children can be either `draw-node`s or `tfragment`s. All the children are always the same type. + +The first `drawable-inline-array` in the `drawable-tree-tfrag` is the full tree structure. It starts with some number of children that is smaller than 8. And from there on, all children are `draw-node` (always 8 or fewer children) or a `tfragment` directly. So this is the deepest possible tree. I believe the max depth seen is 6? + +The next `drawable-inline-array` starts with a list of all nodes at with a depth of 1 from one of the top-level nodes of the first. So this has at most 64 entries. Like the previous tree, all the children from here on have 8 or fewer children. + +This pattern continues. The n-th `drawable-inline-array` is a list of all nodes at depth n in the "real" tree. + +There are two tricks to this: +First, if the `drawable-inline-array` contains `draw-node`s, the type is actually `drawable-inline-array-node`. Unlike a `draw-node`, which can only contain 8 children, a `drawable-inline-array` can contain a huge number of children. The final `drawable-inline-array` is a list of a all children at the final depth, so it's always an array of `tfragment`. In this case, the type of the `drawable-inline-array` is a `drawable-inline-array-tfrag`, and it's just a giant list of all the `tfragment`s in the level. + +The second trick is that the `draw-node`s and `tfragment`s are stored only once, even if they appear in multiple `drawable-inline-array`s. They used the weird "node = length + pointer to inline array of children" format and sorted nodes by depth to enable this. + + +## Tfrag renderers +The `tfrag-methods.gc` file has the methods of `drawable` that call the actual "draw" implementations in `tfrag.gc`. There is a near and "normal" version of the renderer. So far, it seems like trans/low-res, ice, dirt, etc don't have separate rendering code (or are part of the main `tfrag` program). + +It looks possible for `tfragment`s to be drawn using the generic renderer, but so far I can't find the code where this happens. + +## Tfrag data +Tfrag also uses the `adgif-shader` idea. I believe the shaders are per-`tfragment` (though some may share, or there may be tricks to avoid resending the shader if consecutive `tfragment`s use the same settings). + +I don't know if the `adgif-shader` is always 5 quadwords, like for sprite. It seems possible to use the `adgif-shader` just to set up texturing, but also have some other stuff. I believe that we currently log in these shaders and link to textures, so we can probably learn something from inspecting these. + +There are 4 sets of data in `tfragment`: base, common, level0, level1. Each has its own DMA transfer (consisting of a address to data, plus a length). The details of which goes where is not clear yet. I _think_ that sometimes not all 4 are valid. There are only 3 start addresses stored, and the three DMA chains may be the same, or overlap. + +The DMA data itself seems to only be loading data. It uses `unpack` (V4-16, V4-32, V3-32, V4-8) and `STROW`, `STMOD`, `STCYCL` to set up fancy unpacking tricks. No other VIFcodes have been found in any level. + +Additionally, there are some color palettes that use the time-of-day system. \ No newline at end of file diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 0bdfcc3569..3cc90b919e 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -80,12 +80,9 @@ set(RUNTIME_SOURCE overlord/srpc.cpp overlord/ssound.cpp overlord/stream.cpp - graphics/dma/dma.cpp graphics/gfx.cpp graphics/display.cpp graphics/sceGraphicsInterface.cpp - graphics/dma/dma_copy.cpp - graphics/dma/gs.cpp graphics/opengl_renderer/BucketRenderer.cpp graphics/opengl_renderer/debug_gui.cpp graphics/opengl_renderer/DirectRenderer.cpp diff --git a/game/graphics/opengl_renderer/AdgifHandler.h b/game/graphics/opengl_renderer/AdgifHandler.h index 5ccf511310..b928333bda 100644 --- a/game/graphics/opengl_renderer/AdgifHandler.h +++ b/game/graphics/opengl_renderer/AdgifHandler.h @@ -1,6 +1,6 @@ #pragma once -#include "game/graphics/dma/gs.h" +#include "common/dma/gs.h" class AdgifHelper { public: diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index e23e2ce04b..6af79c333d 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -2,7 +2,7 @@ #include #include -#include "game/graphics/dma/dma_chain_read.h" +#include "common/dma/dma_chain_read.h" #include "game/graphics/opengl_renderer/Shader.h" #include "game/graphics/texture/TexturePool.h" #include "game/graphics/opengl_renderer/Profiler.h" diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp index 65b8fb6ec8..d6cb0019d7 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.cpp +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -1,5 +1,5 @@ #include "DirectRenderer.h" -#include "game/graphics/dma/gs.h" +#include "common/dma/gs.h" #include "common/log/log.h" #include "third-party/fmt/core.h" #include "game/graphics/pipelines/opengl.h" diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h index fc82fd4ecb..a1a0a6d412 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.h +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -2,7 +2,7 @@ #include #include "game/graphics/opengl_renderer/BucketRenderer.h" -#include "game/graphics/dma/gs.h" +#include "common/dma/gs.h" #include "common/math/Vector.h" #include "common/util/SmallVector.h" #include "game/graphics/pipelines/opengl.h" diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index 7eaca19002..820296e47a 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -3,7 +3,7 @@ #include #include -#include "game/graphics/dma/dma_chain_read.h" +#include "common/dma/dma_chain_read.h" #include "game/graphics/opengl_renderer/Shader.h" #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/Profiler.h" diff --git a/game/graphics/opengl_renderer/SpriteRenderer.h b/game/graphics/opengl_renderer/SpriteRenderer.h index c67f0f4fa5..79154c12e9 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.h +++ b/game/graphics/opengl_renderer/SpriteRenderer.h @@ -2,7 +2,7 @@ #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/DirectRenderer.h" -#include "game/graphics/dma/gs.h" +#include "common/dma/gs.h" #include "common/math/Vector.h" using math::Matrix4f; diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index 0bf584d663..0169703b47 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -6,7 +6,7 @@ */ #include "common/util/Timer.h" -#include "game/graphics/dma/dma.h" +#include "common/dma/dma.h" class FrameTimeRecorder { public: diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index ff538c553b..bfe2e96d6b 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -17,7 +17,7 @@ #include "game/graphics/display.h" #include "game/graphics/opengl_renderer/OpenGLRenderer.h" #include "game/graphics/texture/TexturePool.h" -#include "game/graphics/dma/dma_copy.h" +#include "common/dma/dma_copy.h" #include "game/system/newpad.h" #include "common/log/log.h" #include "common/goal_constants.h" diff --git a/game/kernel/kmachine.cpp b/game/kernel/kmachine.cpp index b8d5372538..8b34aaa1f4 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/kmachine.cpp @@ -32,8 +32,8 @@ #include "common/util/Timer.h" #include "game/graphics/sceGraphicsInterface.h" #include "game/graphics/gfx.h" -#include "game/graphics/dma/dma_chain_read.h" -#include "game/graphics/dma/dma_copy.h" +#include "common/dma/dma_chain_read.h" +#include "common/dma/dma_copy.h" #include "game/mips2c/mips2c_table.h" #include "game/system/vm/vm.h" #include "game/system/newpad.h" diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 9ac765b861..dfcb45849e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(level_tools) + add_executable(dgo_unpacker dgo_unpacker.cpp) target_link_libraries(dgo_unpacker common) diff --git a/tools/level_tools/BspHeader.cpp b/tools/level_tools/BspHeader.cpp new file mode 100644 index 0000000000..040eeaca28 --- /dev/null +++ b/tools/level_tools/BspHeader.cpp @@ -0,0 +1,1085 @@ +#include "BspHeader.h" + +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/util/DecompilerTypeSystem.h" +#include "tools/level_tools/goal_data_reader.h" +#include "tools/level_tools/Error.h" +#include "common/dma/dma.h" + +namespace level_tools { + +std::string DrawStats::print() const { + std::string result; + result += fmt::format("tfrag tris: {}\n", total_tfrag_tris); + result += fmt::format("tie prototype tris: {}\n", total_tie_prototype_tris); + result += fmt::format("actors: {}\n", total_actors); + result += fmt::format("instances: {}\n", total_tie_instances); + result += fmt::format("total tfragments: {}\n", total_tfragments); + return result; +} + +void Vector::read_from_file(Ref ref) { + if ((ref.byte_offset % 16) != 0) { + throw Error("misaligned vector"); + } + for (int i = 0; i < 4; i++) { + const auto& word = ref.data->words_by_seg.at(ref.seg).at((ref.byte_offset / 4) + i); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("vector didn't get plain data."); + } + memcpy(data + i, &word.data, 4); + } +} + +std::string Vector::print(int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}\n", is, data[0], data[1], data[2], data[3]); + return result; +} + +std::string Vector::print_meters(int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}\n", is, data[0] / 4096.f, + data[1] / 4096.f, data[2] / 4096.f, data[3] / 4096.f); + return result; +} + +void FileInfo::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) { + file_type = read_type_field(ref, "file-type", dts, true); + file_name = read_string_field(ref, "file-name", dts, true); + major_version = read_plain_data_field(ref, "major-version", dts); + minor_version = read_plain_data_field(ref, "minor-version", dts); + maya_file_name = read_string_field(ref, "maya-file-name", dts, true); + tool_debug = read_string_field(ref, "tool-debug", dts, true); + mdb_file_name = read_string_field(ref, "mdb-file-name", dts, true); +} + +std::string FileInfo::print(int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}file-type: {}\n", is, file_type); + result += fmt::format("{}file-name: \"{}\"\n", is, file_name); + result += fmt::format("{}major-version: {}\n", is, major_version); + result += fmt::format("{}minor-version: {}\n", is, minor_version); + result += fmt::format("{}maya-file-name: \"{}\"\n", is, maya_file_name); + result += fmt::format("{}tool-debug: \"{}\"\n", is, tool_debug); + result += fmt::format("{}mdb-file-name: \"{}\"\n", is, mdb_file_name); + return result; +} + +void DrawableTreeUnknown::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& /*dts*/, + DrawStats* /*stats*/) { + type_name = ref.type->get_name(); +} + +std::string DrawableTreeUnknown::print(const PrintSettings& /*settings*/, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}???\n", is, type_name); + return result; +} + +std::string DrawableTreeUnknown::my_type() const { + return type_name; +} + +void DrawableInlineArrayUnknown::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& /*dts*/, + DrawStats* /*stats*/) { + type_name = ref.type->get_name(); +} + +std::string DrawableInlineArrayUnknown::print(const PrintSettings& /*settings*/, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}???\n", is, type_name); + return result; +} + +std::string DrawableInlineArrayUnknown::my_type() const { + return type_name; +} + +std::unique_ptr make_draw_node_child(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + if (ref.type->get_name() == "draw-node") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } else if (ref.type->get_name() == "tfragment") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } else if (ref.type->get_name() == "instance-tie") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } else if (ref.type->get_name() == "drawable-actor") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } else { + throw Error("Unknown child of draw node: {}\n", ref.type->get_name()); + } +} + +int get_child_stride(const std::string& type) { + if (type == "draw-node") { + return 32; + } else if (type == "tfragment") { + return 64; + } else if (type == "instance-tie") { + return 64; + } else if (type == "drawable-actor") { + return 32; + } else { + throw Error("unknown child for stride: {}", type); + } +} + +void TFragmentDebugData::read_from_file(Ref ref, + const decompiler::DecompilerTypeSystem& /*dts*/, + DrawStats* stats) { + u32 data[4]; + auto& words = ref.data->words_by_seg.at(ref.seg); + for (int i = 0; i < 4; i++) { + auto& word = words.at((ref.byte_offset / 4) + i); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("debug data word type: {}\n", (int)word.kind); + } + data[i] = word.data; + } + + memcpy(num_tris, data, 8); + memcpy(num_dverts, data + 2, 8); + + u32 tris = 0; + for (auto num_tri : num_tris) { + tris = std::max(tris, (u32)num_tri); + } + stats->total_tfrag_tris += tris; + + auto& debug_word = words.at(4 + (ref.byte_offset / 4)); + if (debug_word.kind != decompiler::LinkedWord::PLAIN_DATA || debug_word.data != 0) { + throw Error("got debug word."); + } +} + +std::string TFragmentDebugData::print(int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}tris: [{}, {}, {}, {}]\n", is, num_tris[0], num_tris[1], num_tris[2], + num_tris[3]); + result += fmt::format("{}dverts: [{}, {}, {}, {}]\n", is, num_dverts[0], num_dverts[1], + num_dverts[2], num_dverts[3]); + return result; +} + +u32 deref_u32(const Ref& ref, int word_offset) { + if ((ref.byte_offset % 4) != 0) { + throw Error("deref_u32 bad alignment"); + } + const auto& word = ref.data->words_by_seg.at(ref.seg).at(word_offset + (ref.byte_offset / 4)); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("deref_u32 bad kind"); + } + return word.data; +} + +void tfrag_debug_print_unpack(Ref start, int qwc_total) { + int word_offset = 0; + + while (word_offset < qwc_total * 4) { + VifCode next(deref_u32(start, word_offset)); + fmt::print("{} at: {} bytes, {} qw\n", next.print(), word_offset * 4, word_offset / 4); + word_offset++; + switch (next.kind) { + case VifCode::Kind::UNPACK_V4_16: { + VifCodeUnpack up(next); + assert(up.use_tops_flag == true); + assert(up.is_unsigned == true); + // assert(up.addr_qw == 0); + int qw_to_write = next.num; + assert(next.num); + for (int qw = 0; qw < qw_to_write; qw++) { + u32 words[2]; + words[0] = deref_u32(start, word_offset++); + words[1] = deref_u32(start, word_offset++); + u16 unpacked[4]; + memcpy(unpacked, words, 8); + fmt::print(" [{:3d} {:3d} {:3d} {:3d}]\n", unpacked[0], unpacked[1], unpacked[2], + unpacked[3]); + } + + } break; + + case VifCode::Kind::UNPACK_V4_32: { + VifCodeUnpack up(next); + assert(up.use_tops_flag == true); + assert(up.is_unsigned == false); + // assert(up.addr_qw == 9); + assert(next.num); + for (int qw = 0; qw < next.num; qw++) { + u32 words[4]; + words[0] = deref_u32(start, word_offset++); + words[1] = deref_u32(start, word_offset++); + words[2] = deref_u32(start, word_offset++); + words[3] = deref_u32(start, word_offset++); + fmt::print(" [{:3d} {:3d} {:3d} {:3d}]\n", words[0], words[1], words[2], words[3]); + } + } break; + + case VifCode::Kind::UNPACK_V3_32: { + VifCodeUnpack up(next); + assert(up.use_tops_flag == true); + assert(up.is_unsigned == false); + // assert(up.addr_qw == 19); + assert(next.num); + for (int qw = 0; qw < next.num; qw++) { + u32 words[3]; + words[0] = deref_u32(start, word_offset++); + words[1] = deref_u32(start, word_offset++); + words[2] = deref_u32(start, word_offset++); + fmt::print(" [{:3d} {:3d} {:3d}]\n", words[0], words[1], words[2]); + } + } break; + + case VifCode::Kind::STROW: { + u32 words[4]; + words[0] = deref_u32(start, word_offset++); + words[1] = deref_u32(start, word_offset++); + words[2] = deref_u32(start, word_offset++); + words[3] = deref_u32(start, word_offset++); + fmt::print(" row data [{:3d} {:3d} {:3d} {:3d}]\n", words[0], words[1], words[2], words[3]); + } break; + + case VifCode::Kind::STMOD: { + } break; + + case VifCode::Kind::STCYCL: + break; + + case VifCode::Kind::UNPACK_V4_8: { + VifCodeUnpack up(next); + assert(up.use_tops_flag == true); + // assert(up.is_unsigned == false); + // assert(up.addr_qw == 129); + assert(next.num); + for (int qw = 0; qw < next.num; qw++) { + s8 words[4]; + u32 all = deref_u32(start, word_offset++); + memcpy(words, &all, 4); + fmt::print(" [{:3d} {:3d} {:3d} {:3d}]\n", words[0], words[1], words[2], words[3]); + } + } break; + case VifCode::Kind::NOP: + break; + default: + fmt::print("unknown: {}\n", next.print()); + assert(false); + } + } + fmt::print("-------------------------------------------\n"); +} + +void TFragment::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + stats->total_tfragments++; + id = read_plain_data_field(ref, "id", dts); + color_index = read_plain_data_field(ref, "color-index", dts); + debug_data.read_from_file(deref_label(get_field_ref(ref, "debug-data", dts)), dts, stats); + // todo color_indices + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto dma_field = get_field_ref(ref, "dma-qwc", dts); + auto& dma_word = ref.ref.data->words_by_seg.at(dma_field.seg).at(dma_field.byte_offset / 4); + if (dma_word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("bad dma qwc word"); + } + memcpy(dma_qwc, &dma_word.data, 4); + + auto dma_slot = get_field_ref(ref, "dma-chain", dts); + + struct DmaInfo { + Ref ref; + std::string label_name; + }; + DmaInfo dmas[3]; + for (int i = 0; i < 3; i++) { + auto& word = dma_slot.data->words_by_seg.at(dma_slot.seg).at((dma_slot.byte_offset / 4)); + dmas[i].ref = deref_label(dma_slot); + dmas[i].label_name = dma_slot.data->labels.at(word.label_id).name; + + dma_slot.byte_offset += 4; + } + + if (stats->debug_print_dma_data) { + fmt::print("qwc's: {} {} {} {}\n", dma_qwc[0], dma_qwc[1], dma_qwc[2], dma_qwc[3]); + // first, common + fmt::print("DMA COMMON {}, {} qwc:\n", dmas[0].label_name, dma_qwc[0]); + tfrag_debug_print_unpack(dmas[0].ref, dma_qwc[0]); + + // next "base" + fmt::print("DMA BASE {}, {} qwc:\n", dmas[1].label_name, dma_qwc[1]); + tfrag_debug_print_unpack(dmas[1].ref, dma_qwc[1]); + + // next "level0" + // fmt::print("DMA LEVEL0 {}, {} qwc:\n", dmas[0].label_name, dma_qwc[3]); + // tfrag_debug_print_unpack(dmas[0].ref, dma_qwc[3]); + + // next "level1" + fmt::print("DMA LEVEL1 {}, {} qwc:\n", dmas[2].label_name, dma_qwc[2]); + tfrag_debug_print_unpack(dmas[2].ref, dma_qwc[2]); + } + + // todo dma + // todo dma + // todo dma + + // todo shader + num_shaders = read_plain_data_field(ref, "num-shaders", dts); + num_base_colors = read_plain_data_field(ref, "num-base-colors", dts); + num_level0_colors = read_plain_data_field(ref, "num-level0-colors", dts); + num_level1_colors = read_plain_data_field(ref, "num-level1-colors", dts); + color_offset = read_plain_data_field(ref, "color-offset", dts); + color_count = read_plain_data_field(ref, "color-count", dts); + assert(read_plain_data_field(ref, "pad0", dts) == 0); + assert(read_plain_data_field(ref, "pad1", dts) == 0); + // todo generic +} + +std::string TFragment::print(const PrintSettings& settings, int indent) const { + if (!settings.print_tfrag) { + return ""; + } + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}color-index: {}\n", is, color_index); + result += fmt::format("{}debug-data:\n", is); + result += debug_data.print(indent + 4); + // debug data + // color indices + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + // dma + // dma + // dma + // dma qwc + result += fmt::format("{}dma-qwc: [{}, {}, {}, {}]\n", is, dma_qwc[0], dma_qwc[1], dma_qwc[2], + dma_qwc[3]); + // shader + result += fmt::format("{}num-shaders: {}\n", is, num_shaders); + result += fmt::format("{}num-base-colors: {}\n", is, num_base_colors); + result += fmt::format("{}num-level0-colors: {}\n", is, num_level0_colors); + result += fmt::format("{}num-level1-colors: {}\n", is, num_level1_colors); + result += fmt::format("{}color-offset: {}\n", is, color_offset); + result += fmt::format("{}color-count: {}\n", is, color_count); + // generic + return result; +} + +void TieFragment::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + num_tris = read_plain_data_field(ref, "num-tris", dts); + num_dverts = read_plain_data_field(ref, "num-dverts", dts); + stats->total_tie_prototype_tris += num_tris; +} + +std::string TieFragment::print(const PrintSettings& /*settings*/, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + result += fmt::format("{}num-tris: {}\n", is, num_tris); + result += fmt::format("{}num-dverts: {}\n", is, num_dverts); + return result; +} + +void DrawableActor::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + stats->total_actors++; +} + +std::string DrawableActor::print(const PrintSettings& /*settings*/, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + return result; +} + +void InstanceTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + bucket_index = read_plain_data_field(ref, "bucket-index", dts); + stats->total_tie_instances++; +} + +std::string InstanceTie::print(const PrintSettings& /*settings*/, int indent) const { + std::string is(indent, ' '); + std::string result; + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + result += fmt::format("{}bucket-index: {}\n", is, bucket_index); + return result; +} + +void DrawNode::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); // 4 + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); // 16 + child_count = read_plain_data_field(ref, "child-count", dts); + flags = read_plain_data_field(ref, "flags", dts); + // todo child + auto first_child = get_field_ref(ref, "child", dts); + auto first_child_obj = deref_label(first_child); + first_child_obj.byte_offset -= 4; + + for (int i = 0; i < child_count; i++) { + children.push_back( + make_draw_node_child(typed_ref_from_basic(first_child_obj, dts), dts, stats)); + first_child_obj.byte_offset += get_child_stride(get_type_of_basic(first_child_obj)); + } + + distance = read_plain_data_field(ref, "distance", dts); +} + +std::string DrawNode::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}child-count: {}\n", is, child_count); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + result += fmt::format("{}flags: {}\n", is, flags); + + if (settings.expand_draw_node) { + for (size_t i = 0; i < children.size(); i++) { + result += fmt::format("{}children [{}] ({}):\n", is, i, children[i]->my_type()); + result += children[i]->print(settings, next_indent); + } + } + + return result; +} + +std::string DrawNode::my_type() const { + return "draw-node"; +} + +void DrawableInlineArrayNode::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + for (int i = 0; i < length; i++) { + Ref obj_ref = data_ref; + obj_ref.byte_offset += 32 * i; // todo not a constant here + auto type = get_type_of_basic(obj_ref); + if (type != "draw-node") { + throw Error("bad draw node type: {}", type); + } + draw_nodes.emplace_back(); + draw_nodes.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats); + } +} + +std::string DrawableInlineArrayNode::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_draw_node) { + for (size_t i = 0; i < draw_nodes.size(); i++) { + result += fmt::format("{}draw-nodes [{}] ({}):\n", is, i, draw_nodes[i].my_type()); + result += draw_nodes[i].print(settings, next_indent); + } + } + + return result; +} + +std::string DrawableInlineArrayNode::my_type() const { + return "drawable-inline-array-node"; +} + +void DrawableInlineArrayTFrag::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + for (int i = 0; i < length; i++) { + Ref obj_ref = data_ref; + obj_ref.byte_offset += 64 * i; // todo not a constant here + auto type = get_type_of_basic(obj_ref); + if (type != "tfragment") { + throw Error("bad draw node type: {}", type); + } + tfragments.emplace_back(); + tfragments.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats); + } +} + +std::string DrawableInlineArrayTFrag::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_draw_node) { + for (size_t i = 0; i < tfragments.size(); i++) { + result += fmt::format("{}draw-nodes [{}] ({}):\n", is, i, tfragments[i].my_type()); + result += tfragments[i].print(settings, next_indent); + } + } + + return result; +} + +std::string DrawableInlineArrayTFrag::my_type() const { + return "drawable-inline-array-tfrag"; +} + +void DrawableInlineArrayTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + for (int i = 0; i < length; i++) { + Ref obj_ref = data_ref; + obj_ref.byte_offset += 64 * i; // todo not a constant here + auto type = get_type_of_basic(obj_ref); + if (type != "instance-tie") { + throw Error("bad draw node type: {}", type); + } + instances.emplace_back(); + instances.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats); + } +} + +std::string DrawableInlineArrayTie::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_draw_node) { + for (size_t i = 0; i < instances.size(); i++) { + result += fmt::format("{}draw-nodes [{}] ({}):\n", is, i, instances[i].my_type()); + result += instances[i].print(settings, next_indent); + } + } + + return result; +} + +std::string DrawableInlineArrayTie::my_type() const { + return "drawable-inline-array-instance-tie"; +} + +std::string PrototypeTie::my_type() const { + return "prototype-tie"; +} + +void PrototypeTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + for (int i = 0; i < length; i++) { + Ref obj_ref = data_ref; + obj_ref.byte_offset += 64 * i; // todo not a constant here + auto type = get_type_of_basic(obj_ref); + if (type != "tie-fragment") { + throw Error("bad draw node type: {}", type); + } + tie_fragments.emplace_back(); + tie_fragments.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats); + } +} + +std::string PrototypeTie::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_draw_node) { + for (size_t i = 0; i < tie_fragments.size(); i++) { + result += fmt::format("{}draw-nodes [{}] ({}):\n", is, i, tie_fragments[i].my_type()); + result += tie_fragments[i].print(settings, next_indent); + } + } + + return result; +} + +std::unique_ptr make_drawable_inline_array( + TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + if (ref.type->get_name() == "drawable-inline-array-node") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } + + if (ref.type->get_name() == "drawable-inline-array-tfrag") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } + + if (ref.type->get_name() == "drawable-inline-array-trans-tfrag") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } + + if (ref.type->get_name() == "drawable-inline-array-instance-tie") { + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; + } + auto result = std::make_unique(); + result->read_from_file(ref, dts, stats); + return result; +} + +void DrawableTreeTfrag::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + if ((data_ref.byte_offset % 4) != 0) { + throw Error("misaligned data array"); + } + + for (int idx = 0; idx < length; idx++) { + Ref array_slot_ref = data_ref; + array_slot_ref.byte_offset += idx * 4; + + Ref object_ref = deref_label(array_slot_ref); + object_ref.byte_offset -= 4; + + arrays.push_back(make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats)); + } +} + +std::string DrawableTreeTfrag::print(const PrintSettings& settings, int indent) const { + if (!settings.expand_drawable_tree_tfrag && my_type() == "drawable-tree-tfrag") { + return ""; + } + + if (!settings.expand_drawable_tree_trans_tfrag && my_type() == "drawable-tree-trans-tfrag") { + return ""; + } + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + for (size_t i = 0; i < arrays.size(); i++) { + result += fmt::format("{}arrays [{}] ({}):\n", is, i, arrays[i]->my_type()); + result += arrays[i]->print(settings, next_indent); + } + + return result; +} + +std::string DrawableTreeTfrag::my_type() const { + return "drawable-tree-tfrag"; +} + +void DrawableTreeActor::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto data_ref = get_field_ref(ref, "data", dts); + if ((data_ref.byte_offset % 4) != 0) { + throw Error("misaligned data array"); + } + + for (int idx = 0; idx < length; idx++) { + Ref array_slot_ref = data_ref; + array_slot_ref.byte_offset += idx * 4; + + Ref object_ref = deref_label(array_slot_ref); + object_ref.byte_offset -= 4; + + arrays.push_back(make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats)); + } +} + +std::string DrawableTreeActor::print(const PrintSettings& settings, int indent) const { + if (!settings.expand_drawable_tree_actor) { + return ""; + } + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + for (size_t i = 0; i < arrays.size(); i++) { + result += fmt::format("{}arrays [{}] ({}):\n", is, i, arrays[i]->my_type()); + result += arrays[i]->print(settings, next_indent); + } + + return result; +} + +std::string DrawableTreeActor::my_type() const { + return "drawable-tree-actor"; +} + +void PrototypeBucketTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + name = read_string_field(ref, "name", dts, true); + flags = read_plain_data_field(ref, "flags", dts); + in_level = read_plain_data_field(ref, "in-level", dts); + utextures = read_plain_data_field(ref, "utextures", dts); + // todo drawables + dists.read_from_file(get_field_ref(ref, "dists", dts)); + rdists.read_from_file(get_field_ref(ref, "rdists", dts)); + + auto next_slot = get_field_ref(ref, "next", dts); + for (int i = 0; i < 4; i++) { + auto& word = ref.ref.data->words_by_seg.at(next_slot.seg).at(i + (next_slot.byte_offset / 4)); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("bad word type in PrototypeBucketTie next"); + } + next[i] = word.data; + } + + auto count_slot = get_field_ref(ref, "count", dts); + for (int i = 0; i < 2; i++) { + auto& word = ref.ref.data->words_by_seg.at(count_slot.seg).at(i + (count_slot.byte_offset / 4)); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("bad word type in PrototypeBucketTie count"); + } + memcpy(count + 2 * i, &word.data, 4); + } + + auto block_slot = get_field_ref(ref, "generic-count", dts); + u8* block_start = (u8*)generic_count; + for (int i = 0; i < 12; i++) { + auto& word = ref.ref.data->words_by_seg.at(block_slot.seg).at(i + (block_slot.byte_offset / 4)); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("bad word type in PrototypeBucketTie slot"); + } + memcpy(block_start + 4 * i, &word.data, 4); + } + + auto geom_start = get_field_ref(ref, "geometry", dts); + for (int i = 0; i < 4; i++) { + auto geom = deref_label(geom_start); + geom.byte_offset -= 4; + + if (get_type_of_basic(geom) != "prototype-tie") { + throw Error("bad type in prototype-bucket-tie: {}", get_type_of_basic(geom)); + } + geometry[i].read_from_file(typed_ref_from_basic(geom, dts), dts, stats); + geom_start.byte_offset += 4; + } + + for (auto x : next) { + assert(x == 0); + } + for (auto x : count) { + assert(x == 0); + } + for (auto x : generic_count) { + assert(x == 0); + } + for (auto x : generic_next) { + assert(x == 0); + } +} + +std::string PrototypeBucketTie::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + // int next_indent = indent + 4; + result += fmt::format("{}name: {}\n", is, name); + result += fmt::format("{}flags: {}\n", is, flags); + result += fmt::format("{}in_level: {}\n", is, in_level); + result += fmt::format("{}utextures: {}\n", is, utextures); + if (settings.expand_drawable_tree_tie_proto_data) { + for (int i = 0; i < 4; i++) { + result += fmt::format("{}geometry[{}]:\n", is, i); + result += geometry[i].print(settings, indent + 4); + } + result += fmt::format("{}dists: {}", is, dists.print_meters()); + result += fmt::format("{}rdists: {}", is, rdists.print()); + // result += fmt::format("{}next: [{}, {}, {}, {}]\n", is, next[0], next[1], next[2], next[3]); + // result += fmt::format("{}count: [{}, {}, {}, {}]\n", is, count[0], count[1], count[2], + // count[3]); result += fmt::format("{}generic_count: [{}, {}, {}, {}]\n", is, + // generic_count[0], + // generic_count[1], generic_count[2], generic_count[3]); + // result += fmt::format("{}generic_next: [{}, {}, {}, {}]\n", is, generic_next[0], + // generic_next[1], + // generic_next[2], generic_next[3]); + result += fmt::format("{}frag_count: [{}, {}, {}, {}]\n", is, frag_count[0], frag_count[1], + frag_count[2], frag_count[3]); + result += fmt::format("{}index_start: [{}, {}, {}, {}]\n", is, index_start[0], index_start[1], + index_start[2], index_start[3]); + result += fmt::format("{}base_qw: [{}, {}, {}, {}]\n", is, base_qw[0], base_qw[1], base_qw[2], + base_qw[3]); + result += fmt::format("{}envmap_rfade: {}\n", is, envmap_rfade); + result += fmt::format("{}envmap_fade_far: {} m\n", is, envmap_fade_far / 4096.f); + } + + return result; +} + +void PrototypeArrayTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + length = read_plain_data_field(ref, "length", dts); + allocated_length = read_plain_data_field(ref, "allocated-length", dts); + content_type = read_type_field(ref, "content-type", dts, true); + auto data_ref = get_field_ref(ref, "data", dts); + + for (u32 i = 0; i < length; i++) { + Ref slot = data_ref; + slot.byte_offset += 4 * i; + Ref thing = deref_label(slot); + thing.byte_offset -= 4; + auto type = get_type_of_basic(thing); + if (type != "prototype-bucket-tie") { + throw Error("bad type in PrototypeArrayTie data: {}\n", type); + } + data.emplace_back(); + data.back().read_from_file(typed_ref_from_basic(thing, dts), dts, stats); + } +} + +std::string PrototypeArrayTie::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}allocated-length: {}\n", is, allocated_length); + result += fmt::format("{}content-type: {}\n", is, content_type); + + for (u32 i = 0; i < data.size(); i++) { + result += fmt::format("{}data [{}]:\n", is, i); + result += data[i].print(settings, next_indent); + } + return result; +} + +void ProxyPrototypeArrayTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + prototype_array_tie.read_from_file( + get_and_check_ref_to_basic(ref, "prototype-array-tie", "prototype-array-tie", dts), dts, + stats); + // TODO wind +} + +std::string ProxyPrototypeArrayTie::print(const PrintSettings& settings, int indent) const { + // just inline it for now + return prototype_array_tie.print(settings, indent); +} + +void DrawableTreeInstanceTie::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + auto pt = deref_label(get_field_ref(ref, "prototypes", dts)); + pt.byte_offset -= 4; + + prototypes.read_from_file(typed_ref_from_basic(pt, dts), dts, stats); + + auto data_ref = get_field_ref(ref, "data", dts); + if ((data_ref.byte_offset % 4) != 0) { + throw Error("misaligned data array"); + } + for (int idx = 0; idx < length; idx++) { + Ref array_slot_ref = data_ref; + array_slot_ref.byte_offset += idx * 4; + + Ref object_ref = deref_label(array_slot_ref); + object_ref.byte_offset -= 4; + + arrays.push_back(make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats)); + } +} + +std::string DrawableTreeInstanceTie::print(const PrintSettings& settings, int indent) const { + if (!settings.expand_drawable_tree_instance_tie && !settings.expand_drawable_tree_tie_proto) { + return ""; + } + std::string is(indent, ' '); + std::string result; + int next_indent = indent + 4; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + result += fmt::format("{}bsphere: {}", is, bsphere.print_meters()); + + if (settings.expand_drawable_tree_tie_proto) { + result += fmt::format("{}prototypes:\n", is); + result += prototypes.print(settings, next_indent); + } + + if (settings.expand_drawable_tree_instance_tie) { + for (size_t i = 0; i < arrays.size(); i++) { + result += fmt::format("{}arrays [{}] ({}):\n", is, i, arrays[i]->my_type()); + result += arrays[i]->print(settings, next_indent); + } + } + + return result; +} + +std::string DrawableTreeInstanceTie::my_type() const { + return "drawable-tree-instance-tie"; +} + +std::unique_ptr make_drawable_tree(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + if (ref.type->get_name() == "drawable-tree-tfrag") { + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; + } + + if (ref.type->get_name() == "drawable-tree-trans-tfrag") { + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; + } + + if (ref.type->get_name() == "drawable-tree-instance-tie") { + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; + } + + if (ref.type->get_name() == "drawable-tree-actor") { + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; + } + auto tree = std::make_unique(); + tree->read_from_file(ref, dts, stats); + return tree; +} + +void DrawableTreeArray::read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + id = read_plain_data_field(ref, "id", dts); + length = read_plain_data_field(ref, "length", dts); + + auto trees_ref = get_field_ref(ref, "trees", dts); + if ((trees_ref.byte_offset % 4) != 0) { + throw Error("misaligned trees array"); + } + + for (int idx = 0; idx < length; idx++) { + Ref array_slot_ref = trees_ref; + array_slot_ref.byte_offset += idx * 4; + + Ref object_ref = deref_label(array_slot_ref); + object_ref.byte_offset -= 4; + + trees.push_back(make_drawable_tree(typed_ref_from_basic(object_ref, dts), dts, stats)); + } +} + +std::string DrawableTreeArray::print(const PrintSettings& settings, int indent) const { + std::string is(indent, ' '); + int next_indent = indent + 4; + std::string result; + result += fmt::format("{}id: {}\n", is, id); + result += fmt::format("{}length: {}\n", is, length); + + for (size_t i = 0; i < trees.size(); i++) { + result += fmt::format("{}trees [{}] ({}):\n", is, i, trees[i]->my_type()); + result += trees[i]->print(settings, next_indent); + } + + return result; +} + +void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) { + TypedRef ref; + ref.ref.byte_offset = 0; + ref.ref.seg = 0; + ref.ref.data = &file; + ref.type = dts.ts.lookup_type("bsp-header"); + + file_info.read_from_file(get_and_check_ref_to_basic(ref, "info", "file-info", dts), dts); + bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); + + visible_list_length = read_plain_data_field(ref, "visible-list-length", dts); + + drawable_tree_array.read_from_file( + get_and_check_ref_to_basic(ref, "drawable-trees", "drawable-tree-array", dts), dts, stats); +} + +std::string BspHeader::print(const PrintSettings& settings) const { + std::string result; + int next_indent = 4; + + result += fmt::format("file-info:\n"); + result += file_info.print(next_indent); + + result += fmt::format("bsphere:\n"); + result += bsphere.print_meters(next_indent); + + result += fmt::format("visible-list-length: {}\n", visible_list_length); + + result += fmt::format("drawable-trees:\n"); + result += drawable_tree_array.print(settings, next_indent); + return result; +} +} // namespace level_tools \ No newline at end of file diff --git a/tools/level_tools/BspHeader.h b/tools/level_tools/BspHeader.h new file mode 100644 index 0000000000..f21209ea0a --- /dev/null +++ b/tools/level_tools/BspHeader.h @@ -0,0 +1,412 @@ +#pragma once + +#include "common/common_types.h" +#include +#include +#include + +#include "tools/level_tools/goal_data_reader.h" + +namespace decompiler { +class LinkedObjectFile; +class DecompilerTypeSystem; +} // namespace decompiler + +namespace level_tools { + +struct Vector { + float data[4]; + + void read_from_file(Ref ref); + + std::string print(int indent = 0) const; + std::string print_meters(int indent = 0) const; +}; + +struct FileInfo { + std::string file_type; + std::string file_name; + u32 major_version; + u32 minor_version; + + std::string maya_file_name; + std::string tool_debug; + std::string mdb_file_name; + + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts); + + std::string print(int indent = 0) const; +}; + +struct PrintSettings { + bool print_tfrag = true; + bool expand_draw_node = true; + bool expand_drawable_tree_tfrag = true; + bool expand_drawable_tree_trans_tfrag = false; + bool expand_drawable_tree_tie_proto = false; + bool expand_drawable_tree_tie_proto_data = false; + bool expand_drawable_tree_instance_tie = false; + bool expand_drawable_tree_actor = false; +}; + +struct DrawStats { + int total_tfrag_tris = 0; + int total_tie_prototype_tris = 0; + int total_actors = 0; + int total_tie_instances = 0; + int total_tfragments = 0; + + bool debug_print_dma_data = false; + + std::string print() const; +}; + +struct Drawable { + virtual void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) = 0; + virtual std::string print(const PrintSettings& settings, int indent) const = 0; + virtual std::string my_type() const = 0; + virtual ~Drawable() = default; +}; + +struct DrawableInlineArray : public Drawable {}; + +struct DrawNode : public Drawable { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + + s16 id; + Vector bsphere; + u8 child_count = 0; + u8 flags = 0; + std::vector> children; + float distance = 0; +}; + +struct EntityActor {}; + +struct DrawableActor : public Drawable { + s16 id; + Vector bsphere; + + EntityActor actor; + + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override { return "drawable-actor"; } +}; + +struct TFragmentDebugData { + u16 num_tris[4]; + u16 num_dverts[4]; + bool has_debug_lines; + + std::string print(int indent) const; + void read_from_file(Ref ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); +}; + +struct TFragment : public Drawable { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override { return "tfragment"; } + + // 0 - 4 type tag + + s16 id; // 4 - 6 + u16 color_index; // 6 - 8 + TFragmentDebugData debug_data; + // u32 color_indices; // 12 - 16 (or colors?) + Vector bsphere; // 16 - 32 + // dma common/level0 // 32 - 36 + // dma base // 36 - 40 + // dma level 1 // 40 - 44 + u8 dma_qwc[4]; + // shader // 48 - 52 + u8 num_shaders; // 52 + u8 num_base_colors; // 53 + u8 num_level0_colors; // 54 + u8 num_level1_colors; // 55 + u8 color_offset; // 56 + u8 color_count; // 57 + // u8 pad0; // 58 + // u8 pad1; // 59 + // generic // 60 - 64 +}; + +struct TieFragment : public Drawable { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override { return "tie-fragment"; } + + Vector bsphere; + u16 num_tris; + u16 num_dverts; + + // todo, lots more +}; + +struct InstanceTie : public Drawable { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override { return "instance-tie"; } + + // (bucket-index uint16 :offset 6) + u16 bucket_index; + Vector bsphere; + + // todo, lots more +}; + +struct DrawableInlineArrayNode : public DrawableInlineArray { + s16 id; + s16 length; + Vector bsphere; + + std::vector draw_nodes; + + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; +}; + +struct DrawableInlineArrayTFrag : public DrawableInlineArray { + s16 id; + s16 length; + Vector bsphere; + + std::vector tfragments; + + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; +}; + +struct DrawableInlineArrayTie : public DrawableInlineArray { + s16 id; + s16 length; + Vector bsphere; + + std::vector instances; + + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; +}; + +struct DrawableInlineArrayTransTFrag : public DrawableInlineArrayTFrag { + std::string my_type() const override { return "drawable-inline-array-trans-tfrag"; } +}; + +struct DrawableInlineArrayUnknown : public DrawableInlineArray { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + std::string type_name; +}; + +struct DrawableTree : public Drawable {}; + +struct DrawableTreeTfrag : public DrawableTree { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + + s16 id; + s16 length; + // todo time of day stuff + Vector bsphere; + + std::vector> arrays; +}; + +struct DrawableTreeActor : public DrawableTree { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + + s16 id; + s16 length; + // todo time of day stuff + Vector bsphere; + + std::vector> arrays; +}; + +struct PrototypeTie : public DrawableInlineArray { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + s16 id; + s16 length; + + Vector bsphere; + std::vector tie_fragments; +}; + +struct PrototypeBucketTie { + std::string name; // 4 - 8 + u32 flags; // 8 - 12 + u16 in_level; // 12 - 14 + u16 utextures; // 14 - 16 + PrototypeTie geometry[4]; + + Vector dists; + Vector rdists; + u32 next[4]; + u16 count[4]; + + u16 generic_count[4]; + u32 generic_next[4]; + u8 frag_count[4]; + u8 index_start[4]; + u16 base_qw[4]; + + float envmap_rfade; + float envmap_fade_far; + + // todo envmap shader + // todo collide-frag + // todo tie-colors + // todo data + + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + std::string print(const PrintSettings& settings, int indent) const; +}; + +struct PrototypeArrayTie { + u32 length; + u32 allocated_length; + std::string content_type; + std::vector data; + + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + std::string print(const PrintSettings& settings, int indent) const; +}; + +struct ProxyPrototypeArrayTie { + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + std::string print(const PrintSettings& settings, int indent) const; + + PrototypeArrayTie prototype_array_tie; + // todo wind vectors. +}; + +struct DrawableTreeInstanceTie : public DrawableTree { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + + s16 id; + s16 length; + ProxyPrototypeArrayTie prototypes; + Vector bsphere; + + std::vector> arrays; +}; + +struct DrawableTreeTransTfrag : public DrawableTreeTfrag { + std::string my_type() const override { return "drawable-tree-trans-tfrag"; } +}; + +struct DrawableTreeUnknown : public DrawableTree { + void read_from_file(TypedRef ref, + const decompiler::DecompilerTypeSystem& dts, + DrawStats* stats) override; + std::string print(const PrintSettings& settings, int indent) const override; + std::string my_type() const override; + std::string type_name; +}; + +struct DrawableTreeArray { + s16 id; + s16 length; + + void read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + + std::string print(const PrintSettings& settings, int indent) const; + + std::vector> trees; +}; + +struct BspHeader { + // (info file-info :offset 4) + FileInfo file_info; + + // (bsphere vector :inline :offset-assert 16) + Vector bsphere; + + // (all-visible-list (pointer uint16) :offset-assert 32) + + // (visible-list-length int32 :offset-assert 36) + s32 visible_list_length = -1; + + // (drawable-trees drawable-tree-array :offset-assert 40) + DrawableTreeArray drawable_tree_array; + + // (pat pointer :offset-assert 44) + // (pat-length int32 :offset-assert 48) + // + // ;; some osrt of texture remapping info + // (texture-remap-table (pointer uint64) :offset-assert 52) + // (texture-remap-table-len int32 :offset-assert 56) + // + // (texture-ids (pointer texture-id) :offset-assert 60) + // (texture-page-count int32 :offset-assert 64) + // + // (unk-zero-0 basic :offset-assert 68) + // + // (name symbol :offset-assert 72) + // (nickname symbol :offset-assert 76) + // (vis-info level-vis-info 8 :offset-assert 80) + // (actors drawable-inline-array-actor :offset-assert 112) + // (cameras (array entity-camera) :offset-assert 116) + // (nodes (inline-array bsp-node) :offset-assert 120) + // + // (level level :offset-assert 124) + // (current-leaf-idx uint16 :offset-assert 128) + // (unk-data-2 uint16 9 :offset-assert 130) + // + // (boxes box8s-array :offset-assert 148) + // (current-bsp-back-flags uint32 :offset-assert 152) + // (ambients drawable-inline-array-ambient :offset-assert 156) + // (unk-data-4 float :offset-assert 160) + // (unk-data-5 float :offset-assert 164) + // (adgifs adgif-shader-array :offset-assert 168) + // (actor-birth-order (pointer uint32) :offset-assert 172) + // (split-box-indices (pointer uint16) :offset-assert 176) + // (unk-data-8 uint32 55 :offset-assert 180) + + void read_from_file(const decompiler::LinkedObjectFile& file, + const decompiler::DecompilerTypeSystem& dts, DrawStats* stats); + + std::string print(const PrintSettings& settings) const; +}; +} // namespace level_tools diff --git a/tools/level_tools/CMakeLists.txt b/tools/level_tools/CMakeLists.txt new file mode 100644 index 0000000000..3b8f4c2f3b --- /dev/null +++ b/tools/level_tools/CMakeLists.txt @@ -0,0 +1,7 @@ + + +add_library(level_tools goal_data_reader.cpp BspHeader.cpp) +target_link_libraries(level_tools fmt common decomp) + +add_executable(level_dump level_dump/main.cpp) +target_link_libraries(level_dump fmt common decomp level_tools) \ No newline at end of file diff --git a/tools/level_tools/Error.h b/tools/level_tools/Error.h new file mode 100644 index 0000000000..3d507351f4 --- /dev/null +++ b/tools/level_tools/Error.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "third-party/fmt/core.h" + +class Error : public std::runtime_error { + public: + template + Error(const std::string& format, Args&&... args) + : std::runtime_error(fmt::format(format, std::forward(args)...)) {} +}; \ No newline at end of file diff --git a/tools/level_tools/goal_data_reader.cpp b/tools/level_tools/goal_data_reader.cpp new file mode 100644 index 0000000000..efeed12eb9 --- /dev/null +++ b/tools/level_tools/goal_data_reader.cpp @@ -0,0 +1,281 @@ +#include "goal_data_reader.h" + +#include "decompiler/util/DecompilerTypeSystem.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" + +#include "tools/level_tools/Error.h" + +void read_plain_data_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + int size_bytes, + u8* out) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + + if (field_info.field.is_dynamic() || field_info.field.is_array() || + field_info.field.is_inline()) { + throw Error("Field {} is dynamic/array/inline and can't be used with read_plain_data_field", + field_name); + } + + auto field_type = dts.ts.lookup_type(field_info.type); + if (field_type->is_reference()) { + throw Error("Field {} is a reference and can't be used with read_plain_data_field", field_name); + } + + if (field_type->is_boxed()) { + throw Error("Field {} is boxed and can't be used with read_plain_data_field", field_name); + } + + int field_size_bytes = field_type->get_size_in_memory(); + + if (field_size_bytes != size_bytes) { + throw Error( + "Field {} size mismatch. Type system thinks it is {} bytes, but tried to load as {} bytes.", + field_name, field_size_bytes, size_bytes); + } + + const auto& words = object.ref.data->words_by_seg.at(object.ref.seg); + for (int byte = 0; byte < size_bytes; byte++) { + int byte_in_words = byte + object.ref.byte_offset + field_info.field.offset(); + + int word_idx = byte_in_words / 4; + int byte_in_word = byte_in_words % 4; + + const auto& word = words.at(word_idx); + if (word.kind != decompiler::LinkedWord::PLAIN_DATA) { + throw Error("Error reading byte {} of field {} (in data, byte {}). Didn't get plain data.", + byte, field_name, byte_in_words); + } + + out[byte] = word.get_byte(byte_in_word); + } +} + +TypedRef get_and_check_ref_to_basic(const TypedRef& object, + const std::string& field_name, + const std::string& expected_type, + const decompiler::DecompilerTypeSystem& dts) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + + if (field_info.field.is_dynamic() || field_info.field.is_array() || + field_info.field.is_inline()) { + throw Error( + "Field {} is dynamic/array/inline and can't be used with get_and_check_ref_to_basic", + field_name); + } + + auto field_type = dts.ts.lookup_type(field_info.type); + auto basic_type = dynamic_cast(field_type); + if (!basic_type) { + throw Error("Field {} is not a basic and can't be used with read_plain_data_field", field_name); + } + + int byte_in_words = object.ref.byte_offset + field_info.field.offset(); + if ((byte_in_words % 4) != 0) { + throw Error("Field {} was misaligned.", field_name); + } + + const auto& word = object.ref.data->words_by_seg.at(object.ref.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::PTR) { + throw Error("get_and_check_ref_to_basic did not get a label"); + } + + const auto& label = object.ref.data->labels.at(word.label_id); + + Ref ref; + ref.data = object.ref.data; + ref.seg = label.target_segment; + ref.byte_offset = label.offset - 4; // basic offset. + + if ((ref.byte_offset % 4) != 0) { + throw Error("get_and_check_ref_to_basic had a misaligned basic"); + } + + const auto& type_tag = object.ref.data->words_by_seg.at(ref.seg).at(ref.byte_offset / 4); + if (type_tag.kind != decompiler::LinkedWord::TYPE_PTR) { + throw Error("get_and_check_ref_to_basic did not find a type tag"); + } + + if (type_tag.symbol_name != expected_type) { + throw Error("get_and_check_ref_to_basic found type {} for field {}, expected {}", + type_tag.symbol_name, field_name, expected_type); + } + + TypedRef tr; + tr.ref = ref; + tr.type = dts.ts.lookup_type(type_tag.symbol_name); + return tr; +} + +std::string read_symbol_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + + if (field_info.field.is_dynamic() || field_info.field.is_array() || + field_info.field.is_inline()) { + throw Error("Field {} is dynamic/array/inline and can't be used with read_symbol_field", + field_name); + } + + if (field_info.type != TypeSpec("symbol")) { + throw Error("Field {} has type {} and can't be used with read_symbol_field", field_name, + field_info.type.print()); + } + + int byte_in_words = object.ref.byte_offset + field_info.field.offset(); + if ((byte_in_words % 4) != 0) { + throw Error("Field {} was misaligned.", field_name); + } + + const auto& word = object.ref.data->words_by_seg.at(object.ref.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::SYM_PTR) { + throw Error("read_symbol_field did not get a symbol (offset {} words)", byte_in_words / 4); + } + + return word.symbol_name; +} + +std::string read_type_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + bool ignore_field_type) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + + if (field_info.field.is_dynamic() || field_info.field.is_array() || + field_info.field.is_inline()) { + throw Error("Field {} is dynamic/array/inline and can't be used with read_type_field", + field_name); + } + + if (!ignore_field_type && field_info.type != TypeSpec("type")) { + throw Error("Field {} has type {} and can't be used with read_type_field", field_name, + field_info.type.print()); + } + + int byte_in_words = object.ref.byte_offset + field_info.field.offset(); + if ((byte_in_words % 4) != 0) { + throw Error("Field {} was misaligned.", field_name); + } + + const auto& word = object.ref.data->words_by_seg.at(object.ref.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::TYPE_PTR) { + throw Error("read_type_field did not get a symbol (offset {} words)", byte_in_words / 4); + } + + return word.symbol_name; +} + +std::string read_string_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + bool ignore_field_type) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + + if (field_info.field.is_dynamic() || field_info.field.is_array() || + field_info.field.is_inline()) { + throw Error("Field {} is dynamic/array/inline and can't be used with read_string_field", + field_name); + } + + if (!ignore_field_type && field_info.type != TypeSpec("string")) { + throw Error("Field {} has type {} and can't be used with read_string_field", field_name, + field_info.type.print()); + } + + int byte_in_words = object.ref.byte_offset + field_info.field.offset(); + if ((byte_in_words % 4) != 0) { + throw Error("Field {} was misaligned.", field_name); + } + const auto& word = object.ref.data->words_by_seg.at(object.ref.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::PTR) { + throw Error("read_string_field did not get a label (offset {} words)", byte_in_words / 4); + } + + const auto& label = object.ref.data->labels.at(word.label_id); + return object.ref.data->get_goal_string_by_label(label); +} + +Ref get_field_ref(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts) { + FieldLookupInfo field_info = dts.ts.lookup_field_info(object.type->get_name(), field_name); + Ref result = object.ref; + result.byte_offset += field_info.field.offset(); + return result; +} + +std::string get_type_of_basic(const Ref& object) { + int byte_in_words = object.byte_offset; + if ((byte_in_words % 4) != 0) { + throw Error("Basic in get_type_of_basic was misaligned."); + } + + const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::TYPE_PTR) { + throw Error("get_type_of_basic did not get a type tag (offset {} words)", byte_in_words / 4); + } + + return word.symbol_name; +} + +TypedRef typed_ref_from_basic(const Ref& object, const decompiler::DecompilerTypeSystem& dts) { + int byte_in_words = object.byte_offset; + if ((byte_in_words % 4) != 0) { + throw Error("Basic in typed_ref_from_basic was misaligned."); + } + + const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::TYPE_PTR) { + throw Error("typed_ref_from_basic did not get a type tag (offset {} words)", byte_in_words / 4); + } + + TypedRef result; + result.ref = object; + result.type = dts.ts.lookup_type(word.symbol_name); + return result; +} + +Ref deref_label(const Ref& object) { + int byte_in_words = object.byte_offset; + if ((byte_in_words % 4) != 0) { + throw Error("ptr in deref_label was misaligned."); + } + + const auto& word = object.data->words_by_seg.at(object.seg).at(byte_in_words / 4); + + if (word.kind != decompiler::LinkedWord::PTR) { + throw Error("deref_label did not get a label (offset {} words)", byte_in_words / 4); + } + + const auto& lab = object.data->labels.at(word.label_id); + + Ref result; + result.byte_offset = lab.offset; + result.seg = lab.target_segment; + result.data = object.data; + return result; +} + +std::string inspect_ref(const Ref& ref) { + auto& word = ref.data->words_by_seg.at(ref.seg).at(ref.byte_offset / 4); + switch (word.kind) { + case decompiler::LinkedWord::PLAIN_DATA: + return fmt::format("[0x{:08x} (offset {} bytes)]", word.data, ref.byte_offset % 4); + case decompiler::LinkedWord::PTR: + return fmt::format("[{}]", ref.data->labels.at(word.label_id).name); + case decompiler::LinkedWord::SYM_PTR: + return fmt::format("['{}]", word.symbol_name); + case decompiler::LinkedWord::TYPE_PTR: + return fmt::format("[t'{}]", word.symbol_name); + default: + assert(false); + } +} \ No newline at end of file diff --git a/tools/level_tools/goal_data_reader.h b/tools/level_tools/goal_data_reader.h new file mode 100644 index 0000000000..510440e3d4 --- /dev/null +++ b/tools/level_tools/goal_data_reader.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace decompiler { +class DecompilerTypeSystem; +class LinkedObjectFile; +} // namespace decompiler + +class Type; + +struct Ref { + const decompiler::LinkedObjectFile* data = nullptr; + int seg = -1; + int byte_offset = 1; +}; + +struct TypedRef { + Ref ref; + Type* type = nullptr; +}; + +void read_plain_data_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + int size_bytes, + u8* out); + +template +T read_plain_data_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts) { + u8 data[sizeof(T)]; + read_plain_data_field(object, field_name, dts, sizeof(T), data); + + T result; + memcpy(&result, data, sizeof(T)); + return result; +} + +TypedRef get_and_check_ref_to_basic(const TypedRef& object, + const std::string& field_name, + const std::string& expected_type, + const decompiler::DecompilerTypeSystem& dts); + +std::string read_symbol_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts); + +std::string read_type_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + bool ignore_field_type); + +std::string read_string_field(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts, + bool ignore_field_type); + +Ref get_field_ref(const TypedRef& object, + const std::string& field_name, + const decompiler::DecompilerTypeSystem& dts); + +std::string get_type_of_basic(const Ref& object); + +TypedRef typed_ref_from_basic(const Ref& object, const decompiler::DecompilerTypeSystem& dts); + +Ref deref_label(const Ref& object); + +std::string inspect_ref(const Ref& ref); \ No newline at end of file diff --git a/tools/level_tools/level_dump/main.cpp b/tools/level_tools/level_dump/main.cpp new file mode 100644 index 0000000000..55a02d6ab2 --- /dev/null +++ b/tools/level_tools/level_dump/main.cpp @@ -0,0 +1,93 @@ +#include "third-party/fmt/core.h" +#include "common/util/FileUtil.h" +#include "decompiler/ObjectFile/LinkedObjectFile.h" +#include "decompiler/ObjectFile/LinkedObjectFileCreation.h" +#include "common/util/DgoReader.h" + +#include "tools/level_tools/goal_data_reader.h" +#include "tools/level_tools/BspHeader.h" + + +#include "common/util/assert.h" + +/*! + * Get the level data from a DGO File. + * Will ignore all the other things in the level DGO and just return the bsp file. + */ +decompiler::LinkedObjectFile load_bsp_from_dgo(const std::string& file_name, + decompiler::DecompilerTypeSystem& dts) { + std::string short_name = file_util::base_name(file_name); + fmt::print("Loading DGO file: {}\n", short_name); + auto dgo_file_data = file_util::read_binary_file(file_name); + + auto dgo = DgoReader(short_name, dgo_file_data); + auto entries = dgo.entries(); + assert(entries.size() > 0); + + const auto& level_file = entries.back(); + + fmt::print("Using level file: {}, size {} kB\n", level_file.internal_name, + level_file.data.size() / 1024); + + return decompiler::to_linked_object_file(level_file.data, level_file.internal_name, dts, 1); +} + +bool is_valid_bsp(const decompiler::LinkedObjectFile& file) { + if (file.segments != 1) { + fmt::print("Got {} segments, but expected 1\n", file.segments); + return false; + } + + auto& first_word = file.words_by_seg.at(0).at(0); + if (first_word.kind != decompiler::LinkedWord::TYPE_PTR) { + fmt::print("Expected the first word to be a type pointer, but it wasn't.\n"); + return false; + } + + if (first_word.symbol_name != "bsp-header") { + fmt::print("Expected to get a bsp-header, but got {} instead.\n", first_word.symbol_name); + return false; + } + + return true; +} + +int main(int argc, char** argv) { + try { + fmt::print("Level Dump Tool\n"); + + if (argc != 2) { + fmt::print("Usage: level_dump \n"); + return 1; + } + + fmt::print("Setting up types...\n"); + decompiler::DecompilerTypeSystem dts; + dts.parse_type_defs({"decompiler", "config", "all-types.gc"}); + + std::string file_name = argv[1]; + + fmt::print("Loading data...\n"); + auto data = load_bsp_from_dgo(file_name, dts); + data.set_ordered_label_names(); + + if (!is_valid_bsp(data)) { + fmt::print("Invalid level file.\n"); + return 1; + } + + level_tools::DrawStats draw_stats; + // draw_stats.debug_print_dma_data = true; + level_tools::BspHeader bsp_header; + bsp_header.read_from_file(data, dts, &draw_stats); + + level_tools::PrintSettings settings; + fmt::print("{}\n", bsp_header.print(settings)); + fmt::print("Stats:\n{}\n", draw_stats.print()); + + } catch (const std::exception& e) { + fmt::print("Error: {}\n", e.what()); + } + + return 0; +} \ No newline at end of file