OpenTTD Source 20250312-master-gcdcc6b491d
squirrel.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include "../debug.h"
12#include "squirrel_std.hpp"
13#include "../error_func.h"
14#include "../fileio_func.h"
15#include "../string_func.h"
16#include "script_fatalerror.hpp"
17#include "../settings_type.h"
18#include <sqstdaux.h>
19#include <../squirrel/sqpcheader.h>
20#include <../squirrel/sqvm.h>
21
22#include "../safeguards.h"
23
24/*
25 * If changing the call paths into the scripting engine, define this symbol to enable full debugging of allocations.
26 * This lets you track whether the allocator context is being switched correctly in all call paths.
27#define SCRIPT_DEBUG_ALLOCATIONS
28 */
29
31private:
32 std::allocator<uint8_t> allocator;
33 size_t allocated_size = 0;
41 bool error_thrown = false;
42
43 static const size_t SAFE_LIMIT = 0x8000000;
44
45#ifdef SCRIPT_DEBUG_ALLOCATIONS
46 std::map<void *, size_t> allocations;
47#endif
48
54 void CheckAllocationAllowed(size_t requested_size)
55 {
56 /* When an error has been thrown, we are allocating just a bit of memory for the stack trace. */
57 if (this->error_thrown) return;
58
59 if (this->allocated_size + requested_size <= this->allocation_limit) return;
60
61 /* Do not allow allocating more than the allocation limit. */
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);
65 throw Script_FatalError(msg);
66 }
67
74 void *DoAlloc(SQUnsignedInteger requested_size)
75 {
76 try {
77 void *p = this->allocator.allocate(requested_size);
78 assert(p != nullptr);
79 this->allocated_size += requested_size;
80
81#ifdef SCRIPT_DEBUG_ALLOCATIONS
82 assert(this->allocations.find(p) == this->allocations.end());
83 this->allocations[p] = requested_size;
84#endif
85 return p;
86 } catch (const std::bad_alloc &) {
87 /* The OS did not have enough memory to allocate the object, regardless of the
88 * limit imposed by OpenTTD on the amount of memory that may be allocated. */
89 if (this->error_thrown) {
90 /* The allocation is called in the error handling of a memory allocation
91 * failure, then not being able to allocate that small amount of memory
92 * means there is no other choice than to bug out completely. */
93 FatalError("Out of memory. Cannot allocate {} bytes", requested_size);
94 }
95
96 this->error_thrown = true;
97 std::string msg = fmt::format("Out of memory. Cannot allocate {} bytes", requested_size);
98 throw Script_FatalError(msg);
99 }
100 }
101
102public:
103 size_t GetAllocatedSize() const { return this->allocated_size; }
104
105 void CheckLimit() const
106 {
107 if (this->allocated_size > this->allocation_limit) throw Script_FatalError("Maximum memory allocation exceeded");
108 }
109
110 void Reset()
111 {
112 assert(this->allocated_size == 0);
113 this->error_thrown = false;
114 }
115
116 void *Malloc(SQUnsignedInteger size)
117 {
118 this->CheckAllocationAllowed(size);
119 return this->DoAlloc(size);
120 }
121
122 void *Realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
123 {
124 if (p == nullptr) {
125 return this->Malloc(size);
126 }
127 if (size == 0) {
128 this->Free(p, oldsize);
129 return nullptr;
130 }
131
132 this->CheckAllocationAllowed(size - oldsize);
133
134 void *new_p = this->DoAlloc(size);
135 memcpy(new_p, p, std::min(oldsize, size));
136 this->Free(p, oldsize);
137
138 return new_p;
139 }
140
141 void Free(void *p, SQUnsignedInteger size)
142 {
143 if (p == nullptr) return;
144 this->allocator.deallocate(reinterpret_cast<uint8_t*>(p), size);
145 this->allocated_size -= size;
146
147#ifdef SCRIPT_DEBUG_ALLOCATIONS
148 assert(this->allocations.at(p) == size);
149 this->allocations.erase(p);
150#endif
151 }
152
154 {
155 this->allocation_limit = static_cast<size_t>(_settings_game.script.script_max_memory_megabytes) << 20;
156 if (this->allocation_limit == 0) this->allocation_limit = SAFE_LIMIT; // in case the setting is somehow zero
157 }
158
160 {
161#ifdef SCRIPT_DEBUG_ALLOCATIONS
162 assert(this->allocations.empty());
163#endif
164 }
165};
166
167ScriptAllocator *_squirrel_allocator = nullptr;
168
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); }
172
173size_t Squirrel::GetAllocatedMemory() const noexcept
174{
175 assert(this->allocator != nullptr);
176 return this->allocator->GetAllocatedSize();
177}
178
179
180void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
181{
182 std::string msg = fmt::format("Error {}:{}/{}: {}", source, line, column, desc);
183
184 /* Check if we have a custom print function */
185 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
186 engine->crashed = true;
187 SQPrintFunc *func = engine->print_func;
188 if (func == nullptr) {
189 Debug(misc, 0, "[Squirrel] Compile error: {}", msg);
190 } else {
191 (*func)(true, msg);
192 }
193}
194
195void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
196{
197 /* Check if we have a custom print function */
198 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
199 if (func == nullptr) {
200 fmt::print(stderr, "{}", s);
201 } else {
202 (*func)(true, s);
203 }
204}
205
206void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
207{
208 /* Set the print function to something that prints to stderr */
209 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
210 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
211
212 /* Check if we have a custom print function */
213 std::string msg = fmt::format("Your script made an error: {}\n", error);
214 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
215 SQPrintFunc *func = engine->print_func;
216 if (func == nullptr) {
217 fmt::print(stderr, "{}", msg);
218 } else {
219 (*func)(true, msg);
220 }
221
222 /* Print below the error the stack, so the users knows what is happening */
223 sqstd_printcallstack(vm);
224 /* Reset the old print function */
225 sq_setprintfunc(vm, pf);
226}
227
228SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
229{
230 const SQChar *sErr = nullptr;
231
232 if (sq_gettop(vm) >= 1) {
233 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
234 Squirrel::RunError(vm, sErr);
235 return 0;
236 }
237 }
238
239 Squirrel::RunError(vm, "unknown error");
240 return 0;
241}
242
243void Squirrel::PrintFunc(HSQUIRRELVM vm, const std::string &s)
244{
245 /* Check if we have a custom print function */
246 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
247 if (func == nullptr) {
248 fmt::print("{}", s);
249 } else {
250 (*func)(false, s);
251 }
252}
253
254void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
255{
256 ScriptAllocatorScope alloc_scope(this);
257
258 sq_pushstring(this->vm, method_name, -1);
259
260 if (size != 0) {
261 void *ptr = sq_newuserdata(vm, size);
262 memcpy(ptr, userdata, size);
263 }
264
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);
269}
270
271void Squirrel::AddConst(const char *var_name, int value)
272{
273 ScriptAllocatorScope alloc_scope(this);
274
275 sq_pushstring(this->vm, var_name, -1);
276 sq_pushinteger(this->vm, value);
277 sq_newslot(this->vm, -3, SQTrue);
278}
279
280void Squirrel::AddConst(const char *var_name, bool value)
281{
282 ScriptAllocatorScope alloc_scope(this);
283
284 sq_pushstring(this->vm, var_name, -1);
285 sq_pushbool(this->vm, value);
286 sq_newslot(this->vm, -3, SQTrue);
287}
288
289void Squirrel::AddClassBegin(const char *class_name)
290{
291 ScriptAllocatorScope alloc_scope(this);
292
293 sq_pushroottable(this->vm);
294 sq_pushstring(this->vm, class_name, -1);
295 sq_newclass(this->vm, SQFalse);
296}
297
298void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
299{
300 ScriptAllocatorScope alloc_scope(this);
301
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);
308 return;
309 }
310 sq_newclass(this->vm, SQTrue);
311}
312
314{
315 ScriptAllocatorScope alloc_scope(this);
316
317 sq_newslot(vm, -3, SQFalse);
318 sq_pop(vm, 1);
319}
320
321bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
322{
323 assert(!this->crashed);
324 ScriptAllocatorScope alloc_scope(this);
325
326 int top = sq_gettop(this->vm);
327 /* Go to the instance-root */
328 sq_pushobject(this->vm, instance);
329 /* Find the function-name inside the script */
330 sq_pushstring(this->vm, method_name, -1);
331 if (SQ_FAILED(sq_get(this->vm, -2))) {
332 sq_settop(this->vm, top);
333 return false;
334 }
335 sq_settop(this->vm, top);
336 return true;
337}
338
339bool Squirrel::Resume(int suspend)
340{
341 assert(!this->crashed);
342 ScriptAllocatorScope alloc_scope(this);
343
344 /* Did we use more operations than we should have in the
345 * previous tick? If so, subtract that from the current run. */
346 if (this->overdrawn_ops > 0 && suspend > 0) {
347 this->overdrawn_ops -= suspend;
348 /* Do we need to wait even more? */
349 if (this->overdrawn_ops >= 0) return true;
350
351 /* We can now only run whatever is "left". */
352 suspend = -this->overdrawn_ops;
353 }
354
355 this->crashed = !sq_resumecatch(this->vm, suspend);
356 this->overdrawn_ops = -this->vm->_ops_till_suspend;
357 this->allocator->CheckLimit();
358 return this->vm->_suspended != 0;
359}
360
362{
363 assert(!this->crashed);
364 ScriptAllocatorScope alloc_scope(this);
365 sq_resumeerror(this->vm);
366}
367
369{
370 ScriptAllocatorScope alloc_scope(this);
371 sq_collectgarbage(this->vm);
372}
373
374bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
375{
376 assert(!this->crashed);
377 ScriptAllocatorScope alloc_scope(this);
378 this->allocator->CheckLimit();
379
380 /* Store the stack-location for the return value. We need to
381 * restore this after saving or the stack will be corrupted
382 * if we're in the middle of a DoCommand. */
383 SQInteger last_target = this->vm->_suspended_target;
384 /* Store the current top */
385 int top = sq_gettop(this->vm);
386 /* Go to the instance-root */
387 sq_pushobject(this->vm, instance);
388 /* Find the function-name inside the script */
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);
393 return false;
394 }
395 /* Call the method */
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);
399 /* Reset the top, but don't do so for the script main function, as we need
400 * a correct stack when resuming. */
401 if (suspend == -1 || !this->IsSuspended()) sq_settop(this->vm, top);
402 /* Restore the return-value location. */
403 this->vm->_suspended_target = last_target;
404
405 return true;
406}
407
408bool Squirrel::CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend)
409{
410 HSQOBJECT ret;
411 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
412 if (ret._type != OT_STRING) return false;
413 *res = StrMakeValid(ObjectToString(&ret));
414 return true;
415}
416
417bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
418{
419 HSQOBJECT ret;
420 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
421 if (ret._type != OT_INTEGER) return false;
422 *res = ObjectToInteger(&ret);
423 return true;
424}
425
426bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
427{
428 HSQOBJECT ret;
429 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
430 if (ret._type != OT_BOOL) return false;
431 *res = ObjectToBool(&ret);
432 return true;
433}
434
435/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
436{
437 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
438
439 int oldtop = sq_gettop(vm);
440
441 /* First, find the class */
442 sq_pushroottable(vm);
443
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);
448 } else {
449 sq_pushstring(vm, class_name, -1);
450 }
451
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);
455 return false;
456 }
457
458 /* Create the instance */
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);
462 return false;
463 }
464
465 if (instance != nullptr) {
466 /* Find our instance */
467 sq_getstackobj(vm, -1, instance);
468 /* Add a reference to it, so it survives for ever */
469 sq_addref(vm, instance);
470 }
471 sq_remove(vm, -2); // Class-name
472 sq_remove(vm, -2); // Root-table
473
474 /* Store it in the class */
475 sq_setinstanceup(vm, -1, real_instance);
476 if (release_hook != nullptr) sq_setreleasehook(vm, -1, release_hook);
477
478 if (instance != nullptr) sq_settop(vm, oldtop);
479
480 return true;
481}
482
483bool Squirrel::CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
484{
485 ScriptAllocatorScope alloc_scope(this);
486 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, nullptr);
487}
488
489/* static */ SQUserPointer Squirrel::GetRealInstance(HSQUIRRELVM vm, int index, const char *tag)
490{
491 if (index < 0) index += sq_gettop(vm) + 1;
492 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(vm));
493 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), tag);
494 sq_pushroottable(vm);
495 sq_pushstring(vm, class_name);
496 sq_get(vm, -2);
497 sq_push(vm, index);
498 if (sq_instanceof(vm) == SQTrue) {
499 sq_pop(vm, 3);
500 SQUserPointer ptr = nullptr;
501 if (SQ_SUCCEEDED(sq_getinstanceup(vm, index, &ptr, nullptr))) return ptr;
502 }
503 throw sq_throwerror(vm, fmt::format("parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
504}
505
506Squirrel::Squirrel(const char *APIName) :
507 APIName(APIName), allocator(new ScriptAllocator())
508{
509 this->Initialize();
510}
511
513{
514 ScriptAllocatorScope alloc_scope(this);
515
516 this->global_pointer = nullptr;
517 this->print_func = nullptr;
518 this->crashed = false;
519 this->overdrawn_ops = 0;
520 this->vm = sq_open(1024);
521
522 /* Handle compile-errors ourself, so we can display it nicely */
523 sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError);
524 sq_notifyallexceptions(this->vm, _debug_script_level > 5);
525 /* Set a good print-function */
526 sq_setprintfunc(this->vm, &Squirrel::PrintFunc);
527 /* Handle runtime-errors ourself, so we can display it nicely */
528 sq_newclosure(this->vm, &Squirrel::_RunError, 0);
529 sq_seterrorhandler(this->vm);
530
531 /* Set the foreign pointer, so we can always find this instance from within the VM */
532 sq_setforeignptr(this->vm, this);
533
534 sq_pushroottable(this->vm);
536
537 /* Set consts table as delegate of root table, so consts/enums defined via require() are accessible */
538 sq_pushconsttable(this->vm);
539 sq_setdelegate(this->vm, -2);
540}
541
542class SQFile {
543private:
544 FileHandle file;
545 size_t size;
546 size_t pos;
547
548public:
549 SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0) {}
550
551 size_t Read(void *buf, size_t elemsize, size_t count)
552 {
553 assert(elemsize != 0);
554 if (this->pos + (elemsize * count) > this->size) {
555 count = (this->size - this->pos) / elemsize;
556 }
557 if (count == 0) return 0;
558 size_t ret = fread(buf, elemsize, count, this->file);
559 this->pos += ret * elemsize;
560 return ret;
561 }
562};
563
564static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
565{
566 unsigned char c;
567 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
568 return 0;
569}
570
571static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
572{
573 char buffer[5];
574
575 /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */
576 if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0;
577 uint len = Utf8EncodedCharLen(buffer[0]);
578 if (len == 0) return -1;
579
580 /* Read the remaining bits. */
581 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
582
583 /* Convert the character, and when definitely invalid, bail out as well. */
584 char32_t c;
585 if (Utf8Decode(&c, buffer) != len) return -1;
586
587 return c;
588}
589
590static char32_t _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
591{
592 unsigned short c;
593 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (char32_t)c;
594 return 0;
595}
596
597static char32_t _io_file_lexfeed_UCS2_swap(SQUserPointer file)
598{
599 unsigned short c;
600 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) {
601 c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00);
602 return (char32_t)c;
603 }
604 return 0;
605}
606
607static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
608{
609 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
610 if (ret == 0) return -1;
611 return ret;
612}
613
614SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
615{
616 ScriptAllocatorScope alloc_scope(this);
617
618 std::optional<FileHandle> file = std::nullopt;
619 size_t size;
620 if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
621 file = FioFOpenFile(filename, "rb", AI_DIR, &size);
622 if (!file.has_value()) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
623 } else if (strncmp(this->GetAPIName(), "GS", 2) == 0) {
624 file = FioFOpenFile(filename, "rb", GAME_DIR, &size);
625 if (!file.has_value()) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
626 } else {
627 NOT_REACHED();
628 }
629
630 if (!file.has_value()) {
631 return sq_throwerror(vm, "cannot open the file");
632 }
633 unsigned short bom = 0;
634 if (size >= 2) {
635 if (fread(&bom, 1, sizeof(bom), *file) != sizeof(bom)) return sq_throwerror(vm, "cannot read the file");;
636 }
637
638 SQLEXREADFUNC func;
639 switch (bom) {
640 case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
641 if (fseek(*file, -2, SEEK_CUR) < 0) {
642 return sq_throwerror(vm, "cannot seek the file");
643 }
644
645 SQFile f(std::move(*file), size);
646 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
647 return SQ_OK;
648 }
649 return sq_throwerror(vm, "Couldn't read bytecode");
650 }
651 case 0xFFFE:
652 /* Either this file is encoded as big-endian and we're on a little-endian
653 * machine, or this file is encoded as little-endian and we're on a big-endian
654 * machine. Either way, swap the bytes of every word we read. */
655 func = _io_file_lexfeed_UCS2_swap;
656 size -= 2; // Skip BOM
657 break;
658 case 0xFEFF:
659 func = _io_file_lexfeed_UCS2_no_swap;
660 size -= 2; // Skip BOM
661 break;
662 case 0xBBEF: // UTF-8
663 case 0xEFBB: { // UTF-8 on big-endian machine
664 /* Similarly, check the file is actually big enough to finish checking BOM */
665 if (size < 3) {
666 return sq_throwerror(vm, "I/O error");
667 }
668 unsigned char uc;
669 if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
670 return sq_throwerror(vm, "Unrecognized encoding");
671 }
672 func = _io_file_lexfeed_UTF8;
673 size -= 3; // Skip BOM
674 break;
675 }
676 default: // ASCII
677 func = _io_file_lexfeed_ASCII;
678 /* Account for when we might not have fread'd earlier */
679 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
680 return sq_throwerror(vm, "cannot seek the file");
681 }
682 break;
683 }
684
685 SQFile f(std::move(*file), size);
686 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
687 return SQ_OK;
688 }
689 return SQ_ERROR;
690}
691
692bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root)
693{
694 ScriptAllocatorScope alloc_scope(this);
695
696 /* Make sure we are always in the root-table */
697 if (in_root) sq_pushroottable(vm);
698
699 SQInteger ops_left = vm->_ops_till_suspend;
700 /* Load and run the script */
701 if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) {
702 sq_push(vm, -2);
703 if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue, 100000))) {
704 sq_pop(vm, 1);
705 /* After compiling the file we want to reset the amount of opcodes. */
706 vm->_ops_till_suspend = ops_left;
707 return true;
708 }
709 }
710
711 vm->_ops_till_suspend = ops_left;
712 Debug(misc, 0, "[squirrel] Failed to compile '{}'", script);
713 return false;
714}
715
716bool Squirrel::LoadScript(const std::string &script)
717{
718 return LoadScript(this->vm, script);
719}
720
721Squirrel::~Squirrel()
722{
723 this->Uninitialize();
724}
725
727{
728 ScriptAllocatorScope alloc_scope(this);
729
730 /* Remove the delegation */
731 sq_pushroottable(this->vm);
732 sq_pushnull(this->vm);
733 sq_setdelegate(this->vm, -2);
734 sq_pop(this->vm, 1);
735
736 /* Clean up the stuff */
737 sq_pop(this->vm, 1);
738 sq_close(this->vm);
739
740 /* Reset memory allocation errors. */
741 this->allocator->Reset();
742}
743
745{
746 this->Uninitialize();
747 this->Initialize();
748}
749
750void Squirrel::InsertResult(bool result)
751{
752 ScriptAllocatorScope alloc_scope(this);
753
754 sq_pushbool(this->vm, result);
755 if (this->IsSuspended()) { // Called before resuming a suspended script?
756 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
757 vm->Pop();
758 }
759}
760
761void Squirrel::InsertResult(int result)
762{
763 ScriptAllocatorScope alloc_scope(this);
764
765 sq_pushinteger(this->vm, result);
766 if (this->IsSuspended()) { // Called before resuming a suspended script?
767 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
768 vm->Pop();
769 }
770}
771
772/* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
773{
774 vm->DecreaseOps(ops);
775}
776
778{
779 return this->vm->_suspended != 0;
780}
781
783{
784 return this->crashed;
785}
786
788{
789 this->crashed = true;
790}
791
793{
794 ScriptAllocatorScope alloc_scope(this);
795 return sq_can_suspend(this->vm);
796}
797
799{
800 return this->vm->_ops_till_suspend;
801}
A throw-class that is given when the script made a fatal error.
void AddClassEnd()
Finishes adding a class to the global scope.
Definition squirrel.cpp:313
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition squirrel.cpp:339
void * global_pointer
Can be set by who ever initializes Squirrel.
Definition squirrel.hpp:32
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
Definition squirrel.cpp:195
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition squirrel.cpp:368
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
Definition squirrel.hpp:31
void Reset()
Completely reset the engine; start from scratch.
Definition squirrel.cpp:744
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:798
bool LoadScript(const std::string &script)
Load a script.
Definition squirrel.cpp:716
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition squirrel.cpp:777
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.
Definition squirrel.cpp:254
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
Definition squirrel.cpp:206
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
Definition squirrel.cpp:792
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
Definition squirrel.hpp:33
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
Definition squirrel.hpp:210
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
Definition squirrel.cpp:180
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
Definition squirrel.cpp:228
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition squirrel.cpp:173
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition squirrel.cpp:772
static void PrintFunc(HSQUIRRELVM vm, const std::string &s)
If a user runs 'print' inside a script, this function gets the params.
Definition squirrel.cpp:243
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition squirrel.cpp:782
void CrashOccurred()
Set the script status to crashed.
Definition squirrel.cpp:787
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
Definition squirrel.cpp:614
void Initialize()
Perform all initialization steps to create the engine.
Definition squirrel.cpp:512
bool crashed
True if the squirrel script made an error.
Definition squirrel.hpp:34
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, const char *tag)
Get the real-instance pointer.
Definition squirrel.cpp:489
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
Definition squirrel.hpp:220
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition squirrel.cpp:374
void AddClassBegin(const char *class_name)
Adds a class to the global scope.
Definition squirrel.cpp:289
int overdrawn_ops
The amount of operations we have overdrawn.
Definition squirrel.hpp:35
void Uninitialize()
Perform all the cleanups for the engine.
Definition squirrel.cpp:726
const char * GetAPIName()
Get the API name.
Definition squirrel.hpp:47
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:361
void AddConst(const char *var_name, int value)
Adds a const to the stack.
Definition squirrel.cpp:271
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.
Definition squirrel.cpp:435
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:321
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Exactly the same as CreateClassInstanceVM, only callable without instance of Squirrel.
Definition squirrel.cpp:483
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Definition squirrel.hpp:215
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
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.
Definition fileio.cpp:243
@ 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.
Definition settings.cpp:58
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.
Definition string.cpp:125
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition string.cpp:437
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
Definition squirrel.cpp:43
bool error_thrown
Whether the error has already been thrown, so to not throw secondary errors in the handling of the al...
Definition squirrel.cpp:41
size_t allocation_limit
Maximum this allocator may use before allocations fail.
Definition squirrel.cpp:34
void * DoAlloc(SQUnsignedInteger requested_size)
Internal helper to allocate the given amount of bytes.
Definition squirrel.cpp:74
void CheckAllocationAllowed(size_t requested_size)
Checks whether an allocation is allowed by the memory limit set for the script.
Definition squirrel.cpp:54
size_t allocated_size
Sum of allocated data size.
Definition squirrel.cpp:33
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated