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