[merc] Add merc extraction (#1356)

* docs for ee merc code

* wip

* more extraction stuff

* partial mat1 working

* mat1

* cleanup

* partial mat2 and mat3 support

* merc extraction seems to work
This commit is contained in:
water111
2022-05-11 22:53:53 -04:00
committed by GitHub
parent 84b2ba43be
commit be976d2e69
23 changed files with 4539 additions and 235 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ struct GifTag {
};
u32 nloop() const {
return data[0] & 0x7f; // 15 bits
return data[0] & 0x7fff; // 15 bits
}
bool eop() const { return data[0] & (1ull << 15); }
+2
View File
@@ -54,9 +54,11 @@ add_library(
level_extractor/BspHeader.cpp
level_extractor/extract_collide_frags.cpp
level_extractor/extract_level.cpp
level_extractor/extract_merc.cpp
level_extractor/extract_tfrag.cpp
level_extractor/extract_tie.cpp
level_extractor/extract_shrub.cpp
level_extractor/MercData.cpp
ObjectFile/LinkedObjectFile.cpp
ObjectFile/LinkedObjectFileCreation.cpp
+20 -20
View File
@@ -8281,19 +8281,19 @@
)
(deftype merc-byte-header (structure)
((srcdest-off uint8 :offset-assert 0)
(rgba-off uint8 :offset-assert 1)
(lump-off uint8 :offset-assert 2)
(fp-off uint8 :offset-assert 3)
(mat1-cnt uint8 :offset-assert 4)
(mat2-cnt uint8 :offset-assert 5)
(mat3-cnt uint8 :offset-assert 6)
(samecopy-cnt uint8 :offset-assert 7)
(crosscopy-cnt uint8 :offset-assert 8)
(strip-len uint8 :offset-assert 9)
(mm-quadword-fp-off uint8 :offset-assert 10)
(mm-quadword-size uint8 :offset-assert 11)
(perc-off uint8 :offset-assert 12)
((srcdest-off uint8 :offset-assert 0) ; 0x
(rgba-off uint8 :offset-assert 1) ; 0y
(lump-off uint8 :offset-assert 2) ; 0z
(fp-off uint8 :offset-assert 3) ; 0w
(mat1-cnt uint8 :offset-assert 4) ; 1x
(mat2-cnt uint8 :offset-assert 5) ; 1y
(mat3-cnt uint8 :offset-assert 6) ; 1z
(samecopy-cnt uint8 :offset-assert 7) ; 1w
(crosscopy-cnt uint8 :offset-assert 8) ; 2x
(strip-len uint8 :offset-assert 9) ; 2y
(mm-quadword-fp-off uint8 :offset-assert 10) ; 2z
(mm-quadword-size uint8 :offset-assert 11) ; 2w
(perc-off uint8 :offset-assert 12) ; 3x
(mat-slot uint8 10 :offset-assert 13)
)
:method-count-assert 9
@@ -8336,7 +8336,7 @@
((x-add float :offset-assert 0)
(y-add float :offset-assert 4)
(z-add float :offset-assert 8)
(shader-cnt uint8 :offset-assert 12)
(shader-cnt uint8 :offset-assert 12) ;; only 7 bits used? maybe flag in top bit?
(kick-info-offset uint8 :offset-assert 13)
(kick-info-step uint8 :offset-assert 14)
(hword-cnt uint8 :offset-assert 15)
@@ -8357,7 +8357,7 @@
)
(deftype merc-fragment-control (structure)
((unsigned-four-count uint8 :offset-assert 0)
((unsigned-four-count uint8 :offset-assert 0) ;; (in 32-bit words, not qw)
(lump-four-count uint8 :offset-assert 1) ;; merc-vtx
(fp-qwc uint8 :offset-assert 2) ;; merc-fp-header
(mat-xfer-count uint8 :offset-assert 3)
@@ -8557,11 +8557,11 @@
)
(deftype merc-vu1-low-mem (structure)
((tri-strip-gif gs-gif-tag :inline :offset-assert 0) ;; was qword
(ad-gif gs-gif-tag :inline :offset-assert 16) ;; was qword
(hvdf-offset vector :inline :offset-assert 32)
(perspective uint128 4 :offset-assert 48)
(fog vector :inline :offset-assert 112)
((tri-strip-gif gs-gif-tag :inline :offset-assert 0) ;; was qword 0
(ad-gif gs-gif-tag :inline :offset-assert 16) ;; was qword 1
(hvdf-offset vector :inline :offset-assert 32) ;; 2
(perspective uint128 4 :offset-assert 48) ;; 3, 4, 5, 6
(fog vector :inline :offset-assert 112) ;; 7
)
:method-count-assert 9
:size-assert #x80
-11
View File
@@ -206,17 +206,6 @@ std::string TFragmentDebugData::print(int indent) const {
return result;
}
u32 deref_u32(const Ref& ref, int word_offset) {
if ((ref.byte_offset % 4) != 0) {
throw Error("deref_u32 bad alignment");
}
const auto& word = ref.data->words_by_seg.at(ref.seg).at(word_offset + (ref.byte_offset / 4));
if (word.kind() != decompiler::LinkedWord::PLAIN_DATA) {
throw Error("deref_u32 bad kind: {}", (int)word.kind());
}
return word.data;
}
void tfrag_debug_print_unpack(Ref start, int qwc_total) {
int word_offset = 0;
+1 -8
View File
@@ -7,6 +7,7 @@
#include <optional>
#include "decompiler/util/goal_data_reader.h"
#include "decompiler/level_extractor/common_formats.h"
namespace decompiler {
class LinkedObjectFile;
@@ -15,8 +16,6 @@ class DecompilerTypeSystem;
namespace level_tools {
u32 deref_u32(const Ref& ref, int word_offset);
struct PrintSettings {
bool print_tfrag = false;
bool expand_draw_node = false;
@@ -726,12 +725,6 @@ struct DrawableTreeArray {
std::vector<std::unique_ptr<DrawableTree>> trees;
};
// levels may remap textures if they provide one that should be shared
struct TextureRemap {
u32 original_texid;
u32 new_texid;
};
// The "file info"
struct FileInfo {
std::string file_type;
+402
View File
@@ -0,0 +1,402 @@
#include "MercData.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "third-party/fmt/core.h"
#include "common/dma/gs.h"
namespace decompiler {
void MercCtrlHeader::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
xyz_scale = read_plain_data_field<float>(tr, "xyz-scale", dts);
st_out_a = read_plain_data_field<u32>(tr, "st-out-a", dts);
st_out_b = read_plain_data_field<u32>(tr, "st-out-b", dts);
st_vif_add = read_plain_data_field<u32>(tr, "st-vif-add", dts);
st_int_off = read_plain_data_field<u16>(tr, "st-int-off", dts);
st_int_scale = read_plain_data_field<u16>(tr, "st-int-scale", dts);
effect_count = read_plain_data_field<u32>(tr, "effect-count", dts);
blend_target_count = read_plain_data_field<u32>(tr, "blend-target-count", dts);
fragment_count = read_plain_data_field<u16>(tr, "fragment-count", dts);
tri_count = read_plain_data_field<u16>(tr, "tri-count", dts);
matrix_count = read_plain_data_field<u8>(tr, "matrix-count", dts);
shader_count = read_plain_data_field<u8>(tr, "shader-count", dts);
transform_vertex_count = read_plain_data_field<u16>(tr, "transform-vertex-count", dts);
dvert_count = read_plain_data_field<u16>(tr, "dvert-count", dts);
one_mat_count = read_plain_data_field<u16>(tr, "one-mat-count", dts);
two_mat_count = read_plain_data_field<u16>(tr, "two-mat-count", dts);
two_mat_reuse_count = read_plain_data_field<u16>(tr, "two-mat-reuse-count", dts);
three_mat_count = read_plain_data_field<u16>(tr, "three-mat-count", dts);
three_mat_reuse_count = read_plain_data_field<u16>(tr, "three-mat-reuse-count", dts);
shader_upload_count = read_plain_data_field<u8>(tr, "shader-upload-count", dts);
matrix_upload_count = read_plain_data_field<u8>(tr, "matrix-upload-count", dts);
same_copy_count = read_plain_data_field<u16>(tr, "same-copy-count", dts);
cross_copy_count = read_plain_data_field<u16>(tr, "cross-copy-count", dts);
num_verts = read_plain_data_field<u16>(tr, "num-verts", dts);
longest_edge = read_plain_data_field<float>(tr, "longest-edge", dts);
// todo masksk
envmap_tint = read_plain_data_field<u32>(tr, "envmap-tint", dts);
needs_clip = read_plain_data_field<u8>(tr, "needs-clip", dts);
use_isometric = read_plain_data_field<u8>(tr, "use-isometric", dts);
use_attached_shader = read_plain_data_field<u8>(tr, "use-attached-shader", dts);
display_triangles = read_plain_data_field<u8>(tr, "display-triangles", dts);
death_vertex_skip = read_plain_data_field<u16>(tr, "death-vertex-skip", dts);
death_start_vertex = read_plain_data_field<u16>(tr, "death-start-vertex", dts);
death_effect = read_plain_data_field<u32>(tr, "death-effect", dts);
use_translucent = read_plain_data_field<u8>(tr, "use-translucent", dts);
display_this_fragment = read_plain_data_field<u8>(tr, "display-this-fragment", dts);
}
std::string MercCtrlHeader::print() const {
std::string result;
result += fmt::format(" xyz_scale: {}\n", xyz_scale);
result += fmt::format(" st_out_a: 0x{:x}\n", st_out_a);
result += fmt::format(" st_out_b: 0x{:x}\n", st_out_b);
result += fmt::format(" st_vif_add: 0x{:x}\n", st_vif_add);
result += fmt::format(" st_int_off: 0x{:x}\n", st_int_off);
result += fmt::format(" st_int_scale: {}\n", st_int_scale);
result += fmt::format(" effect_count: {}\n", effect_count);
result += fmt::format(" blend_target_count: {}\n", blend_target_count);
result += fmt::format(" fragment_count: {}\n", fragment_count);
result += fmt::format(" tri_count: {}\n", tri_count);
result += fmt::format(" matrix_count: {}\n", matrix_count);
result += fmt::format(" shader_count: {}\n", shader_count);
result += fmt::format(" transform_vertex_count: {}\n", transform_vertex_count);
result += fmt::format(" dvert_count: {}\n", dvert_count);
result += fmt::format(" one_mat_count: {}\n", one_mat_count);
result += fmt::format(" two_mat_count: {}\n", two_mat_count);
result += fmt::format(" two_mat_reuse_count: {}\n", two_mat_reuse_count);
result += fmt::format(" three_mat_count: {}\n", three_mat_count);
result += fmt::format(" three_mat_reuse_count: {}\n", three_mat_reuse_count);
result += fmt::format(" shader_upload_count: {}\n", shader_upload_count);
result += fmt::format(" matrix_upload_count: {}\n", matrix_upload_count);
result += fmt::format(" same_copy_count: {}\n", same_copy_count);
result += fmt::format(" cross_copy_count: {}\n", cross_copy_count);
result += fmt::format(" num_verts: {}\n", num_verts);
result += fmt::format(" longest_edge: {}\n", longest_edge);
result += fmt::format(" envmap_tint: {}\n", envmap_tint);
result += fmt::format(" needs_clip: {}\n", needs_clip);
result += fmt::format(" use_isometric: {}\n", use_isometric);
result += fmt::format(" use_attached_shader: {}\n", use_attached_shader);
result += fmt::format(" display_triangles: {}\n", display_triangles);
result += fmt::format(" death_vertex_skip: {}\n", death_vertex_skip);
result += fmt::format(" death_start_vertex: {}\n", death_start_vertex);
result += fmt::format(" death_effect: {}\n", death_effect);
result += fmt::format(" use_translucent: {}\n", use_translucent);
result += fmt::format(" display_this_fragment: {}\n", display_this_fragment);
return result;
}
TypedRef MercFragmentControl::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
unsigned_four_count = read_plain_data_field<u8>(tr, "unsigned-four-count", dts);
lump_four_count = read_plain_data_field<u8>(tr, "lump-four-count", dts);
fp_qwc = read_plain_data_field<u8>(tr, "fp-qwc", dts);
mat_xfer_count = read_plain_data_field<u8>(tr, "mat-xfer-count", dts);
ASSERT(mat_xfer_count < 10);
Ref dest_data_ref = get_field_ref(tr, "mat-dest-data", dts);
for (u8 i = 0; i < mat_xfer_count; i++) {
auto& entry = mat_dest_data.emplace_back();
entry.matrix_number = deref_u8(dest_data_ref, i * 2);
entry.matrix_dest = deref_u8(dest_data_ref, i * 2 + 1);
}
tr.ref.byte_offset += (4 + 2 * mat_dest_data.size());
return tr;
}
void MercFpHeader::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
x_add = read_plain_data_field<float>(tr, "x-add", dts);
y_add = read_plain_data_field<float>(tr, "y-add", dts);
z_add = read_plain_data_field<float>(tr, "z-add", dts);
shader_cnt = read_plain_data_field<u8>(tr, "shader-cnt", dts);
kick_info_offset = read_plain_data_field<u8>(tr, "kick-info-offset", dts);
kick_info_step = read_plain_data_field<u8>(tr, "kick-info-step", dts);
hword_cnt = read_plain_data_field<u8>(tr, "hword-cnt", dts);
}
std::string MercFpHeader::print() const {
std::string result;
result += fmt::format(" x_add: {}\n", x_add);
result += fmt::format(" y_add: {}\n", y_add);
result += fmt::format(" z_add: {}\n", z_add);
result += fmt::format(" shader_cnt: {}\n", shader_cnt);
result += fmt::format(" kick_info_offset: {}\n", kick_info_offset);
result += fmt::format(" kick_info_step: {}\n", kick_info_step);
result += fmt::format(" hword_cnt: {}\n", hword_cnt);
return result;
}
void MercByteHeader::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
srcdest_off = read_plain_data_field<u8>(tr, "srcdest-off", dts);
rgba_off = read_plain_data_field<u8>(tr, "rgba-off", dts);
lump_off = read_plain_data_field<u8>(tr, "lump-off", dts);
fp_off = read_plain_data_field<u8>(tr, "fp-off", dts);
mat1_cnt = read_plain_data_field<u8>(tr, "mat1-cnt", dts);
mat2_cnt = read_plain_data_field<u8>(tr, "mat2-cnt", dts);
mat3_cnt = read_plain_data_field<u8>(tr, "mat3-cnt", dts);
samecopy_cnt = read_plain_data_field<u8>(tr, "samecopy-cnt", dts);
crosscopy_cnt = read_plain_data_field<u8>(tr, "crosscopy-cnt", dts);
strip_len = read_plain_data_field<u8>(tr, "strip-len", dts);
mm_quadword_fp_off = read_plain_data_field<u8>(tr, "mm-quadword-fp-off", dts);
mm_quadword_size = read_plain_data_field<u8>(tr, "mm-quadword-size", dts);
perc_off = read_plain_data_field<u8>(tr, "perc-off", dts);
auto ms = get_field_ref(tr, "perc-off", dts);
bool got_end = false;
for (int i = 0; i < MAT_SLOTS; i++) {
ms.byte_offset++;
mat_slot[i] = deref_u8(ms, 0);
if (mat_slot[i] == 128) {
got_end = true;
} else {
// ASSERT(!got_end);
if (got_end) {
// fmt::print("got something after the end\n"); // todo, should investigate more
}
}
}
}
std::string MercByteHeader::print() const {
std::string result;
result += fmt::format(" srcdest_off: {}\n", srcdest_off);
result += fmt::format(" rgba_off: {}\n", rgba_off);
result += fmt::format(" lump_off: {}\n", lump_off);
result += fmt::format(" fp_off: {}\n", fp_off);
result += fmt::format(" mat1_cnt: {}\n", mat1_cnt);
result += fmt::format(" mat2_cnt: {}\n", mat2_cnt);
result += fmt::format(" mat3_cnt: {}\n", mat3_cnt);
result += fmt::format(" samecopy_cnt: {}\n", samecopy_cnt);
result += fmt::format(" crosscopy_cnt: {}\n", crosscopy_cnt);
result += fmt::format(" strip_len: {}\n", strip_len);
result += fmt::format(" mm_quadword_fp_off: {}\n", mm_quadword_fp_off);
result += fmt::format(" mm_quadword_size: {}\n", mm_quadword_size);
result += fmt::format(" perc_off: {}\n", perc_off);
for (int i = 0; i < MAT_SLOTS; i++) {
result += fmt::format(" mat_slot[{}]: {}\n", i, mat_slot[i]);
}
return result;
}
std::string MercFragmentControl::print() const {
std::string result;
result += fmt::format(" unsigned_four_count: {}\n", unsigned_four_count);
result += fmt::format(" lump_four_count: {}\n", lump_four_count);
result += fmt::format(" fp_qwc: {}\n", fp_qwc);
result += fmt::format(" mat_xfer_count: {}\n", mat_xfer_count);
for (u8 i = 0; i < mat_xfer_count; i++) {
result += fmt::format(" mat[{}] {} -> {}\n", i, mat_dest_data[i].matrix_number,
mat_dest_data[i].matrix_dest);
}
return result;
}
std::string MercShader::print() const {
std::string result;
result += fmt::format(" output_offset: {}\n", output_offset);
result += fmt::format(" strip_tag: 0x{:x}\n", next_strip_nloop);
return result;
}
TypedRef MercFragment::from_ref(TypedRef tr,
const DecompilerTypeSystem& dts,
const MercFragmentControl& control,
const MercCtrlHeader& main_control) {
// fmt::print("frag::from_ref:\n{}\n", control.print());
TypedRef byte_hdr(get_field_ref(tr, "header", dts), dts.ts.lookup_type("merc-byte-header"));
header.from_ref(byte_hdr, dts);
// fmt::print("{}\n", header.print());
// all these offsets are super confusing.
// the DMA transfers require source and dest addresses/sized to have alignment of 16 bytes.
// so the data is padded.
// the transfers increase size by 4x due to VIF unpacking
// But transferring this padding exactly would result in up to 63 bytes of wasted space.
// so they cheat the destination pointers to be slightly overlapping so the next transfer
// overlaps the padding of the previous.
// as a result, the "in VU" offsets are different from "in main memory"
// let's figure it out from bones.gc asm
// u4
// lbu s0, 0(gp) (fragment.control.unsigned-four-count)
// daddiu v0, s0, 3
// srl v0, v0, 2
// dsll32 s0, v0, 4
// daddu t3, t2, s0
u32 my_u4_count = ((control.unsigned_four_count + 3) / 4) * 16;
// fmt::print("my u4: {} ({} qwc)\n", my_u4_count, my_u4_count / 16);
for (u32 w = 0; w < my_u4_count / 4; w++) {
u32 val = deref_u32(tr.ref, w);
memcpy(unsigned_four_including_header.emplace_back().data(), &val, 4);
}
// l4
// lbu s2, 1(gp)
// daddiu s0, s2, 3
// srl s0, s0, 2
// dsll32 s2, s0, 4
u32 my_l4_count = my_u4_count + ((control.lump_four_count + 3) / 4) * 16;
// fmt::print("my l4: {} ({} qwc)\n", my_l4_count, my_l4_count / 16);
// end of lump should align with mm (main memory?) fp off. which
// is used for accessing the fp data in main memory.
ASSERT(my_l4_count / 16 == header.mm_quadword_fp_off);
// row.x/y is st-vif-add from the merc-ctrl-header.
// row.z = 0x47800000, row.w = 0x4b010000
math::Vector<u32, 4> row(main_control.st_vif_add, main_control.st_vif_add, 0x47800000,
0x4b010000);
for (u32 w = my_u4_count / 4; w < my_l4_count / 4; w++) {
ASSERT((w * 4) < header.mm_quadword_fp_off * 16);
u32 val = deref_u32(tr.ref, w);
math::Vector<u8, 4> as_u8s;
memcpy(as_u8s.data(), &val, 4);
math::Vector<u32, 4> as_u32s = as_u8s.cast<u32>();
as_u32s += row;
memcpy(lump4_unpacked.emplace_back().data(), as_u32s.data(), 16);
}
// fp header
Ref fp_ref = tr.ref;
fp_ref.byte_offset += 16 * header.mm_quadword_fp_off;
fp_header.from_ref(TypedRef(fp_ref, dts.ts.lookup_type("merc-fp-header")), dts);
fp_ref.byte_offset += 16;
// fp shaders
for (int i = 0; i < fp_header.shader_cnt; i++) {
auto& shader = shaders.emplace_back();
// adgif0
u8 adgif0_addr = deref_u8(fp_ref, 8);
ASSERT(adgif0_addr == (u8)GsRegisterAddress::TEX0_1);
shader.output_offset = deref_u32(fp_ref, 3);
shader.tex0 = GsTex0(deref_u64(fp_ref, 0));
fp_ref.byte_offset += 16;
// adgif1
u8 adgif1_addr = deref_u8(fp_ref, 8);
ASSERT(adgif1_addr == (u8)GsRegisterAddress::TEX1_1);
shader.tex1 = GsTex1(deref_u64(fp_ref, 0));
u16 stash = deref_u32(fp_ref, 3);
shader.original_tex = deref_u32(fp_ref, 2);
shader.next_strip_nloop = stash & 0x7fff;
ASSERT((!!(stash & 0x8000)) == (i == fp_header.shader_cnt - 1)); // set eop on last
fp_ref.byte_offset += 16;
// adgif2
u8 adgif2_addr = deref_u8(fp_ref, 8);
ASSERT(adgif2_addr == (u8)GsRegisterAddress::MIPTBP1_1);
fp_ref.byte_offset += 16;
// adgif3
u8 adgif3_addr = deref_u8(fp_ref, 8);
ASSERT(adgif3_addr == (u8)GsRegisterAddress::CLAMP_1);
shader.clamp = deref_u64(fp_ref, 0);
fp_ref.byte_offset += 16;
// adgif4
u8 adgif4_addr = deref_u8(fp_ref, 8);
ASSERT(adgif4_addr == (u8)GsRegisterAddress::ALPHA_1);
shader.alpha = GsAlpha(deref_u64(fp_ref, 0));
fp_ref.byte_offset += 16;
}
tr.ref.byte_offset += (header.mm_quadword_size) * 16;
// let's verify the matrix slots here.
int used_matrix_slots = 0;
for (auto x : header.mat_slot) {
if (x) {
used_matrix_slots++;
} else {
break;
}
}
ASSERT(used_matrix_slots == control.mat_xfer_count);
for (int i = 0; i < used_matrix_slots; i++) {
ASSERT(header.mat_slot[i] == control.mat_dest_data.at(i).matrix_dest);
}
return tr;
}
std::string MercFragment::print() const {
std::string result;
result += fmt::format(" + BYTE-HEADER\n");
result += header.print();
result += fmt::format(" + FP-HEADER\n");
result += fp_header.print();
for (const auto& shader : shaders) {
result += fmt::format(" + SHADER\n");
result += shader.print();
}
return result;
}
void MercEffect::from_ref(TypedRef tr,
const DecompilerTypeSystem& dts,
const MercCtrlHeader& main_control) {
effect_bits = read_plain_data_field<u8>(tr, "effect-bits", dts);
frag_count = read_plain_data_field<u16>(tr, "frag-count", dts);
blend_frag_count = read_plain_data_field<u16>(tr, "blend-frag-count", dts);
tri_count = read_plain_data_field<u16>(tr, "tri-count", dts);
dvert_count = read_plain_data_field<u16>(tr, "dvert-count", dts);
envmap_usage = read_plain_data_field<u8>(tr, "envmap-usage", dts);
// do frag-ctrls
TypedRef fc(deref_label(get_field_ref(tr, "frag-ctrl", dts)),
dts.ts.lookup_type("merc-fragment-control"));
for (u32 i = 0; i < frag_count; i++) {
fc = frag_ctrl.emplace_back().from_ref(fc, dts);
}
// do actual frags
TypedRef f(deref_label(get_field_ref(tr, "frag-geo", dts)), dts.ts.lookup_type("merc-fragment"));
for (u32 i = 0; i < frag_count; i++) {
f = frag_geo.emplace_back().from_ref(f, dts, frag_ctrl.at(i), main_control);
}
}
std::string MercEffect::print() {
std::string result;
result += fmt::format(" effect_bits: {}\n", effect_bits);
result += fmt::format(" frag_count: {}\n", frag_count);
result += fmt::format(" blend_frag_count: {}\n", blend_frag_count);
result += fmt::format(" tri_count: {}\n", tri_count);
result += fmt::format(" dvert_count: {}\n", dvert_count);
result += fmt::format(" envmap_usage: {}\n", envmap_usage);
for (u32 i = 0; i < frag_count; i++) {
result += fmt::format(" +FRAGMENT {}\n", i);
result += fmt::format(" + CTRL\n");
result += frag_ctrl[i].print();
result += fmt::format(" + GEO\n");
result += frag_geo[i].print();
}
return result;
}
void MercCtrl::from_ref(TypedRef tr, const DecompilerTypeSystem& dts) {
name = read_string_field(tr, "name", dts, false);
num_joints = read_plain_data_field<s32>(tr, "num-joints", dts);
auto merc_ctrl_header_ref =
TypedRef(get_field_ref(tr, "header", dts), dts.ts.lookup_type("merc-ctrl-header"));
header.from_ref(merc_ctrl_header_ref, dts);
auto eff_ref = TypedRef(get_field_ref(tr, "effect", dts), dts.ts.lookup_type("merc-effect"));
for (u32 i = 0; i < header.effect_count; i++) {
effects.emplace_back().from_ref(eff_ref, dts, header);
eff_ref.ref.byte_offset += 32; //
}
}
std::string MercCtrl::print() {
std::string result;
result += fmt::format("name: {}\n", name);
result += fmt::format("num_joints: {}\n", num_joints);
result += "+ HEADER\n";
result += header.print();
result += "\n";
for (auto& eff : effects) {
result += fmt::format("+ EFFECT\n{}\n", eff.print());
}
return result;
}
} // namespace decompiler
+192
View File
@@ -0,0 +1,192 @@
#pragma once
#include <string>
#include <vector>
#include "common/common_types.h"
#include "decompiler/util/goal_data_reader.h"
#include "common/math/Vector.h"
#include "common/dma/gs.h"
namespace decompiler {
/*!
* per-ctrl information. the first qw is uploaded to vu1
*/
struct MercCtrlHeader {
float xyz_scale;
u32 st_magic;
u32 st_out_a;
u32 st_out_b;
u32 st_vif_add;
u16 st_int_off;
u16 st_int_scale;
u32 effect_count;
u32 blend_target_count;
u16 fragment_count;
u16 tri_count;
u8 matrix_count;
u8 shader_count;
u16 transform_vertex_count;
u16 dvert_count;
u16 one_mat_count;
u16 two_mat_count;
u16 two_mat_reuse_count;
u16 three_mat_count;
u16 three_mat_reuse_count;
u8 shader_upload_count;
u8 matrix_upload_count;
u16 same_copy_count;
u16 cross_copy_count;
u16 num_verts;
float longest_edge;
// todo (eye-ctrl merc-eye-ctrl :offset-assert 64)
u32 masks[3];
// (dummy-bytes uint8 48 :offset 32)
u32 envmap_tint;
// todo (query basic :offset 36)
u8 needs_clip;
u8 use_isometric;
u8 use_attached_shader;
u8 display_triangles;
u16 death_vertex_skip;
u16 death_start_vertex;
u32 death_effect;
u8 use_translucent;
u8 display_this_fragment;
void from_ref(TypedRef tr, const DecompilerTypeSystem& dts);
std::string print() const;
};
/*!
* the unsigned4 data of a fragment starts with this.
*/
struct MercByteHeader {
u8 srcdest_off;
u8 rgba_off;
u8 lump_off;
u8 fp_off;
u8 mat1_cnt;
u8 mat2_cnt;
u8 mat3_cnt;
u8 samecopy_cnt;
u8 crosscopy_cnt;
u8 strip_len;
u8 mm_quadword_fp_off;
u8 mm_quadword_size;
u8 perc_off;
static constexpr int MAT_SLOTS = 10;
u8 mat_slot[MAT_SLOTS];
void from_ref(TypedRef tr, const DecompilerTypeSystem& dts);
std::string print() const;
};
static_assert(sizeof(MercByteHeader) == 0x17);
/*!
* the fp data of a fragment starts with this.
*/
struct MercFpHeader {
float x_add;
float y_add;
float z_add;
u8 shader_cnt;
u8 kick_info_offset;
u8 kick_info_step;
u8 hword_cnt;
void from_ref(TypedRef tr, const DecompilerTypeSystem& dts);
std::string print() const;
};
struct MercShader {
GsTex0 tex0;
GsTex1 tex1;
// skip mip
u64 clamp;
GsAlpha alpha;
u16 output_offset;
u16 next_strip_nloop;
u32 original_tex;
std::string print() const;
};
/*!
* info about a matrix to upload.
* it maps a per-model matrix (matrix_number) to a slot in vu1 memory (matrix_dest)
*/
struct MercMatDest {
u8 matrix_number;
u8 matrix_dest;
};
/*!
* per-fragment info that doesn't go to the VU
*/
struct MercFragmentControl {
// memory layout
u8 unsigned_four_count;
u8 lump_four_count;
u8 fp_qwc;
// matrix upload info
u8 mat_xfer_count;
std::vector<MercMatDest> mat_dest_data; // inline, dynamic
TypedRef from_ref(TypedRef tr, const DecompilerTypeSystem& dts);
std::string print() const;
};
/*!
* the per-vu1 upload/call data for merc.
*/
struct MercFragment {
// part1: unsigned4. it is expanded to u32 by VIF.
MercByteHeader header;
std::vector<math::Vector<u8, 4>>
unsigned_four_including_header; // repeats the data above. always 0 in vu.
// part2: lump4. it is converted to floats by VIF.
std::vector<math::Vector<float, 4>> lump4_unpacked; // at lump_off qw in VU.
// part3: fp. it contains the fp header, shaders, and ??
MercFpHeader fp_header;
std::vector<MercShader> shaders;
std::vector<u8> extra_fp_data; // ??
TypedRef from_ref(TypedRef tr,
const DecompilerTypeSystem& dts,
const MercFragmentControl& control,
const MercCtrlHeader& main_control);
std::string print() const;
};
struct MercEffect {
//((frag-geo merc-fragment :offset-assert 0) ;; ?
std::vector<MercFragment> frag_geo;
// (frag-ctrl merc-fragment-control :offset-assert 4)
std::vector<MercFragmentControl> frag_ctrl;
// (blend-data merc-blend-data :offset-assert 8) ??
// (blend-ctrl merc-blend-ctrl :offset-assert 12) ??
// (dummy0 uint8 :offset-assert 16) ??
u8 effect_bits;
u16 frag_count;
u16 blend_frag_count;
u16 tri_count;
u16 dvert_count;
// (dummy1 uint8 :offset-assert 26) ??
u8 envmap_usage;
// (extra-info merc-extra-info :offset-assert 28) ??
void from_ref(TypedRef tr, const DecompilerTypeSystem& dts, const MercCtrlHeader& main_control);
std::string print();
};
struct MercCtrl {
std::string name;
s32 num_joints;
MercCtrlHeader header;
std::vector<MercEffect> effects;
void from_ref(TypedRef tr, const DecompilerTypeSystem& dts);
std::string print();
};
} // namespace decompiler
@@ -0,0 +1,11 @@
#pragma once
#include "common/common_types.h"
namespace level_tools {
// levels may remap textures if they provide one that should be shared
struct TextureRemap {
u32 original_texid;
u32 new_texid;
};
} // namespace level_tools
@@ -57,32 +57,6 @@ float u32_to_float(u32 in) {
return r;
}
u16 deref_u16(const Ref& ref, int array_idx) {
u32 u32_offset = array_idx / 2;
u32 u32_val = level_tools::deref_u32(ref, u32_offset);
if (array_idx & 1) {
return u32_val >> 16;
} else {
return (u16)u32_val;
}
}
s8 deref_s8(const Ref& ref, int byte) {
u32 u32_offset = byte / 4;
u32 u32_val = level_tools::deref_u32(ref, u32_offset);
s8 vals[4];
memcpy(vals, &u32_val, 4);
return vals[byte & 3];
}
u8 deref_u8(const Ref& ref, int byte) {
u32 u32_offset = byte / 4;
u32 u32_val = level_tools::deref_u32(ref, u32_offset);
u8 vals[4];
memcpy(vals, &u32_val, 4);
return vals[byte & 3];
}
void unpack_part1_collide_list_item(CollideListItem& item) {
int in_idx = 0;
int out_idx = 0;
@@ -204,7 +178,7 @@ void extract_pats(CollideListItem& item) {
for (auto& f : item.unpacked.faces) {
auto pat_idx = deref_u8(item.mesh->packed_data, byte_offset++);
u32 pat = level_tools::deref_u32(item.mesh->pat_array, pat_idx);
u32 pat = deref_u32(item.mesh->pat_array, pat_idx);
// fmt::print("pat @ {} is 0x{:x}\n", pat_idx, pat);
f.pat = pat;
}
+83 -48
View File
@@ -7,6 +7,7 @@
#include "decompiler/level_extractor/extract_tie.h"
#include "decompiler/level_extractor/extract_shrub.h"
#include "decompiler/level_extractor/extract_collide_frags.h"
#include "decompiler/level_extractor/extract_merc.h"
#include "common/util/compress.h"
#include "common/util/FileUtil.h"
#include "common/util/SimpleThreadGroup.h"
@@ -134,51 +135,32 @@ void confirm_textures_identical(const TextureDB& tex_db) {
}
}
/*!
* Extract common textures found in GAME.CGO
*/
void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name);
return;
void extract_art_groups_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::vector<level_tools::TextureRemap>& tex_remap,
const std::string& dgo_name,
tfrag3::Level& level_data,
bool dump_level) {
const auto& files = db.obj_files_by_dgo.at(dgo_name);
for (const auto& file : files) {
if (file.name.length() > 3 && !file.name.compare(file.name.length() - 3, 3, "-ag")) {
const auto& ag_file = db.lookup_record(file);
extract_merc(ag_file, tex_db, db.dts, tex_remap, level_data, dump_level);
}
}
if (tex_db.textures.size() == 0) {
lg::warn("Skipping common extract because there were no textures in the input");
return;
}
confirm_textures_identical(tex_db);
tfrag3::Level tfrag_level;
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
print_memory_usage(tfrag_level, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
file_util::write_binary_file(file_util::get_file_path({fmt::format(
"assets/{}.fr3", dgo_name.substr(0, dgo_name.length() - 4))}),
compressed.data(), compressed.size());
}
void extract_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level,
bool extract_collision) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name);
return;
}
std::vector<level_tools::TextureRemap> extract_bsp_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level,
bool extract_collision,
tfrag3::Level& level_data) {
auto bsp_rec = get_bsp_file(db.obj_files_by_dgo.at(dgo_name));
if (!bsp_rec) {
lg::warn("Skipping extract for {} because the BSP file was not found", dgo_name);
return;
return {};
}
std::string level_name = bsp_rec->name.substr(0, bsp_rec->name.length() - 4);
@@ -203,9 +185,6 @@ void extract_from_level(const ObjectFileDB& db,
"drawable-tree-tfrag", "drawable-tree-trans-tfrag", "drawable-tree-dirt-tfrag",
"drawable-tree-ice-tfrag", "drawable-tree-lowres-tfrag", "drawable-tree-lowres-trans-tfrag"};
int i = 0;
tfrag3::Level tfrag_level;
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
std::vector<const level_tools::DrawableTreeInstanceTie*> all_ties;
for (auto& draw_tree : bsp_header.drawable_tree_array.trees) {
@@ -226,19 +205,19 @@ void extract_from_level(const ObjectFileDB& db,
expected_missing_textures = it->second;
}
extract_tfrag(as_tfrag_tree, fmt::format("{}-{}", dgo_name, i++),
bsp_header.texture_remap_table, tex_db, expected_missing_textures, tfrag_level,
bsp_header.texture_remap_table, tex_db, expected_missing_textures, level_data,
dump_level);
} else if (draw_tree->my_type() == "drawable-tree-instance-tie") {
auto as_tie_tree = dynamic_cast<level_tools::DrawableTreeInstanceTie*>(draw_tree.get());
ASSERT(as_tie_tree);
extract_tie(as_tie_tree, fmt::format("{}-{}-tie", dgo_name, i++),
bsp_header.texture_remap_table, tex_db, tfrag_level, dump_level);
bsp_header.texture_remap_table, tex_db, level_data, dump_level);
} else if (draw_tree->my_type() == "drawable-tree-instance-shrub") {
auto as_shrub_tree =
dynamic_cast<level_tools::shrub_types::DrawableTreeInstanceShrub*>(draw_tree.get());
ASSERT(as_shrub_tree);
extract_shrub(as_shrub_tree, fmt::format("{}-{}-shrub", dgo_name, i++),
bsp_header.texture_remap_table, tex_db, {}, tfrag_level, dump_level);
bsp_header.texture_remap_table, tex_db, {}, level_data, dump_level);
} else if (draw_tree->my_type() == "drawable-tree-collide-fragment" && extract_collision) {
auto as_collide_frags =
dynamic_cast<level_tools::DrawableTreeCollideFragment*>(draw_tree.get());
@@ -246,14 +225,40 @@ void extract_from_level(const ObjectFileDB& db,
ASSERT(!got_collide);
got_collide = true;
extract_collide_frags(as_collide_frags, all_ties, fmt::format("{}-{}-collide", dgo_name, i++),
tfrag_level, dump_level);
level_data, dump_level);
} else {
// fmt::print(" unsupported tree {}\n", draw_tree->my_type());
}
}
level_data.level_name = level_name;
tfrag_level.level_name = level_name;
return bsp_header.texture_remap_table;
}
/*!
* Extract stuff found in GAME.CGO.
* Even though GAME.CGO isn't technically a level, the decompiler/loader treat it like one,
* but the bsp stuff is just empty. It will contain only textures/art groups.
*/
void extract_common(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
bool dump_levels) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name);
return;
}
if (tex_db.textures.size() == 0) {
lg::warn("Skipping common extract because there were no textures in the input");
return;
}
confirm_textures_identical(tex_db);
tfrag3::Level tfrag_level;
add_all_textures_from_level(tfrag_level, dgo_name, tex_db);
extract_art_groups_from_level(db, tex_db, {}, dgo_name, tfrag_level, dump_levels);
Serializer ser;
tfrag_level.serialize(ser);
auto compressed =
@@ -266,6 +271,36 @@ void extract_from_level(const ObjectFileDB& db,
compressed.data(), compressed.size());
}
void extract_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level,
bool extract_collision) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name);
return;
}
tfrag3::Level level_data;
add_all_textures_from_level(level_data, dgo_name, tex_db);
// the bsp header file data
auto tex_remap = extract_bsp_from_level(db, tex_db, dgo_name, hacks, dump_level,
extract_collision, level_data);
extract_art_groups_from_level(db, tex_db, tex_remap, dgo_name, level_data, dump_level);
Serializer ser;
level_data.serialize(ser);
auto compressed =
compression::compress_zstd(ser.get_save_result().first, ser.get_save_result().second);
print_memory_usage(level_data, ser.get_save_result().second);
fmt::print("compressed: {} -> {} ({:.2f}%)\n", ser.get_save_result().second, compressed.size(),
100.f * compressed.size() / ser.get_save_result().second);
file_util::write_binary_file(file_util::get_file_path({fmt::format(
"assets/{}.fr3", dgo_name.substr(0, dgo_name.length() - 4))}),
compressed.data(), compressed.size());
}
void extract_all_levels(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::vector<std::string>& dgo_names,
@@ -273,7 +308,7 @@ void extract_all_levels(const ObjectFileDB& db,
const DecompileHacks& hacks,
bool debug_dump_level,
bool extract_collision) {
extract_common(db, tex_db, common_name);
extract_common(db, tex_db, common_name, debug_dump_level);
SimpleThreadGroup threads;
threads.run(
[&](int idx) {
@@ -6,13 +6,6 @@
#include "decompiler/ObjectFile/ObjectFileDB.h"
namespace decompiler {
void extract_from_level(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const DecompileHacks& hacks,
bool dump_level,
bool extract_collision);
void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name);
// extract everything
void extract_all_levels(const ObjectFileDB& db,
+849
View File
@@ -0,0 +1,849 @@
#include "extract_merc.h"
#include "decompiler/util/goal_data_reader.h"
#include "decompiler/level_extractor/MercData.h"
#include "common/util/FileUtil.h"
#include "common/util/colors.h"
namespace decompiler {
// number of slots on VU1 data memory to store matrices
constexpr int MERC_VU1_MATRIX_SLOTS = 18;
// the size of each "matrix". Includes a transformation and rotation matrix (for normals)
constexpr int MERC_MATRIX_STRIDE = 7;
// converts a vu1 address to an index of a matrix slot.
// as far as I can tell, nothing in the game uses indices, they are always addresses already
u32 vu1_addr_to_matrix_slot(u32 addr) {
ASSERT(addr >= 6);
ASSERT(addr < (6 + MERC_MATRIX_STRIDE * MERC_VU1_MATRIX_SLOTS));
addr -= 6;
ASSERT((addr % MERC_MATRIX_STRIDE) == 0);
return addr / MERC_MATRIX_STRIDE;
}
u32 matrix_slot_to_vu1_addr(u32 slot) {
ASSERT(slot < MERC_VU1_MATRIX_SLOTS);
return 6 + (slot * MERC_MATRIX_STRIDE);
}
/*!
* The GS settings of a merc draw (shader + tex)
*/
struct MercGsState {
// the blending, clamp, etc settings. from adgif shaders
DrawMode mode;
// the texture to use, as a "pc combo" texture index.
u32 pc_combo_tex_id;
u64 as_u64() const { return (((u64)pc_combo_tex_id) << 32) | mode.as_int(); }
};
/*!
* This is all the state that's required to understand how to draw a vertex. including both the GS
* state, and stuff (matrices) left behind in VU memory. The matrix slots are matrix indices into
* the original bones matrices (all the matrices in the skeleton). An index of -1 indicates that
* there is no known matrix in this slot.
*/
struct MercState {
MercGsState merc_draw_mode;
// vu1_matrix_slots[x] = y
// where x is the slot in VU1 memory, and y is the matrix index for the bones/skeleton stuff.
std::array<int, MERC_VU1_MATRIX_SLOTS> vu1_matrix_slots;
MercState() { vu1_matrix_slots.fill(-1); }
};
/*!
* Required information for a "draw", consisting of a strip of triangles (represented as indices for
* OpenGL triangle strip) and the draw settings.
*/
struct MercDraw {
size_t ctrl_idx; // which merc ctrl in the level we correspond to
size_t effect_idx; // which effect within that control
size_t frag_idx; // which frag within that effect
// note that the above triple isn't enough to uniquely identify a draw - there can be multiple
// draws within a fragment.
// draw settings
MercState state;
// opengl indices. currently into a per-effect vertex list (likely to merge into a giant buffer
// eventually)
std::vector<u32> indices;
// where we would be in VU1 data memory (used in construction)
u32 vtx_offset; // relative to writing output zone
u32 vtx_nloop; // nloop that goes in the gif tag drawing us.
};
/*!
* Merc Vertex. Not the format we'll want in the game data files, but most useful as an intermediate
* when building up draws.
*/
struct MercUnpackedVtx {
int kind = 0; // 1, 2, or 3 matrix
math::Vector3f pos; // position
math::Vector3f nrm; // normal (as input to the merc math, pretty sure legnth is bogus)
math::Vector2f st; // texture coordinates
math::Vector<u8, 4> rgba;
int skel_mats[3];
float mat_weights[3];
u16 dst0;
u16 dst1;
};
/*!
* An entire merc-effect, split into draws.
* Note that copied or multiply-placed vertices will be de-deduplicated, but not identical vertices
* that actually appear in the input to merc.
*/
struct ConvertedMercEffect {
size_t ctrl_idx;
size_t effect_idx;
// draws from all fragments.
std::vector<MercDraw> draws;
std::vector<MercUnpackedVtx> vertices;
};
/*!
* Extract a merc-ctrl data structure. This is mostly just copying the GOAL data to C++ classes,
* but does include the effect of processing the DMA data through VIF.
*/
MercCtrl extract_merc_ctrl(const LinkedObjectFile& file,
const DecompilerTypeSystem& dts,
int word_idx) {
Ref ref;
ref.data = &file;
ref.seg = 0;
ref.byte_offset = word_idx * 4;
auto tr = typed_ref_from_basic(ref, dts);
MercCtrl ctrl;
ctrl.from_ref(tr, dts); // the merc data import
return ctrl;
}
/*!
* Find the word indices for the merc ctrls (the type tags)
*/
std::vector<int> find_merc_ctrls(const LinkedObjectFile& file) {
std::vector<int> result;
for (size_t i = 0; i < file.words_by_seg.at(0).size(); i++) {
const auto& word = file.words_by_seg[0][i];
if (word.kind() == LinkedWord::TYPE_PTR && word.symbol_name() == "merc-ctrl") {
result.push_back(i);
}
}
return result;
}
namespace {
/*!
* Merc models tend to have strange texture ids. I don't really understand why.
* On login, the texture is checked against a list of textures in the bsp, and replaced with this.
* It doesn't seem to be related to sharing textures between levels - the yakow texture uses this.
*/
u32 remap_texture(u32 original, const std::vector<level_tools::TextureRemap>& map) {
auto masked = original & 0xffffff00;
for (auto& t : map) {
if (t.original_texid == masked) {
return t.new_texid | 20;
}
}
return original;
}
/*!
* Set the alpha fields of a DrawMode (for PC renderers) based on gs alpha register
*/
void update_mode_from_alpha1(GsAlpha reg, DrawMode& mode) {
if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && reg.b_mode() == GsAlpha::BlendMode::DEST &&
reg.c_mode() == GsAlpha::BlendMode::SOURCE && reg.d_mode() == GsAlpha::BlendMode::DEST) {
// (Cs - Cd) * As + Cd
// Cs * As + (1 - As) * Cd
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.c_mode() == GsAlpha::BlendMode::SOURCE &&
reg.d_mode() == GsAlpha::BlendMode::DEST) {
// (Cs - 0) * As + Cd
// Cs * As + (1) * CD
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_SRC_DST);
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.d_mode() == GsAlpha::BlendMode::DEST) {
ASSERT(reg.fix() == 128);
// Cv = (Cs - 0) * FIX + Cd
// if fix = 128, it works out to 1.0
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_FIX_DST);
// src plus dest
} else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE &&
reg.b_mode() == GsAlpha::BlendMode::DEST &&
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.d_mode() == GsAlpha::BlendMode::DEST) {
// Cv = (Cs - Cd) * FIX + Cd
ASSERT(reg.fix() == 64);
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_FIX_DST);
} else if (reg.a_mode() == GsAlpha::BlendMode::DEST &&
reg.b_mode() == GsAlpha::BlendMode::SOURCE &&
reg.c_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED &&
reg.d_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED) {
}
else {
// unsupported blend: a 0 b 1 c 0 d 2 is this part of generic?
fmt::print("unsupported blend: a {} b {} c {} d {}\n", (int)reg.a_mode(), (int)reg.b_mode(),
(int)reg.c_mode(), (int)reg.d_mode());
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
// ASSERT(false);
}
}
/*!
* Convert merc shader to PC draw mode
*/
DrawMode process_draw_mode(const MercShader& info) {
DrawMode mode;
/*
* (new 'static 'gs-test
:ate #x1
:atst (gs-atest greater-equal)
:aref #x26
:zte #x1
:ztst (gs-ztest greater-equal)
)
*/
mode.enable_at();
mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL);
mode.set_aref(0x26);
mode.set_alpha_fail(GsTest::AlphaFail::KEEP);
mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL);
mode.enable_zt();
mode.enable_depth_write();
mode.set_depth_test(GsTest::ZTest::GEQUAL);
// check these
mode.disable_ab();
mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST);
// the alpha matters (maybe?)
update_mode_from_alpha1(info.alpha, mode);
// the clamp matters
if (!(info.clamp == 0b101 || info.clamp == 0 || info.clamp == 1 || info.clamp == 0b100)) {
ASSERT_MSG(false, fmt::format("clamp: 0x{:x}", info.clamp));
}
mode.set_clamp_s_enable(info.clamp & 0b1);
mode.set_clamp_t_enable(info.clamp & 0b100);
return mode;
}
float u32_as_float(u32 in) {
float out;
memcpy(&out, &in, sizeof(float));
return out;
}
u32 float_as_u32(float in) {
u32 out;
memcpy(&out, &in, sizeof(float));
return out;
}
} // namespace
/*!
* Representing something that can be written to the merc output buffer. 1 qw
*/
struct MercOutputQuadword {
enum class Kind {
INVALID, // uninitialized, or in the middle of another thing
VTX_START, // the first qw of a vertex
SHADER_START, // the first qw of a shader (the giftag)
PRIM_START // the giftag for a list of vertices
} kind = Kind::INVALID;
// if we're a vertex
u32 vtx_idx = -1; // index in the effect's vertex list.
bool adc = false; // if our adc flag is set. this differs between copies, so put it here.
// if we're a primitive giftag, the number of vertices that come after us.
u32 nloop_count = 0;
};
struct MercMemory {
std::array<MercOutputQuadword, 1024> memory;
};
/*!
* Add vertices from a fragment to memory. Emulates the unpacking.
*/
void handle_frag(const std::string& debug_name,
const MercCtrlHeader& ctrl_header,
const MercFragment& frag,
const MercFragmentControl& frag_ctrl,
const MercState& state,
std::vector<MercUnpackedVtx>& effect_vertices,
MercMemory& memory) {
(void)frag_ctrl;
(void)debug_name;
// fmt::print("handling frag: {}\n", debug_name);
// fmt::print("{}\n", frag.print());
// we'll iterate through the lump and rgba data
int lump_ptr = 0; // vertex data starts at the beginning of "lump"
int rgba_ptr = frag.header.rgba_off; // rgba is in u4's
int perc_ptr = frag.header.perc_off;
int last_mat2_perc = perc_ptr - 1;
int perc_toggle = 0;
u32 mat1_cnt = frag.header.mat1_cnt;
u32 mat12_cnt = frag.header.mat2_cnt + mat1_cnt;
u32 mat123_cnt = frag.header.mat3_cnt + mat12_cnt;
// loop through vertices.
int prev_mat0 = -1;
int prev_mat1 = -1;
int prev_mat2 = -1;
for (size_t i = 0; i < mat123_cnt; i++) {
u32 current_vtx_idx = effect_vertices.size(); // idx in effect vertex list.
auto& vtx = effect_vertices.emplace_back();
if (i < mat1_cnt) {
vtx.kind = 1; // 1 matrix
} else if (i < mat12_cnt) {
vtx.kind = 2; // 2 matrix
} else {
vtx.kind = 3;
}
// the three quadwords in the source data
auto v0 = frag.lump4_unpacked.at(lump_ptr);
auto v1 = frag.lump4_unpacked.at(lump_ptr + 1);
auto v2 = frag.lump4_unpacked.at(lump_ptr + 2);
// ilwr.x vi08, vi01 ;; load mat0 from vertex
u16 mat0_addr;
memcpy(&mat0_addr, &v0.x(), 2);
u16 mat1_addr;
memcpy(&mat1_addr, &v0.y(), 2);
if (vtx.kind == 1) {
vtx.skel_mats[0] = state.vu1_matrix_slots.at(vu1_addr_to_matrix_slot(mat0_addr));
vtx.skel_mats[1] = -1;
vtx.skel_mats[2] = -1;
vtx.mat_weights[0] = 1.f;
vtx.mat_weights[1] = 0.f;
vtx.mat_weights[2] = 0.f;
ASSERT(vtx.skel_mats[0] >= 0);
} else if (vtx.kind == 2) {
u8 m0 = mat0_addr & 0x7f;
u8 m1 = mat1_addr & 0x7f;
if (m0 == 0x7f) {
ASSERT(prev_mat0 != -1);
} else {
prev_mat0 = vu1_addr_to_matrix_slot(m0);
prev_mat1 = vu1_addr_to_matrix_slot(m1);
}
vtx.skel_mats[0] = state.vu1_matrix_slots.at(prev_mat0);
vtx.skel_mats[1] = state.vu1_matrix_slots.at(prev_mat1);
vtx.skel_mats[2] = -1;
if (m0 != 0x7f && i != mat1_cnt) {
if (perc_toggle) {
perc_ptr++;
}
perc_toggle = !perc_toggle;
}
auto perc = frag.unsigned_four_including_header.at(perc_ptr);
last_mat2_perc = perc_ptr;
if (!perc_toggle) {
vtx.mat_weights[0] = perc.x() / 255.f;
vtx.mat_weights[1] = perc.y() / 255.f;
} else {
vtx.mat_weights[0] = perc.z() / 255.f;
vtx.mat_weights[1] = perc.w() / 255.f;
}
vtx.mat_weights[2] = 0.f;
float sum = vtx.mat_weights[0] + vtx.mat_weights[1];
ASSERT(std::abs(1.f - sum) < 1e-6);
} else if (vtx.kind == 3) {
u8 m0 = mat0_addr & 0x7f;
u8 m1 = mat1_addr & 0x7f;
if (i == mat12_cnt) {
perc_ptr = last_mat2_perc;
}
if (m0 == 0x7f) {
ASSERT(prev_mat0 != -1);
} else {
perc_ptr++;
prev_mat0 = vu1_addr_to_matrix_slot(m0);
prev_mat1 = vu1_addr_to_matrix_slot(m1);
prev_mat2 = vu1_addr_to_matrix_slot(frag.unsigned_four_including_header.at(perc_ptr).w());
}
vtx.skel_mats[0] = state.vu1_matrix_slots.at(prev_mat0);
vtx.skel_mats[1] = state.vu1_matrix_slots.at(prev_mat1);
vtx.skel_mats[2] = state.vu1_matrix_slots.at(prev_mat2);
auto perc = frag.unsigned_four_including_header.at(perc_ptr);
vtx.mat_weights[0] = perc.x() / 255.f;
vtx.mat_weights[1] = perc.y() / 255.f;
vtx.mat_weights[2] = perc.z() / 255.f;
float sum = vtx.mat_weights[0] + vtx.mat_weights[1] + vtx.mat_weights[2];
ASSERT(std::abs(1.f - sum) < 1e-6);
} else {
ASSERT(false);
}
u16 mat1;
memcpy(&mat1, &v0.y(), 2);
u16 mat0;
memcpy(&mat0, &v0.x(), 2);
// add.zw vf08, vf08, vf17 ;; lump offset
// vf17 = [2048, 255, -65537, xyz-add.x] (the ?? is set per fragment)
v0.z() += -65537;
v0.w() += frag.fp_header.x_add;
// add.xyzw vf11, vf11, vf18 ;; lump offset
// vf18 = [st-out-X, st-out-X, -65537, xyz-add.y] (X = a if xtop = 0, X = b otherwise)
v1.x() += u32_as_float(ctrl_header.st_out_a);
v1.y() += u32_as_float(ctrl_header.st_out_a);
v1.z() += -65537;
v1.w() += frag.fp_header.y_add;
// add.xyzw vf14, vf14, vf19 ;; lump offset
// vf19 = [st-magic, st-magic, -65537, xyz-add.z]
v2.x() += u32_as_float(ctrl_header.st_magic);
v2.y() += u32_as_float(ctrl_header.st_magic);
v2.z() += -65537;
v2.w() += frag.fp_header.z_add;
vtx.pos = math::Vector3f(v0.w(), v1.w(), v2.w());
vtx.nrm = math::Vector3f(v0.z(), v1.z(), v2.z());
// vtx.mat1 = 0; // not used like this
vtx.dst0 = float_as_u32(v1.x()) - 371; // xtop to output buffer offset
vtx.dst1 = float_as_u32(v1.y()) - 371;
vtx.rgba = frag.unsigned_four_including_header.at(rgba_ptr);
// crazy flag logic to set adc
s16 mat1_flag = mat1;
s16 mat0_flag = mat0;
bool dst0_adc, dst1_adc;
if (vtx.kind == 1) {
ASSERT(mat1_flag == -1 || mat1_flag == 0 || mat1_flag == 1);
dst0_adc = mat1_flag <= 0;
dst1_adc = dst0_adc && (mat1_flag != 0);
dst0_adc = !dst0_adc;
dst1_adc = !dst1_adc;
} else {
// adc logic
// ilw.y vi09, -6(vi01)
s16 vi09 = mat1_flag;
// move.xyzw vf21, vf08
bool vf21_has_adc = false;
bool vf08_has_adc = false;
if (!(vi09 > 0)) {
vf21_has_adc = true;
}
// ibgtz vi09, L47
//
// addx.w vf21, vf21, vf17
//
// L47:
// ilw.x vi09, -9(vi01)
vi09 = mat0_flag;
// ftoi4.xyzw vf21, vf21
//
// sq.xyzw vf21, 2(vi10)
dst0_adc = !vf21_has_adc;
if (!(vi09 >= 0)) {
vf21_has_adc = vf08_has_adc;
}
dst1_adc = !vf21_has_adc;
// ibgez vi09, L50
//
// ftoi4.xyzw vf21, vf08
//
// L50:
// sq.xyzw vf21, 2(vi13)
// dst0_adc = mat1_flag <= 0;
// dst1_adc = dst0_adc && (mat0_flag >= 0);
// dst0_adc = !dst0_adc;
// dst1_adc = !dst1_adc;
// fmt::print("{}\n", dst1_adc);
}
// write to two spots in memory
auto& dst0_mem = memory.memory.at(vtx.dst0);
auto& dst1_mem = memory.memory.at(vtx.dst1);
dst0_mem.kind = MercOutputQuadword::Kind::VTX_START;
dst0_mem.vtx_idx = current_vtx_idx;
dst0_mem.adc = dst0_adc;
memory.memory.at(vtx.dst0 + 1).kind = MercOutputQuadword::Kind::INVALID;
memory.memory.at(vtx.dst0 + 2).kind = MercOutputQuadword::Kind::INVALID;
dst1_mem.kind = MercOutputQuadword::Kind::VTX_START;
dst1_mem.vtx_idx = current_vtx_idx;
dst1_mem.adc = dst1_adc;
memory.memory.at(vtx.dst1 + 1).kind = MercOutputQuadword::Kind::INVALID;
memory.memory.at(vtx.dst1 + 2).kind = MercOutputQuadword::Kind::INVALID;
/*
fmt::print("place vertex {} @ {} {}: {} (adc {} {}) {}\n", current_vtx_idx, vtx.dst0, vtx.dst1,
vtx.pos.to_string_aligned(), dst0_adc, dst1_adc, mat1_flag);
*/
lump_ptr += 3; // advance 3 qw
rgba_ptr++;
}
}
/*!
* Build OpenGL index list from a single GIF packet.
* TODO: should check we aren't putting in x x R x x R
*/
std::vector<u32> index_list_from_packet(u32 vtx_ptr,
u32 nloop,
const MercMemory& memory,
const std::vector<MercUnpackedVtx>& vertices) {
std::vector<u32> result;
u32 prev_vtx = UINT32_MAX;
result.push_back(UINT32_MAX);
while (nloop) {
auto& vtx_mem = memory.memory.at(vtx_ptr);
if (vtx_mem.kind == MercOutputQuadword::Kind::VTX_START) {
bool adc = vtx_mem.adc;
if (adc) {
result.push_back(vtx_mem.vtx_idx);
} else {
result.push_back(UINT32_MAX);
result.push_back(prev_vtx);
result.push_back(vtx_mem.vtx_idx);
}
prev_vtx = vtx_mem.vtx_idx;
} else {
// missing vertex!
fmt::print("MISSING VERTEX at {}\n", vtx_ptr);
result.push_back(UINT32_MAX);
}
vtx_ptr += 3;
nloop--;
}
result.push_back(UINT32_MAX);
return result;
}
/*!
* Dump draws to obj file.
*/
std::string debug_dump_to_obj(const std::vector<MercDraw>& draws,
const std::vector<MercUnpackedVtx>& vertices) {
std::string result;
std::vector<math::Vector4f> verts;
std::vector<math::Vector<int, 3>> faces;
for (auto& draw : draws) {
// add verts...
int queue[2];
int q_idx = 0;
for (size_t ii = 2; ii < draw.indices.size(); ii++) {
u32 v0 = draw.indices[ii - 2];
u32 v1 = draw.indices[ii - 1];
u32 v2 = draw.indices[ii - 0];
if (v0 != UINT32_MAX && v1 != UINT32_MAX && v2 != UINT32_MAX) {
faces.emplace_back(v0 + 1, v1 + 1, v2 + 1);
}
}
for (auto& idx : draw.indices) {
if (idx == UINT32_MAX) {
q_idx = 0;
} else {
if (q_idx >= 2) {
faces.emplace_back(queue[0] + 1, queue[1] + 1, idx + 1);
}
queue[(q_idx++) % 2] = idx;
}
}
}
for (auto& vtx : vertices) {
result += fmt::format("v {} {} {}\n", vtx.pos.x() / 1024.f, vtx.pos.y() / 1024.f,
vtx.pos.z() / 1024.f);
}
for (auto& face : faces) {
result += fmt::format("f {}/{} {}/{} {}/{}\n", face.x(), face.x(), face.y(), face.y(), face.z(),
face.z());
}
return result;
}
math::Vector4<u16> vtx_to_rgba_bone_debug(const MercUnpackedVtx& vtx) {
math::Vector4<u16> result;
result.fill(0);
for (int i = 0; i < 3; i++) {
if (vtx.skel_mats[i] == -1 || vtx.mat_weights[i] == 0) {
continue;
}
u32 rgba_packed = colors::common_colors[vtx.skel_mats[i] % colors::COLOR_COUNT];
result.x() += ((rgba_packed >> 16) & 0xff) * vtx.mat_weights[i];
result.y() += ((rgba_packed >> 8) & 0xff) * vtx.mat_weights[i];
result.z() += ((rgba_packed >> 0) & 0xff) * vtx.mat_weights[i];
}
return result;
}
std::string debug_dump_to_ply(const std::vector<MercDraw>& draws,
const std::vector<MercUnpackedVtx>& vertices) {
std::vector<math::Vector4f> verts;
std::vector<math::Vector<int, 3>> faces;
for (auto& draw : draws) {
// add verts...
for (size_t ii = 2; ii < draw.indices.size(); ii++) {
u32 v0 = draw.indices[ii - 2];
u32 v1 = draw.indices[ii - 1];
u32 v2 = draw.indices[ii - 0];
if (v0 != UINT32_MAX && v1 != UINT32_MAX && v2 != UINT32_MAX) {
faces.emplace_back(v0, v1, v2);
}
}
}
std::string result = fmt::format(
"ply\nformat ascii 1.0\nelement vertex {}\nproperty float x\nproperty float y\nproperty "
"float z\nproperty uchar red\nproperty uchar green\nproperty uchar blue\nelement face "
"{}\nproperty list uchar int vertex_index\nend_header\n",
vertices.size(), faces.size());
for (auto& vtx : vertices) {
auto rgba = vtx_to_rgba_bone_debug(vtx);
result += fmt::format("{} {} {} {} {} {}\n", vtx.pos.x() / 1024.f, vtx.pos.y() / 1024.f,
vtx.pos.z() / 1024.f, rgba[0], rgba[1], rgba[2]);
}
for (auto& face : faces) {
result += fmt::format("3 {} {} {}\n", face.x(), face.y(), face.z());
}
return result;
}
ConvertedMercEffect convert_merc_effect(const MercEffect& input_effect,
const MercCtrlHeader& ctrl_header,
const TextureDB& tdb,
const std::vector<level_tools::TextureRemap>& map,
const std::string& debug_name,
size_t ctrl_idx,
size_t effect_idx,
bool dump) {
ConvertedMercEffect result;
result.ctrl_idx = ctrl_idx;
result.effect_idx = effect_idx;
// full reset of state per effect.
// we have no idea what the previous effect draw will be - it might be given to
// mercneric.
bool shader_set = false; // no previous shader can reliably be known
MercState merc_state; // current gs settings/matrix slots
MercMemory merc_memories[2]; // double buffered output
int memory_buffer_toggle = 0; // which output we're in
for (size_t fi = 0; fi < input_effect.frag_ctrl.size(); fi++) {
const auto& frag = input_effect.frag_geo[fi];
const auto& frag_ctrl = input_effect.frag_ctrl[fi];
// first, deal with matrices
for (size_t mi = 0; mi < frag_ctrl.mat_xfer_count; mi++) {
const auto& xfer = frag_ctrl.mat_dest_data[mi];
merc_state.vu1_matrix_slots[vu1_addr_to_matrix_slot(xfer.matrix_dest)] = xfer.matrix_number;
}
if (!shader_set) {
// if we don't have a shader set, we shouldn't have anything that reuses the last shader
ASSERT(frag.header.strip_len == 0);
// and we should set the shader
ASSERT(frag.fp_header.shader_cnt > 0);
shader_set = true;
}
// run the frag.
// this will add vertices to the per-effect vertex lists and also update the merc memory
// to point to these.
handle_frag(debug_name, ctrl_header, frag, frag_ctrl, merc_state, result.vertices,
merc_memories[memory_buffer_toggle]);
// we'll add draws after this draw, but wait to actually populate the index lists until
// we've processed all the vertices.
size_t first_draw_to_update = result.draws.size();
// continuation of a previous shader
if (frag.header.strip_len) {
// add the continued draw
auto& new_draw = result.draws.emplace_back();
new_draw.ctrl_idx = ctrl_idx;
new_draw.effect_idx = effect_idx;
new_draw.frag_idx = fi;
new_draw.state = merc_state;
new_draw.vtx_offset = 1;
// just remember where it started. we might copy some vertices from a later draw in this
// fragment back to this draw, and at this point we don't know what the adc flags would be.
// (or their index, because we are deduplicated)
new_draw.vtx_nloop = frag.header.strip_len;
}
// loop over fresh shaders
for (size_t i = 0; i < frag.fp_header.shader_cnt; i++) {
const auto& shader = frag.shaders.at(i);
// update merc state from shader (will hold over to next fragment, if needed)
merc_state.merc_draw_mode.mode = process_draw_mode(shader);
u32 new_tex = remap_texture(shader.original_tex, map);
// texture the texture page/texture index, and convert to a PC port texture ID
u32 tpage = new_tex >> 20;
u32 tidx = (new_tex >> 8) & 0b1111'1111'1111;
u32 tex_combo = (((u32)tpage) << 16) | tidx;
// look up the texture to make sure it's valid
auto tex = tdb.textures.find(tex_combo);
ASSERT(tex != tdb.textures.end());
// remember the texture id
merc_state.merc_draw_mode.pc_combo_tex_id = tex_combo;
// add the draw
auto& new_draw = result.draws.emplace_back();
new_draw.ctrl_idx = ctrl_idx;
new_draw.effect_idx = effect_idx;
new_draw.frag_idx = fi;
new_draw.state = merc_state;
// write the shader to memory
merc_memories[memory_buffer_toggle].memory.at(shader.output_offset).kind =
MercOutputQuadword::Kind::SHADER_START;
for (int mi = 1; mi < 6; mi++) {
// fill 5qw of adgif data with invalid. make sure we don't read verts from here
merc_memories[memory_buffer_toggle].memory.at(shader.output_offset + mi).kind =
MercOutputQuadword::Kind::INVALID;
}
// write the giftag for primitives
auto& prim_packet = merc_memories[memory_buffer_toggle].memory.at(shader.output_offset + 6);
prim_packet.kind = MercOutputQuadword::Kind::PRIM_START;
prim_packet.nloop_count = shader.next_strip_nloop;
// update draw from hidden fields in adgif.
new_draw.vtx_offset = shader.output_offset + 7;
new_draw.vtx_nloop = shader.next_strip_nloop;
}
// copy from other places inside this output buffer
u32 srcdst_ptr = frag.header.srcdest_off;
for (u32 sci = 0; sci < frag.header.samecopy_cnt; sci++) {
auto& cpy = frag.unsigned_four_including_header[srcdst_ptr];
// fmt::print("sci: {}\n", cpy.to_string_hex_byte());
u32 src = cpy[0];
auto& vert = merc_memories[memory_buffer_toggle].memory.at(src);
u32 dst = cpy[1];
auto& dvert = merc_memories[memory_buffer_toggle].memory.at(dst);
if (vert.kind == MercOutputQuadword::Kind::VTX_START) {
dvert = vert;
if (cpy[3]) {
// dvert.adc = true;
dvert.adc = !dvert.adc;
}
} else {
fmt::print("sc missing vert\n");
dvert.kind = MercOutputQuadword::Kind::INVALID;
}
srcdst_ptr++;
}
// "cross" copy from the other output buffer
for (u32 cci = 0; cci < frag.header.crosscopy_cnt; cci++) {
auto& cpy = frag.unsigned_four_including_header[srcdst_ptr];
// fmt::print("cci: {}\n", cpy.to_string_hex_byte());
u32 src = cpy[0];
auto& vert = merc_memories[memory_buffer_toggle ^ 1].memory.at(src);
u32 dst = cpy[1];
auto& dvert = merc_memories[memory_buffer_toggle].memory.at(dst);
if (vert.kind == MercOutputQuadword::Kind::VTX_START) {
dvert = vert;
if (cpy[3]) {
// dvert.adc = true;
dvert.adc = !dvert.adc;
}
} else {
fmt::print("cc missing vert\n");
dvert.kind = MercOutputQuadword::Kind::INVALID;
}
srcdst_ptr++;
}
// now that we've copied all vertices, create index lists.
for (size_t i = first_draw_to_update; i < result.draws.size(); i++) {
auto& draw = result.draws[i];
draw.indices = index_list_from_packet(draw.vtx_offset, draw.vtx_nloop,
merc_memories[memory_buffer_toggle], result.vertices);
}
memory_buffer_toggle ^= 1;
}
if (dump) {
file_util::write_text_file(
file_util::get_file_path(
{"debug_out/merc", fmt::format("{}_{}.ply", debug_name, effect_idx)}),
debug_dump_to_ply(result.draws, result.vertices));
}
return result;
}
/*!
* Top-level merc extraction
*/
void extract_merc(const ObjectFileData& ag_data,
const TextureDB& tex_db,
const DecompilerTypeSystem& dts,
const std::vector<level_tools::TextureRemap>& map,
tfrag3::Level& /*out*/,
bool dump_level) {
if (dump_level) {
file_util::create_dir_if_needed(file_util::get_file_path({"debug_out/merc"}));
}
// find all merc-ctrls in the object file
auto ctrl_locations = find_merc_ctrls(ag_data.linked_data);
// extract them. this does very basic unpacking of data, as done by the VIF/DMA on PS2.
std::vector<MercCtrl> ctrls;
for (auto location : ctrl_locations) {
auto ctrl = extract_merc_ctrl(ag_data.linked_data, dts, location);
ctrls.push_back(ctrl);
}
// extract draws. this does no regrouping yet.
std::vector<ConvertedMercEffect> all_effects;
for (size_t ci = 0; ci < ctrls.size(); ci++) {
for (size_t ei = 0; ei < ctrls[ci].effects.size(); ei++) {
all_effects.push_back(convert_merc_effect(ctrls[ci].effects[ei], ctrls[ci].header, tex_db,
map, ctrls[ci].name, ci, ei, dump_level));
}
}
}
} // namespace decompiler
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "decompiler/data/TextureDB.h"
#include "common/custom_data/Tfrag3Data.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "decompiler/level_extractor/common_formats.h"
namespace decompiler {
void extract_merc(const ObjectFileData& ag_data,
const TextureDB& tex_db,
const DecompilerTypeSystem& dts,
const std::vector<level_tools::TextureRemap>& map,
tfrag3::Level& out,
bool dump_level);
}
+60
View File
@@ -280,4 +280,64 @@ std::string inspect_ref(const Ref& ref) {
default:
ASSERT(false);
}
}
u32 deref_u32(const Ref& ref, int word_offset) {
if ((ref.byte_offset % 4) != 0) {
throw Error("deref_u32 bad alignment");
}
const auto& word = ref.data->words_by_seg.at(ref.seg).at(word_offset + (ref.byte_offset / 4));
if (word.kind() != decompiler::LinkedWord::PLAIN_DATA) {
throw Error("deref_u32 bad kind: {}", (int)word.kind());
}
return word.data;
}
u64 deref_u64(const Ref& ref, int dw_offset) {
if ((ref.byte_offset % 8) != 0) {
throw Error("deref_u64 bad alignment");
}
const auto& word0 = ref.data->words_by_seg.at(ref.seg).at(ref.byte_offset / 4 + (dw_offset * 2));
const auto& word1 =
ref.data->words_by_seg.at(ref.seg).at(1 + (ref.byte_offset / 4) + (dw_offset * 2));
if (word0.kind() != decompiler::LinkedWord::PLAIN_DATA) {
throw Error("deref_u64 bad kind: {}", (int)word0.kind());
}
if (word1.kind() != decompiler::LinkedWord::PLAIN_DATA) {
throw Error("deref_u64 bad kind: {}", (int)word1.kind());
}
u64 result = word1.data;
result <<= 32;
result |= word0.data;
return result;
}
u16 deref_u16(const Ref& ref, int array_idx) {
u32 u32_offset = array_idx / 2;
u32 u32_val = deref_u32(ref, u32_offset);
if (array_idx & 1) {
return u32_val >> 16;
} else {
return (u16)u32_val;
}
}
s8 deref_s8(const Ref& ref, int byte) {
u32 u32_offset = byte / 4;
u32 u32_val = deref_u32(ref, u32_offset);
s8 vals[4];
memcpy(vals, &u32_val, 4);
return vals[byte & 3];
}
u8 deref_u8(const Ref& ref, int byte) {
u32 total_offset = ref.byte_offset + byte;
const auto& word = ref.data->words_by_seg.at(ref.seg).at(total_offset / 4);
if (word.kind() != decompiler::LinkedWord::PLAIN_DATA) {
throw Error("deref_u32 bad kind: {}", (int)word.kind());
}
u8 vals[4];
memcpy(vals, &word.data, 4);
return vals[total_offset & 3];
}
+8 -2
View File
@@ -20,6 +20,9 @@ struct Ref {
struct TypedRef {
Ref ref;
Type* type = nullptr;
TypedRef() = default;
TypedRef(const Ref& r, Type* t) : ref(r), type(t) {}
};
void read_plain_data_field(const TypedRef& object,
@@ -34,7 +37,6 @@ T read_plain_data_field(const TypedRef& object,
const decompiler::DecompilerTypeSystem& dts) {
u8 data[sizeof(T)];
read_plain_data_field(object, field_name, dts, sizeof(T), data);
T result;
memcpy(&result, data, sizeof(T));
return result;
@@ -68,5 +70,9 @@ std::string get_type_of_basic(const Ref& object);
TypedRef typed_ref_from_basic(const Ref& object, const decompiler::DecompilerTypeSystem& dts);
Ref deref_label(const Ref& object);
u32 deref_u32(const Ref& ref, int word_offset);
u16 deref_u16(const Ref& ref, int array_idx);
s8 deref_s8(const Ref& ref, int byte);
u8 deref_u8(const Ref& ref, int byte);
u64 deref_u64(const Ref& ref, int dw_offset);
std::string inspect_ref(const Ref& ref);
+2 -2
View File
@@ -444,10 +444,10 @@ goos::Object decompile_sparticle_sound_spec(const std::vector<LinkedWord>& /*wor
goos::Object decompile_sparticle_group_item(const TypeSpec& type,
const DecompilerLabel& label,
const std::vector<DecompilerLabel>& labels,
const std::vector<DecompilerLabel>& /*labels*/,
const std::vector<std::vector<LinkedWord>>& words,
const TypeSystem& ts,
const LinkedObjectFile* file) {
const LinkedObjectFile* /*file*/) {
// auto normal = decompile_structure(type, label, labels, words, ts, file, false);
// fmt::print("Doing: {}\n", normal.print());
auto uncast_type_info = ts.lookup_type(type);
+420
View File
@@ -0,0 +1,420 @@
Loop over effect (skip those flagged with use-mercneric)
Loop over fragments
First thing is the "row" data.
row.x/y is st-vif-add from the merc-ctrl-header.
row.z = 0x47800000, row.w = 0x4b010000
Next is the unsigned-four.
This is unpacked with unpack8.
It always goes to 140 in the VU memory.
The source data is just the merc-fragment (I believe it includes merc-byte-header)
After is the lump-four. This has a STMOD enabled and is unpack8.
It goes after the unsigned-four (variable size) in VU memory.
The source data is after. Rounding in static data is ((u4c + 3) >> 2) << 4.
After is the Floating Point data. This is copied as unpack32.
_only_ on the first fragment of an effect, there's an upload to 132 of 8 qw:
the first 7 are lights.
the final is the first quadword of the merc-ctrl-header (xyz-scale, st-magic, st-out-a, st-out-b)
there are secrets hidden in the lights:
- light 0's w is some flag with ignore alpha in it.
Next is (optional) matrix uploads.
There is a loop of transfers. These are all size 7 qw.
Next is the MSCAL!
It has a different number of the first fragment of an effect.
This tells merc to load the light stuff.
End Loop over fragments
Increment effect
Increment effect info
Decrement effect count
update some next-merc thing in the scratchpad
# Merc Renderer
## Memory layout
Unpacks adgif shaders/giftags to the output memory.
Can reuse the last shader from the last frag in the effect.
## Matrix Setup
The matrix contains a "tmat" and "nmat". The "tmat" transforms the point and the "nmat" rotates the normals.
Matrices that are freshly uploaded with this fragment are preprocessed to include the effect of the perspective matrix. The inputs are in registers 8, 10, 12, 25, and the outputs are 9, 11, 13, 26. The output is written over the input.
```
mula.xyzw ACC, vf15, vf08
maddz.xyzw vf09, vf16, vf08
mula.xyzw ACC, vf15, vf10
maddz.xyzw vf11, vf16, vf10
mula.xyzw ACC, vf15, vf12
maddz.xyzw vf13, vf16, vf12
addax.xyzw vf20, vf00
madda.xyzw ACC, vf27, vf25
maddz.xyzw vf26, vf28, vf25
```
with
```
vf15 = [spdx, spdy, spdz, 0]
vf16 = [0, 0, 0, spdw]
vf27 = [pdx, pdy, pdz, 0]
vf28 = [0, 0, 0, pdw]
```
(see `merc_asm.asm` for how to compute these in more detail)
## Vertex layout
The "lump" data contains the vertices. The layout is:
```
mat0, mat1, nrmx, posx
dst0, dst1, nrmy, posy
texs, text, nrmz, posz
```
It begins with `mat1-cnt` vertices that are deformed by only a single matrix.
The `rgba-offset` points to somewhere in the u4 data. Each vertex has a single rgba, stored as unpacked u8's.
## Mat1 Loop
`vi01` is the lump pointer that reads vertices, `vf17`, `vf18`, `vf19` are the "lump offsets" to be applied to the unpacked vertices.
The operations are:
```asm
ilwr.x vi08, vi01 ;; load mat0 from vertex
lqi.xyzw vf08, vi01 ;; load vertex qw 0
lqi.xyzw vf11, vi01 ;; load vertex qw 1
lqi.xyzw vf14, vi01 ;; load vertex qw 2
lq.xyz vf29, 4(vi08) ;; load nmat0
lq.xyz vf30, 5(vi08) ;; load nmat1
lq.xyzw vf31, 6(vi08) ;; load nmat2
add.zw vf08, vf08, vf17 ;; lump offset
add.xyzw vf11, vf11, vf18 ;; lump offset
add.xyzw vf14, vf14, vf19 ;; lump offset
mtir vi10, vf11.x ;; get dest0
mtir vi13, vf11.y ;; get dest1
;; rotate normal
mulaz.xyzw ACC, vf29, vf08
maddaz.xyzw ACC, vf30, vf11
maddz.xyz vf11, vf31, vf14
;; load tmat
lq.xyzw vf25, 0(vi08)
lq.xyzw vf26, 1(vi08)
lq.xyzw vf27, 2(vi08)
lq.xyzw vf28, 3(vi08)
;; get normal 1/length
erleng.xyz P, vf11
;; transform point
mulaw.xyzw ACC, vf25, vf08
maddaw.xyzw ACC, vf26, vf11
maddw.xyzw vf08, vf27, vf14
add.xyzw vf08, vf08, vf28
;; clear nrmz
mr32.z vf14, vf00
;; ONLY if merc prime miniw.w vf08, vf08, vf01
;; perspective divide
div Q, vf01.w, vf08.w
mul.xyz vf08, vf08, Q
mul.xyzw vf14, vf14, Q
;; load rgba
lqi.xyzw vf23, vi03
;; hvdf offset
add.xyzw vf08, vf08, vf22
;; normalize normal
mfp.w vf20, P
mulw.xyzw vf11, vf11, vf20
;; fog max
miniw.w vf08, vf08, vf03
;; fetch mat1 (note that vi01 is incremented 3x in pipeline)
ilw.y vi09, -6(vi01)
;; dot product with light
mulax.xyzw ACC, vf01, vf11
madday.xyzw ACC, vf02, vf11
maddz.xyzw vf11, vf03, vf11
;; fog min
maxw.w vf08, vf08, vf02
;; rgba itof
itof0.xyzw vf23, vf23
;; light clamp
maxx.xyzw vf11, vf11, vf00
move.xyzw vf21, vf08
;; color the lights
mulax.xyzw ACC, vf04, vf11
madday.xyzw ACC, vf05, vf11
maddaz.xyzw ACC, vf06, vf11
;; IF vi09 <= 0
addx.w vf21, vf21, vf17
;; add ambient lighting
maddw.xyzw vf11, vf07, vf00
;; ftoi the vertex position
ftoi4.xyzw vf21, vf21
;; apply vertex color
mul.xyzw vf11, vf11, vf23
;; store dest
sq.xyzw vf21, 2(vi10)
;; IF vi09 == 0
ftoi4.xyzw vf21, vf08
;; store st
sq.xyzw vf14, 0(vi10)
sq.xyzw vf14, 0(vi13)
;; store second position
sq.xyzw vf21, 2(vi13)
;; final light
miniy.xyzw vf11, vf11, vf17
ftoi0.xyzw vf11, vf11
sq.xyzw vf11, 1(vi10)
sq.xyzw vf11, 1(vi13)
```
Note: it might be that the last vertex can't change its adc flag for dst2. Maybe only in some cases. Worth checking more if there are stripping issues.
## Mat2 Loop
NOTE: we might need to advance perc by 1 at the beginning if there are any mat0's.
```asm
;; compute perc ptr
ilw.x vi02, 3(vi12)
iadd vi02, vi02, vi12
;; load vertex
lqi.xyzw vf08, vi01
lqi.xyzw vf11, vi01
lqi.xyzw vf14, vi01
;; and perc
lqi.xyzw vf24, vi02
;; extract mat idx
mtir vi10, vf08.x
mtir vi13, vf08.y
;; convert perc
itof0.xyzw vf24, vf24
;; lump offset
add.zw vf08, vf08, vf17
add.xyzw vf11, vf11, vf18
add.xyzw vf14, vf14, vf19
;; mask off sign bit of mat0 and mat1
iand vi10, vi10, vi05 (vi05 = 0x7f) <- looks like a 0 here means "reuse prev mat"
iand vi13, vi13, vi05
;; scale perc
mulw.xyzw vf24, vf24, vf29 ;; vf29.w = 0.003921569
;; load matrices
lq.xyzw vf20, 0(vi10) ;; tmat0.0
lq.xyzw vf25, 0(vi13) ;; tmat1.0
lq.xyzw vf23, 1(vi10) ;; tmat0.1
lq.xyzw vf26, 1(vi13) ;; tmat1.1
lq.xyzw vf20, 2(vi10) ;; tmat0.2
lq.xyzw vf27, 2(vi13) ;; tmat1.2
lq.xyzw vf23, 3(vi10) ;; tmat0.3
lq.xyzw vf28, 3(vi13) ;; tmat1.3
lq.xyzw vf20, 4(vi10) ;; nmat0.0
lq.xyz vf29, 4(vi13) ;; nmat1.0
lq.xyzw vf23, 5(vi10) ;; nmat0.1
lq.xyz vf30, 5(vi13) ;; nmat1.1
lq.xyzw vf20, 6(vi10) ;; nmat0.2
lq.xyzw vf31, 6(vi13) ;; nmat1.2
;; multiply rows by perc.
;; mat0 uses perc.x, mat0 uses perc.y. Matrices are added.
;; ex: tmat2 = tmat0.2 * perc.x + tmat1.2 * perc.y
Results are
vf25 = tmat0
vf26 = tmat1
vf27 = tmat2
vf28 = tmat3
vf29 = nmat0
vf30 = nmat1
vf31 = nmat2
;; rotate normal
mulaz.xyzw ACC, vf29, vf08
maddaz.xyzw ACC, vf30, vf11
maddz.xyz vf11, vf31, vf14
;; transform point
mulaw.xyzw ACC, vf25, vf08
maddaw.xyzw ACC, vf26, vf11
maddw.xyzw vf08, vf27, vf14
add.xyzw vf08, vf08, vf28
;; length of normal
erleng.xyz P, vf11
;; rgba offset
ilwr.y vi03, vi12
iadd vi03, vi03, vi12
;; persepctive divide
div Q, vf01.w, vf08.w
mul.xyz vf08, vf08, Q
mul.xyzw vf14, vf14, Q
;; load rgba
lqi.xyzw vf23, vi03
;; hvdf offset
add.xyzw vf08, vf08, vf22
;; normalize normal
mfp.w vf20, P
mulw.xyzw vf11, vf11, vf20
;; fog
miniw.w vf08, vf08, vf03
maxw.w vf08, vf08, vf02
;; go get mat1 again
ilw.y vi09, -6(vi01)
;; light dot product
mulax.xyzw ACC, vf01, vf11
madday.xyzw ACC, vf02, vf11
maddz.xyzw vf11, vf03, vf11
;; vertex color convert
itof0.xyzw vf23, vf23
;; light clamp
maxx.xyzw vf11, vf11, vf00
;; adc logic
ilw.y vi09, -6(vi01)
move.xyzw vf21, vf08
ibgtz vi09, L47
addx.w vf21, vf21, vf17
L47:
ilw.x vi09, -9(vi01)
ftoi4.xyzw vf21, vf21
ilw.x vi09, -9(vi01)
sq.xyzw vf21, 2(vi10)
ibgez vi09, L50
ftoi4.xyzw vf21, vf08
L50:
sq.xyzw vf21, 2(vi13)
```
## Final Copies
assuming no mercprime, leaving out pipe flush (which is a tangled mess to flush the pipes of whatever of the 3 different loops were)
```
;; vi08 = mercprime flag
ilw.w vi08, 1(vi00)
;; find the byte header again
xtop vi02
iaddiu vi04, vi02, 0x8c
;; srcdest-off
ilwr.x vi05, vi04
;; samecopy-cnt
ilw.w vi06, 1(vi04)
;; crosscopy-cnt
ilw.x vi07, 2(vi04)
;; zero out vf25, vf26
minix.xyzw vf25, vf00, vf00
minix.xyzw vf26, vf00, vf00
;; compute srcdest table address
iadd vi05, vi05, vi04
;; compute output zone
iaddiu vi04, vi02, 0x173
;; compute srcdest cross copy table address
iadd vi06, vi06, vi05
;; compute end of cross copy
iadd vi07, vi07, vi06
;; compute address of the _old_ output buffer
iaddiu vi08, vi00, 0x1ba
isub vi08, vi08, vi02
iaddiu vi08, vi08, 0x173
;; move addrs to vf
mfir.x vf25, vi04 ;; vf25.x = output zone
mfir.y vf25, vi04 ;; vf25.y = output zone
mfir.x vf26, vi08 ;; vf26.x = old output zone
mfir.y vf26, vi04 ;; vf26.y = output zone
;; set up
maxx.xyzw vf13, vf13, vf00 ;; vf13 = 0
maxi.xy vf27, vf00, I, I = 8388608.0 ;; vf27.xy = [8388608.0 8388608.0]
maxi.w vf27, vf00, I, I = 256 ;; vf27.w = 256
itof0.xyzw vf25, vf25
itof0.xyzw vf26, vf26
ior vi02, vi05, vi00 ;; vi02 = srcdst table
add.xyzw vf25, vf25, vf27 ;; hack float add trick
add.xyzw vf26, vf26, vf27
miniy.xyzw vf13, vf13, vf17 ;; float tricks
ibne vi06, vi05, L150 ;; branch if there are samecopys
ior vi06, vi07, vi00 | max.xyzw vf25, vf26, vf26
L150:
;; load from copy table
lqi.xyzw vf27, vi05
;; table to float
itof0.xyzw vf27, vf27
```
File diff suppressed because it is too large Load Diff
+3
View File
@@ -578,6 +578,9 @@ uint32_t link_control::work_v2() {
if (m_segment_process == 0) {
m_heap_gap =
m_object_data - m_heap->current; // distance between end of heap and start of object
if (m_object_data.offset < m_heap->current.offset) {
ASSERT(false);
}
}
if (m_heap_gap <
+1 -1
View File
@@ -853,7 +853,7 @@ u32 MC_check_result() {
* You can call this at any time.
* The slot includes the four save slots (8 banks), and a few other files.
*/
void MC_get_status(s32 slot, Ptr<mc_slot_info> info) {
void MC_get_status(s32 /*slot*/, Ptr<mc_slot_info> info) {
// slot is ignored, so you'll get the same thing regardless of what slot you pick
info->handle = 0;
+133 -105
View File
@@ -714,20 +714,20 @@ u64 execute(void* ctxt) {
c->sq(gp, 112, sp); // sq gp, 112(sp)
c->mov64(t8, a3); // or t8, a3, r0
c->mov64(v1, t0); // or v1, t0, r0
c->lui(t0, 4096); // lui t0, 4096
c->lui(t1, 18304); // lui t1, 18304
c->lui(t0, 4096); // lui t0, 4096 0x1000
c->lui(t1, 18304); // lui t1, 18304 0x4780
c->daddiu(t0, t0, 1); // daddiu t0, t0, 1
c->dsll32(t1, t1, 0); // dsll32 t1, t1, 0
c->lui(a3, 12288); // lui a3, 12288
c->lui(t7, 19201); // lui t7, 19201
c->lui(a3, 12288); // lui a3, 12288 0x3000
c->lui(t7, 19201); // lui t7, 19201 0x4B01
c->pcpyld(t0, a3, t0); // pcpyld t0, a3, t0
c->lbu(a3, 58, a0); // lbu a3, 58(a0)
c->pcpyld(t1, t7, t1); // pcpyld t1, t7, t1
c->lui(t2, 28160); // lui t2, 28160
c->lui(t2, 28160); // lui t2, 28160 0x6E00
c->addiu(t7, r0, 8); // addiu t7, r0, 8
c->multu3(a3, a3, t7); // multu3 a3, a3, t7
c->lui(t3, 1280); // lui t3, 1280
c->lui(t4, 27648); // lui t4, 27648
c->lui(t3, 1280); // lui t3, 1280 0x500
c->lui(t4, 27648); // lui t4, 27648 0x6C00
c->dsll32(t2, t2, 0); // dsll32 t2, t2, 0
c->dsll32(t4, t4, 0); // dsll32 t4, t4, 0
c->daddu(t4, t4, t3); // daddu t4, t4, t3
@@ -735,11 +735,11 @@ u64 execute(void* ctxt) {
c->daddiu(t3, t3, 1); // daddiu t3, t3, 1
c->daddu(a0, a3, a0); // daddu a0, a3, a0
c->pcpyld(t2, t2, r0); // pcpyld t2, t2, r0
c->lw(t7, 24, a0); // lw t7, 24(a0)
c->lw(t7, 24, a0); // lw t7, 24(a0) // load the merc-ctrl!
c->pcpyld(t3, t3, r0); // pcpyld t3, t3, r0
c->pcpyld(t4, t4, r0); // pcpyld t4, t4, r0
c->mov64(a0, a2); // or a0, a2, r0
c->lui(t5, 12288); // lui t5, 12288
c->lui(t5, 12288); // lui t5, 12288 // 0x3000
c->lui(t6, 4096); // lui t6, 4096
c->daddiu(t5, t5, 7); // daddiu t5, t5, 7
c->lui(t9, 5120); // lui t9, 5120
@@ -748,144 +748,172 @@ u64 execute(void* ctxt) {
c->dsll32(a3, a3, 0); // dsll32 a3, a3, 0
c->dsll32(t9, t8, 0); // dsll32 t9, t8, 0
c->pcpyld(t5, a3, t5); // pcpyld t5, a3, t5
c->lwu(t8, 52, t7); // lwu t8, 52(t7)
c->lwu(t8, 52, t7); // lwu t8, 52(t7) // effect count.
c->pcpyld(t6, t9, t6); // pcpyld t6, t9, t6
c->daddiu(t9, t7, 108); // daddiu t9, t7, 108
c->daddiu(t9, t7, 108); // daddiu t9, t7, 108 // the actual merc-effect
c->load_symbol(a3, cache.merc_bucket_info); // lw a3, *merc-bucket-info*(s7)
c->daddiu(ra, a3, 124); // daddiu ra, a3, 124
c->daddiu(ra, a3, 124); // daddiu ra, a3, 124 // effect bucket infos
// effect loop!
block_1:
c->lbu(gp, 4, ra); // lbu gp, 4(ra)
c->lbu(gp, 4, ra); // lbu gp, 4(ra) effect.use-mercneric
c->load_symbol(a3, cache.merc_global_stats); // lw a3, *merc-global-stats*(s7)
c->daddu(a3, r0, a3); // daddu a3, r0, a3
bc = c->sgpr64(gp) != 0; // bne gp, r0, L77
c->lhu(s4, 2, a3); // lhu s4, 2(a3)
if (bc) {goto block_11;} // branch non-likely
c->lhu(s4, 2, a3); // lhu s4, 2(a3) merc-global-stats.merc.fragments
if (bc) {goto block_11;} // branch non-likely skip mercneric effects
c->lhu(s3, 18, t9); // lhu s3, 18(t9)
c->lwu(gp, 4, a3); // lwu gp, 4(a3)
c->lhu(s5, 22, t9); // lhu s5, 22(t9)
c->daddu(s4, s4, s3); // daddu s4, s4, s3
c->lwu(s3, 8, a3); // lwu s3, 8(a3)
c->lhu(s2, 24, t9); // lhu s2, 24(t9)
c->daddu(gp, gp, s5); // daddu gp, gp, s5
c->sh(s4, 2, a3); // sh s4, 2(a3)
c->sw(gp, 4, a3); // sw gp, 4(a3)
c->daddu(s5, s3, s2); // daddu s5, s3, s2
c->lwu(t2, 0, t9); // lwu t2, 0(t9)
c->lwu(gp, 4, t9); // lwu gp, 4(t9)
c->lui(s4, 12288); // lui s4, 12288
c->dsll32(t2, t2, 0); // dsll32 t2, t2, 0
c->sw(s5, 8, a3); // sw s5, 8(a3)
c->or_(t2, t2, s4); // or t2, t2, s4
c->addiu(s5, r0, 0); // addiu s5, r0, 0
c->lhu(s4, 18, t9); // lhu s4, 18(t9)
// ra is the effect-info
// t9 is the effect
// a3 is merc-global-stats
c->lhu(s3, 18, t9); // lhu s3, 18(t9) // s3 = effect.frag-count
c->lwu(gp, 4, a3); // lwu gp, 4(a3) // gp = global-stats.merc.tris
c->lhu(s5, 22, t9); // lhu s5, 22(t9) // s5 = merc-effect.tri-count
c->daddu(s4, s4, s3); // daddu s4, s4, s3 // inc frag count
c->lwu(s3, 8, a3); // lwu s3, 8(a3) // s3 = global-stats.merc.dverts
c->lhu(s2, 24, t9); // lhu s2, 24(t9) // s2 = merc-effect.dvert-count
c->daddu(gp, gp, s5); // daddu gp, gp, s5 // inc tri count
c->sh(s4, 2, a3); // sh s4, 2(a3) // store frag count
c->sw(gp, 4, a3); // sw gp, 4(a3) // store tri count
c->daddu(s5, s3, s2); // daddu s5, s3, s2 // inc dvert count
c->lwu(t2, 0, t9); // lwu t2, 0(t9) // t2 = merc-fragment
c->lwu(gp, 4, t9); // lwu gp, 4(t9) // gp = merc-fragment-control
c->lui(s4, 12288); // lui s4, 12288 // dma thing?
c->dsll32(t2, t2, 0); // dsll32 t2, t2, 0 // dma merc-fragment
c->sw(s5, 8, a3); // sw s5, 8(a3) // store dvert stats
c->or_(t2, t2, s4); // or t2, t2, s4 // lower 64 of dma tag?
c->addiu(s5, r0, 0); // addiu s5, r0, 0 // s5 = 0 (frag counter)
c->lhu(s4, 18, t9); // lhu s4, 18(t9) // s4 = effect.frag-count
block_3:
c->lbu(s0, 0, gp); // lbu s0, 0(gp)
// fragment loop
// (note: frag 0 gets added before header)
// 0 = strow tag
// 16 = strow data
// 32 =
// DMA: 0xe1e903000000f
// vif0: 0x0
// vif1: 0x6e39c08c
// unpack8 (top true): 140, 57
block_3:
c->lbu(s0, 0, gp); // lbu s0, 0(gp) // s0 = fragment-control.unsigned-four-count (4-byte word count in EE mem)
// nop // sll r0, r0, 0
c->lbu(s2, 1, gp); // lbu s2, 1(gp)
c->xori(s1, r0, 49292); // xori s1, r0, 49292
c->lbu(s3, 2, gp); // lbu s3, 2(gp)
c->daddiu(v0, s0, 3); // daddiu v0, s0, 3
c->lw(a3, 44, t7); // lw a3, 44(t7)
c->srl(v0, v0, 2); // srl v0, v0, 2
c->sq(t0, 0, a2); // sq t0, 0(a2)
c->xor_(t2, t2, v0); // xor t2, t2, v0
c->sq(t2, 32, a2); // sq t2, 32(a2)
c->xor_(t2, t2, v0); // xor t2, t2, v0
c->sh(s1, 44, a2); // sh s1, 44(a2)
c->daddu(s1, s1, s0); // daddu s1, s1, s0
c->sb(s0, 46, a2); // sb s0, 46(a2)
c->dsll32(s0, v0, 4); // dsll32 s0, v0, 4
c->daddu(t3, t2, s0); // daddu t3, t2, s0
c->daddiu(s0, s2, 3); // daddiu s0, s2, 3
c->sw(a3, 12, a2); // sw a3, 12(a2)
c->srl(s0, s0, 2); // srl s0, s0, 2
c->sq(t1, 16, a2); // sq t1, 16(a2)
c->lbu(s2, 1, gp); // lbu s2, 1(gp) // s2 = fragment-control.lump-four-count
c->xori(s1, r0, 49292); // xori s1, r0, 49292 // maybe vif crap 0xC08C
c->lbu(s3, 2, gp); // lbu s3, 2(gp) // s3 = fragment-control.fp-qwc
c->daddiu(v0, s0, 3); // daddiu v0, s0, 3 // v0 = unsigned-four-count + 3
c->lw(a3, 44, t7); // lw a3, 44(t7) // a3 = merc-ctrl.header.st-vif-add
c->srl(v0, v0, 2); // srl v0, v0, 2 // v0 = qwc to transfer for unsigned-four-count
c->sq(t0, 0, a2); // sq t0, 0(a2) // dma/vif template for strow setup
c->xor_(t2, t2, v0); // xor t2, t2, v0 // add qwc
c->sq(t2, 32, a2); // sq t2, 32(a2) // this is the 0xC08C first unpack 8 with top. always to 140. unsigned-four data.
c->xor_(t2, t2, v0); // xor t2, t2, v0 // remove qwc
c->sh(s1, 44, a2); // sh s1, 44(a2) // here's the 0xc08c store
c->daddu(s1, s1, s0); // daddu s1, s1, s0 // s1 is VU data ptr (qw), inc by number of 4 byte words because we're unpacking 4x.
c->sb(s0, 46, a2); // sb s0, 46(a2) // store qw to unpack in viftag (output qw's)
c->dsll32(s0, v0, 4); // dsll32 s0, v0, 4 // qw -> bytes for input unsigned-fours (add offset to addr field of dma tag, it's 4 + 32 bit shift)
c->daddu(t3, t2, s0); // daddu t3, t2, s0 // t3 = next
c->daddiu(s0, s2, 3); // daddiu s0, s2, 3 // s0 = lump-four-count + 3
c->sw(a3, 12, a2); // sw a3, 12(a2) // st-vif-add's x.
c->srl(s0, s0, 2); // srl s0, s0, 2 // lump fours / 4
c->sq(t1, 16, a2); // sq t1, 16(a2) // row y (will be overwritten) z w (nop).
// store the dma tag for the lump fours
c->xor_(t3, t3, s0); // xor t3, t3, s0
c->sq(t3, 48, a2); // sq t3, 48(a2)
c->xor_(t3, t3, s0); // xor t3, t3, s0
c->sh(s1, 60, a2); // sh s1, 60(a2)
c->daddu(s1, s1, s2); // daddu s1, s1, s2
c->sb(s2, 62, a2); // sb s2, 62(a2)
c->dsll32(s2, s0, 4); // dsll32 s2, s0, 4
c->sw(a3, 16, a2); // sw a3, 16(a2)
c->daddu(t4, t3, s2); // daddu t4, t3, s2
c->xor_(t4, t4, s3); // xor t4, t4, s3
c->sh(s1, 60, a2); // sh s1, 60(a2) // lump 4 destination.
c->daddu(s1, s1, s2); // daddu s1, s1, s2 // inc VU dest ptr
c->sb(s2, 62, a2); // sb s2, 62(a2) // unpack qwc
c->dsll32(s2, s0, 4); // dsll32 s2, s0, 4 // EE bytes
c->sw(a3, 16, a2); // sw a3, 16(a2) // row y overwrite with st-vif-add
c->daddu(t4, t3, s2); // daddu t4, t3, s2 // next dma
c->xor_(t4, t4, s3); // xor t4, t4, s3 // fp-qwc
c->xori(a3, s1, 16384); // xori a3, s1, 16384
c->sq(t4, 64, a2); // sq t4, 64(a2)
c->sq(t4, 64, a2); // sq t4, 64(a2) // dma for fp's
c->xor_(t4, t4, s3); // xor t4, t4, s3
c->sb(s3, 78, a2); // sb s3, 78(a2)
c->dsll32(s3, s3, 4); // dsll32 s3, s3, 4
c->sh(a3, 76, a2); // sh a3, 76(a2)
c->daddu(t2, t4, s3); // daddu t2, t4, s3
c->lbu(s3, 3, gp); // lbu s3, 3(gp)
c->daddiu(gp, gp, 4); // daddiu gp, gp, 4
c->sb(s3, 78, a2); // sb s3, 78(a2) // unpack qwc
c->dsll32(s3, s3, 4); // dsll32 s3, s3, 4 // bytes (in upper 32)
c->sh(a3, 76, a2); // sh a3, 76(a2) // destination in VU
c->daddu(t2, t4, s3); // daddu t2, t4, s3 // next dma
c->lbu(s3, 3, gp); // lbu s3, 3(gp) // frag-ctrl.mat-xfer-count
c->daddiu(gp, gp, 4); // daddiu gp, gp, 4 // gp = frag-ctrl.mat-dest-data
// skip ahead if on not-first fragment.
bc = c->sgpr64(s5) != 0; // bne s5, r0, L73
c->daddiu(a2, a2, 80); // daddiu a2, a2, 80
if (bc) {goto block_5;} // branch non-likely
// on first, need to set up lights and stuff common to all fragments.
// setup 8 qw upload (132 - 140)
c->sd(t6, 0, a2); // sd t6, 0(a2)
c->addiu(s2, r0, 8); // addiu s2, r0, 8
c->sd(t6, 8, a2); // sd t6, 8(a2)
c->lui(a3, 27656); // lui a3, 27656
c->sb(s2, 0, a2); // sb s2, 0(a2)
c->daddiu(a3, a3, 132); // daddiu a3, a3, 132
c->daddiu(a3, a3, 132); // daddiu a3, a3, 132 // (inc global dma buf)
c->load_symbol(s2, cache.merc_bucket_info); // lw s2, *merc-bucket-info*(s7)
c->daddu(s2, r0, s2); // daddu s2, r0, s2
c->sw(a3, 12, a2); // sw a3, 12(a2)
c->lq(a3, 0, s2); // lq a3, 0(s2)
c->lq(s1, 16, s2); // lq s1, 16(s2)
c->lq(s0, 32, s2); // lq s0, 32(s2)
c->lq(v0, 48, s2); // lq v0, 48(s2)
c->sq(a3, 16, a2); // sq a3, 16(a2)
c->sq(s1, 32, a2); // sq s1, 32(a2)
c->sq(s0, 48, a2); // sq s0, 48(a2)
c->sq(v0, 64, a2); // sq v0, 64(a2)
c->lq(a3, 64, s2); // lq a3, 64(s2)
c->lq(s1, 80, s2); // lq s1, 80(s2)
c->lq(s0, 96, s2); // lq s0, 96(s2)
c->lui(v0, 16261); // lui v0, 16261
c->lq(s2, 28, t7); // lq s2, 28(t7)
c->daddiu(v0, v0, 619); // daddiu v0, v0, 619
c->sq(a3, 80, a2); // sq a3, 80(a2)
c->lbu(a3, 5, ra); // lbu a3, 5(ra)
c->sq(s1, 96, a2); // sq s1, 96(a2)
c->sq(s0, 112, a2); // sq s0, 112(a2)
c->dsubu(a3, v0, a3); // dsubu a3, v0, a3
c->lq(a3, 0, s2); // lq a3, 0(s2) // load l0
c->lq(s1, 16, s2); // lq s1, 16(s2) // load l1
c->lq(s0, 32, s2); // lq s0, 32(s2) // load l2
c->lq(v0, 48, s2); // lq v0, 48(s2) // load l3
c->sq(a3, 16, a2); // sq a3, 16(a2) // store l0
c->sq(s1, 32, a2); // sq s1, 32(a2) // store l1
c->sq(s0, 48, a2); // sq s0, 48(a2) // store l2
c->sq(v0, 64, a2); // sq v0, 64(a2) // store l3
c->lq(a3, 64, s2); // lq a3, 64(s2) // load l4
c->lq(s1, 80, s2); // lq s1, 80(s2) // load l5
c->lq(s0, 96, s2); // lq s0, 96(s2) // load l6
c->lui(v0, 16261); // lui v0, 16261 // 0x3F85
c->lq(s2, 28, t7); // lq s2, 28(t7) // first qw of merc-ctrl header.
c->daddiu(v0, v0, 619); // daddiu v0, v0, 619 // 0x26B
c->sq(a3, 80, a2); // sq a3, 80(a2) // store l4
c->lbu(a3, 5, ra); // lbu a3, 5(ra) // effect-info.ignore-alpha
c->sq(s1, 96, a2); // sq s1, 96(a2) // store l5
c->sq(s0, 112, a2); // sq s0, 112(a2) // store l6
c->dsubu(a3, v0, a3); // dsubu a3, v0, a3 //
c->sq(s2, 128, a2); // sq s2, 128(a2)
c->sw(a3, 28, a2); // sw a3, 28(a2)
c->daddiu(a2, a2, 144); // daddiu a2, a2, 144
// after first frag setup
block_5:
bc = c->sgpr64(s3) == 0; // beq s3, r0, L75
c->addiu(s2, r0, 128); // addiu s2, r0, 128
c->addiu(s2, r0, 128); // addiu s2, r0, 128 // s2 = mat size
if (bc) {goto block_8;} // branch non-likely
c->lbu(a3, 0, gp); // lbu a3, 0(gp)
c->lbu(a3, 0, gp); // lbu a3, 0(gp) // a3 = mat-number
block_7:
c->multu3(s1, a3, s2); // multu3 s1, a3, s2
c->sq(t5, 0, a2); // sq t5, 0(a2)
c->lbu(s0, 1, gp); // lbu s0, 1(gp)
c->daddiu(gp, gp, 2); // daddiu gp, gp, 2
c->lbu(a3, 0, gp); // lbu a3, 0(gp)
c->daddiu(s3, s3, -1); // daddiu s3, s3, -1
c->sb(s0, 12, a2); // sb s0, 12(a2)
c->daddiu(a2, a2, 16); // daddiu a2, a2, 16
c->daddu(s1, s1, a1); // daddu s1, s1, a1
c->multu3(s1, a3, s2); // multu3 s1, a3, s2 // s1 = mat-number * 128
c->sq(t5, 0, a2); // sq t5, 0(a2) // dma template
c->lbu(s0, 1, gp); // lbu s0, 1(gp) // s0 = mat-dest
c->daddiu(gp, gp, 2); // daddiu gp, gp, 2 // inc mat-dest-data pr
c->lbu(a3, 0, gp); // lbu a3, 0(gp) // load for next iter (ugh)
c->daddiu(s3, s3, -1); // daddiu s3, s3, -1 // dec count
c->sb(s0, 12, a2); // sb s0, 12(a2) // store matrix destination.
c->daddiu(a2, a2, 16); // daddiu a2, a2, 16 // increment dma output.
c->daddu(s1, s1, a1); // daddu s1, s1, a1 // matrix data + 128 * mat-number
// nop // sll r0, r0, 0
bc = c->sgpr64(s3) != 0; // bne s3, r0, L74
c->sw(s1, -12, a2); // sw s1, -12(a2)
bc = c->sgpr64(s3) != 0; // bne s3, r0, L74 // see if we're done
c->sw(s1, -12, a2); // sw s1, -12(a2) // store pointer in input matrix data in dma tag
if (bc) {goto block_7;} // branch non-likely
block_8:
c->sq(t6, 0, a2); // sq t6, 0(a2)
c->daddiu(a2, a2, 16); // daddiu a2, a2, 16
bc = c->sgpr64(s5) != 0; // bne s5, r0, L76
c->daddiu(s5, s5, 1); // daddiu s5, s5, 1
c->sq(t6, 0, a2); // sq t6, 0(a2) // dma tag template
c->daddiu(a2, a2, 16); // daddiu a2, a2, 16 // inc
bc = c->sgpr64(s5) != 0; // bne s5, r0, L76 // skip ahead on non-first fragment
c->daddiu(s5, s5, 1); // daddiu s5, s5, 1 // inc fragment counter
if (bc) {goto block_10;} // branch non-likely
c->mov64(a3, v1); // or a3, v1, r0
+1 -1
View File
@@ -93,7 +93,7 @@ void Deci2Server::read_data() {
int got = 0;
while (got < desired_size) {
ASSERT(got + desired_size < buffer.size());
ASSERT(got + desired_size < (int)buffer.size());
auto x = read_from_socket(accepted_socket, buffer.data() + got, desired_size - got);
if (want_exit_callback()) {
return;
+2 -2
View File
@@ -77,7 +77,7 @@ std::optional<std::string> ReplServer::get_msg() {
// Say hello
ping_response(new_socket);
// Track the new socket
if (client_sockets.size() < max_clients) {
if ((int)client_sockets.size() < max_clients) {
client_sockets.insert(new_socket);
} else {
// TODO - Respond with NO
@@ -114,7 +114,7 @@ std::optional<std::string> ReplServer::get_msg() {
int expected_size = header->length;
int got = 0;
while (got < expected_size) {
if (got + expected_size > buffer.size()) {
if (got + expected_size > (int)buffer.size()) {
fmt::print(stderr,
"[nREPL:{}]: Bad message, aborting the read. Got :{}, Expected: {}, Buffer "
"Size: {}",