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