OpenTTD Source  20241108-master-g80f628063a
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 
37  size_t allocated_size;
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 
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 
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 
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 
386 {
387  assert(!this->crashed);
388  ScriptAllocatorScope alloc_scope(this);
389  sq_resumeerror(this->vm);
390 }
391 
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 
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);
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  FileHandle file;
552  size_t size;
553  size_t pos;
554 
555 public:
556  SQFile(FileHandle file, size_t size) : file(std::move(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  std::optional<FileHandle> file = std::nullopt;
626  size_t size;
627  if (strncmp(this->GetAPIName(), "AI", 2) == 0) {
628  file = FioFOpenFile(filename, "rb", AI_DIR, &size);
629  if (!file.has_value()) 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.has_value()) file = FioFOpenFile(filename, "rb", GAME_LIBRARY_DIR, &size);
633  } else {
634  NOT_REACHED();
635  }
636 
637  if (!file.has_value()) {
638  return sq_throwerror(vm, "cannot open the file");
639  }
640  unsigned short bom = 0;
641  if (size >= 2) {
642  if (fread(&bom, 1, sizeof(bom), *file) != sizeof(bom)) return sq_throwerror(vm, "cannot read the 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  return sq_throwerror(vm, "cannot seek the file");
650  }
651 
652  SQFile f(std::move(*file), size);
653  if (SQ_SUCCEEDED(sq_readclosure(vm, _io_file_read, &f))) {
654  return SQ_OK;
655  }
656  return sq_throwerror(vm, "Couldn't read bytecode");
657  }
658  case 0xFFFE:
659  /* Either this file is encoded as big-endian and we're on a little-endian
660  * machine, or this file is encoded as little-endian and we're on a big-endian
661  * machine. Either way, swap the bytes of every word we read. */
662  func = _io_file_lexfeed_UCS2_swap;
663  size -= 2; // Skip BOM
664  break;
665  case 0xFEFF:
666  func = _io_file_lexfeed_UCS2_no_swap;
667  size -= 2; // Skip BOM
668  break;
669  case 0xBBEF: // UTF-8
670  case 0xEFBB: { // UTF-8 on big-endian machine
671  /* Similarly, check the file is actually big enough to finish checking BOM */
672  if (size < 3) {
673  return sq_throwerror(vm, "I/O error");
674  }
675  unsigned char uc;
676  if (fread(&uc, 1, sizeof(uc), *file) != sizeof(uc) || uc != 0xBF) {
677  return sq_throwerror(vm, "Unrecognized encoding");
678  }
679  func = _io_file_lexfeed_UTF8;
680  size -= 3; // Skip BOM
681  break;
682  }
683  default: // ASCII
684  func = _io_file_lexfeed_ASCII;
685  /* Account for when we might not have fread'd earlier */
686  if (size >= 2 && fseek(*file, -2, SEEK_CUR) < 0) {
687  return sq_throwerror(vm, "cannot seek the file");
688  }
689  break;
690  }
691 
692  SQFile f(std::move(*file), size);
693  if (SQ_SUCCEEDED(sq_compile(vm, func, &f, filename.c_str(), printerror))) {
694  return SQ_OK;
695  }
696  return SQ_ERROR;
697 }
698 
699 bool Squirrel::LoadScript(HSQUIRRELVM vm, const std::string &script, bool in_root)
700 {
701  ScriptAllocatorScope alloc_scope(this);
702 
703  /* Make sure we are always in the root-table */
704  if (in_root) sq_pushroottable(vm);
705 
706  SQInteger ops_left = vm->_ops_till_suspend;
707  /* Load and run the script */
708  if (SQ_SUCCEEDED(LoadFile(vm, script, SQTrue))) {
709  sq_push(vm, -2);
710  if (SQ_SUCCEEDED(sq_call(vm, 1, SQFalse, SQTrue, 100000))) {
711  sq_pop(vm, 1);
712  /* After compiling the file we want to reset the amount of opcodes. */
713  vm->_ops_till_suspend = ops_left;
714  return true;
715  }
716  }
717 
718  vm->_ops_till_suspend = ops_left;
719  Debug(misc, 0, "[squirrel] Failed to compile '{}'", script);
720  return false;
721 }
722 
723 bool Squirrel::LoadScript(const std::string &script)
724 {
725  return LoadScript(this->vm, script);
726 }
727 
728 Squirrel::~Squirrel()
729 {
730  this->Uninitialize();
731 }
732 
734 {
735  ScriptAllocatorScope alloc_scope(this);
736 
737  /* Remove the delegation */
738  sq_pushroottable(this->vm);
739  sq_pushnull(this->vm);
740  sq_setdelegate(this->vm, -2);
741  sq_pop(this->vm, 1);
742 
743  /* Clean up the stuff */
744  sq_pop(this->vm, 1);
745  sq_close(this->vm);
746 
747  assert(this->allocator->allocated_size == 0);
748 
749  /* Reset memory allocation errors. */
750  this->allocator->error_thrown = false;
751 }
752 
754 {
755  this->Uninitialize();
756  this->Initialize();
757 }
758 
759 void Squirrel::InsertResult(bool result)
760 {
761  ScriptAllocatorScope alloc_scope(this);
762 
763  sq_pushbool(this->vm, result);
764  if (this->IsSuspended()) { // Called before resuming a suspended script?
765  vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
766  vm->Pop();
767  }
768 }
769 
770 void Squirrel::InsertResult(int result)
771 {
772  ScriptAllocatorScope alloc_scope(this);
773 
774  sq_pushinteger(this->vm, result);
775  if (this->IsSuspended()) { // Called before resuming a suspended script?
776  vm->GetAt(vm->_stackbase + vm->_suspended_target) = vm->GetUp(-1);
777  vm->Pop();
778  }
779 }
780 
781 /* static */ void Squirrel::DecreaseOps(HSQUIRRELVM vm, int ops)
782 {
783  vm->DecreaseOps(ops);
784 }
785 
787 {
788  return this->vm->_suspended != 0;
789 }
790 
792 {
793  return this->crashed;
794 }
795 
797 {
798  this->crashed = true;
799 }
800 
802 {
803  ScriptAllocatorScope alloc_scope(this);
804  return sq_can_suspend(this->vm);
805 }
806 
808 {
809  return this->vm->_ops_till_suspend;
810 }
void MallocError(size_t size)
Function to exit with an error message after malloc() or calloc() have failed.
Definition: alloc_func.cpp:20
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:337
bool Resume(int suspend=-1)
Resume a VM when it was suspended via a throw.
Definition: squirrel.cpp:363
void * global_pointer
Can be set by who ever initializes Squirrel.
Definition: squirrel.hpp:30
static void ErrorPrintFunc(HSQUIRRELVM vm, const std::string &s)
If an error has to be print, this function is called.
Definition: squirrel.cpp:219
void CollectGarbage()
Tell the VM to do a garbage collection run.
Definition: squirrel.cpp:392
HSQUIRRELVM vm
The VirtualMachine instance for squirrel.
Definition: squirrel.hpp:29
void Reset()
Completely reset the engine; start from scratch.
Definition: squirrel.cpp:753
SQInteger GetOpsTillSuspend()
How many operations can we execute till suspension?
Definition: squirrel.cpp:807
static const char * ObjectToString(HSQOBJECT *ptr)
Convert a Squirrel-object to a string.
Definition: squirrel.hpp:205
bool LoadScript(const std::string &script)
Load a script.
Definition: squirrel.cpp:723
bool IsSuspended()
Did the squirrel code suspend or return normally.
Definition: squirrel.cpp:786
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
static void RunError(HSQUIRRELVM vm, const SQChar *error)
The RunError handler.
Definition: squirrel.cpp:230
bool CanSuspend()
Are we allowed to suspend the squirrel script at this moment?
Definition: squirrel.cpp:801
SQPrintFunc * print_func
Points to either nullptr, or a custom print handler.
Definition: squirrel.hpp:31
static void CompileError(HSQUIRRELVM vm, const SQChar *desc, const SQChar *source, SQInteger line, SQInteger column)
The CompileError handler.
Definition: squirrel.cpp:204
static SQInteger _RunError(HSQUIRRELVM vm)
The internal RunError handler.
Definition: squirrel.cpp:252
size_t GetAllocatedMemory() const noexcept
Get number of bytes allocated by this VM.
Definition: squirrel.cpp:197
static void DecreaseOps(HSQUIRRELVM vm, int amount)
Tell the VM to remove amount ops from the number of ops till suspend.
Definition: squirrel.cpp:781
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
bool HasScriptCrashed()
Find out if the squirrel script made an error before.
Definition: squirrel.cpp:791
void CrashOccurred()
Set the script status to crashed.
Definition: squirrel.cpp:796
SQRESULT LoadFile(HSQUIRRELVM vm, const std::string &filename, SQBool printerror)
Load a file to a given VM.
Definition: squirrel.cpp:621
const char * GetAPIName()
Get the API name.
Definition: squirrel.hpp:45
void Initialize()
Perform all initialization steps to create the engine.
Definition: squirrel.cpp:519
bool crashed
True if the squirrel script made an error.
Definition: squirrel.hpp:32
static bool ObjectToBool(HSQOBJECT *ptr)
Convert a Squirrel-object to a bool.
Definition: squirrel.hpp:215
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
void AddClassBegin(const char *class_name)
Adds a class to the global scope.
Definition: squirrel.cpp:313
int overdrawn_ops
The amount of operations we have overdrawn.
Definition: squirrel.hpp:33
void Uninitialize()
Perform all the cleanups for the engine.
Definition: squirrel.cpp:733
void ResumeError()
Resume the VM with an error so it prints a stack trace.
Definition: squirrel.cpp:385
void AddConst(const char *var_name, int value)
Adds a const to the stack.
Definition: squirrel.cpp:295
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
bool MethodExists(HSQOBJECT instance, const char *method_name)
Check if a method exists in an instance.
Definition: squirrel.cpp:345
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 int ObjectToInteger(HSQOBJECT *ptr)
Convert a Squirrel-object to an integer.
Definition: squirrel.hpp:210
std::unique_ptr< ScriptAllocator > allocator
Allocator object used by this script.
Definition: squirrel.hpp:35
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
std::optional< FileHandle > 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:242
@ AI_LIBRARY_DIR
Subdirectory for all AI libraries.
Definition: fileio_type.h:127
@ GAME_LIBRARY_DIR
Subdirectory for all GS libraries.
Definition: fileio_type.h:129
@ AI_DIR
Subdirectory for all AI files.
Definition: fileio_type.h:126
@ GAME_DIR
Subdirectory for all game scripts.
Definition: fileio_type.h:128
The definition of Script_FatalError.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition: settings.cpp:57
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
void squirrel_register_global_std(Squirrel *engine)
Register all standard functions that are available on first startup.
defines the Squirrel Standard Function class
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:334
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:107
size_t Utf8Decode(char32_t *c, const char *s)
Decode and consume the next UTF-8 encoded character.
Definition: string.cpp:419
int8_t Utf8EncodedCharLen(char c)
Return the length of an UTF-8 encoded value based on a single char.
Definition: string_func.h:124
ScriptSettings script
settings for scripts
In the memory allocator for Squirrel we want to directly use malloc/realloc, so when the OS does not ...
Definition: squirrel.cpp:36
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 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
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