Working variable fields

This commit is contained in:
momo5502 2019-01-19 19:29:35 +01:00
parent 9d83fc2766
commit 5c2e089404
5 changed files with 332 additions and 90 deletions

View File

@ -47,6 +47,8 @@ namespace game
int* g_script_error_level;
jmp_buf* g_script_error;
scr_classStruct_t* g_classMap;
void AddRefToValue(VariableValue* value)
{
if (value->type == SCRIPT_OBJECT)
@ -69,15 +71,42 @@ namespace game
}
}
__declspec(naked) unsigned int find_variable_dedicated(unsigned int parentId, unsigned int name)
{
static DWORD func = 0x4E7ED0;
__asm
{
mov eax, name
mov ecx, parentId
call func
retn
}
}
unsigned int FindVariable(const unsigned int parentId, const unsigned int name)
{
if (is_dedi())
{
return find_variable_dedicated(parentId, name);
}
else
{
return reinterpret_cast<unsigned int(*)(unsigned int, unsigned int)> //
(SELECT_VALUE(0x4C4E70, 0x5651F0, 0x0))(parentId, name);
}
}
__declspec(naked) VariableValue get_entity_field_value_dedicated(unsigned int classnum, int entnum, int _offset)
{
static DWORD func = 0x4F1400;
__asm
{
push _offset
push entnum
mov ecx, classnum
mov eax, 4F1400h
call eax
call func
add esp, 8h
retn
}
@ -150,6 +179,34 @@ namespace game
}
}
__declspec(naked) int scr_set_object_field_dedicated(unsigned int classnum, int entnum, int _offset)
{
static DWORD func = 0x4B15C0;
__asm
{
mov ecx, _offset
mov eax, entnum
push classnum
call func
add esp, 4h
retn
}
}
int Scr_SetObjectField(const unsigned int classnum, const int entnum, const int offset)
{
if (is_dedi())
{
return scr_set_object_field_dedicated(classnum, entnum, offset);
}
else
{
return reinterpret_cast<int(*)(unsigned int, int, int)> //
(SELECT_VALUE(0x42CAD0, 0x52BCC0, 0x0))(classnum, entnum, offset);
}
}
const char* SL_ConvertToString(const unsigned int stringValue)
{
if (!stringValue) return nullptr;
@ -228,6 +285,8 @@ namespace game
native::g_script_error_level = reinterpret_cast<int*>(SELECT_VALUE(0x1BEFCFC, 0x20B21FC, 0x1F5B058));
native::g_script_error = reinterpret_cast<jmp_buf*>(SELECT_VALUE(0x1BF1D18, 0x20B4218, 0x1F5A818));
native::g_classMap = reinterpret_cast<native::scr_classStruct_t*>(SELECT_VALUE(0x92D140, 0x8B4300, 0x7C0408));
native::levelEntityId = reinterpret_cast<unsigned int*>(SELECT_VALUE(0x1BCBCA4, 0x208E1A4, 0x1CD873C));
}
}

View File

@ -62,8 +62,12 @@ namespace game
extern int* g_script_error_level;
extern jmp_buf* g_script_error;
extern scr_classStruct_t* g_classMap;
void AddRefToValue(VariableValue* value);
unsigned int FindVariable(unsigned int parentId, unsigned int name);
VariableValue GetEntityFieldValue(unsigned int classnum, int entnum, int offset);
void* MT_Alloc(int numBytes, int type);
@ -72,6 +76,7 @@ namespace game
void Scr_ClearOutParams();
scr_entref_t Scr_GetEntityIdRef(unsigned int id);
scr_call_t Scr_GetFunc(unsigned int index);
int Scr_SetObjectField(unsigned int classnum, int entnum, int offset);
const char* SL_ConvertToString(unsigned int stringValue);
unsigned int SL_GetString(const char* str, unsigned int user);

View File

@ -412,7 +412,7 @@ namespace game
scr_entref_raw raw;
};
typedef void(__cdecl * scr_call_t)(int entref);
typedef void (__cdecl * scr_call_t)(int entref);
enum scriptType_e
{
@ -422,7 +422,8 @@ namespace game
SCRIPT_VECTOR = 4,
SCRIPT_FLOAT = 5,
SCRIPT_INTEGER = 6,
SCRIPT_END = 8, // Custom
SCRIPT_END = 8,
// Custom
};
struct VariableStackBuffer
@ -455,11 +456,11 @@ namespace game
struct function_stack_t
{
const char *pos;
const char* pos;
unsigned int localId;
unsigned int localVarCount;
VariableValue *top;
VariableValue *startTop;
VariableValue* top;
VariableValue* startTop;
};
struct function_frame_t
@ -470,11 +471,11 @@ namespace game
struct scrVmPub_t
{
unsigned int *localVars;
VariableValue *maxstack;
unsigned int* localVars;
VariableValue* maxstack;
int function_count;
function_frame_t *function_frame;
VariableValue *top;
function_frame_t* function_frame;
VariableValue* top;
/*bool debugCode;
bool abort_on_error;
bool terminal_error;
@ -486,5 +487,13 @@ namespace game
function_frame_t function_frame_start[32];
VariableValue stack[2048];
};
struct scr_classStruct_t
{
unsigned __int16 id;
unsigned __int16 entArrayId;
char charId;
const char* name;
};
}
}

View File

@ -75,6 +75,16 @@ void scripting::entity::notify(const std::string& event, const std::vector<chais
this->environment_->notify(event, this->get_entity_id(), arguments);
}
void scripting::entity::set(const std::string& field, const chaiscript::Boxed_Value& value)
{
this->environment_->set_entity_field(field, this->get_entity_id(), value);
}
chaiscript::Boxed_Value scripting::entity::get(const std::string& field)
{
return this->environment_->get_entity_field(field, this->get_entity_id());
}
scripting::variable::variable(game::native::VariableValue value) : value_(value)
{
game::native::AddRefToValue(&value);
@ -90,6 +100,28 @@ scripting::variable::operator game::native::VariableValue() const
return this->value_;
}
scripting::stack_context::stack_context()
{
this->inparamcount_ = game::native::scr_VmPub->inparamcount;
this->outparamcount_ = game::native::scr_VmPub->outparamcount;
this->top_ = game::native::scr_VmPub->top;
this->maxstack_ = game::native::scr_VmPub->maxstack;
game::native::scr_VmPub->top = this->stack_;
game::native::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
game::native::scr_VmPub->inparamcount = 0;
game::native::scr_VmPub->outparamcount = 0;
}
scripting::stack_context::~stack_context()
{
game::native::Scr_ClearOutParams();
game::native::scr_VmPub->inparamcount = this->inparamcount_;
game::native::scr_VmPub->outparamcount = this->outparamcount_;
game::native::scr_VmPub->top = this->top_;
game::native::scr_VmPub->maxstack = this->maxstack_;
}
void scripting::post_start()
{
on_start([this]()
@ -157,6 +189,8 @@ void scripting::initialize()
this->load_scripts();
notification::listen([this](notification::event* event)
{
try
{
std::vector<chaiscript::Boxed_Value> arguments;
@ -166,8 +200,6 @@ void scripting::initialize()
}
for (auto listener = this->event_listeners_.begin(); listener.is_valid(); ++listener)
{
try
{
if (listener->event == event->name && listener->entity_id == event->entity_id)
{
@ -179,11 +211,24 @@ void scripting::initialize()
listener->callback(arguments);
}
}
for (auto listener = this->generic_event_listeners_.begin(); listener.is_valid(); ++listener)
{
if (listener->event == event->name)
{
if (listener->is_volatile)
{
this->generic_event_listeners_.remove(listener);
}
listener->callback(entity(this, event->entity_id), arguments);
}
}
}
catch (chaiscript::exception::eval_error& e)
{
throw std::runtime_error(e.pretty_print());
}
}
});
}
@ -193,6 +238,9 @@ void scripting::initialize_entity()
this->chai_->add(chaiscript::constructor<entity()>(), "entity");
this->chai_->add(chaiscript::constructor<entity(const entity&)>(), "entity");
this->chai_->add(chaiscript::fun(&entity::get), "get");
this->chai_->add(chaiscript::fun(&entity::set), "set");
this->chai_->add(chaiscript::fun(&entity::on_notify), "onNotify");
this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event,
const std::function<void(const std::vector<chaiscript::Boxed_Value>&)>&
@ -206,6 +254,32 @@ void scripting::initialize_entity()
return lhs = rhs;
}), "=");
this->chai_->add(chaiscript::fun([this](const std::string& event,
const std::function<void(const entity&,
const std::vector<chaiscript::Boxed_Value>&)>&
callback)
{
generic_event_listener listener;
listener.event = event;
listener.is_volatile = false;
listener.callback = callback;
this->generic_event_listeners_.add(listener);
}), "onNotify");
this->chai_->add(chaiscript::fun([this](const std::string& event,
const std::function<void(const entity&,
const std::vector<chaiscript::Boxed_Value>&)>&
callback, const bool is_volatile)
{
generic_event_listener listener;
listener.event = event;
listener.is_volatile = is_volatile;
listener.callback = callback;
this->generic_event_listeners_.add(listener);
}), "onNotify");
// Notification
this->chai_->add(chaiscript::fun(&entity::notify), "vectorNotify");
this->chai_->add(chaiscript::fun([](const entity& ent, const std::string& event)
@ -469,45 +543,92 @@ void scripting::stop_execution()
}
}
int scripting::get_field_id(const int classnum, const std::string& field) const
int scripting::get_field_id(const int classnum, const std::string& field)
{
switch (classnum)
const auto field_name = utils::string::to_lower(field);
const auto class_id = game::native::g_classMap[classnum].id;
const auto field_str = game::native::SL_GetString(field_name.data(), 1);
const auto _ = gsl::finally([field_str]()
{
case 0: // Entity
case 1: // HudElem
case 2: // Pathnode
case 3: // VehPathNode
case 4: // VehTrackSegment
case 6: // PIPElem
game::native::VariableUnion u{};
u.stringValue = field_str;
game::native::RemoveRefToValue(game::native::SCRIPT_STRING, u);
});
default:
return -1;
const auto offset = game::native::FindVariable(class_id, field_str);
if (offset)
{
const auto index = 4 * (offset + 0xC800 * (class_id & 1));
return PINT(SELECT_VALUE(0x1A3BC80, 0x1EFE180, 0x1DC8800))[index];
}
return -1;
}
void scripting::set_entity_field(const std::string& field, const unsigned int entity_id,
const chaiscript::Boxed_Value& value)
{
const auto entref = game::native::Scr_GetEntityIdRef(entity_id);
const int id = get_field_id(entref.raw.classnum, field);
if (id != -1)
{
stack_context _;
this->push_param(value);
game::native::scr_VmPub->outparamcount = game::native::scr_VmPub->inparamcount;
game::native::scr_VmPub->inparamcount = 0;
if (!set_entity_field_safe(entref, id))
{
throw std::runtime_error("Failed to set value for field '" + field + "'");
}
}
else
{
this->entity_fields_[entity_id][field] = value;
}
}
chaiscript::Boxed_Value scripting::get_entity_field(const std::string& field, const unsigned int entity_id)
{
const auto entref = game::native::Scr_GetEntityIdRef(entity_id);
const auto id = this->get_field_id(entref.raw.classnum, field);
if (id != -1)
{
stack_context _;
game::native::VariableValue value{};
if (!get_entity_field_safe(entref, id, &value))
{
throw std::runtime_error("Failed to get value for field '" + field + "'");
}
const auto $ = gsl::finally([value]()
{
game::native::RemoveRefToValue(value.type, value.u);
});
return this->make_boxed(value);
}
else
{
const auto& map = this->entity_fields_[entity_id];
const auto value = map.find(field);
if (value != map.end())
{
return value->second;
}
}
return {};
}
void scripting::notify(const std::string& event, const unsigned int entity_id,
std::vector<chaiscript::Boxed_Value> arguments)
{
const auto old_args = game::native::scr_VmPub->inparamcount;
const auto old_params = game::native::scr_VmPub->outparamcount;
const auto old_stack_top = game::native::scr_VmPub->top;
const auto old_stack_end = game::native::scr_VmPub->maxstack;
game::native::VariableValue stack[512];
game::native::scr_VmPub->top = stack;
game::native::scr_VmPub->maxstack = &stack[ARRAYSIZE(stack) - 1];
game::native::scr_VmPub->inparamcount = 0;
game::native::scr_VmPub->outparamcount = 0;
const auto cleanup = gsl::finally([=]()
{
game::native::Scr_ClearOutParams();
game::native::scr_VmPub->inparamcount = old_args;
game::native::scr_VmPub->outparamcount = old_params;
game::native::scr_VmPub->top = old_stack_top;
game::native::scr_VmPub->maxstack = old_stack_end;
});
stack_context _;
std::reverse(arguments.begin(), arguments.end());
for (const auto& argument : arguments)
@ -534,25 +655,7 @@ chaiscript::Boxed_Value scripting::call(const std::string& function, const unsig
const auto function_ptr = game::native::Scr_GetFunc(function_index);
const auto old_args = game::native::scr_VmPub->inparamcount;
const auto old_params = game::native::scr_VmPub->outparamcount;
const auto old_stack_top = game::native::scr_VmPub->top;
const auto old_stack_end = game::native::scr_VmPub->maxstack;
game::native::VariableValue stack[512];
game::native::scr_VmPub->top = stack;
game::native::scr_VmPub->maxstack = &stack[ARRAYSIZE(stack) - 1];
game::native::scr_VmPub->inparamcount = 0;
game::native::scr_VmPub->outparamcount = 0;
const auto cleanup = gsl::finally([=]()
{
game::native::Scr_ClearOutParams();
game::native::scr_VmPub->inparamcount = old_args;
game::native::scr_VmPub->outparamcount = old_params;
game::native::scr_VmPub->top = old_stack_top;
game::native::scr_VmPub->maxstack = old_stack_end;
});
stack_context _;
std::reverse(arguments.begin(), arguments.end());
for (const auto& argument : arguments)
@ -589,6 +692,38 @@ bool scripting::call_safe(const game::native::scr_call_t function, const game::n
*game::native::g_script_error_level -= 1;
return true;
}
bool scripting::set_entity_field_safe(game::native::scr_entref_t entref, int offset)
{
*game::native::g_script_error_level += 1;
if (setjmp(game::native::g_script_error[*game::native::g_script_error_level]))
{
*game::native::g_script_error_level -= 1;
return false;
}
game::native::Scr_SetObjectField(entref.raw.classnum, entref.raw.entnum, offset);
*game::native::g_script_error_level -= 1;
return true;
}
bool scripting::get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value)
{
*game::native::g_script_error_level += 1;
if (setjmp(game::native::g_script_error[*game::native::g_script_error_level]))
{
value->type = game::native::SCRIPT_NONE;
value->u.intValue = 0;
*game::native::g_script_error_level -= 1;
return false;
}
*value = game::native::GetEntityFieldValue(entref.raw.classnum, entref.raw.entnum, offset);
*game::native::g_script_error_level -= 1;
return true;
}
#pragma warning(pop)
int scripting::find_function_index(const std::string& function, const bool prefer_global)
@ -682,7 +817,7 @@ void scripting::push_param(const chaiscript::Boxed_Value& value) const
throw std::runtime_error("Invalid vector length. Size must be exactly 3");
}
const auto unbox_float = [&real_value, this](size_t index) -> float
const auto unbox_float = [&real_value, this](const size_t index) -> float
{
const auto value = real_value[index];
if (value.get_type_info() == typeid(float))

View File

@ -25,6 +25,9 @@ public:
chaiscript::Boxed_Value call(const std::string& function, const std::vector<chaiscript::Boxed_Value>& arguments) const;
void notify(const std::string& event, const std::vector<chaiscript::Boxed_Value>& arguments) const;
void set(const std::string& field, const chaiscript::Boxed_Value& value);
chaiscript::Boxed_Value get(const std::string& field);
private:
scripting* environment_;
unsigned int entity_id_;
@ -51,6 +54,14 @@ public:
bool is_volatile = false;
};
class generic_event_listener final
{
public:
std::string event = {};
std::function<void(const entity&, const std::vector<chaiscript::Boxed_Value>&)> callback = {};
bool is_volatile = false;
};
void post_start() override;
void post_load() override;
void pre_destroy() override;
@ -61,8 +72,26 @@ public:
static void propagate_error(const std::exception& e);
private:
class stack_context final
{
public:
stack_context();
~stack_context();
private:
game::native::VariableValue stack_[512]{};
game::native::VariableValue *maxstack_;
game::native::VariableValue *top_;
unsigned int inparamcount_;
unsigned int outparamcount_;
};
std::unique_ptr<chaiscript::ChaiScript> chai_;
utils::chain<event_listener> event_listeners_;
utils::chain<generic_event_listener> generic_event_listeners_;
std::unordered_map<unsigned int, std::unordered_map<std::string, chaiscript::Boxed_Value>> entity_fields_;
void add_event_listener(const event_listener& listener);
@ -82,7 +111,12 @@ private:
static void start_execution();
static void stop_execution();
int get_field_id(int classnum, const std::string& field) const;
int get_field_id(int classnum, const std::string& field);
void set_entity_field(const std::string& field, unsigned int entity_id, const chaiscript::Boxed_Value& value);
chaiscript::Boxed_Value get_entity_field(const std::string& field, unsigned int entity_id);
static bool set_entity_field_safe(game::native::scr_entref_t entref, int offset);
static bool get_entity_field_safe(game::native::scr_entref_t entref, int offset, game::native::VariableValue* value);
void notify(const std::string& event, unsigned int entity_id, std::vector<chaiscript::Boxed_Value> arguments);