OpenTTD Source 20260531-master-g0e951f3528
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
344void Squirrel::AddScopedEnumBegin(std::string_view enum_name)
345{
346 ScriptAllocatorScope alloc_scope(this);
347
348 sq_pushstring(this->vm, enum_name);
349 sq_newclass(this->vm, SQFalse);
350}
351
353{
354 ScriptAllocatorScope alloc_scope(this);
355
356 sq_newslot(vm, -3, SQFalse);
357}
358
359bool Squirrel::MethodExists(HSQOBJECT instance, std::string_view method_name)
360{
361 assert(!this->crashed);
362 ScriptAllocatorScope alloc_scope(this);
363
364 int top = sq_gettop(this->vm);
365 /* Go to the instance-root */
366 sq_pushobject(this->vm, instance);
367 /* Find the function-name inside the script */
368 sq_pushstring(this->vm, method_name);
369 if (SQ_FAILED(sq_get(this->vm, -2))) {
370 sq_settop(this->vm, top);
371 return false;
372 }
373 sq_settop(this->vm, top);
374 return true;
375}
376
377bool Squirrel::Resume(int suspend)
378{
379 assert(!this->crashed);
380 ScriptAllocatorScope alloc_scope(this);
381
382 /* Did we use more operations than we should have in the
383 * previous tick? If so, subtract that from the current run. */
384 if (this->overdrawn_ops > 0 && suspend > 0) {
385 this->overdrawn_ops -= suspend;
386 /* Do we need to wait even more? */
387 if (this->overdrawn_ops >= 0) return true;
388
389 /* We can now only run whatever is "left". */
390 suspend = -this->overdrawn_ops;
391 }
392
393 this->crashed = !sq_resumecatch(this->vm, suspend);
394 this->overdrawn_ops = -this->vm->_ops_till_suspend;
395 this->allocator->CheckLimit();
396 return this->vm->_suspended != 0;
397}
398
400{
401 assert(!this->crashed);
402 ScriptAllocatorScope alloc_scope(this);
403 sq_resumeerror(this->vm);
404}
405
407{
408 ScriptAllocatorScope alloc_scope(this);
409 sq_collectgarbage(this->vm);
410}
411
412bool Squirrel::CallMethod(HSQOBJECT instance, std::string_view method_name, HSQOBJECT *ret, int suspend)
413{
414 assert(!this->crashed);
415 ScriptAllocatorScope alloc_scope(this);
416 this->allocator->CheckLimit();
417
418 /* Store the stack-location for the return value. We need to
419 * restore this after saving or the stack will be corrupted
420 * if we're in the middle of a DoCommand. */
421 SQInteger last_target = this->vm->_suspended_target;
422 /* Store the current top */
423 int top = sq_gettop(this->vm);
424 /* Go to the instance-root */
425 sq_pushobject(this->vm, instance);
426 /* Find the function-name inside the script */
427 sq_pushstring(this->vm, method_name);
428 if (SQ_FAILED(sq_get(this->vm, -2))) {
429 Debug(misc, 0, "[squirrel] Could not find '{}' in the class", method_name);
430 sq_settop(this->vm, top);
431 return false;
432 }
433 /* Call the method */
434 sq_pushobject(this->vm, instance);
435 if (SQ_FAILED(sq_call(this->vm, 1, ret == nullptr ? SQFalse : SQTrue, SQTrue, suspend))) return false;
436 if (ret != nullptr) sq_getstackobj(vm, -1, ret);
437 /* Reset the top, but don't do so for the script main function, as we need
438 * a correct stack when resuming. */
439 if (suspend == -1 || !this->IsSuspended()) sq_settop(this->vm, top);
440 /* Restore the return-value location. */
441 this->vm->_suspended_target = last_target;
442
443 return true;
444}
445
446bool Squirrel::CallStringMethod(HSQOBJECT instance, std::string_view method_name, std::string *res, int suspend)
447{
448 HSQOBJECT ret;
449 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
450
451 auto str = ObjectToString(&ret);
452 if (!str.has_value()) return false;
453
454 *res = StrMakeValid(*str);
455 return true;
456}
457
458bool Squirrel::CallIntegerMethod(HSQOBJECT instance, std::string_view method_name, int *res, int suspend)
459{
460 HSQOBJECT ret;
461 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
462 if (ret._type != OT_INTEGER) return false;
463 *res = ObjectToInteger(&ret);
464 return true;
465}
466
467bool Squirrel::CallBoolMethod(HSQOBJECT instance, std::string_view method_name, bool *res, int suspend)
468{
469 HSQOBJECT ret;
470 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
471 if (ret._type != OT_BOOL) return false;
472 *res = ObjectToBool(&ret);
473 return true;
474}
475
476/* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
477{
478 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
479
480 int oldtop = sq_gettop(vm);
481
482 /* First, find the class */
483 sq_pushroottable(vm);
484
485 if (prepend_API_name) {
486 std::string prepended_class_name = fmt::format("{}{}", engine->GetAPIName(), class_name);
487 sq_pushstring(vm, prepended_class_name);
488 } else {
489 sq_pushstring(vm, class_name);
490 }
491
492 if (SQ_FAILED(sq_get(vm, -2))) {
493 Debug(misc, 0, "[squirrel] Failed to find class by the name '{}{}'", prepend_API_name ? engine->GetAPIName() : "", class_name);
494 sq_settop(vm, oldtop);
495 return false;
496 }
497
498 /* Create the instance */
499 if (SQ_FAILED(sq_createinstance(vm, -1))) {
500 Debug(misc, 0, "[squirrel] Failed to create instance for class '{}{}'", prepend_API_name ? engine->GetAPIName() : "", class_name);
501 sq_settop(vm, oldtop);
502 return false;
503 }
504
505 if (instance != nullptr) {
506 /* Find our instance */
507 sq_getstackobj(vm, -1, instance);
508 /* Add a reference to it, so it survives for ever */
509 sq_addref(vm, instance);
510 }
511 sq_remove(vm, -2); // Class-name
512 sq_remove(vm, -2); // Root-table
513
514 /* Store it in the class */
515 sq_setinstanceup(vm, -1, real_instance);
516 if (release_hook != nullptr) sq_setreleasehook(vm, -1, release_hook);
517
518 if (instance != nullptr) sq_settop(vm, oldtop);
519
520 return true;
521}
522
523bool Squirrel::CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
524{
525 ScriptAllocatorScope alloc_scope(this);
526 return Squirrel::CreateClassInstanceVM(this->vm, class_name, real_instance, instance, nullptr);
527}
528
529/* static */ SQUserPointer Squirrel::GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
530{
531 if (index < 0) index += sq_gettop(vm) + 1;
532 Squirrel *engine = static_cast<Squirrel *>(sq_getforeignptr(vm));
533 std::string class_name = fmt::format("{}{}", engine->GetAPIName(), tag);
534 sq_pushroottable(vm);
535 sq_pushstring(vm, class_name);
536 sq_get(vm, -2);
537 sq_push(vm, index);
538 if (sq_instanceof(vm) == SQTrue) {
539 sq_pop(vm, 3);
540 SQUserPointer ptr = nullptr;
541 if (SQ_SUCCEEDED(sq_getinstanceup(vm, index, &ptr, nullptr))) return ptr;
542 }
543 throw sq_throwerror(vm, fmt::format("parameter {} has an invalid type ; expected: '{}'", index - 1, class_name));
544}
545
546Squirrel::Squirrel(std::string_view api_name) :
547 api_name(api_name), allocator(std::make_unique<ScriptAllocator>())
548{
549 this->Initialize();
550}
551
553{
554 ScriptAllocatorScope alloc_scope(this);
555
556 this->global_pointer = nullptr;
557 this->print_func = nullptr;
558 this->crashed = false;
559 this->overdrawn_ops = 0;
560 this->vm = sq_open(1024);
561
562 /* Handle compile-errors ourself, so we can display it nicely */
563 sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError);
564 sq_notifyallexceptions(this->vm, _debug_script_level > 5);
565 /* Set a good print-function */
566 sq_setprintfunc(this->vm, &Squirrel::PrintFunc);
567 /* Handle runtime-errors ourself, so we can display it nicely */
568 sq_newclosure(this->vm, &Squirrel::_RunError, 0);
569 sq_seterrorhandler(this->vm);
570
571 /* Set the foreign pointer, so we can always find this instance from within the VM */
572 sq_setforeignptr(this->vm, this);
573
574 sq_pushroottable(this->vm);
576
577 /* Set consts table as delegate of root table, so consts/enums defined via require() are accessible */
578 sq_pushconsttable(this->vm);
579 sq_setdelegate(this->vm, -2);
580}
581
582class SQFile {
583private:
584 FileHandle file;
585 size_t size;
586 size_t pos;
587 std::string buffer;
588 StringConsumer consumer;
589
590 size_t ReadInternal(std::span<char> buf)
591 {
592 size_t count = buf.size();
593 if (this->pos + count > this->size) {
594 count = this->size - this->pos;
595 }
596 if (count > 0) count = fread(buf.data(), 1, count, this->file);
597 this->pos += count;
598 return count;
599 }
600
601public:
602 SQFile(FileHandle file, size_t size) : file(std::move(file)), size(size), pos(0), consumer(buffer) {}
603
604 StringConsumer &GetConsumer(size_t min_size = 64)
605 {
606 if (this->consumer.GetBytesLeft() < min_size && this->pos < this->size) {
607 this->buffer.erase(0, this->consumer.GetBytesRead());
608
609 size_t buffer_size = this->buffer.size();
610 size_t read_size = Align(min_size - buffer_size, 4096); // read pages of 4096 bytes
611 /* TODO C++23: use std::string::resize_and_overwrite() */
612 this->buffer.resize(buffer_size + read_size);
613 auto dest = std::span(this->buffer.data(), this->buffer.size()).subspan(buffer_size);
614 buffer_size += this->ReadInternal(dest);
615 this->buffer.resize(buffer_size);
616
617 this->consumer = StringConsumer(this->buffer);
618 }
619 return this->consumer;
620 }
621
622 size_t Read(void *buf, size_t max_size)
623 {
624 std::span<char> dest(reinterpret_cast<char *>(buf), max_size);
625
626 auto view = this->consumer.Read(max_size);
627 std::copy(view.data(), view.data() + view.size(), dest.data());
628 size_t result_size = view.size();
629
630 if (result_size < max_size) {
631 assert(!this->consumer.AnyBytesLeft());
632 result_size += this->ReadInternal(dest.subspan(result_size));
633 }
634
635 return result_size;
636 }
637};
638
639static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
640{
641 StringConsumer &consumer = reinterpret_cast<SQFile *>(file)->GetConsumer();
642 return consumer.TryReadUint8().value_or(0); // read as unsigned, otherwise integer promotion breaks it
643}
644
645static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
646{
647 StringConsumer &consumer = reinterpret_cast<SQFile *>(file)->GetConsumer();
648 return consumer.AnyBytesLeft() ? consumer.ReadUtf8(-1) : 0;
649}
650
651static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
652{
653 SQInteger ret = reinterpret_cast<SQFile *>(file)->Read(buf, size);
654 if (ret == 0) return -1;
655 return ret;
656}
657
658SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
659{
660 ScriptAllocatorScope alloc_scope(this);
661
662 std::optional<FileHandle> file = std::nullopt;
663 size_t size;
664 if (this->GetAPIName().starts_with("AI")) {
665 file = FioFOpenFile(filename, "rb", Subdirectory::Ai, &size);
666 if (!file.has_value()) file = FioFOpenFile(filename, "rb", Subdirectory::AiLibrary, &size);
667 } else if (this->GetAPIName().starts_with("GS")) {
668 file = FioFOpenFile(filename, "rb", Subdirectory::Gs, &size);
669 if (!file.has_value()) file = FioFOpenFile(filename, "rb", Subdirectory::GsLibrary, &size);
670 } else {
671 NOT_REACHED();
672 }
673
674 if (!file.has_value()) {
675 return sq_throwerror(vm, "cannot open the file");
676 }
677 unsigned short bom = 0;
678 if (size >= 2) {
679 if (fread(&bom, 1, sizeof(bom), *file) != sizeof(bom)) return sq_throwerror(vm, "cannot read the file");;
680 }
681
682 SQLEXREADFUNC func;
683 switch (bom) {
684 case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
685 if (fseek(*file, -2, SEEK_CUR) < 0) {
686 return sq_throwerror(vm, "cannot seek the file");
687 }
688
689 SQFile f(std::move(*file), size);
690 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
691 return SQ_OK;
692 }
693 return sq_throwerror(vm, "Couldn't read bytecode");
694 }
695 case 0xBBEF: // UTF-8
696 case 0xEFBB: { // UTF-8 on big-endian machine
697 /* Similarly, check the file is actually big enough to finish checking BOM */
698 if (size < 3) {
699 return sq_throwerror(vm, "I/O error");
700 }
701 unsigned char uc;
702 if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
703 return sq_throwerror(vm, "Unrecognized encoding");
704 }
705 func = _io_file_lexfeed_UTF8;
706 size -= 3; // Skip BOM
707 break;
708 }
709 default: // ASCII
710 func = _io_file_lexfeed_ASCII;
711 /* Account for when we might not have fread'd earlier */
712 if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
713 return sq_throwerror(vm, "cannot seek the file");
714 }
715 break;
716 }
717
718 SQFile f(std::move(*file), size);
719 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
720 return SQ_OK;
721 }
722 return SQ_ERROR;
723}
724
725bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root)
726{
727 ScriptAllocatorScope alloc_scope(this);
728
729 /* Make sure we are always in the root-table */
730 if (in_root) sq_pushroottable(vm);
731
732 SQInteger ops_left = vm->_ops_till_suspend;
733 /* Load and run the script */
734 if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) {
735 sq_push(vm, -2);
736 if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue, 100000))) {
737 sq_pop(vm, 1);
738 /* After compiling the file we want to reset the amount of opcodes. */
739 vm->_ops_till_suspend = ops_left;
740 return true;
741 }
742 }
743
744 vm->_ops_till_suspend = ops_left;
745 Debug(misc, 0, "[squirrel] Failed to compile '{}'", script);
746 return false;
747}
748
749bool Squirrel::LoadScript(const std::string &script)
750{
751 return LoadScript(this->vm, script);
752}
753
756{
757 this->Uninitialize();
758}
759
761{
762 ScriptAllocatorScope alloc_scope(this);
763
764 /* Remove the delegation */
765 sq_pushroottable(this->vm);
766 sq_pushnull(this->vm);
767 sq_setdelegate(this->vm, -2);
768 sq_pop(this->vm, 1);
769
770 /* Clean up the stuff */
771 sq_pop(this->vm, 1);
772 sq_close(this->vm);
773
774 /* Reset memory allocation errors. */
775 this->allocator->Reset();
776}
777
779{
780 this->Uninitialize();
781 this->Initialize();
782}
783
784void Squirrel::InsertResult(bool result)
785{
786 ScriptAllocatorScope alloc_scope(this);
787
788 sq_pushbool(this->vm, result);
789 if (this->IsSuspended()) { // Called before resuming a suspended script?
790 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
791 vm->Pop();
792 }
793}
794
795void Squirrel::InsertResult(int result)
796{
797 ScriptAllocatorScope alloc_scope(this);
798
799 sq_pushinteger(this->vm, result);
800 if (this->IsSuspended()) { // Called before resuming a suspended script?
801 vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
802 vm->Pop();
803 }
804}
805
806/* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
807{
808 vm->DecreaseOps(ops);
809}
810
812{
813 return this->vm->_suspended != 0;
814}
815
817{
818 return this->crashed;
819}
820
822{
823 this->crashed = true;
824}
825
827{
828 ScriptAllocatorScope alloc_scope(this);
829 return sq_can_suspend(this->vm);
830}
831
833{
834 return this->vm->_ops_till_suspend;
835}
836
838{
839 _squirrel_allocator->CheckAllocationAllowed(bytes);
840 _squirrel_allocator->allocated_size += bytes;
841}
842
844{
845 _squirrel_allocator->allocated_size -= bytes;
846}
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:377
static SQUserPointer GetRealInstance(HSQUIRRELVM vm, int index, std::string_view tag)
Get the real-instance pointer.
Definition squirrel.cpp:529
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:458
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition squirrel.cpp:406
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:778
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition squirrel.cpp:832
bool LoadScript(const std::string &script)
Load a script.
Definition squirrel.cpp:749
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition squirrel.cpp:811
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
Definition squirrel.cpp:826
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:755
static void DecreaseAllocatedSize(size_t bytes)
Decrease number of bytes allocated in the current script allocator scope.
Definition squirrel.cpp:843
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
void AddScopedEnumBegin(std::string_view enum_name)
Adds an enum to the scope.
Definition squirrel.cpp:344
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition squirrel.cpp:806
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition squirrel.cpp:816
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:467
void CrashOccurred()
Set the script status to crashed.
Definition squirrel.cpp:821
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
Definition squirrel.cpp:658
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:837
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:552
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:340
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:326
void AddScopedEnumEnd()
Finishes adding an enum to the scope.
Definition squirrel.cpp:352
void Uninitialize()
Perform all the cleanups for the engine.
Definition squirrel.cpp:760
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:412
bool MethodExists(HSQOBJECT instance, std::string_view method_name)
Check if a method exists in an instance.
Definition squirrel.cpp:359
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition squirrel.cpp:399
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:476
bool CreateClassInstance(const std::string &class_name, void *real_instance, HSQOBJECT *instance)
Create a class instance.
Definition squirrel.cpp:523
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:446
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Definition squirrel.hpp:333
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:249
Functions for standard in/out file operations.
@ GsLibrary
Subdirectory for all GS libraries.
@ Ai
Subdirectory for all AI files.
Definition fileio_type.h:99
@ Gs
Subdirectory for all game scripts.
@ AiLibrary
Subdirectory for all AI libraries.
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