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