// Copyright 2022 xensik. All rights reserved. // // Use of this source code is governed by a GNU GPLv3 license // that can be found in the LICENSE file. #include "stdafx.hpp" #include "t6.hpp" namespace xsk::arc::t6 { auto disassembler::output() -> assembly::ptr { return std::move(assembly_); } auto disassembler::output_raw() -> std::vector { output_ = std::make_unique(0x100000); output_->write_string("// T6 GSC ASSEMBLY\n"); output_->write_string("// Disassembled by https://github.com/xensik/gsc-tool\n"); for (const auto& func : assembly_->functions) { this->print_function(func); } std::vector output; output.resize(output_->pos()); std::memcpy(output.data(), output_->buffer().data(), output.size()); return output; } void disassembler::disassemble(const std::string& file, std::vector& data) { filename_ = file; script_ = std::make_unique(data); assembly_ = std::make_unique(); exports_.clear(); imports_.clear(); strings_.clear(); animtrees_.clear(); stringlist_.clear(); string_refs_.clear(); anim_refs_.clear(); import_refs_.clear(); labels_.clear(); std::memset(&header_, 0, sizeof(header_)); // header header_.magic = script_->read(); if (header_.magic != magic) { throw error("invalid binary gsc file '" + filename_ + "'!"); } header_.source_crc = script_->read(); header_.include_offset = script_->read(); header_.animtree_offset = script_->read(); header_.cseg_offset = script_->read(); header_.stringtablefixup_offset = script_->read(); header_.exports_offset = script_->read(); header_.imports_offset = script_->read(); header_.fixup_offset = script_->read(); header_.profile_offset = script_->read(); header_.cseg_size = script_->read(); header_.name = script_->read(); header_.stringtablefixup_count = script_->read(); header_.exports_count = script_->read(); header_.imports_count = script_->read(); header_.fixup_count = script_->read(); header_.profile_count = script_->read(); header_.include_count = script_->read(); header_.animtree_count = script_->read(); header_.flags = script_->read(); // string list script_->pos(64); while (script_->pos() < header_.include_offset) { auto pos = script_->pos(); stringlist_.insert({ pos, script_->read_c_string() }); } // include list script_->pos(header_.include_offset); for (auto i = 0; i < header_.include_count; i++) { assembly_->includes.push_back(stringlist_.at(script_->read())); } // animtree list script_->pos(header_.animtree_offset); for (auto i = 0; i < header_.animtree_count; i++) { auto entry = std::make_shared(); entry->name = stringlist_.at(script_->read()); auto ref_count = script_->read(); auto anim_count = script_->read(); script_->seek(2); for (auto j = 0; j < ref_count; j++) { entry->refs.push_back(script_->read()); } for (auto k = 0; k < anim_count; k++) { auto name = stringlist_.at(script_->read()); auto ref = script_->read(); entry->anims.push_back({ name, ref }); } for (auto& anim : entry->anims) { anim_refs_.insert({ anim.ref, entry }); } animtrees_.push_back(entry); } // stringtable list script_->pos(header_.stringtablefixup_offset); for (auto i = 0; i < header_.stringtablefixup_count; i++) { auto entry = std::make_shared(); entry->name = stringlist_.at(script_->read()); auto ref_count = script_->read(); entry->type = script_->read(); for (auto j = 0; j < ref_count; j++) { auto ref = script_->read(); entry->refs.push_back(ref); string_refs_.insert({ ref, entry }); } strings_.push_back(entry); } // import list script_->pos(header_.imports_offset); for (auto i = 0; i < header_.imports_count; i++) { auto entry = std::make_shared(); entry->name = stringlist_.at(script_->read()); entry->space = stringlist_.at(script_->read()); auto ref_count = script_->read(); entry->params = script_->read(); entry->flags = script_->read(); for (auto j = 0; j < ref_count; j++) { auto ref = script_->read(); entry->refs.push_back(ref); import_refs_.insert({ ref, entry }); } imports_.push_back(entry); } // export list script_->pos(header_.exports_offset); for (auto i = 0; i < header_.exports_count; i++) { auto entry = std::make_shared(); entry->checksum = script_->read(); entry->offset = script_->read(); entry->name = stringlist_.at(script_->read()); entry->space = ""; entry->params = script_->read(); entry->flags = script_->read(); exports_.push_back(entry); } for (auto i = 0u; i < exports_.size(); i++) { auto& entry = exports_[i]; if (i < exports_.size() - 1) { entry->size = (exports_[i+1]->offset - entry->offset); auto end_pos = entry->offset + entry->size - 4; script_->pos(end_pos); if (script_->read() == 0) { entry->size -= 4; for (auto j = 1; j < 4; j++) { script_->pos(end_pos - j); auto op = script_->read(); if (op <= 0x01) break; entry->size--; } } } else { entry->size = (header_.cseg_offset + header_.cseg_size) - entry->offset; } script_->pos(entry->offset); assembly_->functions.push_back(std::make_unique()); auto& func = assembly_->functions.back(); func->index = static_cast(script_->pos()); func->size = entry->size; func->params = entry->params; func->flags = entry->flags; func->name = entry->name; this->disassemble_function(func); func->labels = labels_; labels_.clear(); } } void disassembler::disassemble_function(const function::ptr& func) { auto size = func->size; while (size > 0) { func->instructions.push_back(std::make_unique()); auto& inst = func->instructions.back(); inst->index = static_cast(script_->pos()); inst->opcode = script_->read(); inst->size = opcode_size(inst->opcode); if (size < 4 && inst->opcode >= std::uint8_t(opcode::OP_Count)) { func->instructions.pop_back(); break; } this->disassemble_instruction(inst); size -= inst->size; } for (auto i = func->instructions.size() - 1; i >= 1; i--) { auto& inst = func->instructions.at(i); auto& last = func->instructions.at(i-1); if (labels_.contains(inst->index)) break; if (inst->opcode <= 0x01 && (last->opcode > 0x01)) break; func->instructions.pop_back(); } } void disassembler::disassemble_instruction(const instruction::ptr& inst) { switch (static_cast(inst->opcode)) { case opcode::OP_End: case opcode::OP_Return: case opcode::OP_GetUndefined: case opcode::OP_GetZero: case opcode::OP_GetLevelObject: case opcode::OP_GetAnimObject: case opcode::OP_GetSelf: case opcode::OP_GetLevel: case opcode::OP_GetGame: case opcode::OP_GetAnim: case opcode::OP_GetGameRef: case opcode::OP_CreateLocalVariable: case opcode::OP_EvalArray: case opcode::OP_EvalArrayRef: case opcode::OP_ClearArray: case opcode::OP_EmptyArray: case opcode::OP_GetSelfObject: case opcode::OP_SafeSetVariableFieldCached: case opcode::OP_ClearParams: case opcode::OP_CheckClearParams: case opcode::OP_SetVariableField: case opcode::OP_Wait: case opcode::OP_WaitTillFrameEnd: case opcode::OP_PreScriptCall: case opcode::OP_DecTop: case opcode::OP_CastFieldObject: case opcode::OP_CastBool: case opcode::OP_BoolNot: case opcode::OP_BoolComplement: case opcode::OP_Inc: case opcode::OP_Dec: case opcode::OP_Bit_Or: case opcode::OP_Bit_Xor: case opcode::OP_Bit_And: case opcode::OP_Equal: case opcode::OP_NotEqual: case opcode::OP_LessThan: case opcode::OP_GreaterThan: case opcode::OP_LessThanOrEqualTo: case opcode::OP_GreaterThanOrEqualTo: case opcode::OP_ShiftLeft: case opcode::OP_ShiftRight: case opcode::OP_Plus: case opcode::OP_Minus: case opcode::OP_Multiply: case opcode::OP_Divide: case opcode::OP_Modulus: case opcode::OP_SizeOf: case opcode::OP_WaitTill: case opcode::OP_Notify: case opcode::OP_EndOn: case opcode::OP_VoidCodePos: case opcode::OP_Vector: case opcode::OP_RealWait: case opcode::OP_IsDefined: case opcode::OP_VectorScale: case opcode::OP_AnglesToUp: case opcode::OP_AnglesToRight: case opcode::OP_AnglesToForward: case opcode::OP_AngleClamp180: case opcode::OP_VectorToAngles: case opcode::OP_Abs: case opcode::OP_GetTime: case opcode::OP_GetDvar: case opcode::OP_GetDvarInt: case opcode::OP_GetDvarFloat: case opcode::OP_GetDvarVector: case opcode::OP_GetDvarColorRed: case opcode::OP_GetDvarColorGreen: case opcode::OP_GetDvarColorBlue: case opcode::OP_GetDvarColorAlpha: case opcode::OP_FirstArrayKey: case opcode::OP_NextArrayKey: case opcode::OP_ProfileStart: case opcode::OP_ProfileStop: case opcode::OP_SafeDecTop: case opcode::OP_Nop: case opcode::OP_Abort: case opcode::OP_Object: case opcode::OP_ThreadObject: case opcode::OP_EvalLocalVariable: case opcode::OP_EvalLocalVariableRef: break; case opcode::OP_GetByte: case opcode::OP_GetNegByte: inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_GetUnsignedShort: case opcode::OP_GetNegUnsignedShort: inst->size += script_->align(2); inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_GetInteger: inst->size += script_->align(4); inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_GetFloat: inst->size += script_->align(4); inst->data.push_back(utils::string::float_string(script_->read())); break; case opcode::OP_GetVector: inst->size += script_->align(4); inst->data.push_back(utils::string::float_string(script_->read())); inst->data.push_back(utils::string::float_string(script_->read())); inst->data.push_back(utils::string::float_string(script_->read())); break; case opcode::OP_GetString: case opcode::OP_GetIString: disassemble_string(inst); break; case opcode::OP_GetAnimation: disassemble_animation(inst); break; case opcode::OP_WaitTillMatch: inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_VectorConstant: inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_GetHash: inst->size += script_->align(4); inst->data.push_back(resolver::dvar_name(script_->read())); break; case opcode::OP_SafeCreateLocalVariables: disassemble_localvars(inst); break; case opcode::OP_RemoveLocalVariables: case opcode::OP_EvalLocalVariableCached: case opcode::OP_EvalLocalArrayRefCached: case opcode::OP_SafeSetWaittillVariableFieldCached: case opcode::OP_EvalLocalVariableRefCached: inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_EvalFieldVariable: case opcode::OP_EvalFieldVariableRef: case opcode::OP_ClearFieldVariable: disassemble_string(inst); break; case opcode::OP_ScriptFunctionCallPointer: case opcode::OP_ScriptMethodCallPointer: case opcode::OP_ScriptThreadCallPointer: case opcode::OP_ScriptMethodThreadCallPointer: inst->data.push_back(utils::string::va("%i", script_->read())); break; case opcode::OP_GetFunction: disassemble_import(inst); break; case opcode::OP_CallBuiltin: case opcode::OP_CallBuiltinMethod: case opcode::OP_ScriptFunctionCall: case opcode::OP_ScriptMethodCall: case opcode::OP_ScriptThreadCall: case opcode::OP_ScriptMethodThreadCall: script_->seek(1); disassemble_import(inst); break; case opcode::OP_JumpOnFalse: case opcode::OP_JumpOnTrue: case opcode::OP_JumpOnFalseExpr: case opcode::OP_JumpOnTrueExpr: case opcode::OP_Jump: case opcode::OP_JumpBack: case opcode::OP_DevblockBegin: disassemble_jump(inst); break; case opcode::OP_Switch: disassemble_switch(inst); break; case opcode::OP_EndSwitch: disassemble_end_switch(inst); break; default: throw disasm_error(utils::string::va("unhandled opcode 0x%X at index '%04X'!", inst->opcode, inst->index)); } } void disassembler::disassemble_string(const instruction::ptr& inst) { inst->size += script_->align(2); const auto entry = string_refs_.find(script_->pos()); if (entry != string_refs_.end()) { inst->data.push_back(entry->second->name); script_->seek(2); return; } throw disasm_error(utils::string::va("string reference not found at index '%04X'!", inst->index)); } void disassembler::disassemble_animation(const instruction::ptr& inst) { inst->size += script_->align(4); const auto ref = script_->pos(); const auto entry = anim_refs_.find(ref); if (entry != anim_refs_.end()) { inst->data.push_back(entry->second->name); for (const auto& anim : entry->second->anims) { if (anim.ref == ref) { inst->data.push_back(anim.name); script_->seek(4); return; } } } throw disasm_error(utils::string::va("animation reference not found at index '%04X'!", inst->index)); } void disassembler::disassemble_localvars(const instruction::ptr& inst) { const auto count = script_->read(); for (auto i = 0u; i < count; i++) { disassemble_string(inst); inst->size += 2; } } void disassembler::disassemble_import(const instruction::ptr& inst) { inst->size += script_->align(4); script_->seek(4); const auto entry = import_refs_.find(inst->index); if (entry != import_refs_.end()) { inst->data.push_back(entry->second->space); inst->data.push_back(entry->second->name); return; } throw disasm_error(utils::string::va("import reference not found at index '%04X'!", inst->index)); } void disassembler::disassemble_jump(const instruction::ptr& inst) { inst->size += script_->align(2); const auto addr = script_->read() + script_->pos(); const auto label = utils::string::va("loc_%X", addr); inst->data.push_back(label); labels_.insert({ addr, label }); } void disassembler::disassemble_switch(const instruction::ptr& inst) { inst->size += script_->align(4); const auto addr = script_->read() + script_->pos(); const auto label = utils::string::va("loc_%X", addr); inst->data.push_back(label); labels_.insert({ addr, label }); } void disassembler::disassemble_end_switch(const instruction::ptr& inst) { inst->size += script_->align(4); const auto itr = labels_.find(script_->pos()); if (itr != labels_.end()) { for (const auto& entry : assembly_->functions.back()->instructions) { if (opcode(entry->opcode) == opcode::OP_Switch) { if (entry->data[0] == itr->second) { labels_.erase(script_->pos()); const auto label = utils::string::va("loc_%X", inst->index); const auto itr2 = labels_.find(inst->index); if (itr2 == labels_.end()) { labels_.insert({ inst->index, label }); } entry->data[0] = label; break; } } } } auto numerical = false; const auto count = script_->read(); inst->data.push_back(utils::string::va("%i", count)); for (auto i = 0u; i < count; i++) { const auto value = script_->read(); if (value < 0x40000 && value > 0) { inst->data.push_back("case"); const auto& entry = string_refs_.at(script_->pos() - 2); inst->data.push_back(entry->name); } else if (value == 0) { inst->data.push_back("default"); } else { numerical = true; inst->data.push_back("case"); inst->data.push_back(utils::string::va("%i", (value - 0x800000) & 0xFFFFFF)); } const auto addr = script_->read() + script_->pos(); const auto label = utils::string::va("loc_%X", addr); inst->data.push_back(label); labels_.insert({ addr, label }); inst->size += 8; } inst->data.push_back((numerical) ? "i" : "s"); } void disassembler::print_function(const function::ptr& func) { output_->write_string("\n"); output_->write_string(utils::string::va("sub_%s %i %i\n", func->name.data(), func->params, func->flags)); for (const auto& inst : func->instructions) { const auto itr = func->labels.find(inst->index); if (itr != func->labels.end()) { output_->write_string(utils::string::va("\t%s\n", itr->second.data())); } this->print_instruction(inst); } output_->write_string(utils::string::va("end_%s\n", func->name.data())); } void disassembler::print_instruction(const instruction::ptr& inst) { output_->write_string(utils::string::va("\t\t%s(", resolver::opcode_name(inst->opcode).data())); switch (static_cast(inst->opcode)) { case opcode::OP_GetHash: case opcode::OP_GetString: case opcode::OP_GetIString: case opcode::OP_ClearFieldVariable: case opcode::OP_EvalFieldVariable: case opcode::OP_EvalFieldVariableRef: output_->write_string(utils::string::va("\"%s\"", inst->data[0].data())); break; case opcode::OP_GetAnimation: case opcode::OP_GetFunction: case opcode::OP_CallBuiltin: case opcode::OP_CallBuiltinMethod: case opcode::OP_ScriptFunctionCall: case opcode::OP_ScriptMethodCall: case opcode::OP_ScriptThreadCall: case opcode::OP_ScriptMethodThreadCall: output_->write_string(utils::string::va("\"%s\", \"%s\"", inst->data[0].data(), inst->data[1].data())); break; case opcode::OP_SafeCreateLocalVariables: for (const auto& data : inst->data) { output_->write_string(utils::string::va("\"%s\"%s", data.data(), &data == &inst->data.back() ? "" : ", ")); } break; case opcode::OP_EndSwitch: output_->write_string(utils::string::va("%s", inst->data[0].data())); { std::uint32_t totalcase = std::stoul(inst->data[0]); auto numerical = inst->data.back() == "i"; auto index = 0; for (auto casenum = 0u; casenum < totalcase; casenum++) { if (inst->data[1 + index] == "case") { auto fmt = numerical ? ", %s, %s, %s"s : ", %s, \"%s\", %s"s; output_->write_string(utils::string::va(fmt, inst->data[1 + index].data(), inst->data[1 + index + 1].data(), inst->data[1 + index + 2].data())); index += 3; } else if (inst->data[1 + index] == "default") { output_->write_string(utils::string::va(", %s, %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data())); index += 2; } } } break; default: for (const auto& data : inst->data) { output_->write_string(utils::string::va("%s%s", data.data(), &data == &inst->data.back() ? "" : ", ")); } break; } output_->write_string(");\n"); } } // namespace xsk::arc::t6