OpenTTD Source: src/script/squirrel.cpp Source File

OpenTTD Source 14.0-RC3

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 <http://www.gnu.org/licenses/>.
6 */
7
10 #include "../stdafx.h"
11 #include "../debug.h"
12 #include "squirrel_std.hpp"
13 #include "../fileio_func.h"
14 #include "../string_func.h"
15 #include "script_fatalerror.hpp"
16 #include "../settings_type.h"
17 #include <sqstdaux.h>
18 #include <../squirrel/sqpcheader.h>
19 #include <../squirrel/sqvm.h>
20 #include "../core/alloc_func.hpp"
21
30 /*
31 * If changing the call paths into the scripting engine, define this symbol to enable full debugging of allocations.
32 * This lets you track whether the allocator context is being switched correctly in all call paths.
33 #define SCRIPT_DEBUG_ALLOCATIONS
34 */
35
36 struct ScriptAllocator {
37 size_t allocated_size;
38 size_t allocation_limit;
39
45 bool error_thrown;
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
53 void CheckLimit() const
54 {
55 if (this->allocated_size > this->allocation_limit) throw Script_FatalError("Maximum memory allocation exceeded");
56 }
57
67 void CheckAllocation(size_t requested_size, void *p)
68 {
69 if (this->allocated_size + requested_size > this->allocation_limit && !this->error_thrown) {
70 /* Do not allow allocating more than the allocation limit, except when an error is
71 * already as then the allocation is for throwing that error in Squirrel, the
72 * associated stack trace information and while cleaning up the AI. */
73 this->error_thrown = true;
74 std::string msg = fmt::format("Maximum memory allocation exceeded by {} bytes when allocating {} bytes",
75 this->allocated_size + requested_size - this->allocation_limit, requested_size);
76 /* Don't leak the rejected allocation. */
77 free(p);
78 throw Script_FatalError(msg);
79 }
80
81 if (p == nullptr) {
82 /* The OS did not have enough memory to allocate the object, regardless of the
83 * limit imposed by OpenTTD on the amount of memory that may be allocated. */
84 if (this->error_thrown) {
85 /* The allocation is called in the error handling of a memory allocation
86 * failure, then not being able to allocate that small amount of memory
87 * means there is no other choice than to bug out completely. */
88 MallocError(requested_size);
89 }
90
91 this->error_thrown = true;
92 std::string msg = fmt::format("Out of memory. Cannot allocate {} bytes", requested_size);
93 throw Script_FatalError(msg);
94 }
95 }
96
97 void *Malloc(SQUnsignedInteger size)
98 {
99 void *p = malloc(size);
100
101 this->CheckAllocation(size, p);
102
103 this->allocated_size += size;
104
105 #ifdef SCRIPT_DEBUG_ALLOCATIONS
106 assert(p != nullptr);
107 assert(this->allocations.find(p) == this->allocations.end());
108 this->allocations[p] = size;
109 #endif
110
111 return p;
112 }
113
114 void *Realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size)
115 {
116 if (p == nullptr) {
117 return this->Malloc(size);
118 }
119 if (size == 0) {
120 this->Free(p, oldsize);
121 return nullptr;
122 }
123
124 #ifdef SCRIPT_DEBUG_ALLOCATIONS
125 assert(this->allocations[p] == oldsize);
126 this->allocations.erase(p);
127 #endif
128 /* Can't use realloc directly because memory limit check.
129 * If memory exception is thrown, the old pointer is expected
130 * to be valid for engine cleanup.
131 */
132 void *new_p = malloc(size);
133
134 this->CheckAllocation(size - oldsize, new_p);
135
136 /* Memory limit test passed, we can copy data and free old pointer. */
137 memcpy(new_p, p, std::min(oldsize, size));
138 free(p);
139
140 this->allocated_size -= oldsize;
141 this->allocated_size += size;
142
143 #ifdef SCRIPT_DEBUG_ALLOCATIONS
144 assert(new_p != nullptr);
145 assert(this->allocations.find(p) == this->allocations.end());
146 this->allocations[new_p] = size;
147 #endif
148
149 return new_p;
150 }
151
152 void Free(void *p, SQUnsignedInteger size)
153 {
154 if (p == nullptr) return;
155 free(p);
156 this->allocated_size -= size;
157
158 #ifdef SCRIPT_DEBUG_ALLOCATIONS
159 assert(this->allocations.at(p) == size);
160 this->allocations.erase(p);
161 #endif
162 }
163
164 ScriptAllocator()
165 {
166 this->allocated_size = 0;
167 this->allocation_limit = static_cast<size_t>(_settings_game.script.script_max_memory_megabytes) << 20;
168 if (this->allocation_limit == 0) this->allocation_limit = SAFE_LIMIT; // in case the setting is somehow zero
169 this->error_thrown = false;
170 }
171
172 ~ScriptAllocator()
173 {
174 #ifdef SCRIPT_DEBUG_ALLOCATIONS
175 assert(this->allocations.empty());
176 #endif
177 }
178 };
179
186 #include "../safeguards.h"
187
188 ScriptAllocator *_squirrel_allocator = nullptr;
189
190 /* See 3rdparty/squirrel/squirrel/sqmem.cpp for the default allocator implementation, which this overrides */
191 #ifndef SQUIRREL_DEFAULT_ALLOCATOR
192 void *sq_vm_malloc(SQUnsignedInteger size) { return _squirrel_allocator->Malloc(size); }
193 void *sq_vm_realloc(void *p, SQUnsignedInteger oldsize, SQUnsignedInteger size) { return _squirrel_allocator->Realloc(p, oldsize, size); }
194 void sq_vm_free(void *p, SQUnsignedInteger size) { _squirrel_allocator->Free(p, size); }
195 #endif
196
197 size_t Squirrel::GetAllocatedMemory() const noexcept
198 {
199 assert(this->allocator != nullptr);
200 return this->allocator->allocated_size;
201 }
202
203
204 void Squirrel::CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
205 {
206 std::string msg = fmt::format("Error {}:{}/{}: {}", source, line, column, desc);
207
208 /* Check if we have a custom print function */
209 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
210 engine->crashed = true;
211 SQPrintFunc *func = engine->print_func;
212 if (func == nullptr) {
213 Debug(misc, 0, "[Squirrel] Compile error: {}", msg);
214 } else {
215 (*func)(true, msg);
216 }
217 }
218
219 void Squirrel::ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
220 {
221 /* Check if we have a custom print function */
222 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
223 if (func == nullptr) {
224 fmt::print(stderr, "{}", s);
225 } else {
226 (*func)(true, s);
227 }
228 }
229
230 void Squirrel::RunError(HSQUIRRELVM vm, const SQChar *error)
231 {
232 /* Set the print function to something that prints to stderr */
233 SQPRINTFUNCTION pf = sq_getprintfunc(vm);
234 sq_setprintfunc(vm, &Squirrel::ErrorPrintFunc);
235
236 /* Check if we have a custom print function */
237 std::string msg = fmt::format("Your script made an error: {}\n", error);
238 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
239 SQPrintFunc *func = engine->print_func;
240 if (func == nullptr) {
241 fmt::print(stderr, "{}", msg);
242 } else {
243 (*func)(true, msg);
244 }
245
246 /* Print below the error the stack, so the users knows what is happening */
247 sqstd_printcallstack(vm);
248 /* Reset the old print function */
249 sq_setprintfunc(vm, pf);
250 }
251
252 SQInteger Squirrel::_RunError(HSQUIRRELVM vm)
253 {
254 const SQChar *sErr = nullptr;
255
256 if (sq_gettop(vm) >= 1) {
257 if (SQ_SUCCEEDED(sq_getstring(vm, -1, &sErr))) {
258 Squirrel::RunError(vm, sErr);
259 return 0;
260 }
261 }
262
263 Squirrel::RunError(vm, "unknown error");
264 return 0;
265 }
266
267 void Squirrel::PrintFunc(HSQUIRRELVM vm, const std::string &s)
268 {
269 /* Check if we have a custom print function */
270 SQPrintFunc *func = ((Squirrel *)sq_getforeignptr(vm))->print_func;
271 if (func == nullptr) {
272 fmt::print("{}", s);
273 } else {
274 (*func)(false, s);
275 }
276 }
277
278 void Squirrel::AddMethod(const char *method_name, SQFUNCTION proc, uint nparam, const char *params, void *userdata, int size)
279 {
280 ScriptAllocatorScope alloc_scope(this);
281
282 sq_pushstring(this->vm, method_name, -1);
283
284 if (size != 0) {
285 void *ptr = sq_newuserdata(vm, size);
286 memcpy(ptr, userdata, size);
287 }
288
289 sq_newclosure(this->vm, proc, size != 0 ? 1 : 0);
290 if (nparam != 0) sq_setparamscheck(this->vm, nparam, params);
291 sq_setnativeclosurename(this->vm, -1, method_name);
292 sq_newslot(this->vm, -3, SQFalse);
293 }
294
295 void Squirrel::AddConst(const char *var_name, int value)
296 {
297 ScriptAllocatorScope alloc_scope(this);
298
299 sq_pushstring(this->vm, var_name, -1);
300 sq_pushinteger(this->vm, value);
301 sq_newslot(this->vm, -3, SQTrue);
302 }
303
304 void Squirrel::AddConst(const char *var_name, bool value)
305 {
306 ScriptAllocatorScope alloc_scope(this);
307
308 sq_pushstring(this->vm, var_name, -1);
309 sq_pushbool(this->vm, value);
310 sq_newslot(this->vm, -3, SQTrue);
311 }
312
313 void Squirrel::AddClassBegin(const char *class_name)
314 {
315 ScriptAllocatorScope alloc_scope(this);
316
317 sq_pushroottable(this->vm);
318 sq_pushstring(this->vm, class_name, -1);
319 sq_newclass(this->vm, SQFalse);
320 }
321
322 void Squirrel::AddClassBegin(const char *class_name, const char *parent_class)
323 {
324 ScriptAllocatorScope alloc_scope(this);
325
326 sq_pushroottable(this->vm);
327 sq_pushstring(this->vm, class_name, -1);
328 sq_pushstring(this->vm, parent_class, -1);
329 if (SQ_FAILED(sq_get(this->vm, -3))) {
330 Debug(misc, 0, "[squirrel] Failed to initialize class '{}' based on parent class '{}'", class_name, parent_class);
331 Debug(misc, 0, "[squirrel] Make sure that '{}' exists before trying to define '{}'", parent_class, class_name);
332 return;
333 }
334 sq_newclass(this->vm, SQTrue);
335 }
336
337 void Squirrel::AddClassEnd()
338 {
339 ScriptAllocatorScope alloc_scope(this);
340
341 sq_newslot(vm, -3, SQFalse);
342 sq_pop(vm, 1);
343 }
344
345 bool Squirrel::MethodExists(HSQOBJECT instance, const char *method_name)
346 {
347 assert(!this->crashed);
348 ScriptAllocatorScope alloc_scope(this);
349
350 int top = sq_gettop(this->vm);
351 /* Go to the instance-root */
352 sq_pushobject(this->vm, instance);
353 /* Find the function-name inside the script */
354 sq_pushstring(this->vm, method_name, -1);
355 if (SQ_FAILED(sq_get(this->vm, -2))) {
356 sq_settop(this->vm, top);
357 return false;
358 }
359 sq_settop(this->vm, top);
360 return true;
361 }
362
363 bool Squirrel::Resume(int suspend)
364 {
365 assert(!this->crashed);
366 ScriptAllocatorScope alloc_scope(this);
367
368 /* Did we use more operations than we should have in the
369 * previous tick? If so, subtract that from the current run. */
370 if (this->overdrawn_ops > 0 && suspend > 0) {
371 this->overdrawn_ops -= suspend;
372 /* Do we need to wait even more? */
373 if (this->overdrawn_ops >= 0) return true;
374
375 /* We can now only run whatever is "left". */
376 suspend = -this->overdrawn_ops;
377 }
378
379 this->crashed = !sq_resumecatch(this->vm, suspend);
380 this->overdrawn_ops = -this->vm->_ops_till_suspend;
381 this->allocator->CheckLimit();
382 return this->vm->_suspended != 0;
383 }
384
385 void Squirrel::ResumeError()
386 {
387 assert(!this->crashed);
388 ScriptAllocatorScope alloc_scope(this);
389 sq_resumeerror(this->vm);
390 }
391
392 void Squirrel::CollectGarbage()
393 {
394 ScriptAllocatorScope alloc_scope(this);
395 sq_collectgarbage(this->vm);
396 }
397
398 bool Squirrel::CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
399 {
400 assert(!this->crashed);
401 ScriptAllocatorScope alloc_scope(this);
402 this->allocator->CheckLimit();
403
404 /* Store the stack-location for the return value. We need to
405 * restore this after saving or the stack will be corrupted
406 * if we're in the middle of a DoCommand. */
407 SQInteger last_target = this->vm->_suspended_target;
408 /* Store the current top */
409 int top = sq_gettop(this->vm);
410 /* Go to the instance-root */
411 sq_pushobject(this->vm, instance);
412 /* Find the function-name inside the script */
413 sq_pushstring(this->vm, method_name, -1);
414 if (SQ_FAILED(sq_get(this->vm, -2))) {
415 Debug(misc, 0, "[squirrel] Could not find '{}' in the class", method_name);
416 sq_settop(this->vm, top);
417 return false;
418 }
419 /* Call the method */
420 sq_pushobject(this->vm, instance);
421 if (SQ_FAILED(sq_call(this->vm, 1, ret == nullptr ? SQFalse : SQTrue, SQTrue, suspend))) return false;
422 if (ret != nullptr) sq_getstackobj(vm, -1, ret);
423 /* Reset the top, but don't do so for the script main function, as we need
424 * a correct stack when resuming. */
425 if (suspend == -1 || !this->IsSuspended()) sq_settop(this->vm, top);
426 /* Restore the return-value location. */
427 this->vm->_suspended_target = last_target;
428
429 return true;
430 }
431
432 bool Squirrel::CallStringMethod(HSQOBJECT instance, const char *method_name, std::string *res, int suspend)
433 {
434 HSQOBJECT ret;
435 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
436 if (ret._type != OT_STRING) return false;
437 *res = StrMakeValid(ObjectToString(&ret));
438 return true;
439 }
440
441 bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend)
442 {
443 HSQOBJECT ret;
444 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
445 if (ret._type != OT_INTEGER) return false;
446 *res = ObjectToInteger(&ret);
447 return true;
448 }
449
450 bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend)
451 {
452 HSQOBJECT ret;
453 if (!this->CallMethod(instance, method_name, &ret, suspend)) return false;
454 if (ret._type != OT_BOOL) return false;
455 *res = ObjectToBool(&ret);
456 return true;
457 }
458
459 /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const std::string &class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook, bool prepend_API_name)
460 {
461 Squirrel *engine = (Squirrel *)sq_getforeignptr(vm);
462
463 int oldtop = sq_gettop(vm);
464
465 /* First, find the class */
466 sq_pushroottable(vm);
467
468 if (prepend_API_name) {
469 std::string prepended_class_name = engine->GetAPIName();
470 prepended_class_name += class_name;
471 sq_pushstring(vm, prepended_class_name, -1);
472 } else {
473 sq_pushstring(vm, class_name, -1);
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
507 bool 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 Squirrel::Squirrel(const char *APIName) :
514 APIName(APIName), allocator(new ScriptAllocator())
515 {
516 this->Initialize();
517 }
518
519 void Squirrel::Initialize()
520 {
521 ScriptAllocatorScope alloc_scope(this);
522
523 this->global_pointer = nullptr;
524 this->print_func = nullptr;
525 this->crashed = false;
526 this->overdrawn_ops = 0;
527 this->vm = sq_open(1024);
528
529 /* Handle compile-errors ourself, so we can display it nicely */
530 sq_setcompilererrorhandler(this->vm, &Squirrel::CompileError);
531 sq_notifyallexceptions(this->vm, _debug_script_level > 5);
532 /* Set a good print-function */
533 sq_setprintfunc(this->vm, &Squirrel::PrintFunc);
534 /* Handle runtime-errors ourself, so we can display it nicely */
535 sq_newclosure(this->vm, &Squirrel::_RunError, 0);
536 sq_seterrorhandler(this->vm);
537
538 /* Set the foreign pointer, so we can always find this instance from within the VM */
539 sq_setforeignptr(this->vm, this);
540
541 sq_pushroottable(this->vm);
542 squirrel_register_global_std(this);
543
544 /* Set consts table as delegate of root table, so consts/enums defined via require() are accessible */
545 sq_pushconsttable(this->vm);
546 sq_setdelegate(this->vm, -2);
547 }
548
549 class SQFile {
550 private:
551 FILE *file;
552 size_t size;
553 size_t pos;
554
555 public:
556 SQFile(FILE *file, size_t size) : file(file), size(size), pos(0) {}
557
558 size_t Read(void *buf, size_t elemsize, size_t count)
559 {
560 assert(elemsize != 0);
561 if (this->pos + (elemsize * count) > this->size) {
562 count = (this->size - this->pos) / elemsize;
563 }
564 if (count == 0) return 0;
565 size_t ret = fread(buf, elemsize, count, this->file);
566 this->pos += ret * elemsize;
567 return ret;
568 }
569 };
570
571 static char32_t _io_file_lexfeed_ASCII(SQUserPointer file)
572 {
573 unsigned char c;
574 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return c;
575 return 0;
576 }
577
578 static char32_t _io_file_lexfeed_UTF8(SQUserPointer file)
579 {
580 char buffer[5];
581
582 /* Read the first character, and get the length based on UTF-8 specs. If invalid, bail out. */
583 if (((SQFile *)file)->Read(buffer, sizeof(buffer[0]), 1) != 1) return 0;
584 uint len = Utf8EncodedCharLen(buffer[0]);
585 if (len == 0) return -1;
586
587 /* Read the remaining bits. */
588 if (len > 1 && ((SQFile *)file)->Read(buffer + 1, sizeof(buffer[0]), len - 1) != len - 1) return 0;
589
590 /* Convert the character, and when definitely invalid, bail out as well. */
591 char32_t c;
592 if (Utf8Decode(&c, buffer) != len) return -1;
593
594 return c;
595 }
596
597 static char32_t _io_file_lexfeed_UCS2_no_swap(SQUserPointer file)
598 {
599 unsigned short c;
600 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) return (char32_t)c;
601 return 0;
602 }
603
604 static char32_t _io_file_lexfeed_UCS2_swap(SQUserPointer file)
605 {
606 unsigned short c;
607 if (((SQFile *)file)->Read(&c, sizeof(c), 1) > 0) {
608 c = ((c >> 8) & 0x00FF)| ((c << 8) & 0xFF00);
609 return (char32_t)c;
610 }
611 return 0;
612 }
613
614 static SQInteger _io_file_read(SQUserPointer file, SQUserPointer buf, SQInteger size)
615 {
616 SQInteger ret = ((SQFile *)file)->Read(buf, 1, size);
617 if (ret == 0) return -1;
618 return ret;
619 }
620
621 SQRESULT Squirrel::LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
622 {
623 ScriptAllocatorScope alloc_scope(this);
624
625 FILE *file;
626 size_t size;
627 if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
628 file = FioFOpenFile(filename, "rb", AI_DIR, &size);
629 if (file == nullptr) file = FioFOpenFile(filename, "rb", AI_LIBRARY_DIR, &size);
630 } else if (strncmp(this->GetAPIName(), "GS", 2) == 0) {
631 file = FioFOpenFile(filename, "rb", GAME_DIR, &size);
632 if (file == nullptr) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
633 } else {
634 NOT_REACHED();
635 }
636
637 if (file == nullptr) {
638 return sq_throwerror(vm, "cannot open the file");
639 }
640 unsigned short bom = 0;
641 if (size >= 2) {
642 [[maybe_unused]] size_t sr = fread(&bom, 1, sizeof(bom), file);
643 }
644
645 SQLEXREADFUNC func;
646 switch (bom) {
647 case SQ_BYTECODE_STREAM_TAG: { // BYTECODE
648 if (fseek(file, -2, SEEK_CUR) < 0) {
649 FioFCloseFile(file);
650 return sq_throwerror(vm, "cannot seek the file");
651 }
652
653 SQFile f(file, size);
654 if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
655 FioFCloseFile(file);
656 return SQ_OK;
657 }
658 FioFCloseFile(file);
659 return sq_throwerror(vm, "Couldn't read bytecode");
660 }
661 case 0xFFFE:
662 /* Either this file is encoded as big-endian and we're on a little-endian
663 * machine, or this file is encoded as little-endian and we're on a big-endian
664 * machine. Either way, swap the bytes of every word we read. */
665 func = _io_file_lexfeed_UCS2_swap;
666 size -= 2; // Skip BOM
667 break;
668 case 0xFEFF:
669 func = _io_file_lexfeed_UCS2_no_swap;
670 size -= 2; // Skip BOM
671 break;
672 case 0xBBEF: // UTF-8
673 case 0xEFBB: { // UTF-8 on big-endian machine
674 /* Similarly, check the file is actually big enough to finish checking BOM */
675 if (size < 3) {
676 FioFCloseFile(file);
677 return sq_throwerror(vm, "I/O error");
678 }
679 unsigned char uc;
680 if (fread(&uc, 1, sizeof(uc), file) != sizeof(uc) || uc != 0xBF) {
681 FioFCloseFile(file);
682 return sq_throwerror(vm, "Unrecognized encoding");
683 }
684 func = _io_file_lexfeed_UTF8;
685 size -= 3; // Skip BOM
686 break;
687 }
688 default: // ASCII
689 func = _io_file_lexfeed_ASCII;
690 /* Account for when we might not have fread'd earlier */
691 if (size >= 2 && fseek(file, -2, SEEK_CUR) < 0) {
692 FioFCloseFile(file);
693 return sq_throwerror(vm, "cannot seek the file");
694 }
695 break;
696 }
697
698 SQFile f(file, size);
699 if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
700 FioFCloseFile(file);
701 return SQ_OK;
702 }
703 FioFCloseFile(file);
704 return SQ_ERROR;
705 }
706
707 bool 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
731 bool Squirrel::LoadScript(const std::string &script)
732 {
733 return LoadScript(this->vm, script);
734 }
735
736 Squirrel::~Squirrel()
737 {
738 this->Uninitialize();
739 }
740
741 void Squirrel::Uninitialize()
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 assert(this->allocator->allocated_size == 0);
756
757 /* Reset memory allocation errors. */
758 this->allocator->error_thrown = false;
759 }
760
761 void Squirrel::Reset()
762 {
763 this->Uninitialize();
764 this->Initialize();
765 }
766
767 void 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
778 void 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
794 bool Squirrel::IsSuspended()
795 {
796 return this->vm->_suspended != 0;
797 }
798
799 bool Squirrel::HasScriptCrashed()
800 {
801 return this->crashed;
802 }
803
804 void Squirrel::CrashOccurred()
805 {
806 this->crashed = true;
807 }
808
809 bool Squirrel::CanSuspend()
810 {
811 ScriptAllocatorScope alloc_scope(this);
812 return sq_can_suspend(this->vm);
813 }
814
815 SQInteger Squirrel::GetOpsTillSuspend()
816 {
817 return this->vm->_ops_till_suspend;
818 }
ScriptAllocator::allocation_limit
size_t allocation_limit
Maximum this allocator may use before allocations fail.
Definition: squirrel.cpp:38
Squirrel::ObjectToString
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
Definition: squirrel.hpp:205
Squirrel::GetAPIName
const char * GetAPIName()
Get the API name.
Definition: squirrel.hpp:45
GAME_LIBRARY_DIR
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
Definition: fileio_type.h:122
Squirrel::CanSuspend
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
Definition: squirrel.cpp:809
Squirrel::CreateClassInstanceVM
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:459
Squirrel::AddClassBegin
void AddClassBegin(const char *class_name)
Adds a class to the global scope.
Definition: squirrel.cpp:313
ScriptAllocator
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
Definition: squirrel.cpp:36
StrMakeValid
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:114
Squirrel::Reset
void Reset()
Completely reset the engine; start from scratch.
Definition: squirrel.cpp:761
Squirrel::vm
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
Definition: squirrel.hpp:29
ScriptAllocator::CheckAllocation
void CheckAllocation(size_t requested_size, void *p)
Catch all validation for the allocation; did it allocate too much memory according to the allocation ...
Definition: squirrel.cpp:67
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
ScriptAllocator::error_thrown
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
Squirrel::GetOpsTillSuspend
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition: squirrel.cpp:815
Squirrel::Initialize
void Initialize()
Perform all initialization steps to create the engine.
Definition: squirrel.cpp:519
Squirrel
Definition: squirrel.hpp:23
Squirrel::global_pointer
void * global_pointer
Can be set by who ever initializes Squirrel.
Definition: squirrel.hpp:30
Squirrel::ObjectToInteger
static int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Definition: squirrel.hpp:210
Squirrel::HasScriptCrashed
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition: squirrel.cpp:799
Squirrel::AddClassEnd
void AddClassEnd()
Finishes adding a class to the global scope.
Definition: squirrel.cpp:337
ScriptSettings::script_max_memory_megabytes
uint32_t script_max_memory_megabytes
limit on memory a single script instance may have allocated
Definition: settings_type.h:408
AI_DIR
@ AI_DIR
Subdirectory for all AI files.
Definition: fileio_type.h:119
Squirrel::overdrawn_ops
int overdrawn_ops
The amount of operations we have overdrawn.
Definition: squirrel.hpp:33
GameSettings::script
ScriptSettings script
settings for scripts
Definition: settings_type.h:621
free
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:379
Squirrel::Uninitialize
void Uninitialize()
Perform all the cleanups for the engine.
Definition: squirrel.cpp:741
FioFOpenFile
FILE * FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:263
ScriptAllocatorScope
Definition: squirrel.hpp:288
Squirrel::GetAllocatedMemory
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition: squirrel.cpp:197
Squirrel::PrintFunc
static void PrintFunc(HSQUIRRELVM vm, const std::string &s)
If a user runs 'print' inside a script, this function gets the params.
Definition: squirrel.cpp:267
_settings_game
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:55
GAME_DIR
@ GAME_DIR
Subdirectory for all game scripts.
Definition: fileio_type.h:121
ScriptAllocator::allocated_size
size_t allocated_size
Sum of allocated data size.
Definition: squirrel.cpp:37
Squirrel::CallMethod
bool CallMethod(HSQOBJECT instance, const char *method_name, HSQOBJECT *ret, int suspend)
Call a method of an instance, in various flavors.
Definition: squirrel.cpp:398
Squirrel::ObjectToBool
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
Definition: squirrel.hpp:215
squirrel_register_global_std
void squirrel_register_global_std(Squirrel *engine)
Register all standard functions that are available on first startup.
Definition: squirrel_std.cpp:89
Squirrel::AddConst
void AddConst(const char *var_name, int value)
Adds a const to the stack.
Definition: squirrel.cpp:295
Squirrel::ErrorPrintFunc
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
Definition: squirrel.cpp:219
MallocError
void MallocError(size_t size)
Function to exit with an error message after malloc() or calloc() have failed.
Definition: alloc_func.cpp:20
Squirrel::allocator
std::unique_ptr< ScriptAllocator > allocator
Allocator object used by this script.
Definition: squirrel.hpp:35
Squirrel::ResumeError
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition: squirrel.cpp:385
Squirrel::LoadScript
bool LoadScript(const std::string &script)
Load a script.
Definition: squirrel.cpp:731
Utf8EncodedCharLen
int8_t Utf8EncodedCharLen(char c)
Return the length of an UTF-8 encoded value based on a single char.
Definition: string_func.h:123
Squirrel::CollectGarbage
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition: squirrel.cpp:392
Squirrel::MethodExists
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:345
Squirrel::AddMethod
void AddMethod(const char *method_name, SQFUNCTION proc, uint nparam=0, const char *params=nullptr, void *userdata=nullptr, int size=0)
Adds a function to the stack.
Definition: squirrel.cpp:278
Squirrel::CrashOccurred
void CrashOccurred()
Set the script status to crashed.
Definition: squirrel.cpp:804
Squirrel::crashed
bool crashed
True if the squirrel script made an error.
Definition: squirrel.hpp:32
ScriptAllocator::SAFE_LIMIT
static const size_t SAFE_LIMIT
128 MiB, a safe choice for almost any situation
Definition: squirrel.cpp:47
Squirrel::RunError
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
Definition: squirrel.cpp:230
Squirrel::Resume
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition: squirrel.cpp:363
Script_FatalError
A throw-class that is given when the script made a fatal error.
Definition: script_fatalerror.hpp:16
AI_LIBRARY_DIR
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
Definition: fileio_type.h:120
script_fatalerror.hpp
squirrel_std.hpp
Squirrel::IsSuspended
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition: squirrel.cpp:794
Squirrel::print_func
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
Definition: squirrel.hpp:31
Utf8Decode
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition: string.cpp:438
Squirrel::LoadFile
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
Definition: squirrel.cpp:621
Squirrel::CompileError
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
Definition: squirrel.cpp:204
Squirrel::DecreaseOps
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
Squirrel::CreateClassInstance
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
SQFile
Definition: squirrel.cpp:549
FioFCloseFile
void FioFCloseFile(FILE *f)
Close a file in a safe way.
Definition: fileio.cpp:148
_squirrel_allocator
ScriptAllocator * _squirrel_allocator
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
Definition: squirrel.cpp:188
Squirrel::_RunError
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
Definition: squirrel.cpp:252