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