gsc-tool/src/gsc/context.cpp

661 lines
19 KiB
C++

// Copyright 2023 xensik. All rights reserved.
//
// Use of this source code is governed by a GNU GPLv3 license
// that can be found in the LICENSE file.
#include "stdinc.hpp"
#include "context.hpp"
namespace xsk::gsc
{
read_cb_type read_callback = nullptr;
std::unordered_map<std::string, buffer> header_files;
std::set<std::string_view> includes;
std::unordered_map<std::string, std::vector<std::string>> include_cache;
std::set<std::string> new_func_map;
std::set<std::string> new_meth_map;
context::context(gsc::props props, gsc::engine engine, gsc::endian endian, gsc::system system, u32 str_count)
: props_{ props }, engine_{ engine }, endian_{ endian }, system_{ system }, str_count_{ str_count },
source_{ this }, assembler_{ this }, disassembler_{ this }, compiler_{ this }, decompiler_{ this }
{
}
auto context::init(gsc::build build, read_cb_type callback) -> void
{
build_ = build;
read_callback = callback;
}
auto context::cleanup() -> void
{
header_files.clear();
include_cache.clear();
includes.clear();
}
auto context::opcode_size(opcode op) const -> u32
{
switch (op)
{
case opcode::OP_CastFieldObject:
case opcode::OP_plus:
case opcode::OP_GetGameRef:
case opcode::OP_GetThisthread:
case opcode::OP_greater:
case opcode::OP_shift_right:
case opcode::OP_dec:
case opcode::OP_bit_or:
case opcode::OP_equality:
case opcode::OP_ClearLocalVariableFieldCached0:
case opcode::OP_notify:
case opcode::OP_PreScriptCall:
case opcode::OP_GetUndefined:
case opcode::OP_SetLocalVariableFieldCached0:
case opcode::OP_GetLevel:
case opcode::OP_size:
case opcode::OP_AddArray:
case opcode::OP_endon:
case opcode::OP_shift_left:
case opcode::OP_EvalLocalArrayRefCached0:
case opcode::OP_Return:
case opcode::OP_SafeSetVariableFieldCached0:
case opcode::OP_GetSelfObject:
case opcode::OP_GetGame:
case opcode::OP_EvalArray:
case opcode::OP_GetSelf:
case opcode::OP_End:
case opcode::OP_less_equal:
case opcode::OP_EvalLocalVariableCached0:
case opcode::OP_EvalLocalVariableCached1:
case opcode::OP_EvalLocalVariableCached2:
case opcode::OP_EvalLocalVariableCached3:
case opcode::OP_EvalLocalVariableCached4:
case opcode::OP_EvalLocalVariableCached5:
case opcode::OP_ScriptMethodCallPointer:
case opcode::OP_checkclearparams:
case opcode::OP_waittillmatch2:
case opcode::OP_minus:
case opcode::OP_greater_equal:
case opcode::OP_vector:
case opcode::OP_ClearArray:
case opcode::OP_DecTop:
case opcode::OP_CastBool:
case opcode::OP_EvalArrayRef:
case opcode::OP_GetZero:
case opcode::OP_wait:
case opcode::OP_waittill:
case opcode::OP_GetAnimObject:
case opcode::OP_mod:
case opcode::OP_clearparams:
case opcode::OP_ScriptFunctionCallPointer:
case opcode::OP_EmptyArray:
case opcode::OP_BoolComplement:
case opcode::OP_less:
case opcode::OP_BoolNot:
case opcode::OP_waittillFrameEnd:
case opcode::OP_waitframe:
case opcode::OP_GetLevelObject:
case opcode::OP_inc:
case opcode::OP_GetAnim:
case opcode::OP_SetVariableField:
case opcode::OP_divide:
case opcode::OP_multiply:
case opcode::OP_EvalLocalVariableRefCached0:
case opcode::OP_bit_and:
case opcode::OP_voidCodepos:
case opcode::OP_inequality:
case opcode::OP_bit_ex_or:
case opcode::OP_BoolNotAfterAnd:
case opcode::OP_IsDefined:
case opcode::OP_IsTrue:
return 1;
case opcode::OP_SetLocalVariableFieldCached:
case opcode::OP_RemoveLocalVariables:
case opcode::OP_waittillmatch:
case opcode::OP_ScriptMethodChildThreadCallPointer:
case opcode::OP_GetByte:
case opcode::OP_SafeSetWaittillVariableFieldCached:
case opcode::OP_CallBuiltinMethodPointer:
case opcode::OP_EvalLocalVariableCached:
case opcode::OP_ScriptChildThreadCallPointer:
case opcode::OP_EvalLocalVariableObjectCached:
case opcode::OP_GetNegByte:
case opcode::OP_GetAnimTree:
case opcode::OP_EvalLocalArrayCached:
case opcode::OP_ScriptMethodThreadCallPointer:
case opcode::OP_SafeSetVariableFieldCached:
case opcode::OP_EvalLocalVariableRefCached:
case opcode::OP_ScriptThreadCallPointer:
case opcode::OP_ClearLocalVariableFieldCached:
case opcode::OP_EvalLocalArrayRefCached:
case opcode::OP_CallBuiltinPointer:
case opcode::OP_FormalParams:
return 2;
case opcode::OP_JumpOnTrue:
case opcode::OP_JumpOnFalseExpr:
case opcode::OP_jumpback:
case opcode::OP_JumpOnTrueExpr:
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_GetBuiltinFunction:
case opcode::OP_GetNegUnsignedShort:
case opcode::OP_GetBuiltinMethod:
case opcode::OP_endswitch:
case opcode::OP_GetUnsignedShort:
case opcode::OP_JumpOnFalse:
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_CallBuiltin:
case opcode::OP_ScriptLocalMethodCall:
case opcode::OP_ScriptLocalFunctionCall2:
case opcode::OP_ScriptFarFunctionCall2:
case opcode::OP_CallBuiltinMethod:
case opcode::OP_GetLocalFunction:
case opcode::OP_ScriptLocalFunctionCall:
return 4;
case opcode::OP_ScriptLocalMethodThreadCall:
case opcode::OP_ScriptLocalChildThreadCall:
case opcode::OP_ScriptLocalThreadCall:
case opcode::OP_GetInteger:
case opcode::OP_ScriptLocalMethodChildThreadCall:
case opcode::OP_GetFloat:
case opcode::OP_switch:
case opcode::OP_jump:
case opcode::OP_GetUnkxHash:
case opcode::OP_GetUnsignedInt:
case opcode::OP_GetNegUnsignedInt:
return 5;
case opcode::OP_GetStatHash:
case opcode::OP_GetEnumHash:
case opcode::OP_GetDvarHash:
case opcode::OP_GetInteger64:
return 9;
case opcode::OP_GetFarFunction:
case opcode::OP_ScriptFarFunctionCall:
case opcode::OP_ScriptFarMethodCall:
return (props_ & props::farcall) ? 5 : 4;
case opcode::OP_ScriptFarThreadCall:
case opcode::OP_ScriptFarChildThreadCall:
case opcode::OP_ScriptFarMethodThreadCall:
case opcode::OP_ScriptFarMethodChildThreadCall:
return (props_ & props::farcall) ? 6 : 5;
case opcode::OP_CreateLocalVariable:
case opcode::OP_EvalNewLocalArrayRefCached0:
case opcode::OP_SafeCreateVariableFieldCached:
case opcode::OP_SetNewLocalVariableFieldCached0:
return (props_ & props::hash) ? 9 : 2;
case opcode::OP_EvalSelfFieldVariableRef:
case opcode::OP_EvalAnimFieldVariable:
case opcode::OP_EvalLevelFieldVariableRef:
case opcode::OP_SetSelfFieldVariableField:
case opcode::OP_ClearFieldVariable:
case opcode::OP_EvalFieldVariable:
case opcode::OP_SetAnimFieldVariableField:
case opcode::OP_SetLevelFieldVariableField:
case opcode::OP_EvalSelfFieldVariable:
case opcode::OP_EvalFieldVariableRef:
case opcode::OP_EvalLevelFieldVariable:
case opcode::OP_EvalAnimFieldVariableRef:
return (props_ & props::hash) ? 9 : (props_ & props::tok4) ? 5 : 3;
case opcode::OP_GetString:
case opcode::OP_GetIString:
return (props_ & props::str4) ? 5 : 3;
case opcode::OP_GetAnimation:
return (props_ & props::str4) ? 9 : 5;
case opcode::OP_GetVector:
return 13;
case opcode::OP_ClearVariableField:
case opcode::OP_EvalNewLocalVariableRefCached0:
case opcode::OP_NOP:
case opcode::OP_abort:
case opcode::OP_object:
case opcode::OP_thread_object:
case opcode::OP_EvalLocalVariable:
case opcode::OP_EvalLocalVariableRef:
case opcode::OP_breakpoint:
case opcode::OP_assignmentBreakpoint:
case opcode::OP_manualAndAssignmentBreakpoint:
case opcode::OP_prof_begin:
case opcode::OP_prof_end:
case opcode::OP_NativeGetLocalFunction:
case opcode::OP_NativeLocalFunctionCall:
case opcode::OP_NativeLocalFunctionCall2:
case opcode::OP_NativeLocalMethodCall:
case opcode::OP_NativeGetFarFunction:
case opcode::OP_NativeFarFunctionCall:
case opcode::OP_NativeFarFunctionCall2:
case opcode::OP_NativeFarMethodCall:
case opcode::OP_NativeLocalFunctionThreadCall:
case opcode::OP_NativeLocalMethodThreadCall:
case opcode::OP_NativeLocalFunctionChildThreadCall:
case opcode::OP_NativeLocalMethodChildThreadCall:
case opcode::OP_NativeFarFunctionThreadCall:
case opcode::OP_NativeFarMethodThreadCall:
case opcode::OP_NativeFarFunctionChildThreadCall:
case opcode::OP_NativeFarMethodChildThreadCall:
case opcode::OP_EvalNewLocalArrayRefCached0_Precompiled:
case opcode::OP_SetNewLocalVariableFieldCached0_Precompiled:
case opcode::OP_CreateLocalVariable_Precompiled:
case opcode::OP_SafeCreateVariableFieldCached_Precompiled:
case opcode::OP_FormalParams_Precompiled:
case opcode::OP_iw9_139:
case opcode::OP_iw9_140:
case opcode::OP_iw9_141:
case opcode::OP_iw9_142:
case opcode::OP_iw9_143:
case opcode::OP_iw9_144:
case opcode::OP_iw9_166:
default:
throw error(fmt::format("couldn't resolve instruction size for '{}'", opcode_name(op)));
}
}
auto context::opcode_id(opcode op) const -> u8
{
auto const itr = code_map_rev_.find(op);
if (itr != code_map_rev_.end())
{
return itr->second;
}
throw error(fmt::format("couldn't resolve opcode id for '{}'", opcode_name(op)));
}
auto context::opcode_enum(u8 id) const -> opcode
{
auto const itr = code_map_.find(id);
if (itr != code_map_.end())
{
return itr->second;
}
throw error(fmt::format("couldn't resolve opcode enum for '{:02X}'", id));
}
auto context::func_id(std::string const& name) const -> u16
{
if (name.starts_with("_func_"))
{
return static_cast<u16>(std::stoul(name.substr(6), nullptr, 16));
}
auto const itr = func_map_rev_.find(name);
if (itr != func_map_rev_.end())
{
return itr->second;
}
throw error(fmt::format("couldn't resolve builtin function id for {}", name));
}
auto context::func_name(u16 id) const -> std::string
{
auto const itr = func_map_.find(id);
if (itr != func_map_.end())
{
return std::string{ itr->second };
}
return fmt::format("_func_{:04X}", id);
}
auto context::func_exists(std::string const& name) const -> bool
{
if (name.starts_with("_func_")) return true;
auto const itr = func_map_rev_.find(name);
if (itr != func_map_rev_.end())
{
return true;
}
return false;
}
auto context::func_add(std::string const& name, u16 id) -> void
{
auto const itr = func_map_rev_.find(name);
if (itr != func_map_rev_.end())
{
throw error(fmt::format("builtin function '{}' already defined", name));
}
auto const str = new_func_map.find(name);
if (str != new_func_map.end())
{
func_map_.insert({ id, *str });
func_map_rev_.insert({ *str, id });
}
else
{
auto ins = new_func_map.insert(name);
if (ins.second)
{
func_map_.insert({ id, *ins.first });
func_map_rev_.insert({ *ins.first, id });
}
}
}
auto context::meth_id(std::string const& name) const -> u16
{
if (name.starts_with("_meth_"))
{
return static_cast<u16>(std::stoul(name.substr(6), nullptr, 16));
}
auto const itr = meth_map_rev_.find(name);
if (itr != meth_map_rev_.end())
{
return itr->second;
}
throw error(fmt::format("couldn't resolve builtin method id for {}", name));
}
auto context::meth_name(u16 id) const -> std::string
{
auto const itr = meth_map_.find(id);
if (itr != meth_map_.end())
{
return std::string{ itr->second };
}
return fmt::format("_meth_{:04X}", id);
}
auto context::meth_exists(std::string const& name) const -> bool
{
if (name.starts_with("_meth_")) return true;
auto const itr = meth_map_rev_.find(name);
if (itr != meth_map_rev_.end())
{
return true;
}
return false;
}
auto context::meth_add(std::string const& name, u16 id) -> void
{
auto const itr = meth_map_rev_.find(name);
if (itr != meth_map_rev_.end())
{
throw error(fmt::format("builtin method '{}' already defined", name));
}
auto const str = new_meth_map.find(name);
if (str != new_meth_map.end())
{
meth_map_.insert({ id, *str });
meth_map_rev_.insert({ *str, id });
}
else
{
auto ins = new_meth_map.insert(name);
if (ins.second)
{
meth_map_.insert({ id, *ins.first });
meth_map_rev_.insert({ *ins.first, id });
}
}
}
auto context::token_id(std::string const& name) const -> u32
{
if (name.starts_with("_id_"))
{
return static_cast<u32>(std::stoul(name.substr(4), nullptr, 16));
}
auto const itr = token_map_rev_.find(name);
if (itr != token_map_rev_.end())
{
return itr->second;
}
return 0;
}
auto context::token_name(u32 id) const -> std::string
{
auto const itr = token_map_.find(id);
if (itr != token_map_.end())
{
return std::string{ itr->second };
}
return fmt::format("_id_{:04X}", id);
}
auto context::path_id(std::string const& name) const -> u64
{
if (name.starts_with("_id_"))
{
return static_cast<u64>(std::stoull(name.substr(4), nullptr, 16));
}
char const* str = name.data();
u64 hash = 0x47F5817A5EF961BA;
while ( *str )
{
u8 byte = *str++;
if (byte == '\\')
{
byte = '/';
}
else if (static_cast<u8>(byte - 65) <= 25)
{
byte += 32;
}
hash = (u64)0x100000001B3 * ((u64)byte ^ hash);
}
return hash & 0x7FFFFFFFFFFFFFFF;
}
auto context::path_name(u64 id) const -> std::string
{
auto const itr = path_map_.find(id);
if (itr != path_map_.end())
{
return std::string{ itr->second };
}
return fmt::format("_id_{:016X}", id);
}
auto context::hash_id(std::string const& name) const -> u64
{
if (name.starts_with("_id_"))
{
return static_cast<u64>(std::stoull(name.substr(4), nullptr, 16));
}
char const* str = name.data();
u64 hash = 0x79D6530B0BB9B5D1;
while ( *str )
{
u8 byte = *str++;
if (static_cast<u8>(byte - 65) <= 25)
{
byte += 32;
}
hash = (u64)0x10000000233 * ((u64)byte ^ hash);
}
return hash;
}
auto context::hash_name(u64 id) const -> std::string
{
auto const itr = hash_map_.find(id);
if (itr != hash_map_.end())
{
return std::string{ itr->second };
}
return fmt::format("_id_{:016X}", id);
}
auto context::make_token(std::string_view str) const -> std::string
{
if (str.starts_with("_id_") || str.starts_with("_func_") || str.starts_with("_meth_"))
{
return std::string{ str };
}
auto data = std::string{ str.begin(), str.end() };
for (auto i = 0u; i < data.size(); i++)
{
data[i] = static_cast<char>(std::tolower(static_cast<unsigned char>(str[i])));
if (data[i] == '\\') data[i] = '/';
}
return data;
}
auto context::header_file_data(std::string const& name) const -> std::tuple<std::string const*, char const*, usize>
{
auto const itr = header_files.find(name);
if (itr != header_files.end())
{
return { &itr->first, reinterpret_cast<char const*>(itr->second.data), itr->second.size };
}
auto data = read_callback(name);
if (data.first.data != nullptr && data.first.size != 0 && data.second.data == nullptr && data.second.size == 0)
{
auto const res = header_files.insert({ name, data.first });
if (res.second)
{
return { &res.first->first, reinterpret_cast<char const*>(res.first->second.data), res.first->second.size };
}
}
throw error(fmt::format("couldn't open gsh file '{}'", name));
}
auto context::load_include(std::string const& name) -> bool
{
try
{
if (includes.contains(name))
{
return false;
}
includes.insert(name);
if (include_cache.contains(name))
return true;
auto file = read_callback(name);
if (file.first.data == nullptr && file.first.size == 0)
throw std::runtime_error("empty file");
if (file.second.data == nullptr && file.second.size == 0)
{
// process RawFile
auto prog = source_.parse_program(name, file.first);
auto funcs = std::vector<std::string>{};
for (auto const& dec : prog->declarations)
{
if (dec == node::decl_function)
{
funcs.push_back(dec.as_function->name->value);
}
}
include_cache.insert({ name, std::move(funcs) });
}
else
{
// process ScriptFile
auto data = disassembler_.disassemble(file.first, file.second);
auto funcs = std::vector<std::string>{};
for (auto const& fun : data->functions)
{
funcs.push_back(fun->name);
}
include_cache.insert({ name, std::move(funcs) });
}
return true;
}
catch (std::exception const& e)
{
throw error(fmt::format("parsing include file '{}': {}", name, e.what()));
}
}
auto context::init_includes() -> void
{
includes.clear();
}
auto context::is_includecall(std::string const& name, std::string& path) -> bool
{
for (auto const& inc : includes)
{
for (auto const& fun : include_cache.at(std::string{ inc }))
{
if (name == fun)
{
path = inc;
return true;
}
}
}
return false;
}
} // namespace xsk::gsc