diff --git a/decompiler/IR/BasicOpBuilder.cpp b/decompiler/IR/BasicOpBuilder.cpp index 2ac138c610..7549650503 100644 --- a/decompiler/IR/BasicOpBuilder.cpp +++ b/decompiler/IR/BasicOpBuilder.cpp @@ -522,6 +522,17 @@ std::shared_ptr try_dsrav(Instruction& instr, int idx) { return nullptr; } +std::shared_ptr try_dsrlv(Instruction& instr, int idx) { + if (is_gpr_3(instr, InstructionKind::DSRLV, {}, {}, {}) && + !instr.get_src(0).is_reg(make_gpr(Reg::S7)) && !instr.get_src(1).is_reg(make_gpr(Reg::S7))) { + return make_set(IR_Set::REG_64, make_reg(instr.get_dst(0).get_reg(), idx), + std::make_shared(IR_IntMath2::RIGHT_SHIFT_LOGIC, + make_reg(instr.get_src(0).get_reg(), idx), + make_reg(instr.get_src(1).get_reg(), idx))); + } + return nullptr; +} + std::shared_ptr try_sw(Instruction& instr, int idx) { if (instr.kind == InstructionKind::SW && instr.get_src(1).is_sym() && instr.get_src(2).is_reg(make_gpr(Reg::S7))) { @@ -1208,6 +1219,7 @@ std::shared_ptr try_lwu(Instruction& i0, } // namespace void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file) { + (void)file; for (int instr = block.start_word; instr < block.end_word; instr++) { auto& i = func->instructions.at(instr); @@ -1469,6 +1481,9 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec case InstructionKind::DSRAV: result = try_dsrav(i, instr); break; + case InstructionKind::DSRLV: + result = try_dsrlv(i, instr); + break; default: result = nullptr; } diff --git a/decompiler/IR/CfgBuilder.cpp b/decompiler/IR/CfgBuilder.cpp index bffa16626d..6421869998 100644 --- a/decompiler/IR/CfgBuilder.cpp +++ b/decompiler/IR/CfgBuilder.cpp @@ -107,6 +107,7 @@ void clean_up_cond_with_else(std::shared_ptr* ir, LinkedObjectFile& file) { assert(jump_to_next.first->branch_delay.kind == BranchDelay::NOP); // patch the jump to next with a condition. auto replacement = std::make_shared(jump_to_next.first->condition); + replacement->condition.invert(); *(jump_to_next.second) = replacement; // patch the jump at the end of a block. @@ -133,6 +134,208 @@ void clean_up_cond_with_else(std::shared_ptr* ir, LinkedObjectFile& file) { } } +/*! + * Does the instruction in the delay slot set a register to false? + * Note. a beql s7, x followed by a or y, x, r0 will count as this. I don't know why but + * GOAL does this on comparisons to false. + */ +bool delay_slot_sets_false(IR_Branch* branch) { + if (branch->branch_delay.kind == BranchDelay::SET_REG_FALSE) { + return true; + } + + if (branch->condition.kind == Condition::FALSE && + branch->branch_delay.kind == BranchDelay::SET_REG_REG) { + auto reg_check = dynamic_cast(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(branch->branch_delay.source.get()); + assert(reg_read); + return reg_check->reg == reg_read->reg; + } + + return false; +} + +/*! + * Does the instruction in the delay slot set a register to a truthy value, like in a GOAL + * or form branch? Either it explicitly sets #t, or it tests the value for being not false, + * then uses that + */ +bool delay_slot_sets_truthy(IR_Branch* branch) { + if (branch->branch_delay.kind == BranchDelay::SET_REG_TRUE) { + return true; + } + + if (branch->condition.kind == Condition::TRUTHY && + branch->branch_delay.kind == BranchDelay::SET_REG_REG) { + auto reg_check = dynamic_cast(branch->condition.src0.get()); + assert(reg_check); + auto reg_read = dynamic_cast(branch->branch_delay.source.get()); + assert(reg_read); + return reg_check->reg == reg_read->reg; + } + + return false; +} + +/*! + * Try to convert a short circuit to an and. + */ +bool try_clean_up_sc_as_and(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + Register destination; + std::shared_ptr ir_dest = nullptr; + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + if (!delay_slot_sets_false(branch.first)) { + return false; + } + + if (i == 0) { + ir_dest = branch.first->branch_delay.destination; + destination = dynamic_cast(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(branch.first->branch_delay.destination.get())->reg) { + return false; + } + } + } + + ir->kind = IR_ShortCircuit::AND; + ir->final_result = ir_dest; + + // now get rid of the branches + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + auto replacement = std::make_shared(branch.first->condition); + replacement->condition.invert(); + *(branch.second) = replacement; + } + + return true; +} + +/*! + * Try to convert a short circuit to an or. + * Note - this will convert an and to a very strange or, so always use the try as and first. + */ +bool try_clean_up_sc_as_or(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + Register destination; + std::shared_ptr ir_dest = nullptr; + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + if (!delay_slot_sets_truthy(branch.first)) { + return false; + } + assert(dynamic_cast(branch.first->branch_delay.destination.get())); + + if (i == 0) { + ir_dest = branch.first->branch_delay.destination; + destination = dynamic_cast(branch.first->branch_delay.destination.get())->reg; + } else { + if (destination != + dynamic_cast(branch.first->branch_delay.destination.get())->reg) { + return false; + } + } + } + + ir->kind = IR_ShortCircuit::OR; + ir->final_result = ir_dest; + + for (int i = 0; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + auto replacement = std::make_shared(branch.first->condition); + *(branch.second) = replacement; + } + + return true; +} + +void clean_up_sc(std::shared_ptr& ir, LinkedObjectFile& file); + +/*! + * A form like (and x (or y z)) will be recognized as a single SC Vertex by the CFG pass. + * In the case where we fail to clean it up as an AND or an OR, we should attempt splitting. + * Part of the complexity here is that we want to clean up the split recursively so things like + * (and x (or y (and a b))) + * or + * (and x (or y (and a b)) c d (or z)) + * will work correctly. This may require doing more splitting on both sections! + */ +bool try_splitting_nested_sc(std::shared_ptr& ir, LinkedObjectFile& file) { + auto first_branch = get_condition_branch(&ir->entries.front().condition); + assert(first_branch.first); + bool first_is_and = delay_slot_sets_false(first_branch.first); + bool first_is_or = delay_slot_sets_truthy(first_branch.first); + assert(first_is_and != first_is_or); // one or the other but not both! + + int first_different = -1; // the index of the first one that's different. + + for (int i = 1; i < int(ir->entries.size()) - 1; i++) { + auto branch = get_condition_branch(&ir->entries.at(i).condition); + assert(branch.first); + bool is_and = delay_slot_sets_false(branch.first); + bool is_or = delay_slot_sets_truthy(branch.first); + assert(is_and != is_or); + + if (first_different == -1) { + // haven't seen a change yet. + if (first_is_and != is_and) { + // change! + first_different = i; + break; + } + } + } + + assert(first_different != -1); + + std::vector nested_ir; + for (int i = first_different; i < int(ir->entries.size()); i++) { + nested_ir.push_back(ir->entries.at(i)); + } + + auto s = int(ir->entries.size()); + for (int i = first_different; i < s; i++) { + ir->entries.pop_back(); + } + + auto nested_sc = std::make_shared(nested_ir); + clean_up_sc(nested_sc, file); + + // the real trick + IR_ShortCircuit::Entry nested_entry; + nested_entry.condition = nested_sc; + ir->entries.push_back(nested_entry); + + clean_up_sc(ir, file); + + return true; +} + +/*! + * Try to clean up a single short circuit IR. It may get split up into nested IR_ShortCircuits + * if there is a case like (and a (or b c)) + */ +void clean_up_sc(std::shared_ptr& ir, LinkedObjectFile& file) { + (void)file; + assert(ir->entries.size() > 1); + if (!try_clean_up_sc_as_and(ir, file)) { + if (!try_clean_up_sc_as_or(ir, file)) { + if (!try_splitting_nested_sc(ir, file)) { + assert(false); + } + } + } +} + /*! * A GOAL comparison which produces a boolean is recognized as a cond-no-else by the CFG analysis. * But it should not be decompiled as a branching statement. @@ -153,11 +356,6 @@ void convert_cond_no_else_to_compare(std::shared_ptr* ir) { auto condition_as_single = dynamic_cast(cne->entries.front().condition.get()); if (condition_as_single) { - // as far as I can tell this is totally valid but just happens to not appear? - // if this case is ever hit in the future it's fine and we just need to implement this. - // but leaving empty for now so there's fewer things to test. - // assert(false); - auto replacement = std::make_shared( IR_Set::REG_64, dst, std::make_shared(condition.first->condition)); *ir = replacement; @@ -186,6 +384,7 @@ void convert_cond_no_else_to_compare(std::shared_ptr* ir) { * this. */ void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { + (void)file; auto cne = dynamic_cast(ir->get()); assert(cne); for (size_t idx = 0; idx < cne->entries.size(); idx++) { @@ -212,6 +411,7 @@ void clean_up_cond_no_else(std::shared_ptr* ir, LinkedObjectFile& file) { } auto replacement = std::make_shared(jump_to_next.first->condition); + replacement->condition.invert(); *(jump_to_next.second) = replacement; e.cleaned = true; @@ -282,12 +482,19 @@ bool is_int_math_3(IR* ir, return true; } +/*! + * Are these IR's both the same register? False if either is not a register. + */ bool is_same_reg(IR* a, IR* b) { auto ar = dynamic_cast(a); auto br = dynamic_cast(b); return ar && br && ar->reg == br->reg; } +/*! + * Try to convert this SC Vertex into an abs (integer). + * Will return a converted abs IR if successful, or nullptr if its not possible + */ std::shared_ptr try_sc_as_abs(Function& f, LinkedObjectFile& file, ShortCircuit* vtx) { if (vtx->entries.size() != 1) { return nullptr; @@ -344,7 +551,7 @@ std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc } // todo, I think b0 could possibly be something more complicated, depending on how we order. - auto b0 = dynamic_cast(vtx->entries.at(0)); + auto b0 = dynamic_cast(vtx->entries.at(0)); auto b1 = dynamic_cast(vtx->entries.at(1)); if (!b0 || !b1) { return nullptr; @@ -399,8 +606,13 @@ std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc assert(result); assert(value_in); - if (!is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg, - value_in->reg, clobber)) { + + bool is_arith = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_ARITH, result->reg, + value_in->reg, clobber); + bool is_logical = is_int_math_3(dsrav_candidate.get(), IR_IntMath2::RIGHT_SHIFT_LOGIC, + result->reg, value_in->reg, clobber); + + if (!is_arith && !is_logical) { return nullptr; } @@ -422,7 +634,8 @@ std::shared_ptr try_sc_as_ash(Function& f, LinkedObjectFile& file, ShortCirc b0_ir->forms.pop_back(); // add the ash b0_ir->forms.push_back(std::make_shared( - IR_Set::REG_64, dest_ir, std::make_shared(shift_ir, value_ir, clobber_ir))); + IR_Set::REG_64, dest_ir, + std::make_shared(shift_ir, value_ir, clobber_ir, is_arith))); return b0_ptr; } @@ -453,7 +666,7 @@ std::shared_ptr try_sc_as_type_of(Function& f, LinkedObjectFile& file, Short return nullptr; } - auto b0 = dynamic_cast(vtx->entries.at(0)); + auto b0 = dynamic_cast(vtx->entries.at(0)); auto b1 = dynamic_cast(vtx->entries.at(1)); auto b2 = dynamic_cast(vtx->entries.at(2)); @@ -698,6 +911,7 @@ std::shared_ptr cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx) entries.push_back(e); } auto result = std::make_shared(entries); + clean_up_sc(result, file); // todo clean these into real and/or. return result; } else if (dynamic_cast(vtx)) { @@ -795,4 +1009,4 @@ std::shared_ptr build_cfg_ir(Function& function, } catch (std::runtime_error& e) { return nullptr; } -} \ No newline at end of file +} diff --git a/decompiler/IR/IR.cpp b/decompiler/IR/IR.cpp index f2bce71428..277e51b6e7 100644 --- a/decompiler/IR/IR.cpp +++ b/decompiler/IR/IR.cpp @@ -793,8 +793,8 @@ void IR_ShortCircuit::get_children(std::vector>* output) con } goos::Object IR_Ash::to_form(const LinkedObjectFile& file) const { - return pretty_print::build_list(pretty_print::to_symbol("ash"), value->to_form(file), - shift_amount->to_form(file)); + return pretty_print::build_list(pretty_print::to_symbol(is_signed ? "ash.si" : "ash.ui"), + value->to_form(file), shift_amount->to_form(file)); } void IR_Ash::get_children(std::vector>* output) const { diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h index 7d358bd379..4ea0b2ac72 100644 --- a/decompiler/IR/IR.h +++ b/decompiler/IR/IR.h @@ -357,12 +357,15 @@ class IR_ShortCircuit : public IR { class IR_Ash : public IR { public: std::shared_ptr shift_amount, value, clobber; + bool is_signed = true; IR_Ash(std::shared_ptr _shift_amount, std::shared_ptr _value, - std::shared_ptr _clobber) + std::shared_ptr _clobber, + bool _is_signed) : shift_amount(std::move(_shift_amount)), value(std::move(_value)), - clobber(std::move(_clobber)) {} + clobber(std::move(_clobber)), + is_signed(_is_signed) {} goos::Object to_form(const LinkedObjectFile& file) const override; void get_children(std::vector>* output) const override; }; diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 5c5b7fd61a..184dbd34f5 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -592,13 +592,13 @@ void ObjectFileDB::analyze_functions() { // } } - int total_nontrivial_functions = 0; - int total_resolved_nontrivial_functions = 0; + int total_trivial_cfg_functions = 0; int total_named_functions = 0; int total_basic_ops = 0; int total_failed_basic_ops = 0; - int attempted_cfg_irs = 0; + int asm_funcs = 0; + int non_asm_funcs = 0; int successful_cfg_irs = 0; std::map> unresolved_by_length; @@ -624,7 +624,7 @@ void ObjectFileDB::analyze_functions() { total_failed_basic_ops += func.get_failed_basic_op_count(); func.ir = build_cfg_ir(func, *func.cfg, data.linked_data); - attempted_cfg_irs++; + non_asm_funcs++; if (func.ir) { successful_cfg_irs++; } @@ -633,13 +633,11 @@ void ObjectFileDB::analyze_functions() { resolved_cfg_functions++; } } else { - resolved_cfg_functions++; + asm_funcs++; } if (func.basic_blocks.size() > 1 && !func.suspected_asm) { - total_nontrivial_functions++; if (func.cfg->is_fully_resolved()) { - total_resolved_nontrivial_functions++; } else { if (!func.guessed_name.empty()) { unresolved_by_length[func.end_word - func.start_word].push_back( @@ -648,6 +646,10 @@ void ObjectFileDB::analyze_functions() { } } + if (!func.suspected_asm && func.basic_blocks.size() <= 1) { + total_trivial_cfg_functions++; + } + if (!func.guessed_name.empty()) { total_named_functions++; } @@ -657,21 +659,19 @@ void ObjectFileDB::analyze_functions() { // } }); - printf("Found %d functions (%d with nontrivial cfgs)\n", total_functions, - total_nontrivial_functions); + printf("Found %d functions (%d with no control flow)\n", total_functions, + total_trivial_cfg_functions); printf("Named %d/%d functions (%.2f%%)\n", total_named_functions, total_functions, 100.f * float(total_named_functions) / float(total_functions)); + printf("Excluding %d asm functions\n", asm_funcs); printf("Found %d basic blocks in %.3f ms\n", total_basic_blocks, timer.getMs()); printf(" %d/%d functions passed cfg analysis stage (%.2f%%)\n", resolved_cfg_functions, - total_functions, 100.f * float(resolved_cfg_functions) / float(total_functions)); - printf(" %d/%d nontrivial cfg's resolved (%.2f%%)\n", total_resolved_nontrivial_functions, - total_nontrivial_functions, - 100.f * float(total_resolved_nontrivial_functions) / float(total_nontrivial_functions)); + non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs)); int successful_basic_ops = total_basic_ops - total_failed_basic_ops; printf(" %d/%d basic ops converted successfully (%.2f%%)\n", successful_basic_ops, total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops)); - printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, attempted_cfg_irs, - 100.f * float(successful_cfg_irs) / float(attempted_cfg_irs)); + printf(" %d/%d cfgs converted to ir (%.2f%%)\n", successful_cfg_irs, non_asm_funcs, + 100.f * float(successful_cfg_irs) / float(non_asm_funcs)); // for (auto& kv : unresolved_by_length) { // printf("LEN %d\n", kv.first); diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 3697fcaab8..e7dbb862f9 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -1,72 +1,94 @@ - - { - "game_version":1, - // the order here matters. KERNEL and GAME should go first - "dgo_names":["CGO/KERNEL.CGO", "CGO/GAME.CGO"], - /*, "CGO/ENGINE.CGO" - , "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO", "DGO/DEM.DGO", - "DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO", "CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO", - "DGO/LAV.DGO", "DGO/MAI.DGO", "CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO", "DGO/ROL.DGO", - "DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO", "DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO", - "DGO/VI2.DGO", "DGO/VI3.DGO", "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO" - ],*/ + "game_version" : 1, + // the order here matters. KERNEL and GAME should go first + "dgo_names" + : [ "CGO/KERNEL.CGO", "CGO/GAME.CGO" ], + /*, "CGO/ENGINE.CGO" + , "CGO/ART.CGO", "DGO/BEA.DGO", "DGO/CIT.DGO", "CGO/COMMON.CGO", "DGO/DAR.DGO", + "DGO/DEM.DGO", "DGO/FIN.DGO", "DGO/INT.DGO", "DGO/JUB.DGO", "DGO/JUN.DGO", + "CGO/JUNGLE.CGO", "CGO/L1.CGO", "DGO/FIC.DGO", "DGO/LAV.DGO", "DGO/MAI.DGO", + "CGO/MAINCAVE.CGO", "DGO/MIS.DGO", "DGO/OGR.DGO", "CGO/RACERP.CGO", "DGO/ROB.DGO", + "DGO/ROL.DGO", "DGO/SNO.DGO", "DGO/SUB.DGO", "DGO/SUN.DGO", "CGO/SUNKEN.CGO", + "DGO/SWA.DGO", "DGO/TIT.DGO", "DGO/TRA.DGO", "DGO/VI1.DGO", "DGO/VI2.DGO", "DGO/VI3.DGO", + "CGO/VILLAGEP.CGO", "CGO/WATER-AN.CGO" + ],*/ - "write_disassembly":true, - "write_hex_near_instructions":false, - // if false, skips disassembling object files without functions, as these are usually large and not interesting yet. - "disassemble_objects_without_functions":false, + "write_disassembly" : true, + "write_hex_near_instructions" + : false, + // if false, skips disassembling object files without functions, as these are usually large + // and not interesting yet. + "disassemble_objects_without_functions" : false, - // to write out data of each object file - "write_hexdump":false, - // to write out hexdump on the v3 only, to avoid the huge level data files - "write_hexdump_on_v3_only":true, + // to write out data of each object file + "write_hexdump" + : false, + // to write out hexdump on the v3 only, to avoid the huge level data files + "write_hexdump_on_v3_only" + : true, - // to write out "scripts", which are currently just all the linked lists found - "write_scripts":true, + // to write out "scripts", which are currently just all the linked lists found + "write_scripts" : true, - // Experimental Stuff - "find_basic_blocks":true, + // Experimental Stuff + "find_basic_blocks" : true, - "asm_functions_by_name":[ + "asm_functions_by_name" + : [ // gcommon "min", "max", "(method 2 vec4s)", "quad-copy!", "(method 3 vec4s)", "breakpoint-range-set!", // pskernel - "resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", "return-from-exception", - "kernel-read", "kernel-read-function", "kernel-write", "kernel-write-function", "kernel-copy-to-kernel-ram", + "resend-exception", "kernel-set-interrupt-vector", "kernel-set-exception-vector", + "return-from-exception", "kernel-read", "kernel-read-function", "kernel-write", + "kernel-write-function", "kernel-copy-to-kernel-ram", // this one needs more investigation. nothing looks weird about it but it fails... "camera-change-to", - // this one fails due to false compaction where an else case has only a not expression in it. + // two back to back arithmetic shifts... + "texture-relocate", + + // this one fails due to false compaction where an else case has only a not expression in + // it. "master-is-hopeful-better?", // fails for unknown reason "target-falling-anim-trans", "change-brother", - // these are all valid, but use short circuiting branches in strange ways. There's probably a few compiler uses that we're not - "(method 21 actor-link-info)","(method 20 actor-link-info)","(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)", - "debug-menu-item-var-render", "(method 14 level)","add-blue-motion","anim-tester-add-newobj","(method 27 orb-cache-top)", + // merged right typecase... can probably handle this + "cspace-inspect-tree", + + // these are all valid, but use short circuiting branches in strange ways. There's probably + // a few compiler uses that we're not + "(method 21 actor-link-info)", "(method 20 actor-link-info)", + "(method 28 collide-shape-prim-mesh)", "(method 35 collide-shape)", + "debug-menu-item-var-render", "(method 14 level)", "add-blue-motion", + "anim-tester-add-newobj", "(method 27 orb-cache-top)", // real asm - "cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", "render-boundary-quad", - "(method 19 collide-shape-prim-sphere)","vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)", + "cspace<-parented-transformq-joint!", "blerc-a-fragment", "render-boundary-tri", + "render-boundary-quad", "(method 19 collide-shape-prim-sphere)", + "vector-segment-distance-point!", "exp", "(method 11 collide-mesh-cache)", "(method 13 collide-edge-work)", "ambient-inspect", - "(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", "vblank-handler", "vif1-handler", - "vif1-handler-debug", "entity-actor-count", "decompress-frame-data-pair-to-accumulator", - "decompress-frame-data-to-accumulator", "normalize-frame-quaternions", "clear-frame-accumulator", - "generic-copy-vtx-dclr-dtex", "generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm", - "generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", "generic-prepare-dma-single", - "(method 13 collide-shape-prim-sphere)", "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)", - "adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", "draw-bones", "(method 9 collide-mesh-cache)", - "(method 18 collide-shape-prim-sphere)","birth-pickup-at-point", + "(method 11 cpu-thread)", "atan0", "sincos!", "sincos-rad!", "disasm-dma-list", + "vblank-handler", "vif1-handler", "vif1-handler-debug", "entity-actor-count", + "decompress-frame-data-pair-to-accumulator", "decompress-frame-data-to-accumulator", + "normalize-frame-quaternions", "clear-frame-accumulator", "generic-copy-vtx-dclr-dtex", + "generic-no-light-dproc-only", "generic-no-light-proc", "mercneric-bittable-asm", + "generic-tie-decompress", "matrix-axis-sin-cos!", "matrix-axis-sin-cos-vu!", + "generic-prepare-dma-single", "(method 13 collide-shape-prim-sphere)", + "(method 14 collide-shape-prim-sphere)", "(method 12 collide-shape-prim-sphere)", + "adgif-shader<-texture-with-update!", "generic-interp-dproc", "sprite-draw-distorters", + "draw-bones", "(method 9 collide-mesh-cache)", "(method 18 collide-shape-prim-sphere)", + "birth-pickup-at-point", - "collide-do-primitives", "draw-bones-check-longest-edge-asm", - "sp-launch-particles-var", "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)", - "(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", "dma-sync-hang", "generic-no-light-dproc", - "dma-sync-fast", "bsp-camera-asm", - "generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture", "(method 10 collide-edge-hold-list)" - ] -} \ No newline at end of file + "collide-do-primitives", "draw-bones-check-longest-edge-asm", "sp-launch-particles-var", + "(method 15 collide-shape-prim-mesh)", "(method 15 collide-shape-prim-sphere)", + "(method 45 collide-shape)", "cam-layout-save-cam-trans", "kernel-copy-function", + "dma-sync-hang", "generic-no-light-dproc", "dma-sync-fast", "bsp-camera-asm", + "generic-none-dma-wait", "unpack-comp-rle", "level-remap-texture", + "(method 10 collide-edge-hold-list)" + ] +}