ps3 & xbox360 support for iw5
This commit is contained in:
parent
9f465644d4
commit
71a3e25be4
577
src/experimental/iw5_console/xsk/assembler.cpp
Normal file
577
src/experimental/iw5_console/xsk/assembler.cpp
Normal file
@ -0,0 +1,577 @@
|
||||
// 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 "iw5_console.hpp"
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
auto assembler::output_script() -> std::vector<std::uint8_t>
|
||||
{
|
||||
std::vector<std::uint8_t> script;
|
||||
|
||||
if (script_ == nullptr) return script;
|
||||
|
||||
script.resize(script_->pos());
|
||||
std::memcpy(script.data(), script_->buffer().data(), script.size());
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
auto assembler::output_stack() -> std::vector<std::uint8_t>
|
||||
{
|
||||
std::vector<std::uint8_t> stack;
|
||||
|
||||
if (stack_ == nullptr) return stack;
|
||||
|
||||
stack.resize(stack_->pos());
|
||||
std::memcpy(stack.data(), stack_->buffer().data(), stack.size());
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
void assembler::assemble(const std::string& file, std::vector<std::uint8_t>& data)
|
||||
{
|
||||
std::vector<std::string> assembly = utils::string::clean_buffer_lines(data);
|
||||
std::vector<function::ptr> functions;
|
||||
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<function>();
|
||||
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 opdata = utils::string::parse_code(line);
|
||||
|
||||
if (switchnum)
|
||||
{
|
||||
if (opdata[0] == "case" || opdata[0] == "default")
|
||||
{
|
||||
for (auto& entry : opdata)
|
||||
{
|
||||
func->instructions.back()->data.push_back(entry);
|
||||
}
|
||||
switchnum--;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw asm_error("invalid instruction inside endswitch \""s + line + "\"!");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto inst = std::make_unique<instruction>();
|
||||
inst->index = index;
|
||||
inst->opcode = static_cast<std::uint8_t>(resolver::opcode_id(opdata[0]));
|
||||
inst->size = opcode_size(inst->opcode);
|
||||
opdata.erase(opdata.begin());
|
||||
inst->data = std::move(opdata);
|
||||
|
||||
switch (opcode(inst->opcode))
|
||||
{
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
inst->data[0] = inst->data[0].substr(4);
|
||||
break;
|
||||
case opcode::OP_endswitch:
|
||||
switchnum = static_cast<std::uint16_t>(std::stoi(inst->data[0]));
|
||||
inst->size += 7 * switchnum;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
index += inst->size;
|
||||
func->instructions.push_back(std::move(inst));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assemble(file, functions);
|
||||
}
|
||||
|
||||
void assembler::assemble(const std::string& file, std::vector<function::ptr>& funcs)
|
||||
{
|
||||
script_ = std::make_unique<utils::byte_buffer>(0x100000);
|
||||
stack_ = std::make_unique<utils::byte_buffer>(0x100000);
|
||||
filename_ = file;
|
||||
functions_ = std::move(funcs);
|
||||
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(opcode::OP_End));
|
||||
|
||||
for (const auto& func : functions_)
|
||||
{
|
||||
assemble_function(func);
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_function(const function::ptr& func)
|
||||
{
|
||||
labels_ = func->labels;
|
||||
|
||||
func->id = resolver::token_id(func->name);
|
||||
|
||||
stack_->write_endian<std::uint32_t>(func->size);
|
||||
stack_->write_endian<std::uint16_t>(static_cast<std::uint16_t>(func->id));
|
||||
|
||||
if (func->id == 0)
|
||||
{
|
||||
stack_->write_c_string(func->name);
|
||||
}
|
||||
|
||||
for (const auto& inst : func->instructions)
|
||||
{
|
||||
assemble_instruction(inst);
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_instruction(const instruction::ptr& inst)
|
||||
{
|
||||
switch (opcode(inst->opcode))
|
||||
{
|
||||
case opcode::OP_End:
|
||||
case opcode::OP_Return:
|
||||
case opcode::OP_GetUndefined:
|
||||
case opcode::OP_GetZero:
|
||||
case opcode::OP_waittillFrameEnd:
|
||||
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_EvalArray:
|
||||
case opcode::OP_EvalArrayRef:
|
||||
case opcode::OP_EvalLocalArrayRefCached0:
|
||||
case opcode::OP_ClearArray:
|
||||
case opcode::OP_EmptyArray:
|
||||
case opcode::OP_AddArray:
|
||||
case opcode::OP_PreScriptCall:
|
||||
case opcode::OP_ScriptFunctionCallPointer:
|
||||
case opcode::OP_ScriptMethodCallPointer:
|
||||
case opcode::OP_GetLevelObject:
|
||||
case opcode::OP_GetAnimObject:
|
||||
case opcode::OP_GetSelf:
|
||||
case opcode::OP_GetThisthread:
|
||||
case opcode::OP_GetLevel:
|
||||
case opcode::OP_GetGame:
|
||||
case opcode::OP_GetAnim:
|
||||
case opcode::OP_GetGameRef:
|
||||
case opcode::OP_inc:
|
||||
case opcode::OP_dec:
|
||||
case opcode::OP_bit_or:
|
||||
case opcode::OP_bit_ex_or:
|
||||
case opcode::OP_bit_and:
|
||||
case opcode::OP_equality:
|
||||
case opcode::OP_inequality:
|
||||
case opcode::OP_less:
|
||||
case opcode::OP_greater:
|
||||
case opcode::OP_less_equal:
|
||||
case opcode::OP_waittillmatch2:
|
||||
case opcode::OP_waittill:
|
||||
case opcode::OP_notify:
|
||||
case opcode::OP_endon:
|
||||
case opcode::OP_voidCodepos:
|
||||
case opcode::OP_vector:
|
||||
case opcode::OP_greater_equal:
|
||||
case opcode::OP_shift_left:
|
||||
case opcode::OP_shift_right:
|
||||
case opcode::OP_plus:
|
||||
case opcode::OP_minus:
|
||||
case opcode::OP_multiply:
|
||||
case opcode::OP_divide:
|
||||
case opcode::OP_mod:
|
||||
case opcode::OP_size:
|
||||
case opcode::OP_GetSelfObject:
|
||||
case opcode::OP_SafeSetVariableFieldCached0:
|
||||
case opcode::OP_clearparams:
|
||||
case opcode::OP_checkclearparams:
|
||||
case opcode::OP_EvalLocalVariableRefCached0:
|
||||
case opcode::OP_EvalNewLocalVariableRefCached0:
|
||||
case opcode::OP_SetVariableField:
|
||||
case opcode::OP_ClearVariableField:
|
||||
case opcode::OP_SetLocalVariableFieldCached0:
|
||||
case opcode::OP_ClearLocalVariableFieldCached0:
|
||||
case opcode::OP_wait:
|
||||
case opcode::OP_DecTop:
|
||||
case opcode::OP_CastFieldObject:
|
||||
case opcode::OP_CastBool:
|
||||
case opcode::OP_BoolNot:
|
||||
case opcode::OP_BoolComplement:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
break;
|
||||
case opcode::OP_GetByte:
|
||||
case opcode::OP_GetNegByte:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write_endian<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[0])));
|
||||
break;
|
||||
case opcode::OP_GetUnsignedShort:
|
||||
case opcode::OP_GetNegUnsignedShort:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write_endian<std::uint16_t>(static_cast<std::uint16_t>(std::stoi(inst->data[0])));
|
||||
break;
|
||||
case opcode::OP_GetInteger:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write_endian<std::int32_t>(std::stoi(inst->data[0]));
|
||||
break;
|
||||
case opcode::OP_GetFloat:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write_endian<float>(std::stof(inst->data[0]));
|
||||
break;
|
||||
case opcode::OP_GetVector:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
inst->size += script_->align(4);
|
||||
script_->write_endian<float>(std::stof(inst->data[0]));
|
||||
script_->write_endian<float>(std::stof(inst->data[1]));
|
||||
script_->write_endian<float>(std::stof(inst->data[2]));
|
||||
break;
|
||||
case opcode::OP_GetString:
|
||||
case opcode::OP_GetIString:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint16_t>(0);
|
||||
stack_->write_c_string(inst->data[0]);
|
||||
break;
|
||||
case opcode::OP_GetAnimation:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint32_t>(0);
|
||||
stack_->write_c_string(inst->data[0]);
|
||||
stack_->write_c_string(inst->data[1]);
|
||||
break;
|
||||
case opcode::OP_GetAnimTree:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint8_t>(0);
|
||||
stack_->write_c_string(inst->data[0]);
|
||||
break;
|
||||
case opcode::OP_waittillmatch:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[0])));
|
||||
break;
|
||||
case opcode::OP_CreateLocalVariable:
|
||||
case opcode::OP_RemoveLocalVariables:
|
||||
case opcode::OP_EvalLocalVariableCached:
|
||||
case opcode::OP_EvalLocalArrayCached:
|
||||
case opcode::OP_EvalNewLocalArrayRefCached0:
|
||||
case opcode::OP_EvalLocalArrayRefCached:
|
||||
case opcode::OP_SafeCreateVariableFieldCached:
|
||||
case opcode::OP_SafeSetVariableFieldCached:
|
||||
case opcode::OP_SafeSetWaittillVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableRefCached:
|
||||
case opcode::OP_SetNewLocalVariableFieldCached0:
|
||||
case opcode::OP_SetLocalVariableFieldCached:
|
||||
case opcode::OP_ClearLocalVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableObjectCached:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[0])));
|
||||
break;
|
||||
case opcode::OP_EvalLevelFieldVariable:
|
||||
case opcode::OP_EvalAnimFieldVariable:
|
||||
case opcode::OP_EvalSelfFieldVariable:
|
||||
case opcode::OP_EvalFieldVariable:
|
||||
case opcode::OP_EvalLevelFieldVariableRef:
|
||||
case opcode::OP_EvalAnimFieldVariableRef:
|
||||
case opcode::OP_EvalSelfFieldVariableRef:
|
||||
case opcode::OP_EvalFieldVariableRef:
|
||||
case opcode::OP_ClearFieldVariable:
|
||||
case opcode::OP_SetLevelFieldVariableField:
|
||||
case opcode::OP_SetAnimFieldVariableField:
|
||||
case opcode::OP_SetSelfFieldVariableField:
|
||||
assemble_field_variable(inst);
|
||||
break;
|
||||
case opcode::OP_ScriptThreadCallPointer:
|
||||
case opcode::OP_ScriptChildThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodChildThreadCallPointer:
|
||||
case opcode::OP_CallBuiltinPointer:
|
||||
case opcode::OP_CallBuiltinMethodPointer:
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[0])));
|
||||
break;
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
case opcode::OP_GetLocalFunction:
|
||||
assemble_local_call(inst, false);
|
||||
break;
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
assemble_local_call(inst, true);
|
||||
break;
|
||||
case opcode::OP_ScriptFarFunctionCall2:
|
||||
case opcode::OP_ScriptFarFunctionCall:
|
||||
case opcode::OP_ScriptFarMethodCall:
|
||||
case opcode::OP_GetFarFunction:
|
||||
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;
|
||||
default:
|
||||
throw asm_error(utils::string::va("unhandled opcode 0x%X at index '%04X'!", inst->opcode, inst->index));
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_builtin_call(const instruction::ptr& inst, bool method, bool args)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
if (args)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[1])));
|
||||
}
|
||||
|
||||
const auto id = method ? resolver::method_id(inst->data[0]) : resolver::function_id(inst->data[0]);
|
||||
|
||||
script_->write_endian<std::uint16_t>(id);
|
||||
}
|
||||
|
||||
void assembler::assemble_local_call(const instruction::ptr& inst, bool thread)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
const auto addr = resolve_function(inst->data[0]);
|
||||
const auto offset = static_cast<std::int32_t>(addr - inst->index - 1);
|
||||
|
||||
assemble_offset(offset);
|
||||
|
||||
if (thread)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[1])));
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_far_call(const instruction::ptr& inst, bool thread)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
script_->write<std::uint8_t>(0);
|
||||
script_->write<std::uint16_t>(0);
|
||||
|
||||
if (thread)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(std::stoi(inst->data[2])));
|
||||
}
|
||||
|
||||
const auto file_id = resolver::token_id(inst->data[0]);
|
||||
const auto func_id = resolver::token_id(inst->data[1]);
|
||||
|
||||
stack_->write_endian<std::uint16_t>(file_id);
|
||||
if (file_id == 0) stack_->write_c_string(inst->data[0]);
|
||||
stack_->write_endian<std::uint16_t>(func_id);
|
||||
if (func_id == 0) stack_->write_c_string(inst->data[1]);
|
||||
}
|
||||
|
||||
void assembler::assemble_switch(const instruction::ptr& inst)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
const auto addr = resolve_label(inst->data[0]);
|
||||
|
||||
script_->write_endian<std::int32_t>(addr - inst->index - 4);
|
||||
}
|
||||
|
||||
void assembler::assemble_end_switch(const instruction::ptr& inst)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
const auto count = std::stoul(inst->data[0]);
|
||||
|
||||
script_->write_endian<std::uint16_t>(static_cast<std::uint16_t>(count));
|
||||
|
||||
std::uint32_t index = inst->index + 3;
|
||||
|
||||
for (auto i = 0u; i < count; i++)
|
||||
{
|
||||
if (inst->data[1 + (3 * i)] == "case")
|
||||
{
|
||||
if (utils::string::is_number(inst->data[1 + (3 * i) + 1]))
|
||||
{
|
||||
script_->write_endian<uint32_t>((std::stoi(inst->data[1 + (3 * i) + 1]) & 0xFFFFFF) + 0x800000);
|
||||
}
|
||||
else
|
||||
{
|
||||
script_->write_endian<uint32_t>(i + 1);
|
||||
stack_->write_c_string(inst->data[1 + (3 * i) + 1]);
|
||||
}
|
||||
|
||||
index += 4;
|
||||
|
||||
const auto addr = resolve_label(inst->data[1 + (3 * i) + 2]);
|
||||
|
||||
assemble_offset(addr - index);
|
||||
|
||||
index += 3;
|
||||
}
|
||||
else if (inst->data[1 + (3 * i)] == "default")
|
||||
{
|
||||
script_->write_endian<uint32_t>(0);
|
||||
stack_->write_c_string("\x01");
|
||||
|
||||
index += 4;
|
||||
|
||||
const auto addr = resolve_label(inst->data[1 + (3 * i) + 1]);
|
||||
|
||||
assemble_offset(addr - index);
|
||||
|
||||
index += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw asm_error("invalid switch case '" + inst->data[1 + (3 * i)] + "'!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_field_variable(const instruction::ptr& inst)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
auto id = resolver::token_id(inst->data[0]);
|
||||
|
||||
if (id == 0) id = 0xFFFF;
|
||||
|
||||
script_->write_endian<std::uint16_t>(id);
|
||||
|
||||
if (id > max_string_id)
|
||||
{
|
||||
stack_->write<std::uint16_t>(0);
|
||||
stack_->write_c_string(inst->data[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_jump(const instruction::ptr& inst, bool expr, bool back)
|
||||
{
|
||||
script_->write<std::uint8_t>(static_cast<std::uint8_t>(inst->opcode));
|
||||
|
||||
const auto addr = resolve_label(inst->data[0]);
|
||||
|
||||
if (expr)
|
||||
{
|
||||
script_->write_endian<std::int16_t>(static_cast<std::int16_t>(addr - inst->index - 3));
|
||||
}
|
||||
else if (back)
|
||||
{
|
||||
script_->write_endian<std::int16_t>(static_cast<std::int16_t>((inst->index + 3) - addr));
|
||||
}
|
||||
else
|
||||
{
|
||||
script_->write_endian<std::int32_t>(static_cast<std::int32_t>(addr - inst->index - 5));
|
||||
}
|
||||
}
|
||||
|
||||
void assembler::assemble_offset(std::int32_t offset)
|
||||
{
|
||||
std::array<std::uint8_t, 4> bytes = {};
|
||||
|
||||
offset = (offset << 10) >> 8;
|
||||
|
||||
*reinterpret_cast<std::int32_t*>(bytes.data()) = offset;
|
||||
|
||||
script_->write<std::uint8_t>(bytes[2]);
|
||||
script_->write<std::uint8_t>(bytes[1]);
|
||||
script_->write<std::uint8_t>(bytes[0]);
|
||||
}
|
||||
|
||||
auto assembler::resolve_function(const std::string& name) -> std::int32_t
|
||||
{
|
||||
for (const auto& entry : functions_)
|
||||
{
|
||||
if (entry->name == name)
|
||||
{
|
||||
return entry->index;
|
||||
}
|
||||
}
|
||||
|
||||
throw asm_error("couldn't resolve local function address of '" + name + "'!");
|
||||
}
|
||||
|
||||
auto assembler::resolve_label(const std::string& name) -> std::int32_t
|
||||
{
|
||||
for (const auto& entry : labels_)
|
||||
{
|
||||
if (entry.second == name)
|
||||
{
|
||||
return entry.first;
|
||||
}
|
||||
}
|
||||
|
||||
throw asm_error("couldn't resolve label address of '" + name + "'!");
|
||||
}
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
40
src/experimental/iw5_console/xsk/assembler.hpp
Normal file
40
src/experimental/iw5_console/xsk/assembler.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
class assembler : public gsc::assembler
|
||||
{
|
||||
std::string filename_;
|
||||
utils::byte_buffer::ptr script_;
|
||||
utils::byte_buffer::ptr stack_;
|
||||
std::vector<function::ptr> functions_;
|
||||
std::unordered_map<std::uint32_t, std::string> labels_;
|
||||
|
||||
public:
|
||||
auto output_script() -> std::vector<std::uint8_t>;
|
||||
auto output_stack() -> std::vector<std::uint8_t>;
|
||||
void assemble(const std::string& file, std::vector<std::uint8_t>& data);
|
||||
void assemble(const std::string& file, std::vector<function::ptr>& funcs);
|
||||
|
||||
private:
|
||||
void assemble_function(const function::ptr& func);
|
||||
void assemble_instruction(const instruction::ptr& inst);
|
||||
void assemble_builtin_call(const instruction::ptr& inst, bool method, bool args);
|
||||
void assemble_local_call(const instruction::ptr& inst, bool thread);
|
||||
void assemble_far_call(const instruction::ptr& inst, bool thread);
|
||||
void assemble_switch(const instruction::ptr& inst);
|
||||
void assemble_end_switch(const instruction::ptr& inst);
|
||||
void assemble_field_variable(const instruction::ptr& inst);
|
||||
void assemble_jump(const instruction::ptr& inst, bool expr, bool back);
|
||||
void assemble_offset(std::int32_t offset);
|
||||
auto resolve_function(const std::string& name) -> std::int32_t;
|
||||
auto resolve_label(const std::string& name) -> std::int32_t;
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
3636
src/experimental/iw5_console/xsk/compiler.cpp
Normal file
3636
src/experimental/iw5_console/xsk/compiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
170
src/experimental/iw5_console/xsk/compiler.hpp
Normal file
170
src/experimental/iw5_console/xsk/compiler.hpp
Normal file
@ -0,0 +1,170 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
enum class opcode : std::uint8_t;
|
||||
|
||||
class compiler : public gsc::compiler
|
||||
{
|
||||
build mode_;
|
||||
std::string filename_;
|
||||
std::vector<function::ptr> assembly_;
|
||||
function::ptr function_;
|
||||
std::uint32_t index_;
|
||||
std::uint32_t label_idx_;
|
||||
std::uint8_t stack_idx_;
|
||||
std::vector<std::string> local_stack_;
|
||||
std::vector<std::string> local_functions_;
|
||||
std::vector<include_t> includes_;
|
||||
std::vector<animtree_t> animtrees_;
|
||||
std::unordered_map<std::string, ast::expr> constants_;
|
||||
std::vector<block*> break_blks_;
|
||||
std::vector<block*> continue_blks_;
|
||||
bool can_break_;
|
||||
bool can_continue_;
|
||||
bool developer_thread_;
|
||||
|
||||
public:
|
||||
auto output() -> std::vector<function::ptr>;
|
||||
void compile(const std::string& file, std::vector<std::uint8_t>& data);
|
||||
void mode(build mode);
|
||||
|
||||
private:
|
||||
auto parse_buffer(const std::string& file, char* data, size_t size) -> ast::program::ptr;
|
||||
auto parse_file(const std::string& file) -> ast::program::ptr;
|
||||
void compile_program(const ast::program::ptr& program);
|
||||
void emit_include(const ast::include::ptr& include);
|
||||
void emit_declaration(const ast::decl& decl);
|
||||
void emit_decl_usingtree(const ast::decl_usingtree::ptr& animtree);
|
||||
void emit_decl_constant(const ast::decl_constant::ptr& constant);
|
||||
void emit_decl_thread(const ast::decl_thread::ptr& thread);
|
||||
void emit_stmt(const ast::stmt& stmt, const block::ptr& blk, bool last);
|
||||
void emit_stmt_list(const ast::stmt_list::ptr& stmt, const block::ptr& blk, bool last);
|
||||
void emit_stmt_dev(const ast::stmt_dev::ptr& stmt, const block::ptr& blk, bool last);
|
||||
void emit_stmt_expr(const ast::stmt_expr::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_call(const ast::stmt_call::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_assign(const ast::stmt_assign::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_endon(const ast::stmt_endon::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_notify(const ast::stmt_notify::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_wait(const ast::stmt_wait::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_waittill(const ast::stmt_waittill::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_waittillmatch(const ast::stmt_waittillmatch::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_waittillframeend(const ast::stmt_waittillframeend::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_if(const ast::stmt_if::ptr& stmt, const block::ptr& blk, bool last);
|
||||
void emit_stmt_ifelse(const ast::stmt_ifelse::ptr& stmt, const block::ptr& blk, bool last);
|
||||
void emit_stmt_while(const ast::stmt_while::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_dowhile(const ast::stmt_dowhile::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_for(const ast::stmt_for::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_foreach(const ast::stmt_foreach::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_switch(const ast::stmt_switch::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_case(const ast::stmt_case::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_default(const ast::stmt_default::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_break(const ast::stmt_break::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_continue(const ast::stmt_continue::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_return(const ast::stmt_return::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_breakpoint(const ast::stmt_breakpoint::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_prof_begin(const ast::stmt_prof_begin::ptr& stmt, const block::ptr& blk);
|
||||
void emit_stmt_prof_end(const ast::stmt_prof_end::ptr& stmt, const block::ptr& blk);
|
||||
void emit_expr(const ast::expr& expr, const block::ptr& blk);
|
||||
void emit_expr_assign(const ast::expr_assign::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_clear(const ast::expr& expr, const block::ptr& blk);
|
||||
void emit_expr_clear_local(const ast::expr_identifier::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_increment(const ast::expr_increment::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_decrement(const ast::expr_decrement::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_ternary(const ast::expr_ternary::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_binary(const ast::expr_binary::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_and(const ast::expr_and::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_or(const ast::expr_or::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_complement(const ast::expr_complement::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_negate(const ast::expr_negate::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_not(const ast::expr_not::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_call(const ast::expr_call::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_call_pointer(const ast::expr_pointer::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_call_function(const ast::expr_function::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_method(const ast::expr_method::ptr& expr, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_method_pointer(const ast::expr_pointer::ptr& expr, const ast::expr& obj, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_method_function(const ast::expr_function::ptr& expr, const ast::expr& obj, const block::ptr& blk, bool is_stmt);
|
||||
void emit_expr_add_array(const ast::expr_add_array::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_parameters(const ast::expr_parameters::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_arguments(const ast::expr_arguments::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_reference(const ast::expr_reference::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_size(const ast::expr_size::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_tuple(const ast::expr_tuple::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_variable_ref(const ast::expr& expr, const block::ptr& blk, bool set);
|
||||
void emit_expr_array_ref(const ast::expr_array::ptr& expr, const block::ptr& blk, bool set);
|
||||
void emit_expr_field_ref(const ast::expr_field::ptr& expr, const block::ptr& blk, bool set);
|
||||
void emit_expr_local_ref(const ast::expr_identifier::ptr& expr, const block::ptr& blk, bool set);
|
||||
void emit_expr_variable(const ast::expr& expr, const block::ptr& blk);
|
||||
void emit_expr_array(const ast::expr_array::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_field(const ast::expr_field::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_local(const ast::expr_identifier::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_object(const ast::expr& expr, const block::ptr& blk);
|
||||
void emit_expr_vector(const ast::expr_vector::ptr& expr, const block::ptr& blk);
|
||||
void emit_expr_animation(const ast::expr_animation::ptr& expr);
|
||||
void emit_expr_animtree(const ast::expr_animtree::ptr& expr);
|
||||
void emit_expr_istring(const ast::expr_istring::ptr& expr);
|
||||
void emit_expr_string(const ast::expr_string::ptr& expr);
|
||||
void emit_expr_float(const ast::expr_float::ptr& expr);
|
||||
void emit_expr_integer(const ast::expr_integer::ptr& expr);
|
||||
void emit_expr_false(const ast::expr_false::ptr& expr);
|
||||
void emit_expr_true(const ast::expr_true::ptr& expr);
|
||||
void emit_create_local_vars(const block::ptr& blk);
|
||||
void emit_remove_local_vars(const block::ptr& blk);
|
||||
void emit_opcode(opcode op);
|
||||
void emit_opcode(opcode op, const std::string& data);
|
||||
void emit_opcode(opcode op, const std::vector<std::string>& data);
|
||||
void process_thread(const ast::decl_thread::ptr& decl, const block::ptr& blk);
|
||||
void process_stmt(const ast::stmt& stmt, const block::ptr& blk);
|
||||
void process_stmt_list(const ast::stmt_list::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_dev(const ast::stmt_dev::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_expr(const ast::stmt_expr::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_assign(const ast::stmt_assign::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_waittill(const ast::stmt_waittill::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_if(const ast::stmt_if::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_ifelse(const ast::stmt_ifelse::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_while(const ast::stmt_while::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_dowhile(const ast::stmt_dowhile::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_for(const ast::stmt_for::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_foreach(const ast::stmt_foreach::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_switch(const ast::stmt_switch::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_break(const ast::stmt_break::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_continue(const ast::stmt_continue::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_return(const ast::stmt_return::ptr& stmt, const block::ptr& blk);
|
||||
void process_expr(const ast::expr& expr, const block::ptr& blk);
|
||||
void process_expr_tuple(const ast::expr_tuple::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_parameters(const ast::expr_parameters::ptr& decl, const block::ptr& blk);
|
||||
void variable_register(const std::string& name, const block::ptr& blk);
|
||||
void variable_initialize(const ast::expr_identifier::ptr& name, const block::ptr& blk);
|
||||
void variable_create(const ast::expr_identifier::ptr& name, const block::ptr& blk);
|
||||
auto variable_stack_index(const ast::expr_identifier::ptr& name, const block::ptr& blk) -> std::uint8_t;
|
||||
auto variable_create_index(const ast::expr_identifier::ptr& name, const block::ptr& blk) -> std::string;
|
||||
auto variable_access_index(const ast::expr_identifier::ptr& name, const block::ptr& blk) -> std::string;
|
||||
auto variable_initialized(const ast::expr_identifier::ptr& name, const block::ptr& blk) -> bool;
|
||||
auto resolve_function_type(const ast::expr_function::ptr& expr) -> ast::call::type;
|
||||
auto resolve_reference_type(const ast::expr_reference::ptr& expr, bool& method) -> ast::call::type;
|
||||
auto is_constant_condition(const ast::expr& expr) -> bool;
|
||||
auto create_label() -> std::string;
|
||||
auto insert_label() -> std::string;
|
||||
void insert_label(const std::string& label);
|
||||
|
||||
static gsc::include_t include_maps_mp_utility_;
|
||||
static gsc::include_t include_common_scripts_utility_;
|
||||
static gsc::include_t include_common_scripts_createfx_;
|
||||
static gsc::include_t include_maps_mp_gametypes_hud_util_;
|
||||
auto map_known_includes(const std::string& include) -> bool;
|
||||
|
||||
// debug
|
||||
void print_debug_info();
|
||||
void print_opcodes(std::uint32_t index, std::uint32_t size);
|
||||
void print_function(const function::ptr& func);
|
||||
void print_instruction(const instruction::ptr& inst);
|
||||
void print_label(const std::string& label);
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
23
src/experimental/iw5_console/xsk/context.cpp
Normal file
23
src/experimental/iw5_console/xsk/context.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 "iw5_console.hpp"
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
void context::init(build mode, read_cb_type callback)
|
||||
{
|
||||
compiler_.mode(mode);
|
||||
resolver::init(callback);
|
||||
}
|
||||
|
||||
void context::cleanup()
|
||||
{
|
||||
resolver::cleanup();
|
||||
}
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
28
src/experimental/iw5_console/xsk/context.hpp
Normal file
28
src/experimental/iw5_console/xsk/context.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
class context : public gsc::context
|
||||
{
|
||||
iw5_console::assembler assembler_;
|
||||
iw5_console::disassembler disassembler_;
|
||||
iw5_console::compiler compiler_;
|
||||
iw5_console::decompiler decompiler_;
|
||||
|
||||
public:
|
||||
void init(build mode, read_cb_type callback);
|
||||
void cleanup();
|
||||
|
||||
auto assembler() -> gsc::assembler& { return assembler_; }
|
||||
auto disassembler() -> gsc::disassembler& { return disassembler_; }
|
||||
auto compiler() -> gsc::compiler& { return compiler_; }
|
||||
auto decompiler() -> gsc::decompiler& { return decompiler_; }
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
3436
src/experimental/iw5_console/xsk/decompiler.cpp
Normal file
3436
src/experimental/iw5_console/xsk/decompiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
101
src/experimental/iw5_console/xsk/decompiler.hpp
Normal file
101
src/experimental/iw5_console/xsk/decompiler.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
class decompiler : public gsc::decompiler
|
||||
{
|
||||
std::string filename_;
|
||||
ast::program::ptr program_;
|
||||
ast::decl_thread::ptr func_;
|
||||
std::unordered_map<std::uint32_t, std::string> labels_;
|
||||
std::vector<std::string> expr_labels_;
|
||||
std::vector<std::string> tern_labels_;
|
||||
std::stack<ast::node::ptr> stack_;
|
||||
std::vector<block> blocks_;
|
||||
bool in_waittill_;
|
||||
|
||||
public:
|
||||
auto output() -> std::vector<std::uint8_t>;
|
||||
void decompile(const std::string& file, std::vector<function::ptr>& funcs);
|
||||
|
||||
private:
|
||||
void decompile_function(const function::ptr& func);
|
||||
void decompile_instruction(const instruction::ptr& inst);
|
||||
void decompile_expressions(const instruction::ptr& inst);
|
||||
void decompile_statements(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_loops(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_switches(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_ifelses(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_aborts(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_tuples(const ast::stmt_list::ptr& stmt);
|
||||
void decompile_if(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_ifelse(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_ifelse_end(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_inf(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_loop(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_while(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_dowhile(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_for(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_foreach(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end);
|
||||
void decompile_switch(const ast::stmt_list::ptr& stmt, std::size_t begin);
|
||||
auto find_location_reference(const ast::stmt_list::ptr& stmt, std::size_t begin, std::size_t end, const std::string& location) -> bool;
|
||||
auto find_location_index(const ast::stmt_list::ptr& stmt, const std::string& location) -> std::size_t;
|
||||
auto last_location_index(const ast::stmt_list::ptr& stmt, std::size_t index) -> bool;
|
||||
void process_stack(const ast::decl_thread::ptr& thread);
|
||||
void process_parameters(const ast::expr_parameters::ptr& params, const block::ptr& blk);
|
||||
void process_stmt(const ast::stmt& stmt, const block::ptr& blk);
|
||||
void process_stmt_list(const ast::stmt_list::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_expr(const ast::stmt_expr::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_call(const ast::stmt_call::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_assign(const ast::stmt_assign::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_endon(const ast::stmt_endon::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_notify(const ast::stmt_notify::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_wait(const ast::stmt_wait::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_waittill(const ast::stmt_waittill::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_waittillmatch(const ast::stmt_waittillmatch::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_if(const ast::stmt_if::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_ifelse(const ast::stmt_ifelse::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_while(const ast::stmt_while::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_dowhile(const ast::stmt_dowhile::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_for(const ast::stmt_for::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_foreach(const ast::stmt_foreach::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_switch(const ast::stmt_switch::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_cases(const ast::stmt_list::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_break(const ast::stmt_break::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_continue(const ast::stmt_continue::ptr& stmt, const block::ptr& blk);
|
||||
void process_stmt_return(const ast::stmt_return::ptr& stmt, const block::ptr& blk);
|
||||
void process_expr(ast::expr& expr, const block::ptr& blk);
|
||||
void process_expr_assign(ast::expr_assign::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_increment(const ast::expr_increment::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_decrement(const ast::expr_decrement::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_ternary(const ast::expr_ternary::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_binary(const ast::expr_binary::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_and(const ast::expr_and::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_or(const ast::expr_or::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_complement(const ast::expr_complement::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_not(const ast::expr_not::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_call(const ast::expr_call::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_method(const ast::expr_method::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_call_pointer(const ast::expr_pointer::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_call_function(const ast::expr_function::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_method_pointer(const ast::expr_pointer::ptr& expr, ast::expr& obj, const block::ptr& blk);
|
||||
void process_expr_method_function(const ast::expr_function::ptr& expr, ast::expr& obj, const block::ptr& blk);
|
||||
void process_expr_arguments(const ast::expr_arguments::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_add_array(const ast::expr_add_array::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_size(const ast::expr_size::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_tuple(const ast::expr_tuple::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_array(const ast::expr_array::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_field(const ast::expr_field::ptr& expr, const block::ptr& blk);
|
||||
void process_expr_vector(const ast::expr_vector::ptr& vec, const block::ptr& blk);
|
||||
void process_var_create(ast::expr& expr, const block::ptr& blk, bool fromstmt = false);
|
||||
void process_var_access(ast::expr& expr, const block::ptr& blk);
|
||||
void process_var_remove(const ast::asm_remove::ptr& expr, const block::ptr& blk);
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
579
src/experimental/iw5_console/xsk/disassembler.cpp
Normal file
579
src/experimental/iw5_console/xsk/disassembler.cpp
Normal file
@ -0,0 +1,579 @@
|
||||
// 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 "iw5_console.hpp"
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
auto disassembler::output() -> std::vector<function::ptr>
|
||||
{
|
||||
return std::move(functions_);
|
||||
}
|
||||
|
||||
auto disassembler::output_data() -> std::vector<std::uint8_t>
|
||||
{
|
||||
output_ = std::make_unique<utils::byte_buffer>(0x100000);
|
||||
|
||||
output_->write_string("// IW5 GSC ASSEMBLY\n");
|
||||
output_->write_string("// Disassembled by https://github.com/xensik/gsc-tool\n");
|
||||
|
||||
for (const auto& func : functions_)
|
||||
{
|
||||
print_function(func);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> 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<std::uint8_t>& script, std::vector<std::uint8_t>& stack)
|
||||
{
|
||||
filename_ = file;
|
||||
script_ = std::make_unique<utils::byte_buffer>(script);
|
||||
stack_ = std::make_unique<utils::byte_buffer>(stack);
|
||||
functions_.clear();
|
||||
|
||||
script_->seek(1);
|
||||
|
||||
while (stack_->is_avail() && script_->is_avail())
|
||||
{
|
||||
functions_.push_back(std::make_unique<function>());
|
||||
const auto& func = functions_.back();
|
||||
|
||||
func->index = static_cast<std::uint32_t>(script_->pos());
|
||||
func->size = stack_->read_endian<std::uint32_t>();
|
||||
func->id = stack_->read_endian<std::uint16_t>();
|
||||
func->name = func->id == 0 ? stack_->read_c_string() : resolver::token_name(static_cast<std::uint16_t>(func->id));
|
||||
|
||||
dissasemble_function(func);
|
||||
|
||||
func->labels = labels_;
|
||||
labels_.clear();
|
||||
}
|
||||
|
||||
resolve_local_functions();
|
||||
}
|
||||
|
||||
void disassembler::dissasemble_function(const function::ptr& func)
|
||||
{
|
||||
auto size = func->size;
|
||||
|
||||
while (size > 0)
|
||||
{
|
||||
func->instructions.push_back(std::make_unique<instruction>());
|
||||
|
||||
const auto& inst = func->instructions.back();
|
||||
inst->index = static_cast<std::uint32_t>(script_->pos());
|
||||
inst->opcode = script_->read<std::uint8_t>();
|
||||
inst->size = opcode_size(inst->opcode);
|
||||
|
||||
dissasemble_instruction(inst);
|
||||
|
||||
if(inst->size > size)
|
||||
{
|
||||
throw disasm_error("aaaaa");
|
||||
}
|
||||
|
||||
size -= inst->size;
|
||||
}
|
||||
}
|
||||
|
||||
void disassembler::dissasemble_instruction(const instruction::ptr& inst)
|
||||
{
|
||||
switch (opcode(inst->opcode))
|
||||
{
|
||||
case opcode::OP_End:
|
||||
case opcode::OP_Return:
|
||||
case opcode::OP_GetUndefined:
|
||||
case opcode::OP_GetZero:
|
||||
case opcode::OP_waittillFrameEnd:
|
||||
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_EvalArray:
|
||||
case opcode::OP_EvalArrayRef:
|
||||
case opcode::OP_EvalLocalArrayRefCached0:
|
||||
case opcode::OP_ClearArray:
|
||||
case opcode::OP_EmptyArray:
|
||||
case opcode::OP_AddArray:
|
||||
case opcode::OP_PreScriptCall:
|
||||
case opcode::OP_ScriptFunctionCallPointer:
|
||||
case opcode::OP_ScriptMethodCallPointer:
|
||||
case opcode::OP_GetLevelObject:
|
||||
case opcode::OP_GetAnimObject:
|
||||
case opcode::OP_GetSelf:
|
||||
case opcode::OP_GetThisthread:
|
||||
case opcode::OP_GetLevel:
|
||||
case opcode::OP_GetGame:
|
||||
case opcode::OP_GetAnim:
|
||||
case opcode::OP_GetGameRef:
|
||||
case opcode::OP_inc:
|
||||
case opcode::OP_dec:
|
||||
case opcode::OP_bit_or:
|
||||
case opcode::OP_bit_ex_or:
|
||||
case opcode::OP_bit_and:
|
||||
case opcode::OP_equality:
|
||||
case opcode::OP_inequality:
|
||||
case opcode::OP_less:
|
||||
case opcode::OP_greater:
|
||||
case opcode::OP_less_equal:
|
||||
case opcode::OP_waittillmatch2:
|
||||
case opcode::OP_waittill:
|
||||
case opcode::OP_notify:
|
||||
case opcode::OP_endon:
|
||||
case opcode::OP_voidCodepos:
|
||||
case opcode::OP_vector:
|
||||
case opcode::OP_greater_equal:
|
||||
case opcode::OP_shift_left:
|
||||
case opcode::OP_shift_right:
|
||||
case opcode::OP_plus:
|
||||
case opcode::OP_minus:
|
||||
case opcode::OP_multiply:
|
||||
case opcode::OP_divide:
|
||||
case opcode::OP_mod:
|
||||
case opcode::OP_size:
|
||||
case opcode::OP_GetSelfObject:
|
||||
case opcode::OP_SafeSetVariableFieldCached0:
|
||||
case opcode::OP_clearparams:
|
||||
case opcode::OP_checkclearparams:
|
||||
case opcode::OP_EvalLocalVariableRefCached0:
|
||||
case opcode::OP_EvalNewLocalVariableRefCached0:
|
||||
case opcode::OP_SetVariableField:
|
||||
case opcode::OP_ClearVariableField:
|
||||
case opcode::OP_SetLocalVariableFieldCached0:
|
||||
case opcode::OP_ClearLocalVariableFieldCached0:
|
||||
case opcode::OP_wait:
|
||||
case opcode::OP_DecTop:
|
||||
case opcode::OP_CastFieldObject:
|
||||
case opcode::OP_CastBool:
|
||||
case opcode::OP_BoolNot:
|
||||
case opcode::OP_BoolComplement:
|
||||
break;
|
||||
case opcode::OP_GetByte:
|
||||
case opcode::OP_GetNegByte:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
break;
|
||||
case opcode::OP_GetUnsignedShort:
|
||||
case opcode::OP_GetNegUnsignedShort:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read_endian<std::uint16_t>()));
|
||||
break;
|
||||
case opcode::OP_GetInteger:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read_endian<std::int32_t>()));
|
||||
break;
|
||||
case opcode::OP_GetFloat:
|
||||
inst->data.push_back(utils::string::float_string(script_->read_endian<float>()));
|
||||
break;
|
||||
case opcode::OP_GetVector:
|
||||
inst->size += script_->align(4);
|
||||
inst->data.push_back(utils::string::float_string(script_->read_endian<float>()));
|
||||
inst->data.push_back(utils::string::float_string(script_->read_endian<float>()));
|
||||
inst->data.push_back(utils::string::float_string(script_->read_endian<float>()));
|
||||
break;
|
||||
case opcode::OP_GetString:
|
||||
case opcode::OP_GetIString:
|
||||
script_->seek(2);
|
||||
inst->data.push_back(utils::string::to_literal(stack_->read_c_string()));
|
||||
break;
|
||||
case opcode::OP_GetAnimation:
|
||||
script_->seek(4);
|
||||
inst->data.push_back(utils::string::quote(stack_->read_c_string(), false));
|
||||
inst->data.push_back(utils::string::quote(stack_->read_c_string(), false));
|
||||
break;
|
||||
case opcode::OP_GetAnimTree:
|
||||
script_->seek(1);
|
||||
inst->data.push_back(utils::string::quote(stack_->read_c_string(), false));
|
||||
break;
|
||||
case opcode::OP_waittillmatch:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
break;
|
||||
case opcode::OP_CreateLocalVariable:
|
||||
case opcode::OP_RemoveLocalVariables:
|
||||
case opcode::OP_EvalLocalVariableCached:
|
||||
case opcode::OP_EvalLocalArrayCached:
|
||||
case opcode::OP_EvalNewLocalArrayRefCached0:
|
||||
case opcode::OP_EvalLocalArrayRefCached:
|
||||
case opcode::OP_SafeCreateVariableFieldCached:
|
||||
case opcode::OP_SafeSetVariableFieldCached:
|
||||
case opcode::OP_SafeSetWaittillVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableRefCached:
|
||||
case opcode::OP_SetNewLocalVariableFieldCached0:
|
||||
case opcode::OP_SetLocalVariableFieldCached:
|
||||
case opcode::OP_ClearLocalVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableObjectCached:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
break;
|
||||
case opcode::OP_EvalLevelFieldVariable:
|
||||
case opcode::OP_EvalAnimFieldVariable:
|
||||
case opcode::OP_EvalSelfFieldVariable:
|
||||
case opcode::OP_EvalFieldVariable:
|
||||
case opcode::OP_EvalLevelFieldVariableRef:
|
||||
case opcode::OP_EvalAnimFieldVariableRef:
|
||||
case opcode::OP_EvalSelfFieldVariableRef:
|
||||
case opcode::OP_EvalFieldVariableRef:
|
||||
case opcode::OP_ClearFieldVariable:
|
||||
case opcode::OP_SetLevelFieldVariableField:
|
||||
case opcode::OP_SetAnimFieldVariableField:
|
||||
case opcode::OP_SetSelfFieldVariableField:
|
||||
disassemble_field_variable(inst);
|
||||
break;
|
||||
case opcode::OP_ScriptThreadCallPointer:
|
||||
case opcode::OP_ScriptChildThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodChildThreadCallPointer:
|
||||
case opcode::OP_CallBuiltinPointer:
|
||||
case opcode::OP_CallBuiltinMethodPointer:
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
break;
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
disassemble_local_call(inst, false);
|
||||
break;
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
disassemble_local_call(inst, true);
|
||||
break;
|
||||
case opcode::OP_GetFarFunction:
|
||||
case opcode::OP_ScriptFarFunctionCall2:
|
||||
case opcode::OP_ScriptFarFunctionCall:
|
||||
case opcode::OP_ScriptFarMethodCall:
|
||||
disassemble_far_call(inst, false);
|
||||
break;
|
||||
case opcode::OP_ScriptFarThreadCall:
|
||||
case opcode::OP_ScriptFarChildThreadCall:
|
||||
case opcode::OP_ScriptFarMethodThreadCall:
|
||||
case opcode::OP_ScriptFarMethodChildThreadCall:
|
||||
disassemble_far_call(inst, true);
|
||||
break;
|
||||
case opcode::OP_CallBuiltin:
|
||||
disassemble_builtin_call(inst, false, true);
|
||||
break;
|
||||
case opcode::OP_CallBuiltinMethod:
|
||||
disassemble_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:
|
||||
disassemble_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:
|
||||
disassemble_builtin_call(inst, true, false);
|
||||
break;
|
||||
case opcode::OP_jump:
|
||||
disassemble_jump(inst, false, false);
|
||||
break;
|
||||
case opcode::OP_jumpback:
|
||||
disassemble_jump(inst, false, true);
|
||||
break;
|
||||
case opcode::OP_JumpOnFalse:
|
||||
case opcode::OP_JumpOnTrue:
|
||||
case opcode::OP_JumpOnFalseExpr:
|
||||
case opcode::OP_JumpOnTrueExpr:
|
||||
disassemble_jump(inst, true, false);
|
||||
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_builtin_call(const instruction::ptr& inst, bool method, bool args)
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
}
|
||||
|
||||
const auto id = script_->read_endian<std::uint16_t>();
|
||||
const auto name = method ? resolver::method_name(id) : resolver::function_name(id);
|
||||
inst->data.emplace(inst->data.begin(), name);
|
||||
}
|
||||
|
||||
void disassembler::disassemble_local_call(const instruction::ptr& inst, bool thread)
|
||||
{
|
||||
const auto offset = disassemble_offset();
|
||||
|
||||
inst->data.push_back(utils::string::va("%X", offset + inst->index + 1));
|
||||
|
||||
if (thread)
|
||||
{
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
}
|
||||
}
|
||||
|
||||
void disassembler::disassemble_far_call(const instruction::ptr& inst, bool thread)
|
||||
{
|
||||
script_->seek(3);
|
||||
|
||||
if (thread)
|
||||
{
|
||||
inst->data.push_back(utils::string::va("%i", script_->read<std::uint8_t>()));
|
||||
}
|
||||
|
||||
const auto file_id = stack_->read_endian<std::uint16_t>();
|
||||
const auto file_name = file_id == 0 ? stack_->read_c_string() : resolver::token_name(file_id);
|
||||
const auto func_id = stack_->read_endian<std::uint16_t>();
|
||||
const auto func_name = func_id == 0 ? stack_->read_c_string() : resolver::token_name(func_id);
|
||||
|
||||
inst->data.emplace(inst->data.begin(), func_name);
|
||||
inst->data.emplace(inst->data.begin(), file_name);
|
||||
}
|
||||
|
||||
void disassembler::disassemble_switch(const instruction::ptr& inst)
|
||||
{
|
||||
const auto addr = inst->index + 4 + script_->read_endian<std::int32_t>();
|
||||
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)
|
||||
{
|
||||
const auto count = script_->read_endian<std::uint16_t>();
|
||||
inst->data.push_back(utils::string::va("%i", count));
|
||||
|
||||
std::uint32_t index = inst->index + 3;
|
||||
|
||||
if (count)
|
||||
{
|
||||
for (auto i = count; i > 0; i--)
|
||||
{
|
||||
const auto value = script_->read_endian<std::uint32_t>();
|
||||
|
||||
if (value < 0x40000)
|
||||
{
|
||||
const auto data = stack_->read_c_string();
|
||||
|
||||
if (data.data()[0] != 0x01)
|
||||
{
|
||||
inst->data.push_back("case");
|
||||
inst->data.push_back(utils::string::quote(data, false));
|
||||
}
|
||||
else
|
||||
inst->data.push_back("default");
|
||||
}
|
||||
else
|
||||
{
|
||||
inst->data.push_back("case");
|
||||
inst->data.push_back(utils::string::va("%i", (value - 0x800000) & 0xFFFFFF));
|
||||
}
|
||||
|
||||
index += 4;
|
||||
|
||||
const auto addr = disassemble_offset() + index;
|
||||
const auto label = utils::string::va("loc_%X", addr);
|
||||
|
||||
inst->data.push_back(label);
|
||||
labels_.insert({ addr, label });
|
||||
|
||||
index += 3;
|
||||
inst->size += 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disassembler::disassemble_field_variable(const instruction::ptr& inst)
|
||||
{
|
||||
const auto id = script_->read_endian<std::uint16_t>();
|
||||
std::string name;
|
||||
|
||||
if (id > max_string_id)
|
||||
{
|
||||
auto temp = stack_->read_endian<std::uint16_t>();
|
||||
name = temp == 0 ? stack_->read_c_string() : std::to_string(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = resolver::token_name(id);
|
||||
}
|
||||
|
||||
inst->data.push_back(name);
|
||||
}
|
||||
|
||||
void disassembler::disassemble_jump(const instruction::ptr& inst, bool expr, bool back)
|
||||
{
|
||||
std::int32_t addr;
|
||||
|
||||
if (expr)
|
||||
{
|
||||
addr = inst->index + 3 + script_->read_endian<std::int16_t>();
|
||||
}
|
||||
else if (back)
|
||||
{
|
||||
addr = inst->index + 3 - script_->read_endian<std::uint16_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = inst->index + 5 + script_->read_endian<std::int32_t>();
|
||||
}
|
||||
|
||||
const auto label = utils::string::va("loc_%X", addr);
|
||||
|
||||
inst->data.push_back(label);
|
||||
labels_.insert({ addr, label });
|
||||
}
|
||||
|
||||
auto disassembler::disassemble_offset() -> std::int32_t
|
||||
{
|
||||
std::array<std::uint8_t, 4> bytes = {};
|
||||
|
||||
for (auto i = 0; i < 3; i++)
|
||||
{
|
||||
bytes[2 - i] = script_->read<std::uint8_t>();
|
||||
}
|
||||
|
||||
auto offset = *reinterpret_cast<std::int32_t*>(bytes.data());
|
||||
|
||||
offset = (offset << 8) >> 10;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void disassembler::resolve_local_functions()
|
||||
{
|
||||
for (const auto& func : functions_)
|
||||
{
|
||||
for (const auto& inst : func->instructions)
|
||||
{
|
||||
switch (opcode(inst->opcode))
|
||||
{
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
inst->data[0] = resolve_function(inst->data[0]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto disassembler::resolve_function(const std::string& index) -> std::string
|
||||
{
|
||||
if (utils::string::is_hex_number(index))
|
||||
{
|
||||
std::uint32_t idx = std::stoul(index, nullptr, 16);
|
||||
|
||||
for (auto& func : functions_)
|
||||
{
|
||||
if (func->index == idx)
|
||||
{
|
||||
return func->name;
|
||||
}
|
||||
}
|
||||
//return "error";
|
||||
throw disasm_error(utils::string::va("couldn't resolve function name at index '0x%04X'!", idx));
|
||||
}
|
||||
|
||||
throw disasm_error(utils::string::va("\"%s\" is not valid function address!", index.data()));
|
||||
}
|
||||
|
||||
void disassembler::print_function(const function::ptr& func)
|
||||
{
|
||||
output_->write_string("\n");
|
||||
output_->write_string(utils::string::va("sub_%s\n", func->name.data()));
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
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 (opcode(inst->opcode))
|
||||
{
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
output_->write_string(utils::string::va(" sub_%s\n", inst->data[0].data()));
|
||||
break;
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
output_->write_string(utils::string::va(" sub_%s", inst->data[0].data()));
|
||||
output_->write_string(utils::string::va(" %s\n", inst->data[1].data()));
|
||||
break;
|
||||
case opcode::OP_endswitch:
|
||||
output_->write_string(utils::string::va(" %s\n", inst->data[0].data()));
|
||||
{
|
||||
std::uint32_t totalcase = std::stoul(inst->data[0]);
|
||||
auto index = 0;
|
||||
for (auto casenum = 0u; casenum < totalcase; casenum++)
|
||||
{
|
||||
if (inst->data[1 + index] == "case")
|
||||
{
|
||||
output_->write_string(utils::string::va("\t\t\t%s %s %s", 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("\t\t\t%s %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data()));
|
||||
index += 2;
|
||||
}
|
||||
|
||||
output_->write_string("\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
for (auto& d : inst->data)
|
||||
{
|
||||
output_->write_string(utils::string::va(" %s", d.data()));
|
||||
}
|
||||
|
||||
output_->write_string("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
42
src/experimental/iw5_console/xsk/disassembler.hpp
Normal file
42
src/experimental/iw5_console/xsk/disassembler.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
class disassembler : public gsc::disassembler
|
||||
{
|
||||
std::string filename_;
|
||||
utils::byte_buffer::ptr script_;
|
||||
utils::byte_buffer::ptr stack_;
|
||||
utils::byte_buffer::ptr output_;
|
||||
std::vector<function::ptr> functions_;
|
||||
std::unordered_map<std::uint32_t, std::string> labels_;
|
||||
|
||||
public:
|
||||
auto output() -> std::vector<function::ptr>;
|
||||
auto output_data() -> std::vector<std::uint8_t>;
|
||||
void disassemble(const std::string& file, std::vector<std::uint8_t>& script, std::vector<std::uint8_t>& stack);
|
||||
|
||||
private:
|
||||
void dissasemble_function(const function::ptr& func);
|
||||
void dissasemble_instruction(const instruction::ptr& inst);
|
||||
void disassemble_builtin_call(const instruction::ptr& inst, bool method, bool args);
|
||||
void disassemble_local_call(const instruction::ptr& inst, bool thread);
|
||||
void disassemble_far_call(const instruction::ptr& inst, bool thread);
|
||||
void disassemble_switch(const instruction::ptr& inst);
|
||||
void disassemble_end_switch(const instruction::ptr& inst);
|
||||
void disassemble_field_variable(const instruction::ptr& inst);
|
||||
void disassemble_jump(const instruction::ptr& inst, bool expr, bool back);
|
||||
auto disassemble_offset() -> std::int32_t;
|
||||
void resolve_local_functions();
|
||||
auto resolve_function(const std::string& index) -> std::string;
|
||||
void print_function(const function::ptr& func);
|
||||
void print_instruction(const instruction::ptr& inst);
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
@ -13,166 +13,166 @@ auto opcode_size(std::uint8_t id) -> std::uint32_t
|
||||
{
|
||||
switch (opcode(id))
|
||||
{
|
||||
case opcode::OP_End:
|
||||
case opcode::OP_Return:
|
||||
case opcode::OP_GetUndefined:
|
||||
case opcode::OP_GetZero:
|
||||
case opcode::OP_waittillFrameEnd:
|
||||
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_EvalArray:
|
||||
case opcode::OP_EvalArrayRef:
|
||||
case opcode::OP_EvalLocalArrayRefCached0:
|
||||
case opcode::OP_ClearArray:
|
||||
case opcode::OP_EmptyArray:
|
||||
case opcode::OP_AddArray:
|
||||
case opcode::OP_PreScriptCall:
|
||||
case opcode::OP_ScriptFunctionCallPointer:
|
||||
case opcode::OP_ScriptMethodCallPointer:
|
||||
case opcode::OP_GetLevelObject:
|
||||
case opcode::OP_GetAnimObject:
|
||||
case opcode::OP_GetSelf:
|
||||
case opcode::OP_GetThisthread:
|
||||
case opcode::OP_GetLevel:
|
||||
case opcode::OP_GetGame:
|
||||
case opcode::OP_GetAnim:
|
||||
case opcode::OP_GetGameRef:
|
||||
case opcode::OP_inc:
|
||||
case opcode::OP_dec:
|
||||
case opcode::OP_bit_or:
|
||||
case opcode::OP_bit_ex_or:
|
||||
case opcode::OP_bit_and:
|
||||
case opcode::OP_equality:
|
||||
case opcode::OP_inequality:
|
||||
case opcode::OP_less:
|
||||
case opcode::OP_greater:
|
||||
case opcode::OP_less_equal:
|
||||
case opcode::OP_waittillmatch2:
|
||||
case opcode::OP_waittill:
|
||||
case opcode::OP_notify:
|
||||
case opcode::OP_endon:
|
||||
case opcode::OP_voidCodepos:
|
||||
case opcode::OP_vector:
|
||||
case opcode::OP_greater_equal:
|
||||
case opcode::OP_shift_left:
|
||||
case opcode::OP_shift_right:
|
||||
case opcode::OP_plus:
|
||||
case opcode::OP_minus:
|
||||
case opcode::OP_multiply:
|
||||
case opcode::OP_divide:
|
||||
case opcode::OP_mod:
|
||||
case opcode::OP_size:
|
||||
case opcode::OP_GetSelfObject:
|
||||
case opcode::OP_SafeSetVariableFieldCached0:
|
||||
case opcode::OP_clearparams:
|
||||
case opcode::OP_checkclearparams:
|
||||
case opcode::OP_EvalLocalVariableRefCached0:
|
||||
case opcode::OP_EvalNewLocalVariableRefCached0:
|
||||
case opcode::OP_SetVariableField:
|
||||
case opcode::OP_ClearVariableField:
|
||||
case opcode::OP_SetLocalVariableFieldCached0:
|
||||
case opcode::OP_ClearLocalVariableFieldCached0:
|
||||
case opcode::OP_wait:
|
||||
case opcode::OP_DecTop:
|
||||
case opcode::OP_CastFieldObject:
|
||||
case opcode::OP_CastBool:
|
||||
case opcode::OP_BoolNot:
|
||||
case opcode::OP_BoolComplement:
|
||||
return 1;
|
||||
case opcode::OP_GetByte:
|
||||
case opcode::OP_GetNegByte:
|
||||
case opcode::OP_CreateLocalVariable:
|
||||
case opcode::OP_RemoveLocalVariables:
|
||||
case opcode::OP_EvalLocalVariableCached:
|
||||
case opcode::OP_EvalLocalArrayCached:
|
||||
case opcode::OP_EvalNewLocalArrayRefCached0:
|
||||
case opcode::OP_EvalLocalArrayRefCached:
|
||||
case opcode::OP_ScriptThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodChildThreadCallPointer:
|
||||
case opcode::OP_CallBuiltinPointer:
|
||||
case opcode::OP_CallBuiltinMethodPointer:
|
||||
case opcode::OP_SafeCreateVariableFieldCached:
|
||||
case opcode::OP_SafeSetVariableFieldCached:
|
||||
case opcode::OP_SafeSetWaittillVariableFieldCached:
|
||||
case opcode::OP_GetAnimTree:
|
||||
case opcode::OP_EvalLocalVariableRefCached:
|
||||
case opcode::OP_SetNewLocalVariableFieldCached0:
|
||||
case opcode::OP_SetLocalVariableFieldCached:
|
||||
case opcode::OP_ClearLocalVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableObjectCached:
|
||||
return 2;
|
||||
case opcode::OP_GetUnsignedShort:
|
||||
case opcode::OP_GetNegUnsignedShort:
|
||||
case opcode::OP_GetBuiltinFunction:
|
||||
case opcode::OP_GetBuiltinMethod:
|
||||
case opcode::OP_GetString:
|
||||
case opcode::OP_GetIString:
|
||||
case opcode::OP_JumpOnFalseExpr:
|
||||
case opcode::OP_JumpOnTrueExpr:
|
||||
case opcode::OP_jumpback:
|
||||
case opcode::OP_endswitch:
|
||||
case opcode::OP_JumpOnFalse:
|
||||
case opcode::OP_JumpOnTrue:
|
||||
case opcode::OP_waittillmatch:
|
||||
case opcode::OP_EvalLevelFieldVariable:
|
||||
case opcode::OP_EvalAnimFieldVariable:
|
||||
case opcode::OP_EvalSelfFieldVariable:
|
||||
case opcode::OP_EvalFieldVariable:
|
||||
case opcode::OP_EvalLevelFieldVariableRef:
|
||||
case opcode::OP_EvalAnimFieldVariableRef:
|
||||
case opcode::OP_EvalSelfFieldVariableRef:
|
||||
case opcode::OP_EvalFieldVariableRef:
|
||||
case opcode::OP_ClearFieldVariable:
|
||||
case opcode::OP_SetLevelFieldVariableField:
|
||||
case opcode::OP_SetAnimFieldVariableField:
|
||||
case opcode::OP_SetSelfFieldVariableField:
|
||||
case opcode::OP_CallBuiltin0:
|
||||
case opcode::OP_CallBuiltin1:
|
||||
case opcode::OP_CallBuiltin2:
|
||||
case opcode::OP_CallBuiltin3:
|
||||
case opcode::OP_CallBuiltin4:
|
||||
case opcode::OP_CallBuiltin5:
|
||||
case opcode::OP_CallBuiltinMethod0:
|
||||
case opcode::OP_CallBuiltinMethod1:
|
||||
case opcode::OP_CallBuiltinMethod2:
|
||||
case opcode::OP_CallBuiltinMethod3:
|
||||
case opcode::OP_CallBuiltinMethod4:
|
||||
case opcode::OP_CallBuiltinMethod5:
|
||||
return 3;
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_CallBuiltin:
|
||||
case opcode::OP_CallBuiltinMethod:
|
||||
case opcode::OP_ScriptFarFunctionCall2:
|
||||
case opcode::OP_ScriptFarFunctionCall:
|
||||
case opcode::OP_ScriptFarMethodCall:
|
||||
case opcode::OP_GetFarFunction:
|
||||
return 4;
|
||||
case opcode::OP_GetInteger:
|
||||
case opcode::OP_GetFloat:
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
case opcode::OP_ScriptFarThreadCall:
|
||||
case opcode::OP_ScriptFarChildThreadCall:
|
||||
case opcode::OP_ScriptFarMethodThreadCall:
|
||||
case opcode::OP_ScriptFarMethodChildThreadCall:
|
||||
case opcode::OP_GetAnimation:
|
||||
case opcode::OP_switch:
|
||||
case opcode::OP_jump:
|
||||
return 5;
|
||||
case opcode::OP_GetVector:
|
||||
return 13;
|
||||
default:
|
||||
throw std::runtime_error("Couldn't resolve instruction size for " + std::to_string(id));
|
||||
case opcode::OP_End:
|
||||
case opcode::OP_Return:
|
||||
case opcode::OP_GetUndefined:
|
||||
case opcode::OP_GetZero:
|
||||
case opcode::OP_waittillFrameEnd:
|
||||
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_EvalArray:
|
||||
case opcode::OP_EvalArrayRef:
|
||||
case opcode::OP_EvalLocalArrayRefCached0:
|
||||
case opcode::OP_ClearArray:
|
||||
case opcode::OP_EmptyArray:
|
||||
case opcode::OP_AddArray:
|
||||
case opcode::OP_PreScriptCall:
|
||||
case opcode::OP_ScriptFunctionCallPointer:
|
||||
case opcode::OP_ScriptMethodCallPointer:
|
||||
case opcode::OP_GetLevelObject:
|
||||
case opcode::OP_GetAnimObject:
|
||||
case opcode::OP_GetSelf:
|
||||
case opcode::OP_GetThisthread:
|
||||
case opcode::OP_GetLevel:
|
||||
case opcode::OP_GetGame:
|
||||
case opcode::OP_GetAnim:
|
||||
case opcode::OP_GetGameRef:
|
||||
case opcode::OP_inc:
|
||||
case opcode::OP_dec:
|
||||
case opcode::OP_bit_or:
|
||||
case opcode::OP_bit_ex_or:
|
||||
case opcode::OP_bit_and:
|
||||
case opcode::OP_equality:
|
||||
case opcode::OP_inequality:
|
||||
case opcode::OP_less:
|
||||
case opcode::OP_greater:
|
||||
case opcode::OP_less_equal:
|
||||
case opcode::OP_waittillmatch2:
|
||||
case opcode::OP_waittill:
|
||||
case opcode::OP_notify:
|
||||
case opcode::OP_endon:
|
||||
case opcode::OP_voidCodepos:
|
||||
case opcode::OP_vector:
|
||||
case opcode::OP_greater_equal:
|
||||
case opcode::OP_shift_left:
|
||||
case opcode::OP_shift_right:
|
||||
case opcode::OP_plus:
|
||||
case opcode::OP_minus:
|
||||
case opcode::OP_multiply:
|
||||
case opcode::OP_divide:
|
||||
case opcode::OP_mod:
|
||||
case opcode::OP_size:
|
||||
case opcode::OP_GetSelfObject:
|
||||
case opcode::OP_SafeSetVariableFieldCached0:
|
||||
case opcode::OP_clearparams:
|
||||
case opcode::OP_checkclearparams:
|
||||
case opcode::OP_EvalLocalVariableRefCached0:
|
||||
case opcode::OP_EvalNewLocalVariableRefCached0:
|
||||
case opcode::OP_SetVariableField:
|
||||
case opcode::OP_ClearVariableField:
|
||||
case opcode::OP_SetLocalVariableFieldCached0:
|
||||
case opcode::OP_ClearLocalVariableFieldCached0:
|
||||
case opcode::OP_wait:
|
||||
case opcode::OP_DecTop:
|
||||
case opcode::OP_CastFieldObject:
|
||||
case opcode::OP_CastBool:
|
||||
case opcode::OP_BoolNot:
|
||||
case opcode::OP_BoolComplement:
|
||||
return 1;
|
||||
case opcode::OP_GetByte:
|
||||
case opcode::OP_GetNegByte:
|
||||
case opcode::OP_CreateLocalVariable:
|
||||
case opcode::OP_RemoveLocalVariables:
|
||||
case opcode::OP_EvalLocalVariableCached:
|
||||
case opcode::OP_EvalLocalArrayCached:
|
||||
case opcode::OP_EvalNewLocalArrayRefCached0:
|
||||
case opcode::OP_EvalLocalArrayRefCached:
|
||||
case opcode::OP_ScriptThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodThreadCallPointer:
|
||||
case opcode::OP_ScriptMethodChildThreadCallPointer:
|
||||
case opcode::OP_CallBuiltinPointer:
|
||||
case opcode::OP_CallBuiltinMethodPointer:
|
||||
case opcode::OP_waittillmatch:
|
||||
case opcode::OP_SafeCreateVariableFieldCached:
|
||||
case opcode::OP_SafeSetVariableFieldCached:
|
||||
case opcode::OP_SafeSetWaittillVariableFieldCached:
|
||||
case opcode::OP_GetAnimTree:
|
||||
case opcode::OP_EvalLocalVariableRefCached:
|
||||
case opcode::OP_SetNewLocalVariableFieldCached0:
|
||||
case opcode::OP_SetLocalVariableFieldCached:
|
||||
case opcode::OP_ClearLocalVariableFieldCached:
|
||||
case opcode::OP_EvalLocalVariableObjectCached:
|
||||
return 2;
|
||||
case opcode::OP_GetUnsignedShort:
|
||||
case opcode::OP_GetNegUnsignedShort:
|
||||
case opcode::OP_GetBuiltinFunction:
|
||||
case opcode::OP_GetBuiltinMethod:
|
||||
case opcode::OP_GetString:
|
||||
case opcode::OP_GetIString:
|
||||
case opcode::OP_JumpOnFalseExpr:
|
||||
case opcode::OP_JumpOnTrueExpr:
|
||||
case opcode::OP_jumpback:
|
||||
case opcode::OP_endswitch:
|
||||
case opcode::OP_JumpOnFalse:
|
||||
case opcode::OP_JumpOnTrue:
|
||||
case opcode::OP_EvalLevelFieldVariable:
|
||||
case opcode::OP_EvalAnimFieldVariable:
|
||||
case opcode::OP_EvalSelfFieldVariable:
|
||||
case opcode::OP_EvalFieldVariable:
|
||||
case opcode::OP_EvalLevelFieldVariableRef:
|
||||
case opcode::OP_EvalAnimFieldVariableRef:
|
||||
case opcode::OP_EvalSelfFieldVariableRef:
|
||||
case opcode::OP_EvalFieldVariableRef:
|
||||
case opcode::OP_ClearFieldVariable:
|
||||
case opcode::OP_SetLevelFieldVariableField:
|
||||
case opcode::OP_SetAnimFieldVariableField:
|
||||
case opcode::OP_SetSelfFieldVariableField:
|
||||
case opcode::OP_CallBuiltin0:
|
||||
case opcode::OP_CallBuiltin1:
|
||||
case opcode::OP_CallBuiltin2:
|
||||
case opcode::OP_CallBuiltin3:
|
||||
case opcode::OP_CallBuiltin4:
|
||||
case opcode::OP_CallBuiltin5:
|
||||
case opcode::OP_CallBuiltinMethod0:
|
||||
case opcode::OP_CallBuiltinMethod1:
|
||||
case opcode::OP_CallBuiltinMethod2:
|
||||
case opcode::OP_CallBuiltinMethod3:
|
||||
case opcode::OP_CallBuiltinMethod4:
|
||||
case opcode::OP_CallBuiltinMethod5:
|
||||
return 3;
|
||||
case opcode::OP_ScriptLocalFunctionCall2:
|
||||
case opcode::OP_ScriptLocalFunctionCall:
|
||||
case opcode::OP_ScriptLocalMethodCall:
|
||||
case opcode::OP_GetLocalFunction:
|
||||
case opcode::OP_CallBuiltin:
|
||||
case opcode::OP_CallBuiltinMethod:
|
||||
case opcode::OP_ScriptFarFunctionCall2:
|
||||
case opcode::OP_ScriptFarFunctionCall:
|
||||
case opcode::OP_ScriptFarMethodCall:
|
||||
case opcode::OP_GetFarFunction:
|
||||
return 4;
|
||||
case opcode::OP_GetInteger:
|
||||
case opcode::OP_GetFloat:
|
||||
case opcode::OP_ScriptLocalThreadCall:
|
||||
case opcode::OP_ScriptLocalChildThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodThreadCall:
|
||||
case opcode::OP_ScriptLocalMethodChildThreadCall:
|
||||
case opcode::OP_ScriptFarThreadCall:
|
||||
case opcode::OP_ScriptFarChildThreadCall:
|
||||
case opcode::OP_ScriptFarMethodThreadCall:
|
||||
case opcode::OP_ScriptFarMethodChildThreadCall:
|
||||
case opcode::OP_GetAnimation:
|
||||
case opcode::OP_switch:
|
||||
case opcode::OP_jump:
|
||||
return 5;
|
||||
case opcode::OP_GetVector:
|
||||
return 13;
|
||||
default:
|
||||
throw std::runtime_error("Couldn't resolve instruction size for " + std::to_string(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,18 @@
|
||||
|
||||
#include "utils/xsk/utils.hpp"
|
||||
|
||||
#include "assembler.hpp"
|
||||
#include "disassembler.hpp"
|
||||
#include "compiler.hpp"
|
||||
#include "decompiler.hpp"
|
||||
#include "resolver.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
constexpr std::uint16_t max_string_id = 0x8250;
|
||||
|
||||
enum class opcode : std::uint8_t
|
||||
{
|
||||
OP_End = 0,
|
||||
@ -120,7 +129,7 @@ enum class opcode : std::uint8_t
|
||||
OP_ScriptFunctionCallPointer = 0x69,
|
||||
OP_ScriptMethodCallPointer = 0x6A,
|
||||
OP_ScriptThreadCallPointer = 0x6B,
|
||||
OP_ScriptMethodChildThreadCallPointer = 0x6C,
|
||||
OP_ScriptChildThreadCallPointer = 0x6C,
|
||||
OP_ScriptMethodThreadCallPointer = 0x6D,
|
||||
OP_ScriptMethodChildThreadCallPointer = 0x6E,
|
||||
OP_CallBuiltinPointer = 0x6F,
|
||||
@ -153,7 +162,7 @@ enum class opcode : std::uint8_t
|
||||
OP_plus = 0x8A,
|
||||
OP_minus = 0x8B,
|
||||
OP_multiply = 0x8C,
|
||||
OP_divide = 0x8F,
|
||||
OP_divide = 0x8D,
|
||||
OP_mod = 0x8E,
|
||||
OP_size = 0x8F,
|
||||
OP_waittillmatch = 0x90,
|
||||
|
848
src/experimental/iw5_console/xsk/lexer.cpp
Normal file
848
src/experimental/iw5_console/xsk/lexer.cpp
Normal file
@ -0,0 +1,848 @@
|
||||
// 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 "iw5_console.hpp"
|
||||
#include "parser.hpp"
|
||||
#include "lexer.hpp"
|
||||
|
||||
xsk::gsc::iw5_console::parser::symbol_type IW5Clex(xsk::gsc::iw5_console::lexer& lexer)
|
||||
{
|
||||
return lexer.lex();
|
||||
}
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
const std::unordered_map<std::string_view, parser::token::token_kind_type> keyword_map
|
||||
{{
|
||||
{ "#define", parser::token::SH_DEFINE },
|
||||
{ "#undef", parser::token::SH_UNDEF },
|
||||
{ "#ifdef", parser::token::SH_IFDEF },
|
||||
{ "#ifndef", parser::token::SH_IFNDEF },
|
||||
{ "#if", parser::token::SH_IF },
|
||||
{ "#elif", parser::token::SH_ELIF },
|
||||
{ "#else", parser::token::SH_ELSE },
|
||||
{ "#endif", parser::token::SH_ENDIF },
|
||||
{ "#inline", parser::token::INLINE },
|
||||
{ "#include", parser::token::INCLUDE },
|
||||
{ "#using_animtree", parser::token::USINGTREE },
|
||||
{ "#animtree", parser::token::ANIMTREE },
|
||||
{ "endon", parser::token::ENDON },
|
||||
{ "notify", parser::token::NOTIFY },
|
||||
{ "wait", parser::token::WAIT },
|
||||
{ "waittill", parser::token::WAITTILL },
|
||||
{ "waittillmatch", parser::token::WAITTILLMATCH },
|
||||
{ "waittillframeend", parser::token::WAITTILLFRAMEEND },
|
||||
{ "if", parser::token::IF },
|
||||
{ "else", parser::token::ELSE },
|
||||
{ "do", parser::token::DO },
|
||||
{ "while", parser::token::WHILE },
|
||||
{ "for", parser::token::FOR },
|
||||
{ "foreach", parser::token::FOREACH },
|
||||
{ "in", parser::token::IN },
|
||||
{ "switch", parser::token::SWITCH },
|
||||
{ "case", parser::token::CASE },
|
||||
{ "default", parser::token::DEFAULT },
|
||||
{ "break", parser::token::BREAK },
|
||||
{ "continue", parser::token::CONTINUE },
|
||||
{ "return", parser::token::RETURN },
|
||||
{ "breakpoint", parser::token::BREAKPOINT },
|
||||
{ "prof_begin", parser::token::PROFBEGIN },
|
||||
{ "prof_end", parser::token::PROFEND },
|
||||
{ "thread", parser::token::THREAD },
|
||||
{ "childthread", parser::token::CHILDTHREAD },
|
||||
{ "thisthread", parser::token::THISTHREAD },
|
||||
{ "call", parser::token::CALL },
|
||||
{ "true", parser::token::TRUE },
|
||||
{ "false", parser::token::FALSE },
|
||||
{ "undefined", parser::token::UNDEFINED },
|
||||
{ "size", parser::token::SIZE },
|
||||
{ "game", parser::token::GAME },
|
||||
{ "self", parser::token::SELF },
|
||||
{ "anim", parser::token::ANIM },
|
||||
{ "level", parser::token::LEVEL },
|
||||
}};
|
||||
|
||||
buffer::buffer() : length(0)
|
||||
{
|
||||
data = static_cast<char*>(std::malloc(max_buf_size));
|
||||
}
|
||||
|
||||
buffer::~buffer()
|
||||
{
|
||||
if (data) std::free(data);
|
||||
}
|
||||
|
||||
bool buffer::push(char c)
|
||||
{
|
||||
if (length >= max_buf_size)
|
||||
return false;
|
||||
|
||||
data[length++] = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
reader::reader() : buffer_pos(0), bytes_remaining(0), last_byte(0), current_byte(0), state(reader::end)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void reader::init(const char* data, size_t size)
|
||||
{
|
||||
if (data && size)
|
||||
{
|
||||
state = reader::ok;
|
||||
buffer_pos = data;
|
||||
bytes_remaining = static_cast<std::uint32_t>(size);
|
||||
last_byte = 0;
|
||||
current_byte = *data;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = reader::end;
|
||||
buffer_pos = 0;
|
||||
bytes_remaining = 0;
|
||||
last_byte = 0;
|
||||
current_byte = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void reader::advance()
|
||||
{
|
||||
++buffer_pos;
|
||||
|
||||
if (bytes_remaining-- == 1)
|
||||
{
|
||||
state = reader::end;
|
||||
bytes_remaining = 0;
|
||||
last_byte = current_byte;
|
||||
current_byte = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
last_byte = current_byte;
|
||||
current_byte = *buffer_pos;
|
||||
}
|
||||
}
|
||||
|
||||
lexer::lexer(build mode, const std::string& name, const char* data, size_t size) : loc_(location(&name)),
|
||||
locs_(std::stack<location>()), readers_(std::stack<reader>()), header_top_(0), mode_(mode), indev_(false), clean_(true)
|
||||
{
|
||||
reader_.init(data, size);
|
||||
}
|
||||
|
||||
void lexer::push_header(const std::string& file)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (header_top_++ >= 10)
|
||||
throw comp_error(loc_, "maximum gsh depth exceeded '10'");
|
||||
|
||||
auto data = resolver::file_data(file + ".gsh");
|
||||
|
||||
readers_.push(reader_);
|
||||
locs_.push(loc_);
|
||||
loc_.initialize(std::get<0>(data));
|
||||
reader_.init(std::get<1>(data), std::get<2>(data));
|
||||
clean_ = true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw error("parsing header file '" + file + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void lexer::pop_header()
|
||||
{
|
||||
header_top_--;
|
||||
loc_ = locs_.top();
|
||||
locs_.pop();
|
||||
reader_ = readers_.top();
|
||||
readers_.pop();
|
||||
}
|
||||
|
||||
void lexer::ban_header(const location& loc)
|
||||
{
|
||||
if (header_top_ > 0)
|
||||
{
|
||||
throw comp_error(loc, "not allowed inside a gsh file");
|
||||
}
|
||||
}
|
||||
|
||||
auto lexer::lex() -> parser::symbol_type
|
||||
{
|
||||
buffer_.length = 0;
|
||||
state_ = state::start;
|
||||
|
||||
while (true)
|
||||
{
|
||||
const auto& state = reader_.state;
|
||||
auto& last = reader_.last_byte;
|
||||
auto& curr = reader_.current_byte;
|
||||
auto path = false;
|
||||
loc_.step();
|
||||
|
||||
if (state == reader::end)
|
||||
{
|
||||
if (indev_)
|
||||
throw comp_error(loc_, "unmatched devblock start ('/#')");
|
||||
|
||||
if (header_top_ > 0)
|
||||
pop_header();
|
||||
else
|
||||
return parser::make_IW5CEOF(loc_);
|
||||
}
|
||||
|
||||
if (clean_ && last != 0 && last != ' ' && last != '\t' && last != '\n')
|
||||
clean_ = false;
|
||||
|
||||
advance();
|
||||
|
||||
switch (last)
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
loc_.step();
|
||||
continue;
|
||||
case '\n':
|
||||
loc_.lines();
|
||||
loc_.step();
|
||||
clean_ = true;
|
||||
continue;
|
||||
case '\\':
|
||||
throw comp_error(loc_, "invalid token ('\\')");
|
||||
case '/':
|
||||
if (curr != '=' && curr != '#' && curr != '@' && curr != '*' && curr != '/')
|
||||
return parser::make_DIV(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '=')
|
||||
return parser::make_ASSIGN_DIV(loc_);
|
||||
|
||||
if (last == '#')
|
||||
{
|
||||
if (indev_)
|
||||
throw comp_error(loc_, "cannot recurse devblock ('/#')");
|
||||
|
||||
if (mode_ == build::dev)
|
||||
{
|
||||
indev_ = true;
|
||||
return parser::make_DEVBEGIN(loc_);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
throw comp_error(loc_, "unmatched devblock start ('/#')");
|
||||
|
||||
if (curr == '\n')
|
||||
{
|
||||
loc_.lines();
|
||||
loc_.step();
|
||||
}
|
||||
else if (last == '#' && curr == '/')
|
||||
{
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (last == '@')
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
throw comp_error(loc_, "unmatched script doc comment start ('/@')");
|
||||
|
||||
if (curr == '\n')
|
||||
{
|
||||
loc_.lines();
|
||||
loc_.step();
|
||||
}
|
||||
else if (last == '@' && curr == '/')
|
||||
{
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
else if (last == '*')
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
throw comp_error(loc_, "unmatched multiline comment start ('/*')");
|
||||
|
||||
if (curr == '\n')
|
||||
{
|
||||
loc_.lines();
|
||||
loc_.step();
|
||||
}
|
||||
else if (last == '*' && curr == '/')
|
||||
{
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
else if (last == '/')
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if (curr == '\n')
|
||||
break;
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case '#':
|
||||
if (curr == '/')
|
||||
{
|
||||
if (!indev_)
|
||||
throw comp_error(loc_, "unmatched devblock end ('#/')");
|
||||
|
||||
advance();
|
||||
indev_ = false;
|
||||
return parser::make_DEVEND(loc_);
|
||||
}
|
||||
|
||||
buffer_.push(last);
|
||||
advance();
|
||||
|
||||
while (state == reader::ok)
|
||||
{
|
||||
if (last != ' ' || last != '\t')
|
||||
break;
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (state == reader::end || !((last > 64 && last < 91) || (last > 96 && last < 123)))
|
||||
throw comp_error(loc_, "invalid preprocessor directive ('#')");
|
||||
|
||||
state_ = state::preprocessor;
|
||||
goto lex_name;
|
||||
case '*':
|
||||
if (curr != '=' && curr != '/')
|
||||
return parser::make_MUL(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '=')
|
||||
return parser::make_ASSIGN_MUL(loc_);
|
||||
|
||||
throw comp_error(loc_, "unmatched multiline comment end ('*/')");
|
||||
case '"':
|
||||
state_ = state::string;
|
||||
goto lex_string;
|
||||
case '.':
|
||||
if (curr < '0' || curr > '9')
|
||||
return parser::make_DOT(loc_);
|
||||
goto lex_number;
|
||||
case '(':
|
||||
return parser::make_LPAREN(loc_);
|
||||
case ')':
|
||||
return parser::make_RPAREN(loc_);
|
||||
case '{':
|
||||
return parser::make_LBRACE(loc_);
|
||||
case '}':
|
||||
return parser::make_RBRACE(loc_);
|
||||
case '[':
|
||||
return parser::make_LBRACKET(loc_);
|
||||
case ']':
|
||||
return parser::make_RBRACKET(loc_);
|
||||
case ',':
|
||||
return parser::make_COMMA(loc_);
|
||||
case ';':
|
||||
return parser::make_SEMICOLON(loc_);
|
||||
case ':':
|
||||
if (curr != ':')
|
||||
return parser::make_COLON(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_DOUBLECOLON(loc_);
|
||||
case '?':
|
||||
return parser::make_QMARK(loc_);
|
||||
case '=':
|
||||
if (curr != '=')
|
||||
return parser::make_ASSIGN(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_EQUALITY(loc_);
|
||||
case '+':
|
||||
if (curr != '+' && curr != '=')
|
||||
return parser::make_ADD(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '+')
|
||||
return parser::make_INCREMENT(loc_);
|
||||
|
||||
return parser::make_ASSIGN_ADD(loc_);
|
||||
case '-':
|
||||
if (curr != '-' && curr != '=')
|
||||
return parser::make_SUB(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '-')
|
||||
return parser::make_DECREMENT(loc_);
|
||||
|
||||
return parser::make_ASSIGN_SUB(loc_);
|
||||
case '%':
|
||||
if (curr != '=')
|
||||
return parser::make_MOD(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
return parser::make_ASSIGN_MOD(loc_);
|
||||
case '|':
|
||||
if (curr != '|' && curr != '=')
|
||||
return parser::make_BITWISE_OR(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '|')
|
||||
return parser::make_OR(loc_);
|
||||
|
||||
return parser::make_ASSIGN_BW_OR(loc_);
|
||||
case '&':
|
||||
if (curr != '&' && curr != '=' && curr != '"')
|
||||
return parser::make_BITWISE_AND(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '&')
|
||||
return parser::make_AND(loc_);
|
||||
|
||||
if (last == '=')
|
||||
return parser::make_ASSIGN_BW_AND(loc_);
|
||||
|
||||
state_ = state::localize;
|
||||
goto lex_string;
|
||||
case '^':
|
||||
if (curr != '=')
|
||||
return parser::make_BITWISE_EXOR(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_ASSIGN_BW_EXOR(loc_);
|
||||
case '!':
|
||||
if (curr != '=')
|
||||
return parser::make_NOT(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_INEQUALITY(loc_);
|
||||
case '~':
|
||||
return parser::make_COMPLEMENT(loc_);
|
||||
case '<':
|
||||
if (curr != '<' && curr != '=')
|
||||
return parser::make_LESS(loc_);
|
||||
|
||||
advance();
|
||||
if (last == '=')
|
||||
return parser::make_LESS_EQUAL(loc_);
|
||||
|
||||
if (curr != '=')
|
||||
return parser::make_LSHIFT(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_ASSIGN_LSHIFT(loc_);
|
||||
case '>':
|
||||
if (curr != '>' && curr != '=')
|
||||
return parser::make_GREATER(loc_);
|
||||
|
||||
advance();
|
||||
|
||||
if (last == '=')
|
||||
return parser::make_GREATER_EQUAL(loc_);
|
||||
|
||||
if (curr != '=')
|
||||
return parser::make_RSHIFT(loc_);
|
||||
|
||||
advance();
|
||||
return parser::make_ASSIGN_RSHIFT(loc_);
|
||||
default:
|
||||
if (last >= '0' && last <= '9')
|
||||
goto lex_number;
|
||||
else if (last == '_' || (last >= 'A' && last <= 'Z') || (last >= 'a' && last <= 'z'))
|
||||
goto lex_name;
|
||||
|
||||
throw comp_error(loc_, utils::string::va("bad token: \'%c\'", last));
|
||||
}
|
||||
|
||||
lex_string:
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
throw comp_error(loc_, "unmatched string start ('\"')");
|
||||
|
||||
if (curr == '"')
|
||||
{
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
|
||||
if (curr == '\n')
|
||||
throw comp_error(loc_, "unterminated string literal");
|
||||
|
||||
if (curr == '\\')
|
||||
{
|
||||
advance();
|
||||
|
||||
if (state == reader::end)
|
||||
throw comp_error(loc_, "invalid token ('\')");
|
||||
|
||||
char c = curr;
|
||||
switch (curr)
|
||||
{
|
||||
case 't': c = '\t'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 'n': c = '\n'; break;
|
||||
case '"': c = '\"'; break;
|
||||
case '\\': c = '\\'; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (!buffer_.push(c))
|
||||
throw comp_error(loc_, "max string size exceeded");
|
||||
}
|
||||
else if (!buffer_.push(curr))
|
||||
throw comp_error(loc_, "max string size exceeded");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (state_ == state::localize)
|
||||
return parser::make_ISTRING(std::string(buffer_.data, buffer_.length), loc_);
|
||||
|
||||
return parser::make_STRING(std::string(buffer_.data, buffer_.length), loc_);
|
||||
|
||||
lex_name:
|
||||
buffer_.push(last);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if (!(curr == '\\' || curr == '_' || (curr > 64 && curr < 91) || (curr > 96 && curr < 123) || (curr > 47 && curr < 58)))
|
||||
break;
|
||||
|
||||
if (curr == '\\')
|
||||
{
|
||||
if (last == '\\')
|
||||
throw comp_error(loc_, "invalid path '\\\\'");
|
||||
|
||||
path = true;
|
||||
if (!buffer_.push('/'))
|
||||
throw comp_error(loc_, "max string size exceeded");
|
||||
}
|
||||
else if (!buffer_.push(curr))
|
||||
throw comp_error(loc_, "max string size exceeded");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (state_ == state::preprocessor)
|
||||
{
|
||||
auto token = parser::token::IW5CUNDEF;
|
||||
|
||||
if (buffer_.length < 16)
|
||||
{
|
||||
const auto& itr = keyword_map.find(std::string_view(buffer_.data, buffer_.length));
|
||||
|
||||
if (itr != keyword_map.end())
|
||||
{
|
||||
if (itr->second > parser::token::SH_ENDIF)
|
||||
return parser::symbol_type(itr->second, loc_);
|
||||
|
||||
token = itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
preprocessor_run(token);
|
||||
|
||||
state_ = state::start;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer_.length < 17)
|
||||
{
|
||||
const auto& itr = keyword_map.find(std::string_view(buffer_.data, buffer_.length));
|
||||
|
||||
if (itr != keyword_map.end())
|
||||
return parser::symbol_type(itr->second, loc_);
|
||||
}
|
||||
|
||||
if (path)
|
||||
{
|
||||
if (buffer_.data[buffer_.length - 1] == '/')
|
||||
throw comp_error(loc_, "invalid path end '\\'");
|
||||
|
||||
return parser::make_PATH(resolver::make_token(std::string_view(buffer_.data, buffer_.length)), loc_);
|
||||
}
|
||||
|
||||
return parser::make_IDENTIFIER(resolver::make_token(std::string_view(buffer_.data, buffer_.length)), loc_);
|
||||
}
|
||||
|
||||
lex_number:
|
||||
if (last == '.' || last != '0' || (last == '0' && (curr != 'o' && curr != 'b' && curr != 'x')))
|
||||
{
|
||||
buffer_.push(last);
|
||||
|
||||
auto dot = last == '.' ? 1 : 0;
|
||||
auto flt = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if (curr == '\'' && (last == '\'' || last == 'f' || last == '.'))
|
||||
throw comp_error(loc_, "invalid number literal");
|
||||
|
||||
if ((curr == '.' || curr == 'f') && last == '\'')
|
||||
throw comp_error(loc_, "invalid number literal");
|
||||
|
||||
if (curr == '\'')
|
||||
{
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curr == 'f')
|
||||
flt++;
|
||||
else if (curr == '.')
|
||||
dot++;
|
||||
else if (!(curr > 47 && curr < 58))
|
||||
break;
|
||||
|
||||
if (!buffer_.push(curr))
|
||||
throw comp_error(loc_, "number literal size exceeded");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (last == '\'')
|
||||
throw comp_error(loc_, "invalid number literal");
|
||||
|
||||
if (dot > 1 || flt > 1 || (flt && buffer_.data[buffer_.length - 1] != 'f'))
|
||||
throw comp_error(loc_, "invalid number literal");
|
||||
|
||||
if (dot || flt)
|
||||
return parser::make_FLOAT(std::string(buffer_.data, buffer_.length), loc_);
|
||||
|
||||
return parser::make_INTEGER(std::string(buffer_.data, buffer_.length), loc_);
|
||||
}
|
||||
else if (curr == 'o')
|
||||
{
|
||||
advance();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if ((curr == '\'' && (last == '\'' || last == 'o')) || (curr == 'o' && last == '\''))
|
||||
throw comp_error(loc_, "invalid octal literal");
|
||||
|
||||
if (curr == '\'')
|
||||
{
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(curr > 47 && curr < 56))
|
||||
break;
|
||||
|
||||
if (!buffer_.push(curr))
|
||||
throw error("gsc lexer: out of memory!");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (last == '\'' || buffer_.length <= 0)
|
||||
throw comp_error(loc_, "invalid octal literal");
|
||||
|
||||
return parser::make_INTEGER(utils::string::oct_to_dec(buffer_.data), loc_);
|
||||
}
|
||||
else if (curr == 'b')
|
||||
{
|
||||
buffer_.push(last);
|
||||
buffer_.push(curr);
|
||||
advance();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if ((curr == '\'' && (last == '\'' || last == 'b')) || (curr == 'b' && last == '\''))
|
||||
throw comp_error(loc_, "invalid binary literal");
|
||||
|
||||
if (curr == '\'')
|
||||
{
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curr != '0' && curr != '1')
|
||||
break;
|
||||
|
||||
if (!buffer_.push(curr))
|
||||
throw comp_error(loc_, "number literal size exceeded");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (last == '\'' || buffer_.length < 3)
|
||||
throw comp_error(loc_, "invalid binary literal");
|
||||
|
||||
return parser::make_INTEGER(utils::string::bin_to_dec(buffer_.data), loc_);
|
||||
}
|
||||
else if (curr == 'x')
|
||||
{
|
||||
buffer_.push(last);
|
||||
buffer_.push(curr);
|
||||
advance();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (state == reader::end)
|
||||
break;
|
||||
|
||||
if ((curr == '\'' && (last == '\'' || last == 'x')) || (curr == 'x' && last == '\''))
|
||||
throw comp_error(loc_, "invalid hexadecimal literal");
|
||||
|
||||
if (curr == '\'')
|
||||
{
|
||||
advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!((curr > 47 && curr < 58) || (curr > 64 && curr < 71) || (curr > 96 && curr < 103)))
|
||||
break;
|
||||
|
||||
if (!buffer_.push(curr))
|
||||
throw error("gsc lexer: out of memory!");
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (last == '\'' || buffer_.length < 3)
|
||||
throw comp_error(loc_, "invalid hexadecimal literal");
|
||||
|
||||
return parser::make_INTEGER(utils::string::hex_to_dec(buffer_.data), loc_);
|
||||
}
|
||||
|
||||
throw error("UNEXPECTED LEXER INTERNAL ERROR!");
|
||||
}
|
||||
}
|
||||
|
||||
void lexer::advance()
|
||||
{
|
||||
reader_.advance();
|
||||
loc_.end.column++;
|
||||
|
||||
if (reader_.current_byte == '\\') [[unlikely]]
|
||||
preprocessor_wrap();
|
||||
}
|
||||
|
||||
void lexer::preprocessor_wrap()
|
||||
{
|
||||
while (reader_.current_byte == '\\')
|
||||
{
|
||||
if (reader_.bytes_remaining == 1)
|
||||
throw comp_error(loc_, "invalid token ('\\')");
|
||||
|
||||
if (reader_.buffer_pos[1] != '\r' && reader_.buffer_pos[1] != '\n')
|
||||
break;
|
||||
|
||||
if (reader_.buffer_pos[1] == '\r')
|
||||
{
|
||||
if (reader_.bytes_remaining <= 3 || reader_.buffer_pos[2] != '\n')
|
||||
throw comp_error(loc_, "invalid token ('\\')");
|
||||
|
||||
reader_.buffer_pos += 3;
|
||||
reader_.bytes_remaining -= 3;
|
||||
}
|
||||
|
||||
if ((reader_.buffer_pos[1] == '\n'))
|
||||
{
|
||||
if (reader_.bytes_remaining == 2)
|
||||
throw comp_error(loc_, "invalid token ('\\')");
|
||||
|
||||
reader_.buffer_pos += 2;
|
||||
reader_.bytes_remaining -= 2;
|
||||
}
|
||||
|
||||
if (reader_.bytes_remaining == 0)
|
||||
{
|
||||
reader_.state = reader::end;
|
||||
reader_.current_byte = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader_.current_byte = *reader_.buffer_pos;
|
||||
}
|
||||
|
||||
loc_.lines();
|
||||
loc_.step();
|
||||
}
|
||||
}
|
||||
|
||||
void lexer::preprocessor_run(parser::token::token_kind_type token)
|
||||
{
|
||||
if (!clean_)
|
||||
throw comp_error(loc_, "invalid token ('#')");
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case parser::token::SH_DEFINE:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_UNDEF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_IFDEF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_IFNDEF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_IF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_ELIF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_ELSE:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
case parser::token::SH_ENDIF:
|
||||
throw comp_error(loc_, "unimplemented preprocessor directive");
|
||||
break;
|
||||
default:
|
||||
throw comp_error(loc_, "unknown preprocessor directive");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
78
src/experimental/iw5_console/xsk/lexer.hpp
Normal file
78
src/experimental/iw5_console/xsk/lexer.hpp
Normal file
@ -0,0 +1,78 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
constexpr size_t max_buf_size = 0x2000;
|
||||
|
||||
struct buffer
|
||||
{
|
||||
char* data;
|
||||
size_t length;
|
||||
|
||||
buffer();
|
||||
~buffer();
|
||||
bool push(char c);
|
||||
};
|
||||
|
||||
struct reader
|
||||
{
|
||||
enum state_type : std::uint8_t { end, ok };
|
||||
|
||||
const char* buffer_pos;
|
||||
std::uint32_t bytes_remaining;
|
||||
char last_byte;
|
||||
char current_byte;
|
||||
state_type state;
|
||||
|
||||
reader();
|
||||
|
||||
reader(const reader& obj)
|
||||
{
|
||||
std::memcpy(this, &obj, sizeof(reader));
|
||||
}
|
||||
|
||||
reader& operator=(const reader& obj)
|
||||
{
|
||||
std::memcpy(this, &obj, sizeof(reader));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void init(const char* data, size_t size);
|
||||
void advance();
|
||||
};
|
||||
|
||||
class lexer
|
||||
{
|
||||
enum class state : std::uint8_t { start, string, localize, preprocessor };
|
||||
|
||||
reader reader_;
|
||||
buffer buffer_;
|
||||
location loc_;
|
||||
std::stack<location> locs_;
|
||||
std::stack<reader> readers_;
|
||||
std::uint32_t header_top_;
|
||||
state state_;
|
||||
build mode_;
|
||||
bool indev_;
|
||||
bool clean_;
|
||||
|
||||
public:
|
||||
lexer(build mode, const std::string& name, const char* data, size_t size);
|
||||
auto lex() -> parser::symbol_type;
|
||||
void push_header(const std::string& file);
|
||||
void pop_header();
|
||||
void ban_header(const location& loc);
|
||||
|
||||
private:
|
||||
void advance();
|
||||
void preprocessor_wrap();
|
||||
void preprocessor_run(parser::token::token_kind_type token);
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
4668
src/experimental/iw5_console/xsk/parser.cpp
Normal file
4668
src/experimental/iw5_console/xsk/parser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
5550
src/experimental/iw5_console/xsk/parser.hpp
Normal file
5550
src/experimental/iw5_console/xsk/parser.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1780
src/experimental/iw5_console/xsk/resolver.cpp
Normal file
1780
src/experimental/iw5_console/xsk/resolver.cpp
Normal file
File diff suppressed because it is too large
Load Diff
40
src/experimental/iw5_console/xsk/resolver.hpp
Normal file
40
src/experimental/iw5_console/xsk/resolver.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace xsk::gsc::iw5_console
|
||||
{
|
||||
|
||||
class resolver
|
||||
{
|
||||
public:
|
||||
static void init(read_cb_type callback);
|
||||
static void cleanup();
|
||||
|
||||
static auto opcode_id(const std::string& name) -> std::uint8_t;
|
||||
static auto opcode_name(std::uint8_t id) -> std::string;
|
||||
|
||||
static auto function_id(const std::string& name) -> std::uint16_t;
|
||||
static auto function_name(std::uint16_t id) -> std::string;
|
||||
|
||||
static auto method_id(const std::string& name) -> std::uint16_t;
|
||||
static auto method_name(std::uint16_t id) -> std::string;
|
||||
|
||||
static auto token_id(const std::string& name) -> std::uint16_t;
|
||||
static auto token_name(std::uint16_t id) -> std::string;
|
||||
|
||||
static auto find_function(const std::string& name) -> bool;
|
||||
static auto find_method(const std::string& name) -> bool;
|
||||
|
||||
static void add_function(const std::string& name, std::uint16_t id);
|
||||
static void add_method(const std::string& name, std::uint16_t id);
|
||||
|
||||
static auto make_token(std::string_view str) -> std::string;
|
||||
static auto file_data(const std::string& name) -> std::tuple<const std::string*, char*, size_t>;
|
||||
static auto fs_to_game_path(const std::filesystem::path& file) -> std::filesystem::path;
|
||||
};
|
||||
|
||||
} // namespace xsk::gsc::iw5_console
|
Loading…
Reference in New Issue
Block a user