jak-project/decompiler/level_extractor/BspHeader.cpp

2114 lines
80 KiB
C++

#include "BspHeader.h"
#include "common/dma/dma.h"
#include "common/goos/PrettyPrinter.h"
#include "common/log/log.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/util/Error.h"
#include "decompiler/util/goal_data_reader.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);
}
}
void Matrix4h::read_from_file(Ref ref) {
if ((ref.byte_offset % 16) != 0) {
throw Error("misaligned Matrix4h");
}
for (int i = 0; i < 8; 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("Matrix4h didn't get plain data.");
}
memcpy(&data[i * 2], &word.data, 4);
}
}
std::string Vector::print(int indent) const {
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}<vector {} {} {} {}>\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("{}<vector-meters {:.4f} {:.4f} {:.4f} {:.4f}>\n", is, data[0] / 4096.f,
data[1] / 4096.f, data[2] / 4096.f, data[3] / 4096.f);
return result;
}
std::string Vector::print_decimal(int indent) const {
s32 d[4];
memcpy(d, data, 16);
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}<vector {:d} {:d} {:d} {:d}>\n", is, d[0], d[1], d[2], d[3]);
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<u32>(ref, "major-version", dts);
minor_version = read_plain_data_field<u32>(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*/,
GameVersion /*version*/) {
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*/,
GameVersion /*version*/) {
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<Drawable> make_draw_node_child(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
if (ref.type->get_name() == "draw-node") {
auto result = std::make_unique<DrawNode>();
result->read_from_file(ref, dts, stats, version);
return result;
} else if (ref.type->get_name() == "tfragment") {
auto result = std::make_unique<TFragment>();
result->read_from_file(ref, dts, stats, version);
return result;
} else if (ref.type->get_name() == "instance-tie") {
auto result = std::make_unique<InstanceTie>();
result->read_from_file(ref, dts, stats, version);
return result;
} else if (ref.type->get_name() == "drawable-actor") {
auto result = std::make_unique<DrawableActor>();
result->read_from_file(ref, dts, stats, version);
return result;
} else if (ref.type->get_name() == "instance-shrubbery") {
auto result = std::make_unique<shrub_types::InstanceShrubbery>();
result->read_from_file(ref, dts, stats, version);
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 if (type == "instance-shrubbery") {
return 80;
} 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;
}
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));
lg::debug("{} at: {} bytes, {} qw", 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);
lg::debug(" [{:3d} {:3d} {:3d} {:3d}]", 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++);
lg::debug(" [{:3d} {:3d} {:3d} {:3d}]", 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++);
lg::debug(" [{:3d} {:3d} {:3d}]", 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++);
lg::debug(" row data [{:3d} {:3d} {:3d} {:3d}]", 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);
lg::debug(" [{:3d} {:3d} {:3d} {:3d}]", words[0], words[1], words[2], words[3]);
}
} break;
case VifCode::Kind::NOP:
break;
default:
ASSERT_MSG(false, fmt::format("unknown: {}", next.print()));
}
}
lg::debug("-------------------------------------------");
}
std::vector<u8> read_dma_chain(Ref& start, u32 qwc) {
std::vector<u8> result;
result.resize(qwc * 16);
for (u32 word = 0; word < qwc * 4; word++) {
u32 x = deref_u32(start, word);
memcpy(result.data() + (word * 4), &x, 4);
}
return result;
}
void TFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
stats->total_tfragments++;
id = read_plain_data_field<s16>(ref, "id", dts);
color_index = read_plain_data_field<s16>(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) {
// first, common
lg::info("DMA COMMON {}, {} qwc:", dmas[0].label_name, dma_qwc[0]);
tfrag_debug_print_unpack(dmas[0].ref, dma_qwc[0]);
// next "base"
lg::info("DMA BASE {}, {} qwc:", dmas[1].label_name, dma_qwc[1]);
tfrag_debug_print_unpack(dmas[1].ref, dma_qwc[1]);
// next "level0"
// lg::print("DMA LEVEL0 {}, {} qwc:\n", dmas[0].label_name, dma_qwc[3]);
// tfrag_debug_print_unpack(dmas[0].ref, dma_qwc[3]);
// next "level1"
lg::info("DMA LEVEL1 {}, {} qwc:", dmas[2].label_name, dma_qwc[2]);
tfrag_debug_print_unpack(dmas[2].ref, dma_qwc[2]);
lg::info("qwc's: {} {} {} {}", dma_qwc[0], dma_qwc[1], dma_qwc[2], dma_qwc[3]);
}
num_base_colors = read_plain_data_field<u8>(ref, "num-base-colors", dts);
num_level0_colors = read_plain_data_field<u8>(ref, "num-level0-colors", dts);
num_level1_colors = read_plain_data_field<u8>(ref, "num-level1-colors", dts);
num_shaders = read_plain_data_field<u8>(ref, "num-shaders", dts);
color_offset = read_plain_data_field<u8>(ref, "color-offset", dts);
color_count = read_plain_data_field<u8>(ref, "color-count", dts);
dma_base = read_dma_chain(dmas[1].ref, dma_qwc[1]);
if (num_level0_colors > 0 || num_level1_colors > 0) {
// if we're base only, this has junk in it, and it wouldn't be used anyway.
dma_common_and_level0 = read_dma_chain(dmas[0].ref, std::max(dma_qwc[3], dma_qwc[0]));
}
dma_level1 = read_dma_chain(dmas[2].ref, dma_qwc[2]);
// color indices
int num_actual_colors = std::max(num_base_colors, std::max(num_level0_colors, num_level1_colors));
int num_colors = ((num_actual_colors + 3) / 4) * 4;
// each color is a u64 (4x u16 indices)
// color_indices.resize(4 * num_colors);
// 24 = 12 * u32
auto color_idx_start = deref_label(get_field_ref(ref, "color-indices", dts));
for (int i = 0; i < num_colors / 4; i++) {
u32 low = deref_u32(color_idx_start, i * 2);
u32 high = deref_u32(color_idx_start, i * 2 + 1);
color_indices.push_back(low & 0xffff);
color_indices.push_back(low >> 16);
color_indices.push_back(high & 0xffff);
color_indices.push_back(high >> 16);
}
ASSERT((int)color_indices.size() == num_colors);
// todo shader
ASSERT(num_colors / 4 == color_count);
// lg::print("colors: {} {} {}\n", num_base_colors, num_level0_colors, num_level1_colors);
if (version == GameVersion::Jak1) {
ASSERT(read_plain_data_field<u8>(ref, "pad0", dts) == 0);
ASSERT(read_plain_data_field<u8>(ref, "pad1", dts) == 0);
ASSERT(read_plain_data_field<u32>(ref, "generic-u32", dts) == 0);
} else {
ASSERT(read_plain_data_field<u32>(ref, "generic-u32", dts) == 0);
}
}
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 memcpy_plain_data(u8* dst, const Ref& ref, size_t size_bytes) {
const auto& words = ref.data->words_by_seg.at(ref.seg);
for (size_t i = 0; i < size_bytes; i++) {
size_t byte_offset = ref.byte_offset + i;
u8 byte = words.at(byte_offset / 4).get_byte(byte_offset % 4);
memcpy(dst + i, &byte, sizeof(u8));
}
}
void TieFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
switch (version) {
case GameVersion::Jak1:
num_tris = read_plain_data_field<u16>(ref, "num-tris", dts);
num_dverts = read_plain_data_field<u16>(ref, "num-dverts", dts);
break;
case GameVersion::Jak2: {
auto debug_data_ref = TypedRef(deref_label(get_field_ref(ref, "debug", dts)),
dts.ts.lookup_type("tie-fragment-debug"));
num_tris = read_plain_data_field<u16>(debug_data_ref, "num-tris", dts);
num_dverts = read_plain_data_field<u16>(debug_data_ref, "num-dverts", dts);
} break;
default:
ASSERT(false);
}
tex_count = read_plain_data_field<u16>(ref, "tex-count", dts);
gif_count = read_plain_data_field<u16>(ref, "gif-count", dts);
vertex_count = read_plain_data_field<u16>(ref, "vertex-count", dts);
auto gif_data_ref = deref_label(get_field_ref(ref, "gif-ref", dts));
ASSERT((tex_count % 5) == 0);
u32 total_gif_qw = tex_count + gif_count;
gif_data.resize(16 * total_gif_qw);
for (u32 i = 0; i < total_gif_qw * 4; i++) {
auto& word =
gif_data_ref.data->words_by_seg.at(gif_data_ref.seg).at((gif_data_ref.byte_offset / 4) + i);
ASSERT(word.kind() == decompiler::LinkedWord::PLAIN_DATA);
memcpy(gif_data.data() + (i * 4), &word.data, 4);
}
auto points_data_ref = deref_label(get_field_ref(ref, "point-ref", dts));
point_ref.resize(16 * vertex_count);
debug_label_name = inspect_ref(get_field_ref(ref, "point-ref", dts));
for (u32 i = 0; i < vertex_count * 4; i++) {
auto& word = points_data_ref.data->words_by_seg.at(points_data_ref.seg)
.at((points_data_ref.byte_offset / 4) + i);
ASSERT(word.kind() == decompiler::LinkedWord::PLAIN_DATA);
memcpy(point_ref.data() + (i * 4), &word.data, 4);
}
stats->total_tie_prototype_tris += num_tris;
if (version > GameVersion::Jak1) {
u16 normals_qwc = read_plain_data_field<u16>(ref, "normal-count", dts);
if (normals_qwc) {
normals.resize(16 * normals_qwc);
auto normals_data_ref = deref_label(get_field_ref(ref, "normal-ref", dts));
memcpy_plain_data((u8*)normals.data(), normals_data_ref, normals_qwc * 16);
}
} else {
u16 generic_qwc = read_plain_data_field<u32>(ref, "generic-count", dts);
if (generic_qwc) {
generic_data.resize(16 * generic_qwc);
auto generic_data_ref = deref_label(get_field_ref(ref, "generic-ref", dts));
memcpy_plain_data((u8*)generic_data.data(), generic_data_ref, generic_qwc * 16);
}
}
}
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;
}
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,
GameVersion /*version*/) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
bucket_index = read_plain_data_field<u16>(ref, "bucket-index", dts);
id = read_plain_data_field<s16>(ref, "id", dts);
flags = read_plain_data_field<u16>(ref, "flags", dts);
// ASSERT(flags == 0); // TODO
origin.read_from_file(get_field_ref(ref, "origin", dts));
wind_index = read_plain_data_field<u16>(ref, "wind-index", dts);
color_indices = deref_label(get_field_ref(ref, "color-indices", 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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts); // 4
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts)); // 16
child_count = read_plain_data_field<u8>(ref, "child-count", dts);
flags = read_plain_data_field<u8>(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, version));
first_child_obj.byte_offset += get_child_stride(get_type_of_basic(first_child_obj));
}
distance = read_plain_data_field<float>(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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version);
}
}
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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version);
}
}
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 DrawableInlineArrayInstanceTie::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version);
}
}
std::string DrawableInlineArrayInstanceTie::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 DrawableInlineArrayInstanceTie::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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version);
}
}
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<DrawableInlineArray> make_drawable_inline_array(
TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
if (ref.type->get_name() == "drawable-inline-array-node") {
auto result = std::make_unique<DrawableInlineArrayNode>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-tfrag") {
auto result = std::make_unique<DrawableInlineArrayTFrag>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-trans-tfrag") {
auto result = std::make_unique<DrawableInlineArrayTransTFrag>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-tfrag-trans") {
auto result = std::make_unique<DrawableInlineArrayTFragTrans>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-tfrag-water") {
auto result = std::make_unique<DrawableInlineArrayTFragWater>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-instance-tie") {
auto result = std::make_unique<DrawableInlineArrayInstanceTie>();
result->read_from_file(ref, dts, stats, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-instance-shrub") {
auto result = std::make_unique<shrub_types::DrawableInlineArrayInstanceShrub>();
result->read_from_file(ref, dts, stats, version);
return result;
}
auto result = std::make_unique<DrawableInlineArrayUnknown>();
result->read_from_file(ref, dts, stats, version);
return result;
}
void DrawableTreeTfrag::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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");
}
auto palette = deref_label(get_field_ref(ref, "time-of-day-pal", dts));
time_of_day.width = deref_u32(palette, 0);
ASSERT(time_of_day.width == 8);
time_of_day.height = deref_u32(palette, 1);
time_of_day.pad = deref_u32(palette, 2);
ASSERT(time_of_day.pad == 0);
for (int i = 0; i < int(8 * time_of_day.height); i++) {
time_of_day.colors.push_back(deref_u32(palette, 3 + i));
}
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, version));
}
}
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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version));
}
}
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,
GameVersion version) {
name = read_string_field(ref, "name", dts, true);
switch (version) {
case GameVersion::Jak1:
flags = read_plain_data_field<u32>(ref, "flags", dts);
ASSERT(flags == 0 || flags == 2);
break;
case GameVersion::Jak2:
flags = read_plain_data_field<u16>(ref, "flags", dts);
break;
default:
ASSERT(false);
}
in_level = read_plain_data_field<u16>(ref, "in-level", dts);
utextures = read_plain_data_field<u16>(ref, "utextures", dts);
dists.read_from_file(get_field_ref(ref, "dists", dts));
rdists.read_from_file(get_field_ref(ref, "rdists", dts));
stiffness = read_plain_data_field<float>(ref, "stiffness", dts);
if (version == GameVersion::Jak1) {
auto fr = get_field_ref(ref, "collide-frag", dts);
{
const auto& word = fr.data->words_by_seg.at(fr.seg).at(fr.byte_offset / 4);
if (word.kind() == decompiler::LinkedWord::PTR) {
auto p = deref_label(fr);
p.byte_offset -= 4;
collide_frag.read_from_file(typed_ref_from_basic(p, dts), dts, stats, version);
}
}
} else {
if (get_word_kind_for_field(ref, "collide-hash-fragment-array", dts) ==
decompiler::LinkedWord::PTR) {
auto fr = deref_label(get_field_ref(ref, "collide-hash-fragment-array", dts));
fr.byte_offset -= 4;
auto collide_array = typed_ref_from_basic(fr, dts);
int length = read_plain_data_field<int32_t>(collide_array, "length", dts);
auto r = get_field_ref(collide_array, "fragments", dts);
for (int i = 0; i < length; i++) {
collide_hash_frags.push_back(deref_label(r));
r.byte_offset += 4;
}
}
}
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, "frag-count", dts);
u8* block_start = (u8*)frag_count;
for (int i = 0; i < 6; 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, version);
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);
// }
// get the color count data
{
u32 num_color_qwcs = 0;
for (int i = 0; i < 4; i++) {
u32 start = index_start[i];
u32 end = start + frag_count[i];
ASSERT(num_color_qwcs <= end);
num_color_qwcs = std::max(end, num_color_qwcs);
}
auto data_array = get_field_ref(ref, "color-index-qwc", dts);
for (u32 i = 0; i < num_color_qwcs; i++) {
int byte_offset = data_array.byte_offset + i;
auto word = data_array.data->words_by_seg.at(data_array.seg).at(byte_offset / 4);
color_index_qwc.push_back(word.get_byte(byte_offset % 4));
}
}
// get the colors
auto palette = deref_label(get_field_ref(ref, "tie-colors", dts));
time_of_day.width = deref_u32(palette, 0);
ASSERT(time_of_day.width == 8);
time_of_day.height = deref_u32(palette, 1);
time_of_day.pad = deref_u32(palette, 2);
ASSERT(time_of_day.pad == 0);
for (int i = 0; i < int(8 * time_of_day.height); i++) {
time_of_day.colors.push_back(deref_u32(palette, 3 + i));
}
auto fr = get_field_ref(ref, "envmap-shader", dts);
const auto& word = fr.data->words_by_seg.at(fr.seg).at(fr.byte_offset / 4);
if (word.kind() == decompiler::LinkedWord::PTR) {
has_envmap_shader = true;
Ref envmap_shader_ref(deref_label(fr));
for (int i = 0; i < 5 * 16; i++) {
int byte = envmap_shader_ref.byte_offset + i;
u8 val = ref.ref.data->words_by_seg.at(envmap_shader_ref.seg).at(byte / 4).get_byte(byte % 4);
envmap_shader[i] = val;
}
}
if (version > GameVersion::Jak1) {
u32 tint = read_plain_data_field<u32>(ref, "tint-color", dts);
memcpy(jak2_tint_color.data(), &tint, 4);
} else {
jak2_tint_color.fill(0xff);
}
}
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,
GameVersion version) {
length = read_plain_data_field<u32>(ref, "length", dts);
allocated_length = read_plain_data_field<u32>(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, version);
}
}
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,
GameVersion version) {
prototype_array_tie.read_from_file(
get_and_check_ref_to_basic(ref, "prototype-array-tie", "prototype-array-tie", dts), dts,
stats, version);
wind_vectors = deref_label(get_field_ref(ref, "wind-vectors", dts));
}
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,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version);
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, version));
}
}
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";
}
void DrawableTreeCollideFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
s16 length = read_plain_data_field<s16>(ref, "length", dts);
auto data_ref = get_field_ref(ref, "data", dts);
if ((data_ref.byte_offset % 4) != 0) {
throw Error("misaligned data array");
}
Ref array_slot_ref = data_ref;
array_slot_ref.byte_offset += (length - 1) * 4;
Ref object_ref = deref_label(array_slot_ref);
object_ref.byte_offset -= 4;
last_array.read_from_file(typed_ref_from_basic(object_ref, dts), dts, stats, version);
}
std::string DrawableTreeCollideFragment::print(const PrintSettings& settings, int indent) const {
return last_array.print(settings, indent);
}
std::string DrawableTreeCollideFragment::my_type() const {
return "drawable-tree-collide-fragment";
}
void DrawableInlineArrayCollideFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion /*version*/) {
ASSERT(ref.type->get_name() == "drawable-inline-array-collide-fragment");
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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 != "collide-fragment") {
throw Error("bad collide fragment type: {}", type);
}
collide_fragments.emplace_back();
collide_fragments.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats);
}
}
std::string DrawableInlineArrayCollideFragment::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_collide) {
for (u32 i = 0; i < collide_fragments.size(); i++) {
result += fmt::format("{}data [{}]:\n", is, i);
result += collide_fragments[i].print(settings, next_indent);
}
}
return result;
}
std::string DrawableInlineArrayCollideFragment::my_type() const {
return "drawable-inline-array-collide-fragment";
}
void CollideFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
auto r = deref_label(get_field_ref(ref, "mesh", dts));
r.byte_offset -= 4;
mesh.read_from_file(typed_ref_from_basic(r, dts), dts, stats);
}
std::string CollideFragment::print(const PrintSettings& settings, int indent) const {
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}bsphere: {}", is, bsphere.print_meters());
result += mesh.print(settings, indent);
return result;
}
void CollideFragMesh::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* /*stats*/) {
strip_data_len = read_plain_data_field<u16>(ref, "strip-data-len", dts);
poly_count = read_plain_data_field<u16>(ref, "poly-count", dts);
vertex_count = read_plain_data_field<u8>(ref, "vertex-count", dts);
vertex_data_qwc = read_plain_data_field<u8>(ref, "vertex-data-qwc", dts);
total_qwc = read_plain_data_field<u8>(ref, "total-qwc", dts);
base_trans.read_from_file(get_field_ref(ref, "base-trans", dts));
base_trans.data[3] = 0;
packed_data = deref_label(get_field_ref(ref, "packed-data", dts));
pat_array = deref_label(get_field_ref(ref, "pat-array", dts));
}
std::string CollideFragMesh::print(const PrintSettings& /*settings*/, int indent) const {
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}strip-data-len: {}\n", is, strip_data_len);
result += fmt::format("{}poly-count: {}\n", is, poly_count);
result += fmt::format("{}vertex-count: {}\n", is, vertex_count);
result += fmt::format("{}vertex-data-qwc: {}\n", is, vertex_data_qwc);
result += fmt::format("{}total-qwc: {}\n", is, total_qwc);
result += fmt::format("{}base-trans: {}", is, base_trans.print_decimal());
return result;
}
//////////////////////////
// shrub
//////////////////////////
namespace shrub_types {
void DrawableTreeInstanceShrub::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
// the usual drawable stuff
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(ref, "length", dts);
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
// unfortunately, shrub uses the arrays thing differently.
// there's just one top level array, and the nodes are a bit scattered in memory below that.
// it doesn't have the 8 child rule
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, version));
}
// confirm that we have the weird shrub pattern and only found one array.
ASSERT(length == 1);
// now, let's try to discover the remaining arrays (instances).
// basically we just look after the top level array in memory.
// once we find something else (the time of day palette) we know we're at the end.
// the game finds these by traversing the tree, but this is a little easier, and gets us
// the familiar arrays that we used in tie/tfrag.
Ref object_ref = deref_label(data_ref);
object_ref.byte_offset -= 4;
discovered_arrays.push_back(
make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats, version));
bool done = false;
object_ref.byte_offset += 16;
while (!done) {
auto& word = object_ref.data->words_by_seg.at(object_ref.seg).at(object_ref.byte_offset / 4);
if (word.kind() == decompiler::LinkedWord::TYPE_PTR) {
if (word.symbol_name() == "drawable-inline-array-node") {
discovered_arrays.push_back(
make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats, version));
} else if (word.symbol_name() == "drawable-inline-array-instance-shrub") {
discovered_arrays.push_back(
make_drawable_inline_array(typed_ref_from_basic(object_ref, dts), dts, stats, version));
} else if (word.symbol_name() == "time-of-day-palette" ||
word.symbol_name() == "light-hash" ||
word.symbol_name() == "collide-hash-fragment") {
done = true;
} else {
ASSERT(word.symbol_name() == "draw-node" || word.symbol_name() == "instance-shrubbery");
}
}
object_ref.byte_offset += 16;
}
// this "info" thing holds all the prototypes
auto pt = deref_label(get_field_ref(ref, "info", dts));
pt.byte_offset -= 4;
info.read_from_file(typed_ref_from_basic(pt, dts), dts, stats, version);
// time of day palette. we'll want these colors in the FR3 file.
auto palette = deref_label(get_field_ref(ref, "colors-added", dts));
time_of_day.width = deref_u32(palette, 0);
ASSERT(time_of_day.width == 8);
time_of_day.height = deref_u32(palette, 1);
time_of_day.pad = deref_u32(palette, 2);
ASSERT(time_of_day.pad == 0);
for (int i = 0; i < int(8 * time_of_day.height); i++) {
time_of_day.colors.push_back(deref_u32(palette, 3 + i));
}
}
std::string DrawableTreeInstanceShrub::my_type() const {
return "drawable-tree-instance-shrub";
}
std::string DrawableTreeInstanceShrub::print(const level_tools::PrintSettings& settings,
int indent) const {
if (!settings.expand_shrub) {
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_shrub) {
for (size_t i = 0; i < discovered_arrays.size(); i++) {
result += fmt::format("{}arrays [{}] ({}):\n", is, i, discovered_arrays[i]->my_type());
result += discovered_arrays[i]->print(settings, next_indent);
}
result += fmt::format("{}prototypes:\n", is);
result += info.print(settings, next_indent);
}
return result;
}
void InstanceShrubbery::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* /*stats*/,
GameVersion /*version*/) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
bucket_index = read_plain_data_field<u16>(ref, "bucket-index", dts);
id = read_plain_data_field<s16>(ref, "id", dts);
origin.read_from_file(get_field_ref(ref, "origin", dts));
wind_index = read_plain_data_field<u16>(ref, "wind-index", dts);
color_indices = read_plain_data_field<u32>(ref, "color", dts);
flat_normal.read_from_file(get_field_ref(ref, "flat-normal", dts));
}
std::string InstanceShrubbery::print(const level_tools::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);
result += fmt::format("{}flat-normal: {}", is, flat_normal.print_meters());
result += fmt::format("{}color-indices: {}\n", is, color_indices);
result += fmt::format("{}wind-index: {}\n", is, wind_index);
return result;
}
void DrawableInlineArrayInstanceShrub::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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 += 80 * i; // todo not a constant here
auto type = get_type_of_basic(obj_ref);
if (type != "instance-shrubbery") {
throw Error("bad draw node type: {}", type);
}
instances.emplace_back();
instances.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats, version);
}
}
std::string DrawableInlineArrayInstanceShrub::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_shrub) {
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;
}
void PrototypeArrayShrubInfo::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
prototype_inline_array_shrub.read_from_file(
get_and_check_ref_to_basic(ref, "prototype-inline-array-shrub",
"prototype-inline-array-shrub", dts),
dts, stats, version);
wind_vectors = deref_label(get_field_ref(ref, "wind-vectors", dts));
}
std::string PrototypeArrayShrubInfo::print(const level_tools::PrintSettings& settings,
int indent) const {
return prototype_inline_array_shrub.print(settings, indent);
}
void PrototypeInlineArrayShrub::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
length = read_plain_data_field<s16>(ref, "length", dts);
auto data_ref = get_field_ref(ref, "data", dts);
for (int i = 0; i < length; i++) {
Ref thing = data_ref;
// note: unlike tie, these are stored in an inline array.
thing.byte_offset += 112 * i; // todo - not a constant here
auto type = get_type_of_basic(thing);
if (type != "prototype-bucket-shrub") {
throw Error("bad type in PrototypeInlineArrayShrub data: {}\n", type);
}
data.emplace_back();
data.back().read_from_file(typed_ref_from_basic(thing, dts), dts, stats, version);
}
}
std::string PrototypeGenericShrub::print(const level_tools::PrintSettings& settings,
int indent) const {
std::string is(indent, ' ');
std::string result;
int next_indent = indent + 4;
result += fmt::format("{}length: {}\n", is, length);
for (u32 i = 0; i < shrubs.size(); i++) {
result += fmt::format("{}data [{}]:\n", is, i);
result += shrubs[i].print(settings, next_indent);
}
return result;
}
void PrototypeGenericShrub::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
length = read_plain_data_field<s16>(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 thing = data_ref;
// note: unlike tie, these are stored in an inline array.
thing.byte_offset += 4 * i; // 4 byte pointer
thing = deref_label(thing);
thing.byte_offset -= 4; // basic offset
auto type = get_type_of_basic(thing);
if (type != "generic-shrub-fragment") {
throw Error("bad type in PrototypeGenericShrub data: {}\n", type);
}
shrubs.emplace_back();
shrubs.back().read_from_file(typed_ref_from_basic(thing, dts), dts, stats, version);
}
}
std::string PrototypeInlineArrayShrub::print(const level_tools::PrintSettings& settings,
int indent) const {
std::string is(indent, ' ');
std::string result;
int next_indent = indent + 4;
result += fmt::format("{}length: {}\n", is, length);
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 PrototypeBucketShrub::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
name = read_string_field(ref, "name", dts, true);
switch (version) {
case GameVersion::Jak1:
flags = read_plain_data_field<u32>(ref, "flags", dts);
ASSERT(flags == 0 || flags == 2);
break;
case GameVersion::Jak2:
flags = read_plain_data_field<u16>(ref, "flags", dts);
break;
default:
ASSERT(false);
}
if (flags) {
// lid in misty has flag 2, not sure what it means yet.
lg::info("proto: {} flags: {}", name, flags);
}
in_level = read_plain_data_field<u16>(ref, "in-level", dts);
utextures = read_plain_data_field<u16>(ref, "utextures", dts);
dists.read_from_file(get_field_ref(ref, "dists", dts));
rdists.read_from_file(get_field_ref(ref, "rdists", dts));
stiffness = read_plain_data_field<float>(ref, "stiffness", dts);
// 64 to 112 should be zeros
for (int i = 0; i < 12; i++) {
ASSERT(deref_u32(ref.ref, 16 + i) == 0);
}
auto geom_start = get_field_ref(ref, "geometry", dts);
// first geometry is generic and we should always have it.
auto generic_geom_l = deref_label(geom_start);
generic_geom_l.byte_offset -= 4;
if (get_type_of_basic(generic_geom_l) != "prototype-generic-shrub") {
throw Error("bad generic shrub type: {}", get_type_of_basic(generic_geom_l));
}
geom_start.byte_offset += 4;
// second is same data, but in prototype-shrubbery form (for normal shrub renderer)
auto normal_geom = deref_label(geom_start);
normal_geom.byte_offset -= 4;
if (get_type_of_basic(normal_geom) != "prototype-shrubbery") {
throw Error("bad normal shrub type: {}", get_type_of_basic(normal_geom));
}
shrubbery_geom.read_from_file(typed_ref_from_basic(normal_geom, dts), dts, stats, version);
geom_start.byte_offset += 4;
generic_geom.read_from_file(typed_ref_from_basic(generic_geom_l, dts), dts, stats, version);
// todo transparent version
// todo billboard version.
}
std::string PrototypeBucketShrub::print(const level_tools::PrintSettings& settings,
int indent) const {
std::string is(indent, ' ');
std::string result;
result += fmt::format("{}name: {}\n", is, name);
result += fmt::format("{}flags: {}\n", is, flags);
result += fmt::format("{}generic-geometry [0]:\n", is);
result += generic_geom.print(settings, indent + 4);
result += fmt::format("{}normal-geometry [1]:\n", is);
result += shrubbery_geom.print(settings, indent + 4);
return result;
}
void PrototypeShrubbery::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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 != "shrubbery") {
throw Error("bad draw node type: {}", type);
}
shrubs.emplace_back();
shrubs.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats, version);
}
}
std::string PrototypeShrubbery::print(const level_tools::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());
for (size_t i = 0; i < shrubs.size(); i++) {
result += fmt::format("{}draw-nodes [{}] ({}):\n", is, i, shrubs[i].my_type());
result += shrubs[i].print(settings, next_indent);
}
return result;
}
void copy_dma_to_vector(std::vector<u8>* out, Ref data_start, int qwc) {
out->resize(qwc * 16);
for (int i = 0; i < qwc * 4; i++) {
u32 val = deref_u32(data_start, i);
memcpy(out->data() + i * 4, &val, 4);
}
}
void Shrubbery::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* /*stats*/,
GameVersion /*version*/) {
// read the easy ones.
obj_qwc = read_plain_data_field<u8>(ref, "obj-qwc", dts);
vtx_qwc = read_plain_data_field<u8>(ref, "vtx-qwc", dts);
col_qwc = read_plain_data_field<u8>(ref, "col-qwc", dts);
stq_qwc = read_plain_data_field<u8>(ref, "stq-qwc", dts);
auto header_data = deref_label(get_field_ref(ref, "header", dts));
// guess that the header is 24 * 4 = 96 bytes here.
// not sure what it's used for yet.
header.resize(24);
for (int i = 0; i < 24; i++) {
u32 val = deref_u32(header_data, i);
memcpy(header.data() + i, &val, 4);
}
copy_dma_to_vector(&obj, deref_label(get_field_ref(ref, "obj", dts)), obj_qwc);
copy_dma_to_vector(&vtx, deref_label(get_field_ref(ref, "vtx", dts)), vtx_qwc);
copy_dma_to_vector(&col, deref_label(get_field_ref(ref, "col", dts)), col_qwc);
copy_dma_to_vector(&stq, deref_label(get_field_ref(ref, "stq", dts)), stq_qwc);
copy_dma_to_vector(&textures, deref_label(get_field_ref(ref, "textures", dts)), header[0] * 10);
}
std::string Shrubbery::print(const level_tools::PrintSettings& /*settings*/, int indent) const {
std::string is(indent, ' ');
return fmt::format("{} qwcs: {} {} {} {}, tex: {}\n", is, obj_qwc, vtx_qwc, col_qwc, stq_qwc,
header[0] * 2);
}
std::string GenericShrubFragment::print(const level_tools::PrintSettings& /*settings*/,
int indent) const {
std::string is(indent, ' ');
return fmt::format("{} qwcs: {} {} {} {}: {}\n", is, cnt_qwc, vtx_qwc, col_qwc, stq_qwc, vtx_cnt);
}
void GenericShrubFragment::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* /*stats*/,
GameVersion /*version*/) {
cnt_qwc = read_plain_data_field<u8>(ref, "cnt-qwc", dts);
vtx_qwc = read_plain_data_field<u8>(ref, "vtx-qwc", dts);
col_qwc = read_plain_data_field<u8>(ref, "col-qwc", dts);
stq_qwc = read_plain_data_field<u8>(ref, "stq-qwc", dts);
vtx_cnt = read_plain_data_field<u32>(ref, "vtx-cnt", dts);
copy_dma_to_vector(&textures, deref_label(get_field_ref(ref, "textures", dts)), cnt_qwc);
copy_dma_to_vector(&vtx, deref_label(get_field_ref(ref, "vtx", dts)), vtx_qwc);
copy_dma_to_vector(&col, deref_label(get_field_ref(ref, "col", dts)), col_qwc);
copy_dma_to_vector(&stq, deref_label(get_field_ref(ref, "stq", dts)), stq_qwc);
}
} // namespace shrub_types
std::unique_ptr<DrawableTree> make_drawable_tree(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
if (ref.type->get_name() == "drawable-tree-tfrag") {
auto tree = std::make_unique<DrawableTreeTfrag>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-trans-tfrag") {
auto tree = std::make_unique<DrawableTreeTransTfrag>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-lowres-tfrag") {
auto tree = std::make_unique<DrawableTreeLowresTfrag>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-dirt-tfrag") {
auto tree = std::make_unique<DrawableTreeDirtTfrag>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-ice-tfrag") {
auto tree = std::make_unique<DrawableTreeIceTfrag>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-instance-tie") {
auto tree = std::make_unique<DrawableTreeInstanceTie>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-tfrag-trans") {
auto tree = std::make_unique<DrawableTreeTfragTrans>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-tfrag-water") {
auto tree = std::make_unique<DrawableTreeTfragWater>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-actor") {
auto tree = std::make_unique<DrawableTreeActor>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-instance-shrub") {
auto tree = std::make_unique<shrub_types::DrawableTreeInstanceShrub>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-collide-fragment") {
auto tree = std::make_unique<DrawableTreeCollideFragment>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
auto tree = std::make_unique<DrawableTreeUnknown>();
tree->read_from_file(ref, dts, stats, version);
return tree;
}
void DrawableTreeArray::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
id = read_plain_data_field<s16>(ref, "id", dts);
length = read_plain_data_field<s16>(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, version));
}
}
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;
}
template <typename T>
void fill_res_with_value_types(Res& res_tag, const Ref& data) {
ASSERT(res_tag.inlined);
res_tag.inlined_storage = bytes_from_plain_data(data, sizeof(T) * res_tag.count);
}
void EntityActor::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* /*stats*/,
GameVersion /*version*/) {
trans.read_from_file(get_field_ref(ref, "trans", dts));
aid = read_plain_data_field<u32>(ref, "aid", dts);
etype = read_type_field(ref, "etype", dts, false);
task = read_plain_data_field<u8>(ref, "task", dts);
vis_id = read_plain_data_field<u16>(ref, "vis-id", dts);
quat.read_from_file(get_field_ref(ref, "quat", dts));
int res_length = read_plain_data_field<int32_t>(ref, "length", dts);
// int res_allocated_length = read_plain_data_field<int32_t>(ref, "allocated-length", dts);
auto tags = deref_label(get_field_ref(ref, "tag", dts));
auto data_base = deref_label(get_field_ref(ref, "data-base", dts));
for (int i = 0; i < res_length; i++) {
auto& res = res_list.emplace_back();
res.name = read_symbol(tags);
tags.byte_offset += 4;
res.key_frame = deref_float(tags, 0);
tags.byte_offset += 4;
res.elt_type = read_type(tags);
tags.byte_offset += 4;
const u32 vals = deref_u32(tags, 0);
const u32 offset = vals & 0xffff; // 16 bits
res.count = (vals >> 16) & 0x7fff; // 15 bits
res.inlined = vals & 0x8000'0000;
Ref data = data_base;
data.byte_offset += offset;
if (res.elt_type == "string") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_string_ref(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "symbol") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_symbol(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "type") {
ASSERT(!res.inlined);
for (int j = 0; j < res.count; j++) {
res.strings.push_back(read_type(data));
data.byte_offset += 4;
}
} else if (res.elt_type == "vector") {
ASSERT(res.inlined);
res.inlined_storage = bytes_from_plain_data(data, 16 * res.count);
} else if (res.elt_type == "float") {
fill_res_with_value_types<float>(res, data);
} else if (res.elt_type == "int32") {
fill_res_with_value_types<int32_t>(res, data);
} else if (res.elt_type == "int16") {
fill_res_with_value_types<int16_t>(res, data);
} else if (res.elt_type == "int8") {
fill_res_with_value_types<int8_t>(res, data);
} else if (res.elt_type == "uint32") {
fill_res_with_value_types<uint32_t>(res, data);
} else if (res.elt_type == "uint8") {
fill_res_with_value_types<uint8_t>(res, data);
} else if (res.elt_type == "actor-group") {
// TODO: unsupported.
} else if (res.elt_type == "pair") {
ASSERT(res.count == 1);
ASSERT(!res.inlined);
data = deref_label(data);
res.script = data.data->to_form_script(data.seg, (data.byte_offset) / 4, nullptr);
} else {
fmt::print("unhandled elt_type: {}\n", res.elt_type);
ASSERT_NOT_REACHED();
}
tags.byte_offset += 4;
}
}
void DrawableActor::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
bsphere.read_from_file(get_field_ref(ref, "bsphere", dts));
actor.read_from_file(get_and_check_ref_to_basic(ref, "actor", "entity-actor", dts), dts, stats,
version);
stats->total_actors++;
}
void DrawableInlineArrayActor::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version) {
int num_actors = read_plain_data_field<int16_t>(ref, "length", dts);
auto data_ref = get_field_ref(ref, "data", dts);
for (int i = 0; i < num_actors; 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 != "drawable-actor") {
throw Error("bad drawable-actor type: {}", type);
}
drawable_actors.emplace_back();
drawable_actors.back().read_from_file(typed_ref_from_basic(obj_ref, dts), dts, stats, version);
}
}
void CollideHash::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
level_tools::DrawStats* stats,
GameVersion version) {
num_items = read_plain_data_field<uint32_t>(ref, "num-items", dts);
item_array = deref_label(get_field_ref(ref, "item-array", dts));
}
void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file,
const decompiler::DecompilerTypeSystem& dts,
DrawStats* stats,
GameVersion version,
bool only_read_texture_remap) {
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));
texture_remap_table.clear();
s32 tex_remap_len = read_plain_data_field<s32>(ref, "texture-remap-table-len", dts);
if (tex_remap_len > 0) {
auto tex_remap_data = deref_label(get_field_ref(ref, "texture-remap-table", dts));
for (int entry = 0; entry < tex_remap_len; entry++) {
u64 low = deref_u32(tex_remap_data, 2 * entry);
u64 high = deref_u32(tex_remap_data, 2 * entry + 1);
TextureRemap remap;
remap.original_texid = low;
remap.new_texid = high;
texture_remap_table.push_back(remap);
}
}
if (only_read_texture_remap) {
return;
}
switch (version) {
case GameVersion::Jak1:
visible_list_length = read_plain_data_field<s32>(ref, "visible-list-length", dts);
break;
case GameVersion::Jak2:
visible_list_length = read_plain_data_field<s16>(ref, "visible-list-length", dts);
break;
default:
ASSERT(false);
}
drawable_tree_array.read_from_file(
get_and_check_ref_to_basic(ref, "drawable-trees", "drawable-tree-array", dts), dts, stats,
version);
if (version > GameVersion::Jak1) {
auto ff = get_field_ref(ref, "texture-flags", dts);
memcpy_plain_data((u8*)texture_flags, ff, sizeof(u16) * kNumTextureFlags);
}
if (get_word_kind_for_field(ref, "actors", dts) == decompiler::LinkedWord::PTR) {
actors.read_from_file(
get_and_check_ref_to_basic(ref, "actors", "drawable-inline-array-actor", dts), dts, stats,
version);
}
if (version > GameVersion::Jak1 &&
get_word_kind_for_field(ref, "collide-hash", dts) == decompiler::LinkedWord::PTR) {
collide_hash.read_from_file(
get_and_check_ref_to_basic(ref, "collide-hash", "collide-hash", dts), dts, stats, version);
}
}
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