OpenTTD Source 20260911-master-gee2b2ac12a
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(Facility::Misc, Severity::Critical, "[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(Facility::Misc, Severity::Critical, "[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
330 Debug(Facility::Misc, Severity::Critical, "[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(Facility::Misc, Severity::Critical, "[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(Facility::Misc, Severity::Critical, "[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(Facility::Misc, Severity::Critical, "[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, IsVisibleSeverity(Facility::Script, Severity::Debug2));
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(Facility::Misc, Severity::Critical, "[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.
bool IsVisibleSeverity(Facility facility, Severity severity)
Test if debug severity is visible for the given facility.
Definition debug.h:25
#define Debug(facility, severity, format_string,...)
Output a line of debugging information.
Definition debug.h:37
@ Misc
Misc message facility.
Definition debug_type.h:32
@ Script
Script message facility.
Definition debug_type.h:38
@ Critical
Critical, user should know about this.
Definition debug_type.h:15
@ Debug2
Debug #2 - Low level debug messages.
Definition debug_type.h:21
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.
@ Free
Free and has no pre-signal exits or at least one green exit.
Definition signal_func.h:56
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