water111 2023-10-14 13:49:23 -07:00 committed by GitHub
parent ae45037489
commit ec23e6c5d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 367 additions and 24 deletions

View file

@ -50,4 +50,78 @@ math::Vector4f bsphere_of_triangle(const Vector3f* vertices);
inline bool point_in_bsphere(const Vector4f& sphere, const Vector3f& pt) {
return (sphere.xyz() - pt).squared_length() <= (sphere.w() * sphere.w());
}
template <typename T>
math::Matrix<T, 4, 4> affine_inverse(const math::Matrix<T, 4, 4>& in) {
math::Matrix<T, 4, 4> result;
// transpose rotation
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
result(i, j) = in(j, i);
}
}
result(3, 0) = 0;
result(3, 1) = 0;
result(3, 2) = 0;
result(3, 3) = 1;
result(0, 3) = 0;
result(1, 3) = 0;
result(2, 3) = 0;
for (int rx = 0; rx < 3; rx++) {
for (int cx = 0; cx < 3; cx++) {
result(rx, 3) -= result(rx, cx) * in(cx, 3);
}
}
return result;
}
template <typename T>
math::Matrix<T, 4, 4> inverse(const math::Matrix<T, 4, 4>& m) {
math::Matrix<T, 4, 4> im;
T A2323 = m(2, 2) * m(3, 3) - m(2, 3) * m(3, 2);
T A1323 = m(2, 1) * m(3, 3) - m(2, 3) * m(3, 1);
T A1223 = m(2, 1) * m(3, 2) - m(2, 2) * m(3, 1);
T A0323 = m(2, 0) * m(3, 3) - m(2, 3) * m(3, 0);
T A0223 = m(2, 0) * m(3, 2) - m(2, 2) * m(3, 0);
T A0123 = m(2, 0) * m(3, 1) - m(2, 1) * m(3, 0);
T A2313 = m(1, 2) * m(3, 3) - m(1, 3) * m(3, 2);
T A1313 = m(1, 1) * m(3, 3) - m(1, 3) * m(3, 1);
T A1213 = m(1, 1) * m(3, 2) - m(1, 2) * m(3, 1);
T A2312 = m(1, 2) * m(2, 3) - m(1, 3) * m(2, 2);
T A1312 = m(1, 1) * m(2, 3) - m(1, 3) * m(2, 1);
T A1212 = m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1);
T A0313 = m(1, 0) * m(3, 3) - m(1, 3) * m(3, 0);
T A0213 = m(1, 0) * m(3, 2) - m(1, 2) * m(3, 0);
T A0312 = m(1, 0) * m(2, 3) - m(1, 3) * m(2, 0);
T A0212 = m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0);
T A0113 = m(1, 0) * m(3, 1) - m(1, 1) * m(3, 0);
T A0112 = m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0);
T det = m(0, 0) * (m(1, 1) * A2323 - m(1, 2) * A1323 + m(1, 3) * A1223) -
m(0, 1) * (m(1, 0) * A2323 - m(1, 2) * A0323 + m(1, 3) * A0223) +
m(0, 2) * (m(1, 0) * A1323 - m(1, 1) * A0323 + m(1, 3) * A0123) -
m(0, 3) * (m(1, 0) * A1223 - m(1, 1) * A0223 + m(1, 2) * A0123);
det = 1 / det;
im(0, 0) = det * (m(1, 1) * A2323 - m(1, 2) * A1323 + m(1, 3) * A1223);
im(0, 1) = det * -(m(0, 1) * A2323 - m(0, 2) * A1323 + m(0, 3) * A1223);
im(0, 2) = det * (m(0, 1) * A2313 - m(0, 2) * A1313 + m(0, 3) * A1213);
im(0, 3) = det * -(m(0, 1) * A2312 - m(0, 2) * A1312 + m(0, 3) * A1212);
im(1, 0) = det * -(m(1, 0) * A2323 - m(1, 2) * A0323 + m(1, 3) * A0223);
im(1, 1) = det * (m(0, 0) * A2323 - m(0, 2) * A0323 + m(0, 3) * A0223);
im(1, 2) = det * -(m(0, 0) * A2313 - m(0, 2) * A0313 + m(0, 3) * A0213);
im(1, 3) = det * (m(0, 0) * A2312 - m(0, 2) * A0312 + m(0, 3) * A0212);
im(2, 0) = det * (m(1, 0) * A1323 - m(1, 1) * A0323 + m(1, 3) * A0123);
im(2, 1) = det * -(m(0, 0) * A1323 - m(0, 1) * A0323 + m(0, 3) * A0123);
im(2, 2) = det * (m(0, 0) * A1313 - m(0, 1) * A0313 + m(0, 3) * A0113);
im(2, 3) = det * -(m(0, 0) * A1312 - m(0, 1) * A0312 + m(0, 3) * A0112);
im(3, 0) = det * -(m(1, 0) * A1223 - m(1, 1) * A0223 + m(1, 2) * A0123);
im(3, 1) = det * (m(0, 0) * A1223 - m(0, 1) * A0223 + m(0, 2) * A0123);
im(3, 2) = det * -(m(0, 0) * A1213 - m(0, 1) * A0213 + m(0, 2) * A0113);
im(3, 3) = det * (m(0, 0) * A1212 - m(0, 1) * A0212 + m(0, 2) * A0112);
return im;
}
} // namespace math

View file

@ -58,6 +58,7 @@ add_library(
level_extractor/extract_actors.cpp
level_extractor/extract_collide_frags.cpp
level_extractor/extract_common.cpp
level_extractor/extract_joint_group.cpp
level_extractor/extract_level.cpp
level_extractor/extract_merc.cpp
level_extractor/extract_tfrag.cpp

View file

@ -1,5 +1,6 @@
#pragma once
#include "common/common_types.h"
#include "common/math/Vector.h"
namespace level_tools {
@ -8,4 +9,21 @@ struct TextureRemap {
u32 original_texid;
u32 new_texid;
};
struct Joint {
std::string name;
int parent_idx = -1; // -1 for magic ROOT joint.
math::Matrix4f bind_pose_T_w;
};
/*!
* Data extracted from art groups that is not needed for .FR3, but is potentially needed for other
* stuff (skeleton export).
*/
struct ArtData {
std::string art_group_name;
std::string art_name;
std::vector<Joint> joint_group;
};
} // namespace level_tools

View file

@ -0,0 +1,46 @@
#include "extract_joint_group.h"
#include "common/math/geometry.h"
#include "decompiler/util/goal_data_reader.h"
namespace decompiler {
void extract_joint_group(const ObjectFileData& ag_data,
const DecompilerTypeSystem& dts,
GameVersion /*version*/,
std::map<std::string, level_tools::ArtData>& out) {
auto locations = find_objects_with_type(ag_data.linked_data, "art-joint-geo");
for (auto loc : locations) {
TypedRef ref(Ref{&ag_data.linked_data, 0, loc * 4}, dts.ts.lookup_type("art-joint-geo"));
auto name = read_string_field(ref, "name", dts, false);
auto& data = out[name];
data.art_name = name;
data.art_group_name = ag_data.name_in_dgo;
ASSERT(data.joint_group.empty());
const int length = read_plain_data_field<int32_t>(ref, "length", dts);
Ref iter = get_field_ref(ref, "data", dts);
std::map<int, int> offset_to_joint;
for (int i = 0; i < length; i++) {
auto& njoint = data.joint_group.emplace_back();
Ref joint = deref_label(iter);
joint.byte_offset -= 4;
bool inserted = offset_to_joint.insert({joint.byte_offset, i}).second;
ASSERT(inserted);
TypedRef tjoint = typed_ref_from_basic(joint, dts);
ASSERT(tjoint.type->get_name() == "joint");
ASSERT(read_plain_data_field<int32_t>(tjoint, "number", dts) == i);
njoint.name = read_string_field(tjoint, "name", dts, true);
memcpy_from_plain_data((u8*)njoint.bind_pose_T_w.data(),
get_field_ref(tjoint, "bind-pose", dts), 4 * 4 * sizeof(float));
if (get_word_kind_for_field(tjoint, "parent", dts) == LinkedWord::PTR) {
auto pjoint = deref_label(get_field_ref(tjoint, "parent", dts));
njoint.parent_idx = offset_to_joint.at(pjoint.byte_offset - 4);
} else {
ASSERT(i == 0 || i == 1);
}
iter.byte_offset += 4;
}
}
}
} // namespace decompiler

View file

@ -0,0 +1,14 @@
#pragma once
#include "common/custom_data/Tfrag3Data.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "decompiler/data/TextureDB.h"
#include "decompiler/level_extractor/common_formats.h"
namespace decompiler {
void extract_joint_group(const ObjectFileData& ag_data,
const DecompilerTypeSystem& dts,
GameVersion version,
std::map<std::string, level_tools::ArtData>& out);
}

View file

@ -12,6 +12,7 @@
#include "decompiler/level_extractor/BspHeader.h"
#include "decompiler/level_extractor/extract_actors.h"
#include "decompiler/level_extractor/extract_collide_frags.h"
#include "decompiler/level_extractor/extract_joint_group.h"
#include "decompiler/level_extractor/extract_merc.h"
#include "decompiler/level_extractor/extract_shrub.h"
#include "decompiler/level_extractor/extract_tfrag.h"
@ -122,12 +123,14 @@ void extract_art_groups_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::vector<level_tools::TextureRemap>& tex_remap,
const std::string& dgo_name,
tfrag3::Level& level_data) {
tfrag3::Level& level_data,
std::map<std::string, level_tools::ArtData>& art_group_data) {
const auto& files = db.obj_files_by_dgo.at(dgo_name);
for (const auto& file : files) {
if (file.name.length() > 3 && !file.name.compare(file.name.length() - 3, 3, "-ag")) {
const auto& ag_file = db.lookup_record(file);
extract_merc(ag_file, tex_db, db.dts, tex_remap, level_data, false, db.version());
extract_joint_group(ag_file, db.dts, db.version(), art_group_data);
}
}
}
@ -273,8 +276,9 @@ void extract_common(const ObjectFileDB& db,
confirm_textures_identical(tex_db);
tfrag3::Level tfrag_level;
std::map<std::string, level_tools::ArtData> art_group_data;
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level);
extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level, art_group_data);
std::set<std::string> textures_we_have;
@ -322,7 +326,7 @@ void extract_common(const ObjectFileDB& db,
if (dump_levels) {
auto file_path = file_util::get_jak_project_dir() / "glb_out" / "common.glb";
file_util::create_dir_if_needed_for_file(file_path);
save_level_foreground_as_gltf(tfrag_level, file_path);
save_level_foreground_as_gltf(tfrag_level, art_group_data, file_path);
}
}
@ -339,12 +343,14 @@ void extract_from_level(const ObjectFileDB& db,
return;
}
tfrag3::Level level_data;
std::map<std::string, level_tools::ArtData> art_group_data;
add_all_textures_from_level(level_data, dgo_name, tex_db);
// the bsp header file data
auto bsp_header =
extract_bsp_from_level(db, tex_db, dgo_name, config.hacks, extract_collision, level_data);
extract_art_groups_from_level(db, tex_db, bsp_header.texture_remap_table, dgo_name, level_data);
extract_art_groups_from_level(db, tex_db, bsp_header.texture_remap_table, dgo_name, level_data,
art_group_data);
Serializer ser;
level_data.serialize(ser);
@ -366,7 +372,7 @@ void extract_from_level(const ObjectFileDB& db,
auto fore_file_path = file_util::get_jak_project_dir() / "glb_out" /
fmt::format("{}_foreground.glb", level_data.level_name);
file_util::create_dir_if_needed_for_file(fore_file_path);
save_level_foreground_as_gltf(level_data, fore_file_path);
save_level_foreground_as_gltf(level_data, art_group_data, fore_file_path);
}
file_util::write_text_file(entities_folder / fmt::format("{}_actors.json", level_data.level_name),
extract_actors_to_json(bsp_header.actors));

View file

@ -181,20 +181,6 @@ MercCtrl extract_merc_ctrl(const LinkedObjectFile& file,
return ctrl;
}
/*!
* Find the word indices for the merc ctrls (the type tags)
*/
std::vector<int> find_merc_ctrls(const LinkedObjectFile& file) {
std::vector<int> result;
for (size_t i = 0; i < file.words_by_seg.at(0).size(); i++) {
const auto& word = file.words_by_seg[0][i];
if (word.kind() == LinkedWord::TYPE_PTR && word.symbol_name() == "merc-ctrl") {
result.push_back(i);
}
}
return result;
}
namespace {
/*!
* Merc models tend to have strange texture ids. I don't really understand why.
@ -1616,7 +1602,7 @@ void extract_merc(const ObjectFileData& ag_data,
file_util::create_dir_if_needed(file_util::get_file_path({"debug_out/merc"}));
}
// find all merc-ctrls in the object file
auto ctrl_locations = find_merc_ctrls(ag_data.linked_data);
auto ctrl_locations = find_objects_with_type(ag_data.linked_data, "merc-ctrl");
// extract them. this does very basic unpacking of data, as done by the VIF/DMA on PS2.
std::vector<MercCtrl> ctrls;

View file

@ -4,6 +4,7 @@
#include "common/custom_data/Tfrag3Data.h"
#include "common/math/Vector.h"
#include "common/math/geometry.h"
#include "decompiler/level_extractor/tfrag_tie_fixup.h"
@ -11,6 +12,17 @@
namespace {
/*!
* Remove 4096 meter scaling from a transformation matrix.
*/
math::Matrix4f unscale_translation(const math::Matrix4f& in) {
auto out = in;
for (int i = 0; i < 3; i++) {
out(i, 3) /= 4096.;
}
return out;
}
/*!
* Convert fr3 format indices (strip format, with UINT32_MAX as restart) to unstripped tris.
* Assumes that this is the tfrag/tie format of stripping. Will flip tris as needed so the faces
@ -557,6 +569,10 @@ int add_material_for_tex(const tfrag3::Level& level,
int tex_idx,
std::unordered_map<int, int>& tex_image_map,
const DrawMode& draw_mode) {
if (tex_idx < 0) {
// anim textures, just use default material
return 0;
}
int mat_idx = (int)model.materials.size();
auto& mat = model.materials.emplace_back();
auto& tex = level.textures.at(tex_idx);
@ -759,7 +775,114 @@ void add_shrub(const tfrag3::Level& level,
}
}
int make_weights_accessor(const std::vector<tfrag3::MercVertex>& vertices, tinygltf::Model& model) {
// first create a buffer:
int buffer_idx = (int)model.buffers.size();
auto& buffer = model.buffers.emplace_back();
buffer.data.resize(sizeof(float) * 4 * vertices.size());
// and fill it
u8* buffer_ptr = buffer.data.data();
for (const auto& vtx : vertices) {
float weights[4] = {vtx.weights[0], vtx.weights[1], vtx.weights[2], 0};
memcpy(buffer_ptr, weights, 4 * sizeof(float));
buffer_ptr += 4 * sizeof(float);
}
// create a view of this buffer
int buffer_view_idx = (int)model.bufferViews.size();
auto& buffer_view = model.bufferViews.emplace_back();
buffer_view.buffer = buffer_idx;
buffer_view.byteOffset = 0;
buffer_view.byteLength = buffer.data.size();
buffer_view.byteStride = 0; // tightly packed
buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER;
int accessor_idx = (int)model.accessors.size();
auto& accessor = model.accessors.emplace_back();
accessor.bufferView = buffer_view_idx;
accessor.byteOffset = 0;
accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
accessor.count = vertices.size();
accessor.type = TINYGLTF_TYPE_VEC4;
return accessor_idx;
}
int make_bones_accessor(const std::vector<tfrag3::MercVertex>& vertices, tinygltf::Model& model) {
// first create a buffer:
int buffer_idx = (int)model.buffers.size();
auto& buffer = model.buffers.emplace_back();
buffer.data.resize(sizeof(float) * 4 * vertices.size());
// and fill it
u8* buffer_ptr = buffer.data.data();
for (const auto& vtx : vertices) {
s32 indices[4];
for (int i = 0; i < 3; i++) {
indices[i] = vtx.mats[i] ? vtx.mats[i] - 1 : 0;
}
indices[3] = 0;
memcpy(buffer_ptr, indices, 4 * sizeof(s32));
buffer_ptr += 4 * sizeof(s32);
}
// create a view of this buffer
int buffer_view_idx = (int)model.bufferViews.size();
auto& buffer_view = model.bufferViews.emplace_back();
buffer_view.buffer = buffer_idx;
buffer_view.byteOffset = 0;
buffer_view.byteLength = buffer.data.size();
buffer_view.byteStride = 0; // tightly packed
buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER;
int accessor_idx = (int)model.accessors.size();
auto& accessor = model.accessors.emplace_back();
accessor.bufferView = buffer_view_idx;
accessor.byteOffset = 0;
accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; // blender doesn't support INT...
accessor.count = vertices.size();
accessor.type = TINYGLTF_TYPE_VEC4;
return accessor_idx;
}
int make_inv_matrix_bind_poses(const std::vector<level_tools::Joint>& joints,
tinygltf::Model& model) {
// first create a buffer:
int buffer_idx = (int)model.buffers.size();
auto& buffer = model.buffers.emplace_back();
buffer.data.resize(sizeof(float) * 16 * joints.size());
// and fill it
for (int m = 0; m < (int)joints.size(); m++) {
auto matrix = unscale_translation(joints[m].bind_pose_T_w);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
memcpy(buffer.data.data() + sizeof(float) * (i * 4 + j + m * 16), &matrix(j, i), 4);
}
}
}
// create a view of this buffer
int buffer_view_idx = (int)model.bufferViews.size();
auto& buffer_view = model.bufferViews.emplace_back();
buffer_view.buffer = buffer_idx;
buffer_view.byteOffset = 0;
buffer_view.byteLength = buffer.data.size();
buffer_view.byteStride = 0; // tightly packed
buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER;
int accessor_idx = (int)model.accessors.size();
auto& accessor = model.accessors.emplace_back();
accessor.bufferView = buffer_view_idx;
accessor.byteOffset = 0;
accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
accessor.count = joints.size();
accessor.type = TINYGLTF_TYPE_MAT4;
return accessor_idx;
}
void add_merc(const tfrag3::Level& level,
const std::map<std::string, level_tools::ArtData>& art_data,
tinygltf::Model& model,
std::unordered_map<int, int>& tex_image_map) {
const auto& mverts = level.merc_data.vertices;
@ -773,9 +896,12 @@ void add_merc(const tfrag3::Level& level,
level.merc_data.indices, level.merc_data.models, model, draw_to_start, draw_to_count);
int colors = make_color_buffer_accessor(mverts, model);
auto joints_accessor = make_bones_accessor(mverts, model);
auto weights_accessor = make_weights_accessor(mverts, model);
for (size_t model_idx = 0; model_idx < level.merc_data.models.size(); model_idx++) {
const auto& mmodel = level.merc_data.models[model_idx];
const auto& art = art_data.find(mmodel.name);
int node_idx = (int)model.nodes.size();
auto& node = model.nodes.emplace_back();
model.scenes.at(0).nodes.push_back(node_idx);
@ -785,6 +911,54 @@ void add_merc(const tfrag3::Level& level,
mesh.name = node.name;
node.mesh = mesh_idx;
if (art != art_data.end() && !art->second.joint_group.empty()) {
node.skin = model.skins.size();
auto& skin = model.skins.emplace_back();
const auto& game_bones = art->second.joint_group;
int n_bones = game_bones.size();
std::vector<std::vector<int>> children(n_bones);
for (size_t i = 0; i < game_bones.size(); i++) {
if (game_bones[i].parent_idx >= 0) {
children.at(game_bones[i].parent_idx).push_back(i);
}
}
skin.skeleton = model.nodes.size();
for (int i = 0; i < n_bones; i++) {
const auto& gbone = game_bones[i];
skin.joints.push_back(skin.skeleton + i);
auto& snode = model.nodes.emplace_back();
snode.name = gbone.name;
// bind pose is bind_T_w
// for glb we want bind_parent_T_bind_child
// so bindp_T_w * inverse(bindc_T_w)
math::Matrix4f matrix;
if (gbone.parent_idx >= 0) {
matrix = unscale_translation(game_bones.at(gbone.parent_idx).bind_pose_T_w) *
inverse(unscale_translation(gbone.bind_pose_T_w));
} else {
// I think this value is ignored anyway.
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
matrix(r, c) = (r == c) ? 1 : 0;
}
}
}
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
snode.matrix.push_back(matrix(c, r));
}
}
for (auto child : children.at(i)) {
snode.children.push_back(skin.skeleton + child);
}
}
ASSERT(skin.skeleton + n_bones == (int)model.nodes.size());
skin.inverseBindMatrices = make_inv_matrix_bind_poses(game_bones, model);
}
for (size_t effect_idx = 0; effect_idx < mmodel.effects.size(); effect_idx++) {
const auto& effect = mmodel.effects[effect_idx];
for (size_t draw_idx = 0; draw_idx < effect.all_draws.size(); draw_idx++) {
@ -798,6 +972,8 @@ void add_merc(const tfrag3::Level& level,
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
prim.attributes["COLOR_0"] = colors;
prim.attributes["JOINTS_0"] = joints_accessor;
prim.attributes["WEIGHTS_0"] = weights_accessor;
prim.mode = TINYGLTF_MODE_TRIANGLES;
}
}
@ -847,7 +1023,9 @@ void save_level_background_as_gltf(const tfrag3::Level& level, const fs::path& g
true); // write binary
}
void save_level_foreground_as_gltf(const tfrag3::Level& level, const fs::path& glb_file) {
void save_level_foreground_as_gltf(const tfrag3::Level& level,
const std::map<std::string, level_tools::ArtData>& art_data,
const fs::path& glb_file) {
// the top level container for everything is the model.
tinygltf::Model model;
@ -864,7 +1042,7 @@ void save_level_foreground_as_gltf(const tfrag3::Level& level, const fs::path& g
std::unordered_map<int, int> tex_image_map;
add_merc(level, model, tex_image_map);
add_merc(level, art_data, model, tex_image_map);
model.asset.generator = "opengoal";
tinygltf::TinyGLTF gltf;

View file

@ -1,10 +1,16 @@
#pragma once
#include <map>
#include "common/custom_data/Tfrag3Data.h"
#include "common/util/FileUtil.h"
#include "decompiler/level_extractor/common_formats.h"
/*!
* Export the background geometry (tie, tfrag, shrub) to a GLTF binary format (.glb) file.
*/
void save_level_background_as_gltf(const tfrag3::Level& level, const fs::path& glb_file);
void save_level_foreground_as_gltf(const tfrag3::Level& level, const fs::path& glb_file);
void save_level_foreground_as_gltf(const tfrag3::Level& level,
const std::map<std::string, level_tools::ArtData>& art_data,
const fs::path& glb_file);

View file

@ -421,3 +421,15 @@ u8 deref_u8(const Ref& ref, int byte) {
memcpy(vals, &word.data, 4);
return vals[total_offset & 3];
}
std::vector<int> find_objects_with_type(const decompiler::LinkedObjectFile& file,
const std::string& name) {
std::vector<int> result;
for (size_t i = 0; i < file.words_by_seg.at(0).size(); i++) {
const auto& word = file.words_by_seg[0][i];
if (word.kind() == decompiler::LinkedWord::TYPE_PTR && word.symbol_name() == name) {
result.push_back(i);
}
}
return result;
}

View file

@ -91,3 +91,5 @@ u8 deref_u8(const Ref& ref, int byte);
float deref_float(const Ref& ref, int array_idx);
u64 deref_u64(const Ref& ref, int dw_offset);
std::string inspect_ref(const Ref& ref);
std::vector<int> find_objects_with_type(const decompiler::LinkedObjectFile& file,
const std::string& name);