scripting components

This commit is contained in:
Skull Merlin 2022-03-01 22:14:41 +02:00
parent 9d9c4c39ca
commit 3b46baf248
32 changed files with 4383 additions and 0 deletions

View File

@ -0,0 +1,119 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include "game/scripting/entity.hpp"
#include "game/scripting/functions.hpp"
#include "game/scripting/event.hpp"
#include "game/scripting/lua/engine.hpp"
#include "game/scripting/execution.hpp"
#include "scheduler.hpp"
#include "scripting.hpp"
namespace scripting
{
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
namespace
{
utils::hook::detour vm_notify_hook;
utils::hook::detour scr_load_level_hook;
utils::hook::detour g_shutdown_game_hook;
utils::hook::detour scr_set_thread_position_hook;
utils::hook::detour process_script_hook;
std::string current_file;
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
game::VariableValue* top)
{
if (!game::VirtualLobby_Loaded())
{
const auto* string = game::SL_ConvertToString(string_value);
if (string)
{
event e;
e.name = string;
e.entity = notify_list_owner_id;
for (auto* value = top; value->type != game::SCRIPT_END; --value)
{
e.arguments.emplace_back(*value);
}
if (e.name == "connected")
{
scripting::clear_entity_fields(e.entity);
}
lua::engine::notify(e);
}
}
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
}
void scr_load_level_stub()
{
scr_load_level_hook.invoke<void>();
if (!game::VirtualLobby_Loaded())
{
lua::engine::start();
}
}
void g_shutdown_game_stub(const int free_scripts)
{
lua::engine::stop();
return g_shutdown_game_hook.invoke<void>(free_scripts);
}
void process_script_stub(const char* filename)
{
const auto file_id = atoi(filename);
if (file_id)
{
current_file = scripting::find_token(file_id);
}
else
{
current_file = filename;
}
process_script_hook.invoke<void>(filename);
}
void scr_set_thread_position_stub(unsigned int threadName, const char* codePos)
{
const auto function_name = scripting::find_token(threadName);
script_function_table[current_file][function_name] = codePos;
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
vm_notify_hook.create(SELECT_VALUE(0x140320E50, 0x1404479F0 ), vm_notify_stub); // H1MP
// SP address is wrong, but should be ok
scr_load_level_hook.create(SELECT_VALUE(0x140005260, 0x1403727C0), scr_load_level_stub); // H1MP
g_shutdown_game_hook.create(SELECT_VALUE(0x140228BA0, 0x140345A60), g_shutdown_game_stub); // H1MP
scr_set_thread_position_hook.create(SELECT_VALUE(0x1403115E0, 0x140437D10), scr_set_thread_position_stub); // H1MP
process_script_hook.create(SELECT_VALUE(0x14031AB30, 0x1404417E0), process_script_stub); // H1MP
scheduler::loop([]()
{
lua::engine::run_frame();
}, scheduler::pipeline::server);
}
};
}
REGISTER_COMPONENT(scripting::component)

View File

@ -0,0 +1,11 @@
#pragma once
#include <utils/concurrency.hpp>
namespace scripting
{
using shared_table_t = std::unordered_map<std::string, std::string>;
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
extern utils::concurrency::container<shared_table_t> shared_table;
}

View File

@ -0,0 +1,120 @@
#include <std_include.hpp>
#include "entity.hpp"
#include "script_value.hpp"
#include "execution.hpp"
namespace scripting
{
entity::entity()
: entity(0)
{
}
entity::entity(const entity& other) : entity(other.entity_id_)
{
}
entity::entity(entity&& other) noexcept
{
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
entity::entity(const unsigned int entity_id)
: entity_id_(entity_id)
{
this->add();
}
entity::entity(game::scr_entref_t entref)
: entity(game::FindEntityId(entref.entnum, entref.classnum))
{
}
entity::~entity()
{
this->release();
}
entity& entity::operator=(const entity& other)
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
this->add();
}
return *this;
}
entity& entity::operator=(entity&& other) noexcept
{
if (&other != this)
{
this->release();
this->entity_id_ = other.entity_id_;
other.entity_id_ = 0;
}
return *this;
}
unsigned int entity::get_entity_id() const
{
return this->entity_id_;
}
game::scr_entref_t entity::get_entity_reference() const
{
if (!this->entity_id_)
{
const auto not_null = static_cast<uint16_t>(~0ui16);
return game::scr_entref_t{not_null, not_null};
}
return game::Scr_GetEntityIdRef(this->get_entity_id());
}
bool entity::operator==(const entity& other) const noexcept
{
return this->get_entity_id() == other.get_entity_id();
}
bool entity::operator!=(const entity& other) const noexcept
{
return !this->operator==(other);
}
void entity::add() const
{
if (this->entity_id_)
{
game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
}
}
void entity::release() const
{
if (this->entity_id_)
{
game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
}
}
void entity::set(const std::string& field, const script_value& value) const
{
set_entity_field(*this, field, value);
}
template <>
script_value entity::get<script_value>(const std::string& field) const
{
return get_entity_field(*this, field);
}
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
{
return call_function(name, *this, arguments);
}
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "game/game.hpp"
#include "script_value.hpp"
namespace scripting
{
class entity final
{
public:
entity();
entity(unsigned int entity_id);
entity(game::scr_entref_t entref);
entity(const entity& other);
entity(entity&& other) noexcept;
~entity();
entity& operator=(const entity& other);
entity& operator=(entity&& other) noexcept;
void set(const std::string& field, const script_value& value) const;
template <typename T = script_value>
T get(const std::string& field) const;
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
unsigned int get_entity_id() const;
game::scr_entref_t get_entity_reference() const;
bool operator ==(const entity& other) const noexcept;
bool operator !=(const entity& other) const noexcept;
private:
unsigned int entity_id_;
void add() const;
void release() const;
};
template <>
script_value entity::get(const std::string& field) const;
template <typename T>
T entity::get(const std::string& field) const
{
return this->get<script_value>(field).as<T>();
}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "script_value.hpp"
#include "entity.hpp"
namespace scripting
{
struct event
{
std::string name;
entity entity{};
std::vector<script_value> arguments;
};
}

View File

@ -0,0 +1,250 @@
#include <std_include.hpp>
#include "execution.hpp"
#include "safe_execution.hpp"
#include "stack_isolation.hpp"
#include "component/scripting.hpp"
namespace scripting
{
namespace
{
game::VariableValue* allocate_argument()
{
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
++game::scr_VmPub->inparamcount;
return value_ptr;
}
void push_value(const script_value& value)
{
auto* value_ptr = allocate_argument();
*value_ptr = value.get_raw();
game::AddRefToValue(value_ptr->type, value_ptr->u);
}
int get_field_id(const int classnum, const std::string& field)
{
const auto class_id = game::g_classMap[classnum].id;
const auto field_str = game::SL_GetString(field.data(), 0);
const auto _ = gsl::finally([field_str]()
{
game::RemoveRefToValue(game::SCRIPT_STRING, {static_cast<int>(field_str)});
});
const auto offset = game::FindVariable(class_id, field_str);
if (offset)
{
const auto index = 3 * (offset + 0xFA00 * (class_id & 3));
const auto id = reinterpret_cast<PINT64>(SELECT_VALUE(0x149BB5680, 0x14821DF80))[index];
return static_cast<int>(id);
}
return -1;
}
script_value get_return_value()
{
if (game::scr_VmPub->inparamcount == 0)
{
return {};
}
game::Scr_ClearOutParams();
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
}
}
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
{
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
const auto event_id = game::SL_GetString(event.data(), 0);
game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
}
script_value call_function(const std::string& name, const entity& entity,
const std::vector<script_value>& arguments)
{
const auto entref = entity.get_entity_reference();
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
const auto function = find_function(name, !is_method_call);
if (function == nullptr)
{
throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'");
}
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
push_value(*i);
}
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
if (!safe_execution::call(function, entref))
{
throw std::runtime_error(
"Error executing "s + (is_method_call ? "method" : "function") + " '" + name + "'");
}
return get_return_value();
}
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
{
return call_function(name, entity(), arguments);
}
template <>
script_value call(const std::string& name, const std::vector<script_value>& arguments)
{
return call_function(name, arguments);
}
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
{
const auto id = entity.get_entity_id();
stack_isolation _;
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
{
scripting::push_value(*i);
}
game::AddRefToObject(id);
const auto local_id = game::AllocThread(id);
const auto result = game::VM_Execute(local_id, pos, (unsigned int)arguments.size());
game::RemoveRefToObject(result);
return get_return_value();
}
const char* get_function_pos(const std::string& filename, const std::string& function)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
{
throw std::runtime_error("File '" + filename + "' not found");
};
const auto functions = scripting::script_function_table[filename];
if (functions.find(function) == functions.end())
{
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
}
return functions.at(function);
}
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments)
{
const auto pos = get_function_pos(filename, function);
return exec_ent_thread(entity, pos, arguments);
}
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
script_value get_custom_field(const entity& entity, const std::string& field)
{
auto& fields = custom_fields[entity.get_entity_id()];
const auto _field = fields.find(field);
if (_field != fields.end())
{
return _field->second;
}
return {};
}
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto id = entity.get_entity_id();
if (custom_fields[id].find(field) != custom_fields[id].end())
{
custom_fields[id][field] = value;
return;
}
custom_fields[id].insert(std::make_pair(field, value));
}
void clear_entity_fields(const entity& entity)
{
const auto id = entity.get_entity_id();
if (custom_fields.find(id) != custom_fields.end())
{
custom_fields[id].clear();
}
}
void clear_custom_fields()
{
custom_fields.clear();
}
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
{
const auto entref = entity.get_entity_reference();
const int id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
push_value(value);
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
game::scr_VmPub->inparamcount = 0;
if (!safe_execution::set_entity_field(entref, id))
{
throw std::runtime_error("Failed to set value for field '" + field + "'");
}
}
else
{
// Read custom fields
set_custom_field(entity, field, value);
}
}
script_value get_entity_field(const entity& entity, const std::string& field)
{
const auto entref = entity.get_entity_reference();
const auto id = get_field_id(entref.classnum, field);
if (id != -1)
{
stack_isolation _;
game::VariableValue value{};
if (!safe_execution::get_entity_field(entref, id, &value))
{
throw std::runtime_error("Failed to get value for field '" + field + "'");
}
const auto __ = gsl::finally([value]()
{
game::RemoveRefToValue(value.type, value.u);
});
return value;
}
// Add custom fields
return get_custom_field(entity, field);
}
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "game/game.hpp"
#include "entity.hpp"
#include "script_value.hpp"
namespace scripting
{
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
script_value call_function(const std::string& name, const entity& entity,
const std::vector<script_value>& arguments);
template <typename T = script_value>
T call(const std::string& name, const std::vector<script_value>& arguments = {});
template <>
script_value call(const std::string& name, const std::vector<script_value>& arguments);
template <typename T>
T call(const std::string& name, const std::vector<script_value>& arguments)
{
return call<script_value>(name, arguments).as<T>();
}
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
const char* get_function_pos(const std::string& filename, const std::string& function);
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments);
void clear_entity_fields(const entity& entity);
void clear_custom_fields();
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
script_value get_entity_field(const entity& entity, const std::string& field);
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
#include <std_include.hpp>
#include "functions.hpp"
#include <utils/string.hpp>
namespace scripting
{
namespace
{
std::unordered_map<std::string, unsigned> lowercase_map(
const std::unordered_map<std::string, unsigned>& old_map)
{
std::unordered_map<std::string, unsigned> new_map{};
for (auto& entry : old_map)
{
new_map[utils::string::to_lower(entry.first)] = entry.second;
}
return new_map;
}
const std::unordered_map<std::string, unsigned>& get_methods()
{
static auto methods = lowercase_map(method_map);
return methods;
}
const std::unordered_map<std::string, unsigned>& get_functions()
{
static auto function = lowercase_map(function_map);
return function;
}
int find_function_index(const std::string& name, const bool prefer_global)
{
const auto target = utils::string::to_lower(name);
const auto& primary_map = prefer_global
? get_functions()
: get_methods();
const auto& secondary_map = !prefer_global
? get_functions()
: get_methods();
auto function_entry = primary_map.find(target);
if (function_entry != primary_map.end())
{
return function_entry->second;
}
function_entry = secondary_map.find(target);
if (function_entry != secondary_map.end())
{
return function_entry->second;
}
return -1;
}
script_function get_function_by_index(const unsigned index)
{
static const auto function_table = SELECT_VALUE(0x149668F50, 0x147DD1850);
static const auto method_table = SELECT_VALUE(0x14966A670, 0x147DD2F50);
if (index < 0x2DF)
{
return reinterpret_cast<script_function*>(function_table)[index];
}
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
}
}
std::string find_token(unsigned int id)
{
for (const auto& token : token_map)
{
if (token.second == id)
{
return token.first;
}
}
return utils::string::va("_ID%i", id);
}
unsigned int find_token_id(const std::string& name)
{
const auto result = token_map.find(name);
if (result != token_map.end())
{
return result->second;
}
return 0;
}
script_function find_function(const std::string& name, const bool prefer_global)
{
const auto index = find_function_index(name, prefer_global);
if (index < 0) return nullptr;
return get_function_by_index(index);
}
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
extern std::unordered_map<std::string, unsigned> method_map;
extern std::unordered_map<std::string, unsigned> function_map;
extern std::unordered_map<std::string, unsigned> token_map;
using script_function = void(*)(game::scr_entref_t);
std::string find_token(unsigned int id);
unsigned int find_token_id(const std::string& name);
script_function find_function(const std::string& name, const bool prefer_global);
}

View File

@ -0,0 +1,489 @@
#include <std_include.hpp>
#include "context.hpp"
#include "error.hpp"
#include "value_conversion.hpp"
#include "../execution.hpp"
#include "../functions.hpp"
#include "../../../component/command.hpp"
#include "../../../component/logfile.hpp"
#include "../../../component/scripting.hpp"
#include <utils/string.hpp>
namespace scripting::lua
{
namespace
{
std::vector<std::string> load_game_constants()
{
std::vector<std::string> result{};
const auto constants = game::GScr_LoadConsts.get();
ud_t ud;
ud_init(&ud);
ud_set_mode(&ud, 64);
ud_set_pc(&ud, uint64_t(constants));
ud_set_input_buffer(&ud, reinterpret_cast<const uint8_t*>(constants), INT32_MAX);
while (true)
{
ud_disassemble(&ud);
if (ud_insn_mnemonic(&ud) == UD_Iret)
{
break;
}
if (ud_insn_mnemonic(&ud) == UD_Imov)
{
const auto* operand = ud_insn_opr(&ud, 0);
if (operand && operand->type == UD_OP_REG && operand->base == UD_R_ECX)
{
operand = ud_insn_opr(&ud, 1);
if (operand && operand->type == UD_OP_IMM && (operand->base == UD_R_RAX || operand->base == UD_R_EAX))
{
result.emplace_back(reinterpret_cast<const char**>(0x1409C1CE0)[operand->lval.udword]);
}
}
}
if (ud_insn_mnemonic(&ud) == UD_Ilea)
{
const auto* operand = ud_insn_opr(&ud, 0);
if (!operand || operand->type != UD_OP_REG || operand->base != UD_R_RCX)
{
continue;
}
operand = ud_insn_opr(&ud, 1);
if (operand && operand->type == UD_OP_MEM && operand->base == UD_R_RIP)
{
auto* operand_ptr = reinterpret_cast<char*>(ud_insn_len(&ud) + ud_insn_off(&ud) + operand->lval.
sdword);
if (!utils::memory::is_bad_read_ptr(operand_ptr) && utils::memory::is_rdata_ptr(operand_ptr) &&
strlen(operand_ptr) > 0)
{
result.emplace_back(operand_ptr);
}
}
}
if (*reinterpret_cast<unsigned char*>(ud.pc) == 0xCC) break; // int 3
}
return result;
}
const std::vector<std::string>& get_game_constants()
{
static auto constants = load_game_constants();
return constants;
}
void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler)
{
state["level"] = entity{*game::levelEntityId};
auto vector_type = state.new_usertype<vector>("vector", sol::constructors<vector(float, float, float)>());
vector_type["x"] = sol::property(&vector::get_x, &vector::set_x);
vector_type["y"] = sol::property(&vector::get_y, &vector::set_y);
vector_type["z"] = sol::property(&vector::get_z, &vector::set_z);
vector_type["r"] = sol::property(&vector::get_x, &vector::set_x);
vector_type["g"] = sol::property(&vector::get_y, &vector::set_y);
vector_type["b"] = sol::property(&vector::get_z, &vector::set_z);
auto entity_type = state.new_usertype<entity>("entity");
for (const auto& func : method_map)
{
const auto name = utils::string::to_lower(func.first);
entity_type[name.data()] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, entity.call(name, arguments));
};
}
for (const auto& constant : get_game_constants())
{
entity_type[constant] = sol::property(
[constant](const entity& entity, const sol::this_state s)
{
return convert(s, entity.get(constant));
},
[constant](const entity& entity, const sol::this_state s, const sol::lua_value& value)
{
entity.set(constant, convert({s, value}));
});
}
entity_type["set"] = [](const entity& entity, const std::string& field,
const sol::lua_value& value)
{
entity.set(field, convert(value));
};
entity_type["get"] = [](const entity& entity, const sol::this_state s, const std::string& field)
{
return convert(s, entity.get(field));
};
entity_type["notify"] = [](const entity& entity, const sol::this_state s, const std::string& event,
sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
notify(entity, event, arguments);
};
entity_type["onnotify"] = [&handler](const entity& entity, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.entity = entity;
listener.event = event;
listener.is_volatile = false;
return handler.add_event_listener(std::move(listener));
};
entity_type["onnotifyonce"] = [&handler](const entity& entity, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.entity = entity;
listener.event = event;
listener.is_volatile = true;
return handler.add_event_listener(std::move(listener));
};
entity_type["call"] = [](const entity& entity, const sol::this_state s, const std::string& function,
sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, entity.call(function, arguments));
};
entity_type[sol::meta_function::new_index] = [](const entity& entity, const std::string& field,
const sol::lua_value& value)
{
entity.set(field, convert(value));
};
entity_type[sol::meta_function::index] = [](const entity& entity, const sol::this_state s, const std::string& field)
{
return convert(s, entity.get(field));
};
entity_type["getstruct"] = [](const entity& entity, const sol::this_state s)
{
const auto id = entity.get_entity_id();
return scripting::lua::entity_to_struct(s, id);
};
entity_type["struct"] = sol::property([](const entity& entity, const sol::this_state s)
{
const auto id = entity.get_entity_id();
return scripting::lua::entity_to_struct(s, id);
});
entity_type["scriptcall"] = [](const entity& entity, const sol::this_state s, const std::string& filename,
const std::string function, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, call_script_function(entity, filename, function, arguments));
};
struct game
{
};
auto game_type = state.new_usertype<game>("game_");
state["game"] = game();
for (const auto& func : function_map)
{
const auto name = utils::string::to_lower(func.first);
game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, call(name, arguments));
};
}
game_type["call"] = [](const game&, const sol::this_state s, const std::string& function,
sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, call(function, arguments));
};
game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, true);
};
game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, false);
};
game_type["executecommand"] = [](const game&, const std::string& command)
{
command::execute(command, false);
};
game_type["onplayerdamage"] = [](const game&, const sol::protected_function& callback)
{
logfile::add_player_damage_callback(callback);
};
game_type["onplayerkilled"] = [](const game&, const sol::protected_function& callback)
{
logfile::add_player_killed_callback(callback);
};
game_type["getgamevar"] = [](const sol::this_state s)
{
const auto id = *reinterpret_cast<unsigned int*>(0x14815DEB4);
const auto value = ::game::scr_VarGlob->childVariableValue[id];
::game::VariableValue variable{};
variable.type = value.type;
variable.u.uintValue = value.u.u.uintValue;
return convert(s, variable);
};
game_type["getfunctions"] = [entity_type](const game&, const sol::this_state s, const std::string& filename)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
{
throw std::runtime_error("File '" + filename + "' not found");
}
auto functions = sol::table::create(s.lua_state());
for (const auto& function : scripting::script_function_table[filename])
{
functions[function.first] = sol::overload(
[filename, function](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(entity, filename, function.first, arguments));
},
[filename, function](const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function.first, arguments));
}
);
}
return functions;
};
game_type["scriptcall"] = [](const game&, const sol::this_state s, const std::string& filename,
const std::string function, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function, arguments));
};
game_type["detour"] = [](const game&, const sol::this_state s, const std::string& filename,
const std::string function_name, const sol::protected_function& function)
{
const auto pos = get_function_pos(filename, function_name);
logfile::vm_execute_hooks[pos] = function;
auto detour = sol::table::create(function.lua_state());
detour["disable"] = [pos]()
{
logfile::vm_execute_hooks.erase(pos);
};
detour["enable"] = [pos, function]()
{
logfile::vm_execute_hooks[pos] = function;
};
detour["invoke"] = sol::overload(
[filename, function_name](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(entity, filename, function_name, arguments));
},
[filename, function_name](const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
gsl::finally(&logfile::enable_vm_execute_hook);
logfile::disable_vm_execute_hook();
return convert(s, call_script_function(*::game::levelEntityId, filename, function_name, arguments));
}
);
return detour;
};
}
}
context::context(std::string folder)
: folder_(std::move(folder))
, scheduler_(state_)
, event_handler_(state_)
{
this->state_.open_libraries(sol::lib::base,
sol::lib::package,
sol::lib::io,
sol::lib::string,
sol::lib::os,
sol::lib::math,
sol::lib::table);
this->state_["include"] = [this](const std::string& file)
{
this->load_script(file);
};
sol::function old_require = this->state_["require"];
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
this->state_["require"] = [base_path, old_require](const std::string& path)
{
return old_require(base_path + path);
};
this->state_["scriptdir"] = [this]()
{
return this->folder_;
};
setup_entity_type(this->state_, this->event_handler_, this->scheduler_);
printf("Loading script '%s'\n", this->folder_.data());
this->load_script("__init__");
}
context::~context()
{
this->collect_garbage();
this->scheduler_.clear();
this->event_handler_.clear();
this->state_ = {};
}
void context::run_frame()
{
this->scheduler_.run_frame();
this->collect_garbage();
}
void context::notify(const event& e)
{
this->scheduler_.dispatch(e);
this->event_handler_.dispatch(e);
}
void context::collect_garbage()
{
this->state_.collect_garbage();
}
void context::load_script(const std::string& script)
{
if (!this->loaded_scripts_.emplace(script).second)
{
return;
}
const auto file = (std::filesystem::path{this->folder_} / (script + ".lua")).generic_string();
handle_error(this->state_.safe_script_file(file, &sol::script_pass_on_error));
}
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "../event.hpp"
#pragma warning(push)
#pragma warning(disable: 4702)
#define SOL_ALL_SAFETIES_ON 1
#define SOL_PRINT_ERRORS 0
#include <sol/sol.hpp>
#pragma warning(pop)
#include "scheduler.hpp"
#include "event_handler.hpp"
namespace scripting::lua
{
class context
{
public:
context(std::string folder);
~context();
context(context&&) noexcept = delete;
context& operator=(context&&) noexcept = delete;
context(const context&) = delete;
context& operator=(const context&) = delete;
void run_frame();
void notify(const event& e);
void collect_garbage();
private:
sol::state state_{};
std::string folder_;
std::unordered_set<std::string> loaded_scripts_;
scheduler scheduler_;
event_handler event_handler_;
void load_script(const std::string& script);
};
}

View File

@ -0,0 +1,75 @@
#include <std_include.hpp>
#include "engine.hpp"
#include "context.hpp"
#include "../execution.hpp"
#include "../../../component/logfile.hpp"
#include "../../../component/game_module.hpp"
#include <utils/io.hpp>
namespace scripting::lua::engine
{
namespace
{
auto& get_scripts()
{
static std::vector<std::unique_ptr<context>> scripts{};
return scripts;
}
void load_scripts(const std::string& script_dir)
{
if (!utils::io::directory_exists(script_dir))
{
return;
}
const auto scripts = utils::io::list_files(script_dir);
for (const auto& script : scripts)
{
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
{
get_scripts().push_back(std::make_unique<context>(script));
}
}
}
}
void stop()
{
logfile::clear_callbacks();
get_scripts().clear();
}
void start()
{
// No SP until there is a concept
if (game::environment::is_sp())
{
return;
}
stop();
load_scripts(game_module::get_host_module().get_folder() + "/data/scripts/");
load_scripts("h1-mod/scripts/");
load_scripts("data/scripts/");
}
void notify(const event& e)
{
for (auto& script : get_scripts())
{
script->notify(e);
}
}
void run_frame()
{
for (auto& script : get_scripts())
{
script->run_frame();
}
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "../event.hpp"
namespace scripting::lua::engine
{
void start();
void stop();
void notify(const event& e);
void run_frame();
}

View File

@ -0,0 +1,37 @@
#include <std_include.hpp>
#include "error.hpp"
#include "../execution.hpp"
#include "component/console.hpp"
namespace scripting::lua
{
namespace
{
void notify_error()
{
try
{
call("iprintln", {"^1Script execution error!"});
}
catch (...)
{
}
}
}
void handle_error(const sol::protected_function_result& result)
{
if (!result.valid())
{
console::error("************** Script execution error **************\n");
const sol::error err = result;
console::error("%s\n", err.what());
console::error("****************************************************\n");
notify_error();
}
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "context.hpp"
namespace scripting::lua
{
void handle_error(const sol::protected_function_result& result);
}

View File

@ -0,0 +1,174 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
#include "value_conversion.hpp"
namespace scripting::lua
{
event_handler::event_handler(sol::state& state)
: state_(state)
{
auto event_listener_handle_type = state.new_usertype<event_listener_handle>("event_listener_handle");
event_listener_handle_type["clear"] = [this](const event_listener_handle& handle)
{
this->remove(handle);
};
event_listener_handle_type["endon"] = [this](const event_listener_handle& handle, const entity& entity, const std::string& event)
{
this->add_endon_condition(handle, entity, event);
};
}
void event_handler::dispatch(const event& event)
{
bool has_built_arguments = false;
event_arguments arguments{};
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
this->handle_endon_conditions(event);
for (auto i = tasks.begin(); i != tasks.end();)
{
if (i->event != event.name || i->entity != event.entity)
{
++i;
continue;
}
if (!i->is_deleted)
{
if(!has_built_arguments)
{
has_built_arguments = true;
arguments = this->build_arguments(event);
}
handle_error(i->callback(sol::as_args(arguments)));
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
event_listener_handle event_handler::add_event_listener(event_listener&& listener)
{
const uint64_t id = ++this->current_listener_id_;
listener.id = id;
listener.is_deleted = false;
new_callbacks_.access([&listener](task_list& tasks)
{
tasks.emplace_back(std::move(listener));
});
return {id};
}
void event_handler::add_endon_condition(const event_listener_handle& handle, const entity& entity,
const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for(auto& task : tasks)
{
if(task.id == handle.id)
{
task.endon_conditions.emplace_back(entity, event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void event_handler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
void event_handler::remove(const event_listener_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void event_handler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()),
std::move_iterator(new_tasks.end()));
new_tasks = {};
});
});
}
void event_handler::handle_endon_conditions(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for(auto& task : tasks)
{
for(auto& condition : task.endon_conditions)
{
if(condition.first == event.entity && condition.second == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access(deleter);
}
event_arguments event_handler::build_arguments(const event& event) const
{
event_arguments arguments;
for (const auto& argument : event.arguments)
{
arguments.emplace_back(convert(this->state_, argument));
}
return arguments;
}
}

View File

@ -0,0 +1,58 @@
#pragma once
namespace scripting::lua
{
using event_arguments = std::vector<sol::lua_value>;
using event_callback = sol::protected_function;
class event_listener_handle
{
public:
uint64_t id = 0;
};
class event_listener final : public event_listener_handle
{
public:
std::string event = {};
entity entity{};
event_callback callback = {};
bool is_volatile = false;
bool is_deleted = false;
std::vector<std::pair<scripting::entity, std::string>> endon_conditions{};
};
class event_handler final
{
public:
event_handler(sol::state& state);
event_handler(event_handler&&) noexcept = delete;
event_handler& operator=(event_handler&&) noexcept = delete;
event_handler(const scheduler&) = delete;
event_handler& operator=(const event_handler&) = delete;
void dispatch(const event& event);
event_listener_handle add_event_listener(event_listener&& listener);
void clear();
private:
sol::state& state_;
std::atomic_int64_t current_listener_id_ = 0;
using task_list = std::vector<event_listener>;
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
void remove(const event_listener_handle& handle);
void merge_callbacks();
void handle_endon_conditions(const event& event);
void add_endon_condition(const event_listener_handle& handle, const entity& entity, const std::string& event);
event_arguments build_arguments(const event& event) const;
};
}

View File

@ -0,0 +1,171 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
namespace scripting::lua
{
scheduler::scheduler(sol::state& state)
{
auto task_handle_type = state.new_usertype<task_handle>("task_handle");
task_handle_type["clear"] = [this](const task_handle& handle)
{
this->remove(handle);
};
task_handle_type["endon"] = [this](const task_handle& handle, const entity& entity, const std::string& event)
{
this->add_endon_condition(handle, entity, event);
};
}
void scheduler::dispatch(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for(auto& task : tasks)
{
for(auto& condition : task.endon_conditions)
{
if(condition.first == event.entity && condition.second == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access([&](task_list& tasks)
{
deleter(tasks);
new_callbacks_.access(deleter);
});
}
void scheduler::run_frame()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->delay)
{
++i;
continue;
}
i->last_call = now;
if (!i->is_deleted)
{
handle_error(i->callback());
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
void scheduler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds,
const bool is_volatile)
{
return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile);
}
task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay,
const bool is_volatile)
{
const uint64_t id = ++this->current_task_id_;
task task;
task.is_volatile = is_volatile;
task.callback = callback;
task.delay = delay;
task.last_call = std::chrono::steady_clock::now();
task.id = id;
task.is_deleted = false;
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
return {id};
}
void scheduler::add_endon_condition(const task_handle& handle, const entity& entity, const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for(auto& task : tasks)
{
if(task.id == handle.id)
{
task.endon_conditions.emplace_back(entity, event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void scheduler::remove(const task_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void scheduler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
}

View File

@ -0,0 +1,54 @@
#pragma once
#include <utils/concurrency.hpp>
namespace scripting::lua
{
class context;
class task_handle
{
public:
uint64_t id = 0;
};
class task final : public task_handle
{
public:
std::chrono::steady_clock::time_point last_call{};
sol::protected_function callback{};
std::chrono::milliseconds delay{};
bool is_volatile = false;
bool is_deleted = false;
std::vector<std::pair<entity, std::string>> endon_conditions{};
};
class scheduler final
{
public:
scheduler(sol::state& state);
scheduler(scheduler&&) noexcept = delete;
scheduler& operator=(scheduler&&) noexcept = delete;
scheduler(const scheduler&) = delete;
scheduler& operator=(const scheduler&) = delete;
void dispatch(const event& event);
void run_frame();
void clear();
task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile);
task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile);
private:
using task_list = std::vector<task>;
utils::concurrency::container<task_list> new_callbacks_;
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
std::atomic_int64_t current_task_id_ = 0;
void add_endon_condition(const task_handle& handle, const entity& entity, const std::string& event);
void remove(const task_handle& handle);
void merge_callbacks();
};
}

View File

@ -0,0 +1,319 @@
#include <std_include.hpp>
#include "value_conversion.hpp"
#include "../functions.hpp"
#include "../execution.hpp"
#include ".../../component/logfile.hpp"
namespace scripting::lua
{
namespace
{
struct array_value
{
int index;
script_value value;
};
sol::lua_value entity_to_array(lua_State* state, unsigned int id)
{
auto table = sol::table::create(state);
auto metatable = sol::table::create(state);
std::unordered_map<std::string, array_value> values;
const auto offset = 64000 * (id & 3);
auto current = game::scr_VarGlob->objectVariableChildren[id].firstChild;
auto idx = 1;
for (auto i = offset + current; current; i = offset + current)
{
const auto var = game::scr_VarGlob->childVariableValue[i];
if (var.type == game::SCRIPT_NONE)
{
current = var.nextSibling;
continue;
}
const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8));
const auto* str = game::SL_ConvertToString(string_value);
std::string key = string_value < 0x40000 && str
? str
: std::to_string(idx++);
game::VariableValue variable{};
variable.type = var.type;
variable.u = var.u.u;
array_value value;
value.index = i;
value.value = variable;
values[key] = value;
current = var.nextSibling;
}
table["getkeys"] = [values]()
{
std::vector<std::string> _keys;
for (const auto& entry : values)
{
_keys.push_back(entry.first);
}
return _keys;
};
metatable[sol::meta_function::new_index] = [values](const sol::table t, const sol::this_state s,
const sol::lua_value& key_value, const sol::lua_value& value)
{
const auto key = key_value.is<int>()
? std::to_string(key_value.as<int>())
: key_value.as<std::string>();
if (values.find(key) == values.end())
{
return;
}
const auto i = values.at(key).index;
const auto variable = &game::scr_VarGlob->childVariableValue[i];
const auto new_variable = convert({s, value}).get_raw();
game::AddRefToValue(new_variable.type, new_variable.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)new_variable.type;
variable->u.u = new_variable.u;
};
metatable[sol::meta_function::index] = [values](const sol::table t, const sol::this_state s,
const sol::lua_value& key_value)
{
const auto key = key_value.is<int>()
? std::to_string(key_value.as<int>())
: key_value.as<std::string>();
if (values.find(key) == values.end())
{
return sol::lua_value{s, sol::lua_nil};
}
return convert(s, values.at(key).value);
};
metatable[sol::meta_function::length] = [values]()
{
return values.size();
};
table[sol::metatable_key] = metatable;
return {state, table};
}
game::VariableValue convert_function(sol::lua_value value)
{
const auto function = value.as<sol::protected_function>();
const auto index = reinterpret_cast<char*>(logfile::vm_execute_hooks.size());
logfile::vm_execute_hooks[index] = function;
game::VariableValue func;
func.type = game::SCRIPT_FUNCTION;
func.u.codePosValue = index;
return func;
}
sol::lua_value convert_function(lua_State* state, const char* pos)
{
return sol::overload(
[pos](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, exec_ent_thread(entity, pos, arguments));
},
[pos](const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
return convert(s, exec_ent_thread(*game::levelEntityId, pos, arguments));
}
);
}
}
sol::lua_value entity_to_struct(lua_State* state, unsigned int parent_id)
{
auto table = sol::table::create(state);
auto metatable = sol::table::create(state);
const auto offset = 64000 * (parent_id & 3);
metatable[sol::meta_function::new_index] = [offset, parent_id](const sol::table t, const sol::this_state s,
const sol::lua_value& field, const sol::lua_value& value)
{
const auto id = field.is<std::string>()
? scripting::find_token_id(field.as<std::string>())
: field.as<int>();
if (!id)
{
return;
}
const auto variable_id = game::GetVariable(parent_id, id);
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
const auto new_variable = convert({s, value}).get_raw();
game::AddRefToValue(new_variable.type, new_variable.u);
game::RemoveRefToValue(variable->type, variable->u.u);
variable->type = (char)new_variable.type;
variable->u.u = new_variable.u;
};
metatable[sol::meta_function::index] = [offset, parent_id](const sol::table t, const sol::this_state s,
const sol::lua_value& field)
{
const auto id = field.is<std::string>()
? scripting::find_token_id(field.as<std::string>())
: field.as<int>();
if (!id)
{
return sol::lua_value{s, sol::lua_nil};
}
const auto variable_id = game::FindVariable(parent_id, id);
if (!variable_id)
{
return sol::lua_value{s, sol::lua_nil};
}
const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset];
game::VariableValue result{};
result.u = variable.u.u;
result.type = (game::scriptType_e)variable.type;
return convert(s, result);
};
table[sol::metatable_key] = metatable;
return {state, table};
}
script_value convert(const sol::lua_value& value)
{
if (value.is<int>())
{
return {value.as<int>()};
}
if (value.is<unsigned int>())
{
return {value.as<unsigned int>()};
}
if (value.is<bool>())
{
return {value.as<bool>()};
}
if (value.is<double>())
{
return {value.as<double>()};
}
if (value.is<float>())
{
return {value.as<float>()};
}
if (value.is<std::string>())
{
return {value.as<std::string>()};
}
if (value.is<entity>())
{
return {value.as<entity>()};
}
if (value.is<vector>())
{
return {value.as<vector>()};
}
if (value.is<sol::protected_function>())
{
return convert_function(value);
}
return {};
}
sol::lua_value convert(lua_State* state, const script_value& value)
{
if (value.is<int>())
{
return {state, value.as<int>()};
}
if (value.is<float>())
{
return {state, value.as<float>()};
}
if (value.is<std::string>())
{
return {state, value.as<std::string>()};
}
if (value.is<std::map<std::string, script_value>>())
{
return entity_to_struct(state, value.get_raw().u.uintValue);
}
if (value.is<std::vector<script_value>>())
{
return entity_to_array(state, value.get_raw().u.uintValue);
}
if (value.is<std::function<void()>>())
{
return convert_function(state, value.get_raw().u.codePosValue);
}
if (value.is<entity>())
{
return {state, value.as<entity>()};
}
if (value.is<vector>())
{
return {state, value.as<vector>()};
}
return {state, sol::lua_nil};
}
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "context.hpp"
namespace scripting::lua
{
sol::lua_value entity_to_struct(lua_State* state, unsigned int parent_id);
script_value convert(const sol::lua_value& value);
sol::lua_value convert(lua_State* state, const script_value& value);
}

View File

@ -0,0 +1,72 @@
#include <std_include.hpp>
#include "safe_execution.hpp"
#pragma warning(push)
#pragma warning(disable: 4611)
namespace scripting::safe_execution
{
namespace
{
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
{
__try
{
function(entref);
return true;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
}
bool call(const script_function function, const game::scr_entref_t& entref)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
*game::g_script_error_level -= 1;
return false;
}
const auto result = execute_with_seh(function, entref);
*game::g_script_error_level -= 1;
return result;
}
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
*game::g_script_error_level -= 1;
return false;
}
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
*game::g_script_error_level -= 1;
return true;
}
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
{
*game::g_script_error_level += 1;
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
{
value->type = game::SCRIPT_NONE;
value->u.intValue = 0;
*game::g_script_error_level -= 1;
return false;
}
game::GetEntityFieldValue(value, entref.classnum, entref.entnum, offset);
*game::g_script_error_level -= 1;
return true;
}
}
#pragma warning(pop)

View File

@ -0,0 +1,10 @@
#pragma once
#include "functions.hpp"
namespace scripting::safe_execution
{
bool call(script_function function, const game::scr_entref_t& entref);
bool set_entity_field(const game::scr_entref_t& entref, int offset);
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
}

View File

@ -0,0 +1,278 @@
#include <std_include.hpp>
#include "script_value.hpp"
#include "entity.hpp"
namespace scripting
{
/***************************************************************
* Constructors
**************************************************************/
script_value::script_value(const game::VariableValue& value)
: value_(value)
{
}
script_value::script_value(const int value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_INTEGER;
variable.u.intValue = value;
this->value_ = variable;
}
script_value::script_value(const unsigned int value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_INTEGER;
variable.u.uintValue = value;
this->value_ = variable;
}
script_value::script_value(const bool value)
: script_value(static_cast<unsigned>(value))
{
}
script_value::script_value(const float value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_FLOAT;
variable.u.floatValue = value;
this->value_ = variable;
}
script_value::script_value(const double value)
: script_value(static_cast<float>(value))
{
}
script_value::script_value(const char* value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_STRING;
variable.u.stringValue = game::SL_GetString(value, 0);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(variable.type, variable.u);
});
this->value_ = variable;
}
script_value::script_value(const std::string& value)
: script_value(value.data())
{
}
script_value::script_value(const entity& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_OBJECT;
variable.u.pointerValue = value.get_entity_id();
this->value_ = variable;
}
script_value::script_value(const vector& value)
{
game::VariableValue variable{};
variable.type = game::SCRIPT_VECTOR;
variable.u.vectorValue = game::Scr_AllocVector(value);
const auto _ = gsl::finally([&variable]()
{
game::RemoveRefToValue(variable.type, variable.u);
});
this->value_ = variable;
}
/***************************************************************
* Integer
**************************************************************/
template <>
bool script_value::is<int>() const
{
return this->get_raw().type == game::SCRIPT_INTEGER;
}
template <>
bool script_value::is<unsigned int>() const
{
return this->is<int>();
}
template <>
bool script_value::is<bool>() const
{
return this->is<int>();
}
template <>
int script_value::get() const
{
return this->get_raw().u.intValue;
}
template <>
unsigned int script_value::get() const
{
return this->get_raw().u.uintValue;
}
template <>
bool script_value::get() const
{
return this->get_raw().u.uintValue != 0;
}
/***************************************************************
* Float
**************************************************************/
template <>
bool script_value::is<float>() const
{
return this->get_raw().type == game::SCRIPT_FLOAT;
}
template <>
bool script_value::is<double>() const
{
return this->is<float>();
}
template <>
float script_value::get() const
{
return this->get_raw().u.floatValue;
}
template <>
double script_value::get() const
{
return static_cast<double>(this->get_raw().u.floatValue);
}
/***************************************************************
* String
**************************************************************/
template <>
bool script_value::is<const char*>() const
{
return this->get_raw().type == game::SCRIPT_STRING;
}
template <>
bool script_value::is<std::string>() const
{
return this->is<const char*>();
}
template <>
const char* script_value::get() const
{
return game::SL_ConvertToString(static_cast<game::scr_string_t>(this->get_raw().u.stringValue));
}
template <>
std::string script_value::get() const
{
return this->get<const char*>();
}
/***************************************************************
* Array
**************************************************************/
template <>
bool script_value::is<std::vector<script_value>>() const
{
if (this->get_raw().type != game::SCRIPT_OBJECT)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
return type == game::SCRIPT_ARRAY;
}
/***************************************************************
* Struct
**************************************************************/
template <>
bool script_value::is<std::map<std::string, script_value>>() const
{
if (this->get_raw().type != game::SCRIPT_OBJECT)
{
return false;
}
const auto id = this->get_raw().u.uintValue;
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
return type == game::SCRIPT_STRUCT;
}
/***************************************************************
* Function
**************************************************************/
template <>
bool script_value::is<std::function<void()>>() const
{
return this->get_raw().type == game::SCRIPT_FUNCTION;
}
/***************************************************************
* Entity
**************************************************************/
template <>
bool script_value::is<entity>() const
{
return this->get_raw().type == game::SCRIPT_OBJECT;
}
template <>
entity script_value::get() const
{
return entity(this->get_raw().u.pointerValue);
}
/***************************************************************
* Vector
**************************************************************/
template <>
bool script_value::is<vector>() const
{
return this->get_raw().type == game::SCRIPT_VECTOR;
}
template <>
vector script_value::get() const
{
return this->get_raw().u.vectorValue;
}
/***************************************************************
*
**************************************************************/
const game::VariableValue& script_value::get_raw() const
{
return this->value_.get();
}
}

View File

@ -0,0 +1,52 @@
#pragma once
#include "game/game.hpp"
#include "variable_value.hpp"
#include "vector.hpp"
namespace scripting
{
class entity;
class script_value
{
public:
script_value() = default;
script_value(const game::VariableValue& value);
script_value(int value);
script_value(unsigned int value);
script_value(bool value);
script_value(float value);
script_value(double value);
script_value(const char* value);
script_value(const std::string& value);
script_value(const entity& value);
script_value(const vector& value);
template <typename T>
bool is() const;
template <typename T>
T as() const
{
if (!this->is<T>())
{
throw std::runtime_error("Invalid type");
}
return get<T>();
}
const game::VariableValue& get_raw() const;
private:
template <typename T>
T get() const;
variable_value value_{};
};
}

View File

@ -0,0 +1,27 @@
#include <std_include.hpp>
#include "stack_isolation.hpp"
namespace scripting
{
stack_isolation::stack_isolation()
{
this->in_param_count_ = game::scr_VmPub->inparamcount;
this->out_param_count_ = game::scr_VmPub->outparamcount;
this->top_ = game::scr_VmPub->top;
this->max_stack_ = game::scr_VmPub->maxstack;
game::scr_VmPub->top = this->stack_;
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
game::scr_VmPub->inparamcount = 0;
game::scr_VmPub->outparamcount = 0;
}
stack_isolation::~stack_isolation()
{
game::Scr_ClearOutParams();
game::scr_VmPub->inparamcount = this->in_param_count_;
game::scr_VmPub->outparamcount = this->out_param_count_;
game::scr_VmPub->top = this->top_;
game::scr_VmPub->maxstack = this->max_stack_;
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class stack_isolation final
{
public:
stack_isolation();
~stack_isolation();
stack_isolation(stack_isolation&&) = delete;
stack_isolation(const stack_isolation&) = delete;
stack_isolation& operator=(stack_isolation&&) = delete;
stack_isolation& operator=(const stack_isolation&) = delete;
private:
game::VariableValue stack_[512]{};
game::VariableValue* max_stack_;
game::VariableValue* top_;
unsigned int in_param_count_;
unsigned int out_param_count_;
};
}

View File

@ -0,0 +1,68 @@
#include <std_include.hpp>
#include "variable_value.hpp"
namespace scripting
{
variable_value::variable_value(const game::VariableValue& value)
{
this->assign(value);
}
variable_value::variable_value(const variable_value& other) noexcept
{
this->operator=(other);
}
variable_value::variable_value(variable_value&& other) noexcept
{
this->operator=(std::move(other));
}
variable_value& variable_value::operator=(const variable_value& other) noexcept
{
if (this != &other)
{
this->release();
this->assign(other.value_);
}
return *this;
}
variable_value& variable_value::operator=(variable_value&& other) noexcept
{
if (this != &other)
{
this->release();
this->value_ = other.value_;
other.value_.type = game::SCRIPT_NONE;
}
return *this;
}
variable_value::~variable_value()
{
this->release();
}
const game::VariableValue& variable_value::get() const
{
return this->value_;
}
void variable_value::assign(const game::VariableValue& value)
{
this->value_ = value;
game::AddRefToValue(this->value_.type, this->value_.u);
}
void variable_value::release()
{
if (this->value_.type != game::SCRIPT_NONE)
{
game::RemoveRefToValue(this->value_.type, this->value_.u);
this->value_.type = game::SCRIPT_NONE;
}
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class variable_value
{
public:
variable_value() = default;
variable_value(const game::VariableValue& value);
variable_value(const variable_value& other) noexcept;
variable_value(variable_value&& other) noexcept;
variable_value& operator=(const variable_value& other) noexcept;
variable_value& operator=(variable_value&& other) noexcept;
~variable_value();
const game::VariableValue& get() const;
private:
void assign(const game::VariableValue& value);
void release();
game::VariableValue value_{{0}, game::SCRIPT_NONE};
};
}

View File

@ -0,0 +1,85 @@
#include <std_include.hpp>
#include "vector.hpp"
namespace scripting
{
vector::vector(const float* value)
{
for (auto i = 0; i < 3; ++i)
{
this->value_[i] = value[i];
}
}
vector::vector(const game::vec3_t& value)
: vector(&value[0])
{
}
vector::vector(const float x, const float y, const float z)
{
this->value_[0] = x;
this->value_[1] = y;
this->value_[2] = z;
}
vector::operator game::vec3_t&()
{
return this->value_;
}
vector::operator const game::vec3_t&() const
{
return this->value_;
}
game::vec_t& vector::operator[](const size_t i)
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
const game::vec_t& vector::operator[](const size_t i) const
{
if (i >= 3)
{
throw std::runtime_error("Out of bounds.");
}
return this->value_[i];
}
float vector::get_x() const
{
return this->operator[](0);
}
float vector::get_y() const
{
return this->operator[](1);
}
float vector::get_z() const
{
return this->operator[](2);
}
void vector::set_x(const float value)
{
this->operator[](0) = value;
}
void vector::set_y(const float value)
{
this->operator[](1) = value;
}
void vector::set_z(const float value)
{
this->operator[](2) = value;
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "game/game.hpp"
namespace scripting
{
class vector final
{
public:
vector() = default;
vector(const float* value);
vector(const game::vec3_t& value);
vector(float x, float y, float z);
operator game::vec3_t&();
operator const game::vec3_t&() const;
game::vec_t& operator[](size_t i);
const game::vec_t& operator[](size_t i) const;
float get_x() const;
float get_y() const;
float get_z() const;
void set_x(float value);
void set_y(float value);
void set_z(float value);
private:
game::vec3_t value_{0};
};
}