gsc-tool/src/gsc/assembler.cpp
2023-02-19 17:57:25 +01:00

642 lines
20 KiB
C++

// Copyright 2023 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 "stdinc.hpp"
#include "assembler.hpp"
#include "context.hpp"
namespace xsk::gsc
{
assembler::assembler(context const* ctx) : ctx_{ ctx }, script_{ ctx->endian() == endian::big }, stack_{ ctx->endian() == endian::big }
{
}
auto assembler::assemble(assembly const& data) -> std::pair<buffer, buffer>
{
assembly_ = &data;
script_.clear();
stack_.clear();
script_.write<u8>(ctx_->opcode_id(opcode::OP_End));
for (auto const& func : data.functions)
{
assemble_function(*func);
}
return { buffer{ script_.data(), script_.pos() }, buffer{ stack_.data(), stack_.pos() } };
}
auto assembler::assemble_function(function const& func) -> void
{
func_ = &func;
stack_.write<u32>(func.size);
if (ctx_->props() & props::hash)
{
stack_.write<u64>(ctx_->hash_id(func.name));
}
else
{
if (ctx_->props() & props::tok4)
stack_.write<u32>(func.id);
else
stack_.write<u16>(static_cast<u16>(func.id));
if (func.id == 0)
{
stack_.write_cstr(encrypt_string(func.name));
}
}
for (auto const& inst : func.instructions)
{
assemble_instruction(*inst);
}
}
auto assembler::assemble_instruction(instruction const& inst) -> void
{
script_.write<u8>(ctx_->opcode_id(inst.opcode));
switch (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_waittillmatch2:
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_BoolNotAfterAnd:
case opcode::OP_IsDefined:
case opcode::OP_IsTrue:
break;
case opcode::OP_GetByte:
case opcode::OP_GetNegByte:
script_.write<u8>(static_cast<u8>(std::stoul(inst.data[0])));
break;
case opcode::OP_GetUnsignedShort:
case opcode::OP_GetNegUnsignedShort:
script_.write<u16>(static_cast<u16>(std::stoul(inst.data[0])));
break;
case opcode::OP_GetUnsignedInt:
case opcode::OP_GetNegUnsignedInt:
script_.write<u32>(static_cast<u32>(std::stoul(inst.data[0])));
break;
case opcode::OP_GetInteger:
script_.write<i32>(std::stoi(inst.data[0]));
break;
case opcode::OP_GetInteger64:
script_.write<i64>(std::stoll(inst.data[0]));
break;
case opcode::OP_GetFloat:
script_.write<f32>(std::stof(inst.data[0]));
break;
case opcode::OP_GetVector:
script_.align((ctx_->endian() == endian::little) ? 1 : 4);
script_.write<f32>(std::stof(inst.data[0]));
script_.write<f32>(std::stof(inst.data[1]));
script_.write<f32>(std::stof(inst.data[2]));
break;
case opcode::OP_GetString:
case opcode::OP_GetIString:
if (ctx_->props() & props::str4)
script_.write<u32>(0);
else
script_.write<u16>(0);
stack_.write_cstr(encrypt_string(inst.data[0]));
break;
case opcode::OP_GetAnimation:
if (ctx_->props() & props::str4)
script_.write<u64>(0);
else
script_.write<u32>(0);
stack_.write_cstr(encrypt_string(inst.data[0]));
stack_.write_cstr(encrypt_string(inst.data[1]));
break;
case opcode::OP_GetAnimTree:
script_.write<u8>(0);
stack_.write_cstr(encrypt_string(inst.data[0]));
break;
case opcode::OP_GetUnkxHash:
script_.write<u32>(std::stoul(inst.data[0], nullptr, 16));
break;
case opcode::OP_GetStatHash:
case opcode::OP_GetEnumHash:
case opcode::OP_GetDvarHash:
script_.write<u64>(std::stoull(inst.data[0], nullptr, 16));
break;
case opcode::OP_waittillmatch:
script_.write<u8>(static_cast<u8>(std::stoul(inst.data[0])));
break;
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_EvalLocalVariableObjectCached:
case opcode::OP_EvalLocalArrayCached:
script_.write<u8>(static_cast<u8>(std::stoul(inst.data[0])));
break;
case opcode::OP_CreateLocalVariable:
case opcode::OP_EvalNewLocalArrayRefCached0:
case opcode::OP_SafeCreateVariableFieldCached:
case opcode::OP_SetNewLocalVariableFieldCached0:
if (ctx_->props() & props::hash)
script_.write<u64>(ctx_->hash_id(inst.data[0]));
else
script_.write<u8>(static_cast<u8>(std::stoul(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:
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<u8>(static_cast<u8>(std::stoul(inst.data[0])));
break;
case opcode::OP_GetLocalFunction:
case opcode::OP_ScriptLocalFunctionCall2:
case opcode::OP_ScriptLocalFunctionCall:
case opcode::OP_ScriptLocalMethodCall:
assemble_local_call(inst, false);
break;
case opcode::OP_ScriptLocalThreadCall:
case opcode::OP_ScriptLocalChildThreadCall:
case opcode::OP_ScriptLocalMethodThreadCall:
case opcode::OP_ScriptLocalMethodChildThreadCall:
assemble_local_call(inst, true);
break;
case opcode::OP_GetFarFunction:
case opcode::OP_ScriptFarFunctionCall2:
case opcode::OP_ScriptFarFunctionCall:
case opcode::OP_ScriptFarMethodCall:
assemble_far_call(inst, false);
break;
case opcode::OP_ScriptFarThreadCall:
case opcode::OP_ScriptFarChildThreadCall:
case opcode::OP_ScriptFarMethodThreadCall:
case opcode::OP_ScriptFarMethodChildThreadCall:
assemble_far_call(inst, true);
break;
case opcode::OP_CallBuiltin:
assemble_builtin_call(inst, false, true);
break;
case opcode::OP_CallBuiltinMethod:
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:
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:
assemble_builtin_call(inst, true, false);
break;
case opcode::OP_JumpOnFalseExpr:
case opcode::OP_JumpOnTrueExpr:
case opcode::OP_JumpOnFalse:
case opcode::OP_JumpOnTrue:
assemble_jump(inst, true, false);
break;
case opcode::OP_jumpback:
assemble_jump(inst, false, true);
break;
case opcode::OP_jump:
assemble_jump(inst, false, false);
break;
case opcode::OP_switch:
assemble_switch(inst);
break;
case opcode::OP_endswitch:
assemble_end_switch(inst);
break;
case opcode::OP_FormalParams:
assemble_formal_params(inst);
break;
default:
throw asm_error(fmt::format("unhandled opcode {} at index {:04X}", ctx_->opcode_name(inst.opcode), inst.index));
}
}
auto assembler::assemble_builtin_call(instruction const& inst, bool method, bool args) -> void
{
if (args)
{
script_.write<u8>(static_cast<u8>(std::stoi(inst.data[1])));
}
if (ctx_->props() & props::hash)
{
stack_.write_cstr(fmt::format("#xS{:x}", ctx_->hash_id(inst.data[0])));
script_.write<u16>(0);
}
else
{
auto const id = method ? ctx_->meth_id(inst.data[0]) : ctx_->func_id(inst.data[0]);
script_.write<u16>(id);
}
}
auto assembler::assemble_local_call(instruction const& inst, bool thread) -> void
{
auto const addr = resolve_function(inst.data[0]);
auto const offset = static_cast<i32>(addr - inst.index - 1);
assemble_offset(offset);
if (thread)
{
script_.write<u8>(static_cast<u8>(std::stoi(inst.data[1])));
}
}
auto assembler::assemble_far_call(instruction const& inst, bool thread) -> void
{
if (ctx_->props() & props::farcall)
{
if (inst.data[0].empty())
{
auto const addr = resolve_function(inst.data[1]);
auto const offset = static_cast<i32>(addr - inst.index - 1);
script_.write<i32>(static_cast<i32>(offset)); // unsigned?
stack_.write<u64>(0);
stack_.write<u64>(0);
}
else
{
auto path = inst.data[0];
if (!path.starts_with("_id_"))
path.append(ctx_->instance() == instance::server ? ".gsc" : ".csc");
script_.write<u32>(0);
stack_.write<u64>(ctx_->path_id(path));
stack_.write<u64>(ctx_->hash_id(inst.data[1]));
}
if (thread)
{
script_.write<u8>(static_cast<u8>(std::stoi(inst.data[2])));
}
}
else
{
script_.write<u8>(0);
script_.write<u16>(0);
if (thread)
{
script_.write<u8>(static_cast<u8>(std::stoi(inst.data[2])));
}
auto const file_id = ctx_->token_id(inst.data[0]);
auto const func_id = ctx_->token_id(inst.data[1]);
if (ctx_->props() & props::tok4)
stack_.write<u32>(file_id);
else
stack_.write<u16>(static_cast<u16>(file_id));
if (file_id == 0)
stack_.write_cstr(encrypt_string(inst.data[0]));
if (ctx_->props() & props::tok4)
stack_.write<u32>(func_id);
else
stack_.write<u16>(static_cast<u16>(func_id));
if (func_id == 0)
stack_.write_cstr(encrypt_string(inst.data[1]));
}
}
auto assembler::assemble_switch(instruction const& inst) -> void
{
auto const addr = resolve_label(inst.data[0]);
script_.write<i32>(addr - inst.index - 4);
}
auto assembler::assemble_end_switch(instruction const& inst) -> void
{
auto const count = std::stoul(inst.data[0]);
script_.write<u16>(static_cast<u16>(count));
auto type = static_cast<switch_type>(std::stoul(inst.data.back()));
auto index = inst.index + 3u;
for (auto i = 0u; i < count; i++)
{
if (ctx_->engine() == engine::iw9)
{
if (inst.data[1 + (4 * i)] == "case")
{
type = static_cast<switch_type>(std::stoul(inst.data[1 + (4 * i) + 1]));
if (type == switch_type::integer)
{
script_.write<u32>(std::stoi(inst.data[1 + (4 * i) + 2])); //signed?
}
else
{
script_.write<u32>(0);
stack_.write_cstr(inst.data[1 + (4 * i) + 2]);
}
auto const addr = resolve_label(inst.data[1 + (4 * i) + 3]);
script_.write<i16>(static_cast<i16>(addr - index - 4));
script_.write<u8>(0);
script_.write<u8>(static_cast<u8>(type));
index += 8;
}
else if (inst.data[1 + (4 * i)] == "default")
{
auto const addr = resolve_label(inst.data[1 + (4 * i) + 1]);
script_.write<u32>(0);
script_.write<i16>(static_cast<i16>(addr - index - 4));
script_.write<u16>(0);
index += 8;
}
else
{
throw asm_error(fmt::format("invalid switch case {}", inst.data[1 + (4 * i)]));
}
}
else
{
if (inst.data[1 + (3 * i)] == "case")
{
if (type == switch_type::integer)
{
script_.write<u32>((std::stoi(inst.data[1 + (3 * i) + 1]) & 0xFFFFFF) + 0x800000);
}
else
{
script_.write<u32>(i + 1);
stack_.write_cstr(encrypt_string(inst.data[1 + (3 * i) + 1]));
}
auto const addr = resolve_label(inst.data[1 + (3 * i) + 2]);
assemble_offset(addr - index - 4);
index += 7;
}
else if (inst.data[1 + (3 * i)] == "default")
{
script_.write<u32>(0);
stack_.write_cstr("\x01");
auto const addr = resolve_label(inst.data[1 + (3 * i) + 1]);
assemble_offset(addr - index - 4);
index += 7;
}
else
{
throw asm_error(fmt::format("invalid switch case {}", inst.data[1 + (3 * i)]));
}
}
}
}
auto assembler::assemble_field_variable(instruction const& inst) -> void
{
if (ctx_->props() & props::hash)
{
script_.write<u64>(ctx_->hash_id(inst.data[0]));
}
else
{
auto id = ctx_->token_id(inst.data[0]);
if (id == 0) id = 0xFFFFFFFF;
if (ctx_->props() & props::tok4)
script_.write<u32>(id);
else
script_.write<u16>(static_cast<u16>(id));
if (id > ctx_->str_count())
{
if (ctx_->props() & props::tok4)
stack_.write<u32>(0);
else
stack_.write<u16>(0);
stack_.write_cstr(encrypt_string(inst.data[0]));
}
}
}
auto assembler::assemble_formal_params(instruction const& inst) -> void
{
auto const count = std::stoul(inst.data[0]);
script_.write<u8>(static_cast<u8>(count));
for (auto i = 1u; i <= count; i++)
{
if (ctx_->props() & props::hash)
{
script_.write<u64>(ctx_->hash_id(inst.data[i]));
}
else
{
script_.write<u8>(static_cast<u8>(std::stoi(inst.data[i])));
}
}
}
auto assembler::assemble_jump(instruction const& inst, bool expr, bool back) -> void
{
auto const addr = resolve_label(inst.data[0]);
if (expr)
{
script_.write<i16>(static_cast<i16>(addr - inst.index - 3));
}
else if (back)
{
script_.write<i16>(static_cast<i16>((inst.index + 3) - addr));
}
else
{
script_.write<i32>(static_cast<i32>(addr - inst.index - 5));
}
}
auto assembler::assemble_offset(i32 offs) -> void
{
auto bytes = std::array<u8, 4>{};
auto const shift = (ctx_->props() & props::offs8) ? 8 : (ctx_->props() & props::offs9) ? 9 : 10;
offs = (offs << shift) >> 8;
*reinterpret_cast<i32*>(bytes.data()) = offs;
if (ctx_->endian() == endian::little)
{
script_.write<u8>(bytes[0]);
script_.write<u8>(bytes[1]);
script_.write<u8>(bytes[2]);
}
else
{
script_.write<u8>(bytes[2]);
script_.write<u8>(bytes[1]);
script_.write<u8>(bytes[0]);
}
}
auto assembler::resolve_function(std::string const& name) -> std::int32_t
{
for (auto const& entry : assembly_->functions)
{
if (entry->name == name)
{
return entry->index;
}
}
throw asm_error(fmt::format("couldn't resolve local function address of {}", name));
}
auto assembler::resolve_label(std::string const& name) -> std::int32_t
{
for (auto const& entry : func_->labels)
{
if (entry.second == name)
{
return entry.first;
}
}
throw asm_error(fmt::format("couldn't resolve label address of {}", name));
}
auto assembler::encrypt_string(std::string const& str) -> std::string
{
if (str.starts_with("_encstr_") && str.size() % 2 == 0)
{
auto data = std::string{};
data.reserve(str.size() / 2);
for (auto i = 8u; i < str.size(); i += 2)
{
data += static_cast<char>(std::stoul(str.substr(i, 2), 0, 16));
}
return data;
}
return str;
}
} // namespace xsk::gsc