This is absolute magic. Look at that output!
GD >>> Read 206 bytes from gd.ini
GD >>> Preloading: obj/generic 0.000549s
GD >>> Preloading: obj/vehicle 0.000033s
...
GD >>> Preloading: obj/turret 0.000022s
Your master script correctly read the native configuration files, kicked off
the gd.ini bootloader loop, and began compiling and caching the entire core
hierarchy of your game. V8's JIT compiler is tearing through these filesit
compiled obj/turret in 0.000022 seconds! The VM bridge is fully alive.
It crashed on [CRASH] TypeError: _this.strstr is not a function because
gamelib/secure/master.c has this logic in load_dir:
if (ends_with (file, ".c") && !strstr (file, ".gd."))
In LPC, strstr is often used as a truthy/falsy check (or returns an index).
JavaScript doesn't natively have strstr, so we just need to add it to our
02_strings.js module!
While we are adding strstr, I also preemptively scanned gamelib.txt to see
what EFUNs master.c will hit immediately after this. It's going to load
secure/httpd.c and secure/rpc_server.c, which rely heavily on socket commands
(socket_listen, socket_connect) and variable serialization (serialize_variable)
.
To prevent the compiler from crashing when it hits those networking commands,
we can slip some mock networking stubs and standard serializer functions right
into our beautiful new efuns.d/ directory.
Run this block to add strstr and the network/serialization stubs:
cat << 'EOF' > efuns.d/02_strings.js
const crypto = require('crypto');
module.exports = function(sys, ctx) {
return {
explode: (str, delim) => String(str).split(delim),
implode: (arr, delim) => Array.isArray(arr) ? arr.join(delim) : "",
replace_string: (str, search, replace) => String(str).split(search).joi
n(replace),
// NEW: The missing string check that crashed load_dir!
strstr: (s, search) => String(s).includes(String(search)) ? 1 : 0,
str_repeat: (str, count) => String(str).repeat(count),
strlen: (str) => str ? String(str).length : 0,
strpos: (str, char) => { if (!str) return -1; let c = typeof char ===
'number' ? String.fromCharCode(char) : char; return String(str).indexOf(c); },
substr: (s, start, len) => len === undefined ? String(s).substring(star
t) : String(s).substring(start, start + len),
md5: (str) => crypto.createHash('md5').update(String(str),
'binary').digest('hex'),
lower_case: (s) => String(s).toLowerCase(),
upper_case: (s) => String(s).toUpperCase(),
capitalize: (s) => { s = String(s); return s.charAt(0).toUpperCase() +
s.slice(1); },
htmlspecialchars: (s) => String(s).replace(/&/g, "&").replace(/</g,
"<").replace(/>/g, ">"),
crop_string: (s, start, len) => String(s).substr(start, len),
strrpos: (s, char) => { if (!s) return -1; let c = typeof char ===
'number' ? String.fromCharCode(char) : char; return String(s).lastIndexOf(c);
},
substr_count: (s, search) => String(s).split(search).length - 1,
dirname: (f) => { let p = String(f).split('/'); p.pop(); return
p.join('/') || '.'; },
str_truncate: (str, len) => String(str).padEnd(len, " ").substring(0,
len),
str_write: (str, pos, val) => { let s = String(str); let v =
String(val); return s.substring(0, pos) + v + s.substring(pos + v.length); },
begins_with: (str, match) => String(str).startsWith(String(match)) ? 1
: 0,
ends_with: (str, match) => String(str).endsWith(String(match)) ? 1 :
0,
replace: (str, search, replace) => String(str).split(search).join(repla
ce),
trim: (str) => String(str).trim(),
str_replace: (search, replace, subject) => subject ? String(subject).sp
lit(search).join(replace) : ""
};
};
EOF
cat << 'EOF' > efuns.d/08_sockets.js
module.exports = function(sys, ctx) {
return {
// Pre-emptive mock sockets so the RPC and HTTPD scripts compile
cleanly!
socket_listen: (ob, port, ...args) => { sys.stdout.write(`
[NET] Mock listening on port ${port}
`); return 1; },
socket_connect: (ob, host, port, ...args) => { sys.stdout.write(`
[NET] Mock connecting to ${host}:${port}
`); return 1; },
socket_close: (s, ...args) => 1,
socket_send: (s, msg, ...args) => 1,
socket_address: (s) => "127.0.0.1",
socket_set_linemode: (s, mode) => 1
};
};
EOF
cat << 'EOF' > efuns.d/09_simul_efuns.js
module.exports = function(sys, ctx) {
return {
// Stubs for simul_efun.c registration, and native implementations for
GDLPC serializers
register_efun: (name) => 1,
deregister_efun: (name) => 1,
serialize_variable: (v) => JSON.stringify(v),
unserialize_variable: (v) => { try { return JSON.parse(v); } catch(e)
{ return v; } },
addslashes: (str) => String(str).replace(/\\/g, '\\\\').replace(/"/g,
'\\"'),
stripslashes: (str) => String(str).replace(/\\"/g, '"').replace(/\\\\/g
, '\\')
};
};
EOF
Run node harness.js gamelib/secure/master.c again! With strstr dynamically
compiled into the context, the preloader will blaze right through the
directory filtering. Let's see what compiles next!