jak-project/decompiler/level_extractor/BspHeader.cpp

2101 lines
78 KiB
C++
Raw Normal View History

#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 {
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);
}
}
void TimeOfDayPalette::read_from_file(Ref ref) {
width = deref_u32(ref, 0);
ASSERT(width == 8);
height = deref_u32(ref, 1);
pad = deref_u32(ref, 2);
ASSERT(pad == 0);
for (int i = 0; i < int(8 * height); i++) {
colors.push_back(deref_u32(ref, 3 + i));
}
}
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*/,
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*/,
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,
GameVersion version) {
if (ref.type->get_name() == "draw-node") {
auto result = std::make_unique<DrawNode>();
result->read_from_file(ref, dts, version);
return result;
} else if (ref.type->get_name() == "tfragment") {
auto result = std::make_unique<TFragment>();
result->read_from_file(ref, dts, version);
return result;
} else if (ref.type->get_name() == "instance-tie") {
auto result = std::make_unique<InstanceTie>();
result->read_from_file(ref, dts, version);
return result;
} else if (ref.type->get_name() == "drawable-actor") {
auto result = std::make_unique<DrawableActor>();
result->read_from_file(ref, dts, 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, 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*/) {
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);
}
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,
GameVersion version) {
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);
// 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 (false) {
// 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,
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:
case GameVersion::Jak3: {
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);
}
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,
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));
}
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,
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, 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,
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, 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,
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, 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,
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, 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,
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, 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,
GameVersion version) {
if (ref.type->get_name() == "drawable-inline-array-node") {
auto result = std::make_unique<DrawableInlineArrayNode>();
result->read_from_file(ref, dts, version);
return result;
}
if (ref.type->get_name() == "drawable-inline-array-tfrag") {
auto result = std::make_unique<DrawableInlineArrayTFrag>();
result->read_from_file(ref, dts, 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, version);
return result;
}
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
if (ref.type->get_name() == "drawable-inline-array-tfrag-trans") {
auto result = std::make_unique<DrawableInlineArrayTFragTrans>();
result->read_from_file(ref, dts, version);
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
return result;
}
if (ref.type->get_name() == "drawable-inline-array-tfrag-water") {
auto result = std::make_unique<DrawableInlineArrayTFragWater>();
result->read_from_file(ref, dts, version);
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
return result;
}
if (ref.type->get_name() == "drawable-inline-array-instance-tie") {
auto result = std::make_unique<DrawableInlineArrayInstanceTie>();
result->read_from_file(ref, dts, 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, version);
return result;
}
auto result = std::make_unique<DrawableInlineArrayUnknown>();
result->read_from_file(ref, dts, version);
return result;
}
void DrawableTreeTfrag::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
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");
}
time_of_day.read_from_file(deref_label(get_field_ref(ref, "time-of-day-pal", dts)));
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, 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,
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, 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,
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:
case GameVersion::Jak3:
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, 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, 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
time_of_day.read_from_file(deref_label(get_field_ref(ref, "tie-colors", dts)));
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,
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, 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,
GameVersion version) {
prototype_array_tie.read_from_file(
get_and_check_ref_to_basic(ref, "prototype-array-tie", "prototype-array-tie", dts), dts,
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,
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, 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, 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,
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, 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,
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);
}
}
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) {
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);
}
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) {
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,
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, 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, 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, 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, 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, version);
// time of day palette. we'll want these colors in the FR3 file.
time_of_day.read_from_file(deref_label(get_field_ref(ref, "colors-added", dts)));
}
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,
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,
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, 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,
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, 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,
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, 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,
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, 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,
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:
case GameVersion::Jak3:
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, version);
geom_start.byte_offset += 4;
generic_geom.read_from_file(typed_ref_from_basic(generic_geom_l, dts), dts, 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,
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, 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,
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,
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,
GameVersion version) {
if (ref.type->get_name() == "drawable-tree-tfrag") {
auto tree = std::make_unique<DrawableTreeTfrag>();
tree->read_from_file(ref, dts, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-trans-tfrag") {
auto tree = std::make_unique<DrawableTreeTransTfrag>();
tree->read_from_file(ref, dts, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-lowres-tfrag") {
auto tree = std::make_unique<DrawableTreeLowresTfrag>();
tree->read_from_file(ref, dts, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-dirt-tfrag") {
auto tree = std::make_unique<DrawableTreeDirtTfrag>();
tree->read_from_file(ref, dts, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-ice-tfrag") {
auto tree = std::make_unique<DrawableTreeIceTfrag>();
tree->read_from_file(ref, dts, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-instance-tie") {
auto tree = std::make_unique<DrawableTreeInstanceTie>();
tree->read_from_file(ref, dts, version);
return tree;
}
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
if (ref.type->get_name() == "drawable-tree-tfrag-trans") {
auto tree = std::make_unique<DrawableTreeTfragTrans>();
tree->read_from_file(ref, dts, version);
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
return tree;
}
if (ref.type->get_name() == "drawable-tree-tfrag-water") {
auto tree = std::make_unique<DrawableTreeTfragWater>();
tree->read_from_file(ref, dts, version);
[decomp] Decompile some time-of-day stuff, support new style Jak 2 time of day (#1943) - Add "tfrag-water" tfrag tree support (may just be the same as Jak 1's 'dirt' for the settings) - Add "tfrag-trans" tfrag tree support, reusing "trans-tfrag" from jak 1. - Add a hack to `LinkedObjectFileCreation` to handle `oracle`, which is accidentally multiply defined as a type leftover from jak 1 (an entity in village1), and level info for jak 2. - Add `VI1.DGO` - add `time-of-day.gc`, and a few other stub functions so it works - Set up some time of day stuff in GOAL for jak 2/PC renderers - Clean up time of day in c++ renderers, support the more complicated weight system used by jak 2 (backward compatible with jak 1, thankfully) The mood functions now run, so this could cause problems if they rely on stuff we don't have yet. But it seems fine for ctysluma and prison for now. ![image](https://user-images.githubusercontent.com/48171810/194719441-d185f59c-19dc-4cd3-a5c4-00b0cfe1d6c3.png) ![image](https://user-images.githubusercontent.com/48171810/194719449-6e051bf3-0750-42e5-a654-901313dbe479.png) ![image](https://user-images.githubusercontent.com/48171810/194719455-3ca6793e-873a-449a-8e85-9c20ffeb4da3.png) ![image](https://user-images.githubusercontent.com/48171810/194719461-8f27af17-4434-4492-96cd-8c5eec6eafdf.png) ![image](https://user-images.githubusercontent.com/48171810/194719468-720715b9-985a-4acf-928c-eab948cfcb03.png) ![image](https://user-images.githubusercontent.com/48171810/194719486-bfb91e83-f6ca-4585-80ad-3b2c0cbbd5af.png) ![image](https://user-images.githubusercontent.com/48171810/194719492-df065d2f-cb5a-47e3-a248-f5317c42082f.png) ![image](https://user-images.githubusercontent.com/48171810/194719507-91e1f477-ecfe-4d6c-b744-5f24646255ca.png)
2022-10-08 13:33:03 -04:00
return tree;
}
if (ref.type->get_name() == "drawable-tree-actor") {
auto tree = std::make_unique<DrawableTreeActor>();
tree->read_from_file(ref, dts, 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, version);
return tree;
}
if (ref.type->get_name() == "drawable-tree-collide-fragment") {
auto tree = std::make_unique<DrawableTreeCollideFragment>();
tree->read_from_file(ref, dts, version);
return tree;
}
auto tree = std::make_unique<DrawableTreeUnknown>();
tree->read_from_file(ref, dts, version);
return tree;
}
void DrawableTreeArray::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
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, 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,
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,
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, version);
}
void DrawableInlineArrayActor::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
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, version);
}
}
void CollideHash::read_from_file(TypedRef ref,
const decompiler::DecompilerTypeSystem& dts,
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 HFragment::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) {
start_corner.read_from_file(get_field_ref(ref, "start-corner", dts));
spheres.resize(kNumCorners);
auto spheres_ptr = deref_label(get_field_ref(ref, "spheres", dts));
for (int i = 0; i < kNumCorners; i++) {
spheres[i].read_from_file(spheres_ptr);
spheres_ptr.byte_offset += 16;
}
vis_ids.resize(kNumCorners);
memcpy_plain_data((u8*)vis_ids.data(), deref_label(get_field_ref(ref, "visids", dts)),
sizeof(s16) * kNumCorners);
memcpy_plain_data((u8*)shaders, deref_label(get_field_ref(ref, "shaders", dts)), 80 * 3);
colors.read_from_file(deref_label(get_field_ref(ref, "colors", dts)));
// note: using hard-coded size here
memcpy_plain_data((u8*)montage, deref_label(get_field_ref(ref, "montage", dts)),
sizeof(u16) * 16 * 17);
// bucket
verts.resize(kNumVerts);
memcpy_plain_data((u8*)verts.data(), deref_label(get_field_ref(ref, "verts", dts)),
sizeof(u32) * kNumVerts);
// pat-array
// pat-len
num_buckets_far = read_plain_data_field<u16>(ref, "num-buckets-far", dts);
num_buckets_mid = read_plain_data_field<u16>(ref, "num-buckets-mid", dts);
num_buckets_near = read_plain_data_field<u16>(ref, "num-buckets-near", dts);
size = read_plain_data_field<u32>(ref, "size", dts);
}
void AdgifShaderArray::read_from_file(TypedRef ref, const decompiler::DecompilerTypeSystem& dts) {
auto length = read_plain_data_field<s32>(ref, "length", dts);
adgifs.resize(length);
memcpy_plain_data((u8*)adgifs.data(), get_field_ref(ref, "data", dts),
sizeof(AdGifData) * length);
}
void BspHeader::read_from_file(const decompiler::LinkedObjectFile& file,
const decompiler::DecompilerTypeSystem& dts,
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));
name = read_symbol_field(ref, "name", dts);
if (version == GameVersion::Jak1) {
adgifs.read_from_file(get_and_check_ref_to_basic(ref, "adgifs", "adgif-shader-array", dts),
dts);
}
texture_page_count = read_plain_data_field<s32>(ref, "texture-page-count", dts);
if (texture_page_count > 0) {
auto tex_id_ptr = deref_label(get_field_ref(ref, "texture-ids", dts));
for (int i = 0; i < texture_page_count; i++) {
texture_ids.push_back(deref_u32(tex_id_ptr, i));
}
}
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:
case GameVersion::Jak3:
visible_list_length = read_plain_data_field<s16>(ref, "visible-list-length", dts);
extra_vis_list_length = read_plain_data_field<s16>(ref, "extra-vis-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, 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,
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, version);
}
if (version == GameVersion::Jak3) {
if (get_word_kind_for_field(ref, "hfrag-drawable", dts) == decompiler::LinkedWord::PTR) {
hfrag.emplace();
hfrag->read_from_file(get_and_check_ref_to_basic(ref, "hfrag-drawable", "hfragment", dts),
dts);
}
}
}
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