13#include "../error_func.h"
14#include "../fileio_func.h"
15#include "../string_func.h"
17#include "../settings_type.h"
19#include <../squirrel/sqpcheader.h>
20#include <../squirrel/sqvm.h>
22#include "../safeguards.h"
32 std::allocator<uint8_t> allocator;
45#ifdef SCRIPT_DEBUG_ALLOCATIONS
46 std::map<void *, size_t> allocations;
57 if (this->error_thrown)
return;
62 this->error_thrown =
true;
63 std::string msg = fmt::format(
"Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
64 this->allocated_size + requested_size - this->allocation_limit, requested_size);
74 void *
DoAlloc(SQUnsignedInteger requested_size)
77 void *p = this->allocator.allocate(requested_size);
79 this->allocated_size += requested_size;
81#ifdef SCRIPT_DEBUG_ALLOCATIONS
82 assert(this->allocations.find(p) == this->allocations.end());
83 this->allocations[p] = requested_size;
86 }
catch (
const std::bad_alloc &) {
89 if (this->error_thrown) {
93 FatalError(
"Out of memory. Cannot allocate {} bytes", requested_size);
96 this->error_thrown =
true;
97 std::string msg = fmt::format(
"Out of memory. Cannot allocate {} bytes", requested_size);
105 void CheckLimit()
const
107 if (this->allocated_size > this->allocation_limit)
throw Script_FatalError(
"Maximum memory allocation exceeded");
112 assert(this->allocated_size == 0);
113 this->error_thrown =
false;
116 void *Malloc(SQUnsignedInteger size)
122 void *Realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
125 return this->Malloc(size);
128 this->Free(p, oldsize);
134 void *new_p = this->
DoAlloc(size);
135 memcpy(new_p, p, std::min(oldsize, size));
136 this->Free(p, oldsize);
141 void Free(
void *p, SQUnsignedInteger size)
143 if (p ==
nullptr)
return;
144 this->allocator.deallocate(
reinterpret_cast<uint8_t*
>(p), size);
145 this->allocated_size -= size;
147#ifdef SCRIPT_DEBUG_ALLOCATIONS
148 assert(this->allocations.at(p) == size);
149 this->allocations.erase(p);
156 if (this->allocation_limit == 0) this->allocation_limit =
SAFE_LIMIT;
161#ifdef SCRIPT_DEBUG_ALLOCATIONS
162 assert(this->allocations.empty());
169void *sq_vm_malloc(SQUnsignedInteger size) {
return _squirrel_allocator->Malloc(size); }
170void *sq_vm_realloc(
void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) {
return _squirrel_allocator->Realloc(p, oldsize, size); }
171void sq_vm_free(
void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
175 assert(this->allocator !=
nullptr);
176 return this->allocator->GetAllocatedSize();
182 std::string msg = fmt::format(
"Error {}:{}/{}: {}", source, line, column, desc);
188 if (func ==
nullptr) {
189 Debug(misc, 0,
"[Squirrel] Compile error: {}", msg);
199 if (func ==
nullptr) {
200 fmt::print(stderr,
"{}", s);
209 SQPRINTFUNCTION pf = sq_getprintfunc(
vm);
213 std::string msg = fmt::format(
"Your script made an error: {}\n", error);
216 if (func ==
nullptr) {
217 fmt::print(stderr,
"{}", msg);
223 sqstd_printcallstack(
vm);
225 sq_setprintfunc(
vm, pf);
230 const SQChar *sErr =
nullptr;
232 if (sq_gettop(
vm) >= 1) {
233 if (SQ_SUCCEEDED(sq_getstring(
vm, -1, &sErr))) {
247 if (func ==
nullptr) {
254void Squirrel::AddMethod(
const char *method_name, SQFUNCTION proc, uint nparam,
const char *params,
void *userdata,
int size)
258 sq_pushstring(this->
vm, method_name, -1);
261 void *ptr = sq_newuserdata(
vm, size);
262 memcpy(ptr, userdata, size);
265 sq_newclosure(this->
vm, proc, size != 0 ? 1 : 0);
266 if (nparam != 0) sq_setparamscheck(this->
vm, nparam, params);
267 sq_setnativeclosurename(this->
vm, -1, method_name);
268 sq_newslot(this->
vm, -3, SQFalse);
275 sq_pushstring(this->
vm, var_name, -1);
276 sq_pushinteger(this->
vm, value);
277 sq_newslot(this->
vm, -3, SQTrue);
284 sq_pushstring(this->
vm, var_name, -1);
285 sq_pushbool(this->
vm, value);
286 sq_newslot(this->
vm, -3, SQTrue);
293 sq_pushroottable(this->
vm);
294 sq_pushstring(this->
vm, class_name, -1);
295 sq_newclass(this->
vm, SQFalse);
302 sq_pushroottable(this->
vm);
303 sq_pushstring(this->
vm, class_name, -1);
304 sq_pushstring(this->
vm, parent_class, -1);
305 if (SQ_FAILED(sq_get(this->
vm, -3))) {
306 Debug(misc, 0,
"[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
307 Debug(misc, 0,
"[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
310 sq_newclass(this->
vm, SQTrue);
317 sq_newslot(
vm, -3, SQFalse);
326 int top = sq_gettop(this->
vm);
328 sq_pushobject(this->
vm, instance);
330 sq_pushstring(this->
vm, method_name, -1);
331 if (SQ_FAILED(sq_get(this->
vm, -2))) {
332 sq_settop(this->
vm, top);
335 sq_settop(this->
vm, top);
355 this->
crashed = !sq_resumecatch(this->
vm, suspend);
357 this->allocator->CheckLimit();
358 return this->
vm->_suspended != 0;
365 sq_resumeerror(this->
vm);
371 sq_collectgarbage(this->
vm);
378 this->allocator->CheckLimit();
383 SQInteger last_target = this->
vm->_suspended_target;
385 int top = sq_gettop(this->
vm);
387 sq_pushobject(this->
vm, instance);
389 sq_pushstring(this->
vm, method_name, -1);
390 if (SQ_FAILED(sq_get(this->
vm, -2))) {
391 Debug(misc, 0,
"[squirrel] Could not find '{}' in the class", method_name);
392 sq_settop(this->
vm, top);
396 sq_pushobject(this->
vm, instance);
397 if (SQ_FAILED(sq_call(this->
vm, 1, ret ==
nullptr ? SQFalse : SQTrue, SQTrue, suspend)))
return false;
398 if (ret !=
nullptr) sq_getstackobj(
vm, -1, ret);
401 if (suspend == -1 || !this->
IsSuspended()) sq_settop(this->
vm, top);
403 this->
vm->_suspended_target = last_target;
408bool Squirrel::CallStringMethod(HSQOBJECT instance,
const char *method_name, std::string *res,
int suspend)
411 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
412 if (ret._type != OT_STRING)
return false;
417bool Squirrel::CallIntegerMethod(HSQOBJECT instance,
const char *method_name,
int *res,
int suspend)
420 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
421 if (ret._type != OT_INTEGER)
return false;
426bool Squirrel::CallBoolMethod(HSQOBJECT instance,
const char *method_name,
bool *res,
int suspend)
429 if (!this->
CallMethod(instance, method_name, &ret, suspend))
return false;
430 if (ret._type != OT_BOOL)
return false;
435 bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm,
const std::string &class_name,
void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook,
bool prepend_API_name)
439 int oldtop = sq_gettop(
vm);
442 sq_pushroottable(
vm);
444 if (prepend_API_name) {
445 std::string prepended_class_name = engine->
GetAPIName();
446 prepended_class_name += class_name;
447 sq_pushstring(
vm, prepended_class_name, -1);
449 sq_pushstring(
vm, class_name, -1);
452 if (SQ_FAILED(sq_get(
vm, -2))) {
453 Debug(misc, 0,
"[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
454 sq_settop(
vm, oldtop);
459 if (SQ_FAILED(sq_createinstance(
vm, -1))) {
460 Debug(misc, 0,
"[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->
GetAPIName() :
"", class_name);
461 sq_settop(
vm, oldtop);
465 if (instance !=
nullptr) {
467 sq_getstackobj(
vm, -1, instance);
469 sq_addref(
vm, instance);
475 sq_setinstanceup(
vm, -1, real_instance);
476 if (release_hook !=
nullptr) sq_setreleasehook(
vm, -1, release_hook);
478 if (instance !=
nullptr) sq_settop(
vm, oldtop);
491 if (index < 0) index += sq_gettop(
vm) + 1;
493 std::string class_name = fmt::format(
"{}{}", engine->
GetAPIName(), tag);
494 sq_pushroottable(
vm);
495 sq_pushstring(
vm, class_name);
498 if (sq_instanceof(
vm) == SQTrue) {
500 SQUserPointer ptr =
nullptr;
501 if (SQ_SUCCEEDED(sq_getinstanceup(
vm, index, &ptr,
nullptr)))
return ptr;
503 throw sq_throwerror(
vm, fmt::format(
"parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
506Squirrel::Squirrel(
const char *APIName) :
520 this->
vm = sq_open(1024);
524 sq_notifyallexceptions(this->
vm, _debug_script_level > 5);
529 sq_seterrorhandler(this->
vm);
532 sq_setforeignptr(this->
vm,
this);
534 sq_pushroottable(this->
vm);
538 sq_pushconsttable(this->
vm);
539 sq_setdelegate(this->
vm, -2);
549 SQFile(
FileHandle file,
size_t size) : file(std::move(file)), size(size), pos(0) {}
551 size_t Read(
void *buf,
size_t elemsize,
size_t count)
553 assert(elemsize != 0);
554 if (this->pos + (elemsize * count) > this->size) {
555 count = (this->size - this->pos) / elemsize;
557 if (count == 0)
return 0;
558 size_t ret = fread(buf, elemsize, count, this->file);
559 this->pos += ret * elemsize;
564static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
567 if (((
SQFile *)file)->Read(&c,
sizeof(c), 1) > 0)
return c;
571static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
576 if (((
SQFile *)file)->Read(buffer,
sizeof(buffer[0]), 1) != 1)
return 0;
578 if (len == 0)
return -1;
581 if (len > 1 && ((
SQFile *)file)->Read(buffer + 1,
sizeof(buffer[0]), len - 1) != len - 1)
return 0;
590static char32_t _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
593 if (((
SQFile *)file)->Read(&c,
sizeof(c), 1) > 0)
return (
char32_t)c;
597static char32_t _io_file_lexfeed_UCS2_swap(SQUserPointer file)
600 if (((
SQFile *)file)->Read(&c,
sizeof(c), 1) > 0) {
601 c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00);
607static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
609 SQInteger ret = ((
SQFile *)file)->Read(buf, 1, size);
610 if (ret == 0)
return -1;
618 std::optional<FileHandle> file = std::nullopt;
620 if (strncmp(this->
GetAPIName(),
"AI", 2) == 0) {
623 }
else if (strncmp(this->
GetAPIName(),
"GS", 2) == 0) {
630 if (!file.has_value()) {
631 return sq_throwerror(
vm,
"cannot open the file");
633 unsigned short bom = 0;
635 if (fread(&bom, 1,
sizeof(bom), *file) !=
sizeof(bom))
return sq_throwerror(
vm,
"cannot read the file");;
640 case SQ_BYTECODE_STREAM_TAG: {
641 if (fseek(*file, -2, SEEK_CUR) < 0) {
642 return sq_throwerror(
vm,
"cannot seek the file");
645 SQFile f(std::move(*file), size);
646 if (SQ_SUCCEEDED(sq_readclosure(
vm, _io_file_read, &f))) {
649 return sq_throwerror(
vm,
"Couldn't read bytecode");
655 func = _io_file_lexfeed_UCS2_swap;
659 func = _io_file_lexfeed_UCS2_no_swap;
666 return sq_throwerror(
vm,
"I/O error");
669 if (fread(&uc, 1,
sizeof(uc), *file) !=
sizeof(uc) || uc != 0xBF) {
670 return sq_throwerror(
vm,
"Unrecognized encoding");
672 func = _io_file_lexfeed_UTF8;
677 func = _io_file_lexfeed_ASCII;
679 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
680 return sq_throwerror(
vm,
"cannot seek the file");
685 SQFile f(std::move(*file), size);
686 if (SQ_SUCCEEDED(sq_compile(
vm, func, &f, filename.c_str(), printerror))) {
697 if (in_root) sq_pushroottable(
vm);
699 SQInteger ops_left =
vm->_ops_till_suspend;
701 if (SQ_SUCCEEDED(
LoadFile(
vm, script, SQTrue))) {
703 if (SQ_SUCCEEDED(sq_call(
vm, 1, SQFalse, SQTrue, 100000))) {
706 vm->_ops_till_suspend = ops_left;
711 vm->_ops_till_suspend = ops_left;
712 Debug(misc, 0,
"[squirrel] Failed to compile '{}'", script);
731 sq_pushroottable(this->vm);
732 sq_pushnull(this->vm);
733 sq_setdelegate(this->vm, -2);
741 this->allocator->Reset();
750void Squirrel::InsertResult(
bool result)
754 sq_pushbool(this->vm, result);
756 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
761void Squirrel::InsertResult(
int result)
765 sq_pushinteger(this->vm, result);
767 vm->GetAt(
vm->_stackbase +
vm->_suspended_target) =
vm->GetUp(-1);
774 vm->DecreaseOps(ops);
779 return this->vm->_suspended != 0;
795 return sq_can_suspend(this->vm);
800 return this->vm->_ops_till_suspend;
A throw-class that is given when the script made a fatal error.
void AddClassEnd()
Finishes adding a class to the global scope.
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
void * global_pointer
Can be set by who ever initializes Squirrel.
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
void CollectGarbage()
Tell the VM to do a garbage collection run.
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
void Reset()
Completely reset the engine; start from scratch.
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
bool LoadScript(const std::string &script)
Load a script.
bool IsSuspended()
Did the squirrel code suspend or return normally.
void AddMethod(const char *method_name, SQFUNCTION proc, uint nparam=0, const char *params=nullptr, void *userdata=nullptr, int size=0)
Adds a function to the stack.
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
static void PrintFunc(HSQUIRRELVM vm, const std::string &s)
If a user runs 'print' inside a script, this function gets the params.
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
void CrashOccurred()
Set the script status to crashed.
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
void Initialize()
Perform all initialization steps to create the engine.
bool crashed
True if the squirrel script made an error.
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, const char *tag)
Get the real-instance pointer.
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
void AddClassBegin(const char *class_name)
Adds a class to the global scope.
int overdrawn_ops
The amount of operations we have overdrawn.
void Uninitialize()
Perform all the cleanups for the engine.
const char * GetAPIName()
Get the API name.
void ResumeError()
Resume the VM with an error so it prints a stack trace.
void AddConst(const char *var_name, int value)
Adds a const to the stack.
static bool CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name=false)
Creates a class instance.
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
@ AI_DIR
Subdirectory for all AI files.
@ GAME_DIR
Subdirectory for all game scripts.
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
void squirrel_register_global_std(Squirrel *engine)
Register all standard functions that are available on first startup.
defines the Squirrel Standard Function class
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
int8_t Utf8EncodedCharLen(char c)
Return the length of an UTF-8 encoded value based on a single char.
ScriptSettings script
settings for scripts
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
bool error_thrown
Whether the error has already been thrown, so to not throw secondary errors in the handling of the al...
size_t allocation_limit
Maximum this allocator may use before allocations fail.
void * DoAlloc(SQUnsignedInteger requested_size)
Internal helper to allocate the given amount of bytes.
void CheckAllocationAllowed(size_t requested_size)
Checks whether an allocation is allowed by the memory limit set for the script.
size_t allocated_size
Sum of allocated data size.
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated