// Copyright 2021 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 "iw8.hpp" namespace xsk::gsc::iw8 { auto assembler::output_script() -> std::vector { std::vector script; if(script_ == nullptr) return script; script.resize(script_->pos()); memcpy(script.data(), script_->buffer().data(), script.size()); return script; } auto assembler::output_stack() -> std::vector { std::vector stack; if(stack_ == nullptr) return stack; stack.resize(stack_->pos()); memcpy(stack.data(), stack_->buffer().data(), stack.size()); return stack; } void assembler::assemble(const std::string& file, std::vector& data) { std::vector assembly = utils::string::clean_buffer_lines(data); std::vector functions; gsc::function_ptr func = nullptr; std::uint32_t index = 1; std::uint16_t switchnum = 0; for (auto& line : assembly) { if (line == "" || line.substr(0, 2) == "//") { continue; } else if (line.substr(0, 4) == "sub_") { func = std::make_unique(); func->index = index; func->name = line.substr(4); } else if (line.substr(0, 4) == "end_") { if (func != nullptr) { func->size = index - func->index; functions.push_back(std::move(func)); } } else if (line.substr(0, 4) == "loc_") { func->labels[index] = line; } else { auto data = utils::string::parse_code(line); if (switchnum) { if (data[0] == "case" || data[0] == "default") { for (auto& entry : data) { func->instructions.back()->data.push_back(entry); } switchnum--; continue; } throw gsc::asm_error("invalid instruction inside endswitch \""s + line + "\"!"); } else { auto inst = std::make_unique(); inst->index = index; inst->opcode = static_cast(resolver::opcode_id(data[0])); inst->size = opcode_size(inst->opcode); data.erase(data.begin()); inst->data = std::move(data); if (opcode(inst->opcode) == opcode::OP_endswitch) { switchnum = static_cast(std::stoi(inst->data[0])); inst->size += 7 * switchnum; } index += inst->size; func->instructions.push_back(std::move(inst)); } } } this->assemble(file, functions); } void assembler::assemble(const std::string& file, std::vector& functions) { script_ = std::make_unique(0x100000); stack_ = std::make_unique(0x100000); filename_ = file; functions_ = std::move(functions); script_->write(static_cast(opcode::OP_End)); for (const auto& func : functions_) { this->assemble_function(func); } } void assembler::assemble_function(const gsc::function_ptr& func) { labels_ = func->labels; stack_->write(func->size); func->id = func->name.substr(0, 3) == "_ID" ? std::stoi(func->name.substr(3)) : resolver::token_id(func->name); stack_->write(func->id); if (func->id == 0) { stack_->write_c_string(func->name); } for (const auto& inst : func->instructions) { this->assemble_instruction(inst); } } void assembler::assemble_instruction(const gsc::instruction_ptr& inst) { switch (opcode(inst->opcode)) { case opcode::OP_CastFieldObject: case opcode::OP_plus: case opcode::OP_GetGameRef: case opcode::OP_GetThisthread: case opcode::OP_greater: case opcode::OP_shift_right: case opcode::OP_dec: case opcode::OP_bit_or: case opcode::OP_equality: case opcode::OP_ClearLocalVariableFieldCached0: case opcode::OP_notify: case opcode::OP_PreScriptCall: case opcode::OP_GetUndefined: case opcode::OP_SetLocalVariableFieldCached0: case opcode::OP_GetLevel: case opcode::OP_size: case opcode::OP_AddArray: case opcode::OP_endon: case opcode::OP_shift_left: case opcode::OP_EvalLocalArrayRefCached0: case opcode::OP_Return: case opcode::OP_SafeSetVariableFieldCached0: case opcode::OP_GetSelfObject: case opcode::OP_GetGame: case opcode::OP_EvalArray: case opcode::OP_GetSelf: case opcode::OP_End: case opcode::OP_less_equal: case opcode::OP_EvalLocalVariableCached0: case opcode::OP_EvalLocalVariableCached1: case opcode::OP_EvalLocalVariableCached2: case opcode::OP_EvalLocalVariableCached3: case opcode::OP_EvalLocalVariableCached4: case opcode::OP_EvalLocalVariableCached5: case opcode::OP_ScriptMethodCallPointer: case opcode::OP_checkclearparams: case opcode::OP_minus: case opcode::OP_greater_equal: case opcode::OP_vector: case opcode::OP_ClearArray: case opcode::OP_DecTop: case opcode::OP_CastBool: case opcode::OP_EvalArrayRef: case opcode::OP_GetZero: case opcode::OP_wait: case opcode::OP_waittill: case opcode::OP_GetAnimObject: case opcode::OP_mod: case opcode::OP_clearparams: case opcode::OP_ScriptFunctionCallPointer: case opcode::OP_EmptyArray: case opcode::OP_ClearVariableField: case opcode::OP_EvalNewLocalVariableRefCached0: case opcode::OP_BoolComplement: case opcode::OP_less: case opcode::OP_BoolNot: case opcode::OP_waittillFrameEnd: case opcode::OP_waitframe: case opcode::OP_GetLevelObject: case opcode::OP_inc: case opcode::OP_GetAnim: case opcode::OP_SetVariableField: case opcode::OP_divide: case opcode::OP_multiply: case opcode::OP_EvalLocalVariableRefCached0: case opcode::OP_bit_and: case opcode::OP_voidCodepos: case opcode::OP_inequality: case opcode::OP_bit_ex_or: /* case opcode::OP_NOP: case opcode::OP_abort: case opcode::OP_object: case opcode::OP_thread_object: case opcode::OP_EvalLocalVariable: case opcode::OP_EvalLocalVariableRef: case opcode::OP_breakpoint: case opcode::OP_assignmentBreakpoint: case opcode::OP_manualAndAssignmentBreakpoint: */ case opcode::OP_BoolNotAfterAnd: case opcode::OP_IsDefined: case opcode::OP_IsTrue: script_->write(static_cast(inst->opcode)); break; case opcode::OP_GetByte: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; case opcode::OP_GetNegByte: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; case opcode::OP_GetUnsignedShort: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; case opcode::OP_GetNegUnsignedShort: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); case opcode::OP_GetInteger: script_->write(static_cast(inst->opcode)); script_->write(std::stoi(inst->data[0])); break; case opcode::OP_GetFloat: script_->write(static_cast(inst->opcode)); script_->write(std::stof(inst->data[0])); break; case opcode::OP_GetVector: script_->write(static_cast(inst->opcode)); script_->write(std::stof(inst->data[0])); script_->write(std::stof(inst->data[1])); script_->write(std::stof(inst->data[2])); break; case opcode::OP_GetString: case opcode::OP_GetIString: script_->write(static_cast(inst->opcode)); script_->write(0); stack_->write_c_string(utils::string::to_code(inst->data[0])); break; case opcode::OP_GetAnimation: script_->write(static_cast(inst->opcode)); script_->write(0); script_->write(0); stack_->write_c_string(utils::string::unquote(inst->data[0])); stack_->write_c_string(utils::string::unquote(inst->data[1])); break; case opcode::OP_GetAnimTree: script_->write(static_cast(inst->opcode)); script_->write(0); stack_->write_c_string(utils::string::unquote(inst->data[0])); break; case opcode::OP_waittillmatch: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); script_->write(static_cast(opcode::OP_waittillmatch2)); break; case opcode::OP_SetNewLocalVariableFieldCached0: case opcode::OP_EvalNewLocalArrayRefCached0: case opcode::OP_SafeCreateVariableFieldCached: case opcode::OP_ClearLocalVariableFieldCached: case opcode::OP_SetLocalVariableFieldCached: case opcode::OP_RemoveLocalVariables: case opcode::OP_EvalLocalVariableRefCached: case opcode::OP_EvalLocalArrayRefCached: case opcode::OP_SafeSetVariableFieldCached: case opcode::OP_EvalLocalVariableCached: case opcode::OP_SafeSetWaittillVariableFieldCached: case opcode::OP_CreateLocalVariable: case opcode::OP_EvalLocalVariableObjectCached: case opcode::OP_EvalLocalArrayCached: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; case opcode::OP_EvalSelfFieldVariable: case opcode::OP_SetLevelFieldVariableField: case opcode::OP_ClearFieldVariable: case opcode::OP_EvalFieldVariable: case opcode::OP_EvalFieldVariableRef: case opcode::OP_EvalLevelFieldVariable: case opcode::OP_SetAnimFieldVariableField: case opcode::OP_SetSelfFieldVariableField: case opcode::OP_EvalAnimFieldVariableRef: case opcode::OP_EvalLevelFieldVariableRef: case opcode::OP_EvalAnimFieldVariable: case opcode::OP_EvalSelfFieldVariableRef: this->assemble_field_variable(inst); break; case opcode::OP_CallBuiltinPointer: case opcode::OP_CallBuiltinMethodPointer: case opcode::OP_ScriptThreadCallPointer: case opcode::OP_ScriptChildThreadCallPointer: case opcode::OP_ScriptMethodThreadCallPointer: case opcode::OP_ScriptMethodChildThreadCallPointer: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; case opcode::OP_GetLocalFunction: case opcode::OP_ScriptLocalFunctionCall2: case opcode::OP_ScriptLocalFunctionCall: case opcode::OP_ScriptLocalMethodCall: this->assemble_local_call(inst, false); break; case opcode::OP_ScriptLocalThreadCall: case opcode::OP_ScriptLocalChildThreadCall: case opcode::OP_ScriptLocalMethodThreadCall: case opcode::OP_ScriptLocalMethodChildThreadCall: this->assemble_local_call(inst, true); break; case opcode::OP_GetFarFunction: case opcode::OP_ScriptFarFunctionCall2: case opcode::OP_ScriptFarFunctionCall: case opcode::OP_ScriptFarMethodCall: this->assemble_far_call(inst, false); break; case opcode::OP_ScriptFarThreadCall: case opcode::OP_ScriptFarChildThreadCall: case opcode::OP_ScriptFarMethodThreadCall: case opcode::OP_ScriptFarMethodChildThreadCall: this->assemble_far_call(inst, true); break; case opcode::OP_CallBuiltin: this->assemble_builtin_call(inst, false, true); break; case opcode::OP_CallBuiltinMethod: this->assemble_builtin_call(inst, true, true); break; case opcode::OP_GetBuiltinFunction: case opcode::OP_CallBuiltin0: case opcode::OP_CallBuiltin1: case opcode::OP_CallBuiltin2: case opcode::OP_CallBuiltin3: case opcode::OP_CallBuiltin4: case opcode::OP_CallBuiltin5: this->assemble_builtin_call(inst, false, false); break; case opcode::OP_GetBuiltinMethod: case opcode::OP_CallBuiltinMethod0: case opcode::OP_CallBuiltinMethod1: case opcode::OP_CallBuiltinMethod2: case opcode::OP_CallBuiltinMethod3: case opcode::OP_CallBuiltinMethod4: case opcode::OP_CallBuiltinMethod5: this->assemble_builtin_call(inst, true, false); break; case opcode::OP_JumpOnFalseExpr: case opcode::OP_JumpOnTrueExpr: case opcode::OP_JumpOnFalse: case opcode::OP_JumpOnTrue: this->assemble_jump(inst, true, false); break; case opcode::OP_jumpback: this->assemble_jump(inst, false, true); break; case opcode::OP_jump: this->assemble_jump(inst, false, false); break; case opcode::OP_switch: this->assemble_switch(inst); break; case opcode::OP_endswitch: this->assemble_end_switch(inst); break; /* case opcode::OP_prof_begin: script_->write(0); // TODO: skipped data script_->write(0); break; case opcode::OP_prof_end: script_->write(0); // TODO: skipped data break; case opcode::OP_EvalNewLocalArrayRefCached0_Precompiled: case opcode::OP_SetNewLocalVariableFieldCached0_Precompiled: case opcode::OP_CreateLocalVariable_Precompiled: case opcode::OP_SafeCreateVariableFieldCached_Precompiled: script_->write(static_cast(inst->opcode)); script_->write(static_cast(std::stoi(inst->data[0]))); break; */ case opcode::OP_FormalParams: // case opcode::OP_FormalParams_Precompiled: this->assemble_formal_params(inst); break; /* case opcode::OP_NativeGetLocalFunction: case opcode::OP_NativeLocalFunctionCall: case opcode::OP_NativeLocalFunctionCall2: case opcode::OP_NativeLocalMethodCall: this->assemble_local_call(inst, false); break; case opcode::OP_NativeGetFarFunction: case opcode::OP_NativeFarFunctionCall: case opcode::OP_NativeFarFunctionCall2: case opcode::OP_NativeFarMethodCall: this->assemble_far_call(inst, false); break; case opcode::OP_NativeLocalFunctionThreadCall: case opcode::OP_NativeLocalMethodThreadCall: case opcode::OP_NativeLocalFunctionChildThreadCall: case opcode::OP_NativeLocalMethodChildThreadCall: this->assemble_local_call(inst, true); break; case opcode::OP_NativeFarFunctionThreadCall: case opcode::OP_NativeFarMethodThreadCall: case opcode::OP_NativeFarFunctionChildThreadCall: case opcode::OP_NativeFarMethodChildThreadCall: this->assemble_far_call(inst, true); break; */ default: throw gsc::asm_error(utils::string::va("Unhandled opcode 0x%X at index '%04X'!", inst->opcode, inst->index)); } } void assembler::assemble_builtin_call(const gsc::instruction_ptr& inst, bool method, bool arg_num) { script_->write(static_cast(inst->opcode)); std::uint16_t id = 0; if (arg_num) { script_->write(static_cast(std::stoi(inst->data[0]))); if (method) id = inst->data[1].substr(0, 3) == "_ID" ? std::stoi(inst->data[1].substr(3)) : resolver::method_id(inst->data[1]); else id = inst->data[1].substr(0, 3) == "_ID" ? std::stoi(inst->data[1].substr(3)) : resolver::function_id(inst->data[1]); } else { if (method) id = inst->data[0].substr(0, 3) == "_ID" ? std::stoi(inst->data[0].substr(3)) : resolver::method_id(inst->data[0]); else id = inst->data[0].substr(0, 3) == "_ID" ? std::stoi(inst->data[0].substr(3)) : resolver::function_id(inst->data[0]); } script_->write(id); } void assembler::assemble_local_call(const gsc::instruction_ptr& inst, bool thread) { script_->write(static_cast(inst->opcode)); std::int32_t addr = this->resolve_function(inst->data[0]); std::int32_t offset = addr - inst->index - 1; this->assemble_offset(offset); if (thread) { script_->write(static_cast(std::stoi(inst->data[1]))); } } void assembler::assemble_far_call(const gsc::instruction_ptr& inst, bool thread) { script_->write(static_cast(inst->opcode)); script_->write(0); script_->write(0); std::uint32_t file_id = 0; std::uint32_t func_id = 0; if (thread) { script_->write(static_cast(std::stoi(inst->data[0]))); file_id = inst->data[1].substr(0, 3) == "_ID" ? std::stoi(inst->data[1].substr(3)) : resolver::file_id(inst->data[1]); func_id = inst->data[2].substr(0, 3) == "_ID" ? std::stoi(inst->data[2].substr(3)) : resolver::token_id(inst->data[2]); } else { file_id = inst->data[0].substr(0, 3) == "_ID" ? std::stoi(inst->data[0].substr(3)) : resolver::file_id(inst->data[0]); func_id = inst->data[1].substr(0, 3) == "_ID" ? std::stoi(inst->data[1].substr(3)) : resolver::token_id(inst->data[1]); } stack_->write(file_id); if (file_id == 0) stack_->write_c_string(thread ? inst->data[1] : inst->data[0]); stack_->write(func_id); if (func_id == 0) stack_->write_c_string(thread ? inst->data[2] : inst->data[1]); } void assembler::assemble_jump(const gsc::instruction_ptr& inst, bool expr, bool back) { script_->write(static_cast(inst->opcode)); std::int32_t addr = this->resolve_label(inst->data[0]); if (expr) { script_->write(addr - inst->index - 3); } else if (back) { script_->write((inst->index + 3) - addr); } else { script_->write(addr - inst->index - 5); } } void assembler::assemble_field_variable(const gsc::instruction_ptr& inst) { script_->write(static_cast(inst->opcode)); std::uint32_t field_id = 0; if (inst->data[0].substr(0, 3) == "_ID") { field_id = std::stoi(inst->data[0].substr(3)); } else { field_id = resolver::token_id(inst->data[0]); if (field_id == 0) { field_id = 0xFFFFFFFF; } } script_->write(field_id); if (field_id > 0x13738) { stack_->write(0); stack_->write_c_string(inst->data[0]); } } void assembler::assemble_formal_params(const gsc::instruction_ptr& inst) { script_->write(static_cast(inst->opcode)); auto size = std::stoi(inst->data[0]); script_->write(static_cast(size)); for(auto i = 1; i <= size; i++) { script_->write(static_cast(std::stoi(inst->data[i]))); } } void assembler::assemble_switch(const gsc::instruction_ptr& inst) { script_->write(static_cast(inst->opcode)); std::int32_t addr = this->resolve_label(inst->data[0]); script_->write(addr - inst->index - 4); } void assembler::assemble_end_switch(const gsc::instruction_ptr& inst) { script_->write(static_cast(inst->opcode)); std::uint16_t casenum = 0; if (utils::string::is_number(inst->data[0])) { casenum = std::stoi(inst->data[0]); } else { throw gsc::asm_error("invalid endswitch number!"); } script_->write(casenum); std::uint32_t internal_index = inst->index + 3; for (std::uint16_t i = 0; i < casenum; i++) { if (inst->data[1 + (3 * i)] == "case") { if (utils::string::is_number(inst->data[1 + (3 * i) + 1])) { script_->write((std::stoi(inst->data[1 + (3 * i) + 1]) & 0xFFFFFF) + 0x800000); } else { script_->write(i + 1); stack_->write_c_string(utils::string::unquote(inst->data[1 + (3 * i) + 1])); } internal_index += 4; std::int32_t addr = this->resolve_label(inst->data[1 + (3 * i) + 2]); this->assemble_offset(addr - internal_index); internal_index += 3; } else if (inst->data[1 + (3 * i)] == "default") { script_->write(0); stack_->write_c_string("\x01"); internal_index += 4; std::int32_t addr = this->resolve_label(inst->data[1 + (3 * i) + 1]); this->assemble_offset(addr - internal_index); internal_index += 3; } } } void assembler::assemble_offset(std::int32_t offset) { std::array bytes = {}; offset = (offset << 8) >> 8; *reinterpret_cast(bytes.data()) = offset; script_->write(bytes[0]); script_->write(bytes[1]); script_->write(bytes[2]); } auto assembler::resolve_function(const std::string& name) -> std::uint32_t { auto temp = name.substr(0, 4) == "sub_" ? name.substr(4) : name; for (const auto& func : functions_) { if (func->name == temp) { return func->index; } } throw gsc::asm_error("Couldn't resolve local function address of '" + temp + "'!"); } auto assembler::resolve_label(const std::string& name) -> std::uint32_t { for (auto& func : labels_) { if (func.second == name) { return func.first; } } throw gsc::asm_error("Couldn't resolve label address of '" + name + "'!"); } } // namespace xsk::gsc::iw8