@mikrojs/native 0.8.0 → 0.10.0-pr-134.g682936a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/include/mikrojs/mikrojs.h +12 -0
- package/include/mikrojs/platform.h +4 -0
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
- package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
- package/src/mik_app_config.cpp +13 -1
- package/src/mik_text_encoding.cpp +135 -61
- package/src/mikrojs.cpp +8 -0
|
@@ -29,8 +29,20 @@ typedef enum MIKLogFlush {
|
|
|
29
29
|
MIK_LOG_FLUSH_LINE = 1,
|
|
30
30
|
} MIKLogFlush;
|
|
31
31
|
|
|
32
|
+
/* What the device does after an uncaught exception (a "panic"). Mirror of
|
|
33
|
+
* the `onPanic.mode` field in the host-side TS config. */
|
|
34
|
+
typedef enum MIKPanicMode {
|
|
35
|
+
MIK_PANIC_RESTART = 0, /* default: reboot after the grace window */
|
|
36
|
+
MIK_PANIC_DEEP_SLEEP = 1, /* deep-sleep panic_sleep_duration_ms; wake reboots */
|
|
37
|
+
} MIKPanicMode;
|
|
38
|
+
|
|
32
39
|
typedef struct MIKConfig {
|
|
40
|
+
/* Grace window after a panic, awake and reachable, before the action
|
|
41
|
+
* fires (applies to both modes). */
|
|
33
42
|
int panic_restart_delay_ms;
|
|
43
|
+
MIKPanicMode panic_mode;
|
|
44
|
+
/* Deep-sleep length for MIK_PANIC_DEEP_SLEEP; ignored otherwise. */
|
|
45
|
+
int panic_sleep_duration_ms;
|
|
34
46
|
size_t stack_size;
|
|
35
47
|
uint32_t mem_reserved;
|
|
36
48
|
uint32_t fs_read_max; /* 0 = keep runtime default (65536) */
|
|
@@ -14,6 +14,10 @@ typedef struct MIKPlatform {
|
|
|
14
14
|
int64_t (*get_rtc_us)(void); /* RTC timer, survives deep sleep */
|
|
15
15
|
uint32_t (*random)(void);
|
|
16
16
|
void (*restart)(void);
|
|
17
|
+
/** Enter deep sleep for `us` microseconds; the timer wake reboots the
|
|
18
|
+
* chip, so this never returns on hardware. NULL on platforms without
|
|
19
|
+
* deep sleep (hosts), where callers fall back to restart(). */
|
|
20
|
+
void (*deep_sleep_us)(uint64_t us);
|
|
17
21
|
void (*yield)(void);
|
|
18
22
|
size_t (*get_free_system_mem)(void);
|
|
19
23
|
size_t (*get_min_free_system_mem)(void); /* All-time low watermark */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikrojs/native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-pr-134.g682936a",
|
|
4
4
|
"description": "Mikro.js C++ runtime library and Node.js native addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esp32",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"cmake-js": "^8.0.0",
|
|
79
79
|
"node-addon-api": "^8.7.0",
|
|
80
80
|
"node-gyp-build": "^4.8.4",
|
|
81
|
-
"@mikrojs/quickjs": "0.
|
|
81
|
+
"@mikrojs/quickjs": "0.10.0-pr-134.g682936a"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@swc/core": "^1.15.30",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/mik_app_config.cpp
CHANGED
|
@@ -13,6 +13,8 @@ static const char* TAG = "mik_app_config";
|
|
|
13
13
|
|
|
14
14
|
void MIK_DefaultConfig(MIKConfig* config) {
|
|
15
15
|
config->panic_restart_delay_ms = 1000;
|
|
16
|
+
config->panic_mode = MIK_PANIC_RESTART;
|
|
17
|
+
config->panic_sleep_duration_ms = 0;
|
|
16
18
|
config->stack_size = 0;
|
|
17
19
|
config->mem_reserved = 64 * 1024;
|
|
18
20
|
config->fs_read_max = 0; /* 0 = runtime default (65536) */
|
|
@@ -318,9 +320,19 @@ int MIK_LoadConfig(const char* base_path, MIKConfig* config) {
|
|
|
318
320
|
if (buf) {
|
|
319
321
|
double num_val;
|
|
320
322
|
|
|
321
|
-
if (mik__json_get_number(buf, "
|
|
323
|
+
if (mik__json_get_number(buf, "onPanic.delay", &num_val)) {
|
|
322
324
|
config->panic_restart_delay_ms = (int)num_val;
|
|
323
325
|
}
|
|
326
|
+
char panic_mode_str[16];
|
|
327
|
+
if (mik__json_get_string(buf, "onPanic.mode", panic_mode_str,
|
|
328
|
+
sizeof(panic_mode_str))) {
|
|
329
|
+
config->panic_mode = strcmp(panic_mode_str, "deepSleep") == 0
|
|
330
|
+
? MIK_PANIC_DEEP_SLEEP
|
|
331
|
+
: MIK_PANIC_RESTART;
|
|
332
|
+
}
|
|
333
|
+
if (mik__json_get_number(buf, "onPanic.duration", &num_val)) {
|
|
334
|
+
config->panic_sleep_duration_ms = (int)num_val;
|
|
335
|
+
}
|
|
324
336
|
if (mik__json_get_number(buf, "stackSize", &num_val)) {
|
|
325
337
|
config->stack_size = (size_t)num_val;
|
|
326
338
|
}
|
|
@@ -242,44 +242,138 @@ static const JSCFunctionListEntry mik__text_decoder_proto_funcs[] = {
|
|
|
242
242
|
|
|
243
243
|
static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
/* The base64 decoder below is ported from QuickJS-NG (quickjs.c, `b64_decode`
|
|
246
|
+
* and its lookup tables), which implements the WHATWG forgiving-base64 decode
|
|
247
|
+
* algorithm. QuickJS-NG is MIT licensed:
|
|
248
|
+
* Copyright (c) 2017-2024 Fabrice Bellard, Charlie Gordon and contributors.
|
|
249
|
+
* We use only the standard alphabet (atob/btoa never decode base64url) and
|
|
250
|
+
* surface decode failures as our own error type rather than a DOMException. */
|
|
251
|
+
enum { K_VAL = 1u, K_WS = 2u };
|
|
252
|
+
|
|
253
|
+
/* Sextet value per base64 character (0 for non-base64 bytes; validity is gated
|
|
254
|
+
* by b64_flags below). Positional initializer to stay standard C++. */
|
|
255
|
+
static const uint8_t b64_val[256] = {
|
|
246
256
|
/* clang-format off */
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
258
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
259
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,62, 0, 0, 0,63,
|
|
260
|
+
52,53,54,55,56,57,58,59,60,61, 0, 0, 0, 0, 0, 0,
|
|
261
|
+
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
|
|
262
|
+
15,16,17,18,19,20,21,22,23,24,25, 0, 0, 0, 0, 0,
|
|
263
|
+
0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
|
|
264
|
+
41,42,43,44,45,46,47,48,49,50,51, 0, 0, 0, 0, 0,
|
|
265
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
266
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
267
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
268
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
269
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
270
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
271
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
272
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
263
273
|
/* clang-format on */
|
|
264
274
|
};
|
|
265
275
|
|
|
276
|
+
/* Per-byte class: K_WS (2) for ASCII whitespace, K_VAL (1) for the 64 standard
|
|
277
|
+
* base64 alphabet characters, 0 (invalid) otherwise. */
|
|
278
|
+
static const char b64_flags[256] = {
|
|
279
|
+
/* clang-format off */
|
|
280
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 0, 0,
|
|
281
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
282
|
+
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
|
|
283
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
284
|
+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
285
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
|
286
|
+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
287
|
+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
|
288
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
289
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
290
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
291
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
292
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
293
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
294
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
295
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
296
|
+
/* clang-format on */
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/* Implements https://infra.spec.whatwg.org/#forgiving-base64-decode.
|
|
300
|
+
* Writes decoded bytes to dst and returns the count; sets *err on malformed
|
|
301
|
+
* input. dst must have room for at least (len / 4) * 3 + 3 bytes. */
|
|
302
|
+
static size_t b64_decode(const char* src, size_t len, uint8_t* dst, int* err) {
|
|
303
|
+
size_t i, j;
|
|
304
|
+
uint32_t acc;
|
|
305
|
+
int seen, pad;
|
|
306
|
+
unsigned ch;
|
|
307
|
+
|
|
308
|
+
acc = 0;
|
|
309
|
+
seen = 0;
|
|
310
|
+
for (i = 0, j = 0; i < len; i++) {
|
|
311
|
+
ch = (unsigned char)src[i];
|
|
312
|
+
if ((b64_flags[ch] & K_WS)) continue;
|
|
313
|
+
if (!(b64_flags[ch] & K_VAL)) break;
|
|
314
|
+
acc = (acc << 6) | b64_val[ch];
|
|
315
|
+
seen++;
|
|
316
|
+
if (seen == 4) {
|
|
317
|
+
dst[j++] = (acc >> 16) & 0xFF;
|
|
318
|
+
dst[j++] = (acc >> 8) & 0xFF;
|
|
319
|
+
dst[j++] = acc & 0xFF;
|
|
320
|
+
seen = 0;
|
|
321
|
+
acc = 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (seen != 0) {
|
|
326
|
+
if (seen == 3) {
|
|
327
|
+
dst[j++] = (acc >> 10) & 0xFF;
|
|
328
|
+
dst[j++] = (acc >> 2) & 0xFF;
|
|
329
|
+
} else if (seen == 2) {
|
|
330
|
+
dst[j++] = (acc >> 4) & 0xFF;
|
|
331
|
+
} else {
|
|
332
|
+
*err = 1;
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
for (pad = 0; i < len; i++) {
|
|
336
|
+
ch = (unsigned char)src[i];
|
|
337
|
+
if (pad < 2 && ch == '=') {
|
|
338
|
+
pad++;
|
|
339
|
+
} else if (!(b64_flags[ch] & K_WS)) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (pad != 0 && seen + pad != 4) {
|
|
344
|
+
*err = 1;
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
*err = i < len;
|
|
350
|
+
return j;
|
|
351
|
+
}
|
|
352
|
+
|
|
266
353
|
/* btoa: encode a binary string to base64.
|
|
267
|
-
* Per the web spec,
|
|
268
|
-
*
|
|
269
|
-
*
|
|
354
|
+
* Per the web spec, the argument is first coerced to a string (ToString), then
|
|
355
|
+
* each character's code point is treated as a raw byte. We iterate by code
|
|
356
|
+
* point, not by UTF-8 bytes, since JS_ToCStringLen would turn e.g. U+0080 into
|
|
357
|
+
* two bytes (\xC2\x80). */
|
|
270
358
|
static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
271
|
-
JSValue str_val = argv[0];
|
|
359
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
360
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
361
|
+
|
|
272
362
|
JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
|
|
273
363
|
int64_t len;
|
|
274
364
|
if (JS_ToInt64(ctx, &len, len_val)) {
|
|
275
365
|
JS_FreeValue(ctx, len_val);
|
|
366
|
+
JS_FreeValue(ctx, str_val);
|
|
276
367
|
return JS_EXCEPTION;
|
|
277
368
|
}
|
|
278
369
|
JS_FreeValue(ctx, len_val);
|
|
279
370
|
|
|
280
371
|
/* Extract code points into a byte buffer, validating latin1 range */
|
|
281
372
|
uint8_t* bytes = static_cast<uint8_t*>(js_malloc(ctx, len > 0 ? len : 1));
|
|
282
|
-
if (!bytes)
|
|
373
|
+
if (!bytes) {
|
|
374
|
+
JS_FreeValue(ctx, str_val);
|
|
375
|
+
return JS_EXCEPTION;
|
|
376
|
+
}
|
|
283
377
|
|
|
284
378
|
JSAtom charCodeAt_atom = JS_NewAtom(ctx, "charCodeAt");
|
|
285
379
|
for (int64_t i = 0; i < len; i++) {
|
|
@@ -289,6 +383,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
289
383
|
if (JS_IsException(code)) {
|
|
290
384
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
291
385
|
js_free(ctx, bytes);
|
|
386
|
+
JS_FreeValue(ctx, str_val);
|
|
292
387
|
return JS_EXCEPTION;
|
|
293
388
|
}
|
|
294
389
|
int32_t cp;
|
|
@@ -297,6 +392,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
297
392
|
if (cp > 0xFF) {
|
|
298
393
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
299
394
|
js_free(ctx, bytes);
|
|
395
|
+
JS_FreeValue(ctx, str_val);
|
|
300
396
|
return JS_ThrowRangeError(ctx,
|
|
301
397
|
"The string to be encoded contains characters outside of the "
|
|
302
398
|
"Latin1 range");
|
|
@@ -304,6 +400,7 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
304
400
|
bytes[i] = (uint8_t)cp;
|
|
305
401
|
}
|
|
306
402
|
JS_FreeAtom(ctx, charCodeAt_atom);
|
|
403
|
+
JS_FreeValue(ctx, str_val);
|
|
307
404
|
|
|
308
405
|
size_t out_len = 4 * ((len + 2) / 3);
|
|
309
406
|
char* out = static_cast<char*>(js_malloc(ctx, out_len + 1));
|
|
@@ -332,55 +429,32 @@ static JSValue mik__btoa(JSContext* ctx, JSValue this_val, int argc, JSValue* ar
|
|
|
332
429
|
return ret;
|
|
333
430
|
}
|
|
334
431
|
|
|
335
|
-
/* atob: decode a base64 string to a binary string
|
|
432
|
+
/* atob: decode a base64 string to a binary string. The argument is coerced to
|
|
433
|
+
* a string (ToString) per spec, then decoded via the ported forgiving-base64
|
|
434
|
+
* decoder above. */
|
|
336
435
|
static JSValue mik__atob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
436
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
437
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
337
438
|
size_t len;
|
|
338
|
-
const char* str = JS_ToCStringLen(ctx, &len,
|
|
439
|
+
const char* str = JS_ToCStringLen(ctx, &len, str_val);
|
|
440
|
+
JS_FreeValue(ctx, str_val);
|
|
339
441
|
if (!str) return JS_EXCEPTION;
|
|
340
442
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
char* out = static_cast<char*>(js_malloc(ctx, out_cap));
|
|
443
|
+
size_t out_cap = (len / 4) * 3 + 3;
|
|
444
|
+
uint8_t* out = static_cast<uint8_t*>(js_malloc(ctx, out_cap));
|
|
344
445
|
if (!out) {
|
|
345
446
|
JS_FreeCString(ctx, str);
|
|
346
447
|
return JS_EXCEPTION;
|
|
347
448
|
}
|
|
348
449
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
size_t j = 0;
|
|
352
|
-
int pad = 0;
|
|
353
|
-
|
|
354
|
-
for (size_t i = 0; i < len; i++) {
|
|
355
|
-
char ch = str[i];
|
|
356
|
-
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f') continue;
|
|
357
|
-
if (ch == '=') {
|
|
358
|
-
pad++;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (pad > 0) {
|
|
362
|
-
/* Data after padding */
|
|
363
|
-
js_free(ctx, out);
|
|
364
|
-
JS_FreeCString(ctx, str);
|
|
365
|
-
return JS_ThrowSyntaxError(ctx,
|
|
366
|
-
"The string to be decoded is not correctly encoded");
|
|
367
|
-
}
|
|
368
|
-
uint8_t val = b64_decode_table[(uint8_t)ch];
|
|
369
|
-
if (val == 255) {
|
|
370
|
-
js_free(ctx, out);
|
|
371
|
-
JS_FreeCString(ctx, str);
|
|
372
|
-
return JS_ThrowSyntaxError(ctx,
|
|
373
|
-
"The string to be decoded is not correctly encoded");
|
|
374
|
-
}
|
|
375
|
-
accum = (accum << 6) | val;
|
|
376
|
-
bits += 6;
|
|
377
|
-
if (bits >= 8) {
|
|
378
|
-
bits -= 8;
|
|
379
|
-
out[j++] = (char)((accum >> bits) & 0xFF);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
450
|
+
int err = 0;
|
|
451
|
+
size_t j = b64_decode(str, len, out, &err);
|
|
383
452
|
JS_FreeCString(ctx, str);
|
|
453
|
+
if (err) {
|
|
454
|
+
js_free(ctx, out);
|
|
455
|
+
return JS_ThrowSyntaxError(ctx,
|
|
456
|
+
"The string to be decoded is not correctly encoded");
|
|
457
|
+
}
|
|
384
458
|
|
|
385
459
|
/* Convert decoded bytes to a JS string. Bytes 0x80-0xFF must become
|
|
386
460
|
* their corresponding Unicode code points (U+0080-U+00FF), which in
|
package/src/mikrojs.cpp
CHANGED
|
@@ -542,6 +542,14 @@ int MIK_Loop(MIKRuntime* mik_rt) {
|
|
|
542
542
|
if (mik_rt->restart_at_us > 0) {
|
|
543
543
|
const MIKPlatform* platform = MIK_GetPlatform();
|
|
544
544
|
if (platform->get_boot_us() >= mik_rt->restart_at_us) {
|
|
545
|
+
/* The grace window has elapsed; take the configured panic action.
|
|
546
|
+
* In deep-sleep mode the timer wake reboots the chip, so the wake
|
|
547
|
+
* IS the restart — and the live --recover window is forfeit by
|
|
548
|
+
* design (the CPU is suspended). If the platform has no deep-sleep
|
|
549
|
+
* hook (hosts), fall through to a plain restart. */
|
|
550
|
+
if (mik_rt->config.panic_mode == MIK_PANIC_DEEP_SLEEP && platform->deep_sleep_us) {
|
|
551
|
+
platform->deep_sleep_us((uint64_t)mik_rt->config.panic_sleep_duration_ms * 1000);
|
|
552
|
+
}
|
|
545
553
|
platform->restart();
|
|
546
554
|
}
|
|
547
555
|
return 0;
|