Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
49 kB · 897 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898#include <algorithm>#include <array>#include <cmath>#include <cstdint>#include <cstring>#include <memory>#include <string>#include "QuickJsEngine.hpp"extern "C" {#include "third_party/quickjs-ng/quickjs.h"}
#include <stdexcept>
namespace ac::xbox {namespace {struct CallScope { Api* api; };
bool ValidOskiewarMatchId(std::string_view value) { if (value.substr(0, 3) != "ow-") return false; if (value.size() == 23) { for (std::size_t index = 3; index < value.size(); ++index) { const auto character = value[index]; if (!((character >= 'a' && character <= 'z') || character == '-')) return false; } return value[9] == '-' && value[16] == '-'; } const auto name = value.substr(3); std::size_t digit = 0; while (digit < name.size() && name[digit] >= 'a' && name[digit] <= 'z') ++digit; if (digit < 4 || digit > 7 || name.size() - digit < 1 || name.size() - digit > 3) return false; for (; digit < name.size(); ++digit) if (name[digit] < '0' || name[digit] > '9') return false; return true;}
JSValue Wipe(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); int32_t r = 0, g = 0, b = 0; if (!scope || !scope->api || argc < 3 || JS_ToInt32(context, &r, argv[0]) || JS_ToInt32(context, &g, argv[1]) || JS_ToInt32(context, &b, argv[2])) return JS_EXCEPTION; scope->api->graphics.wipe({static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}); return JS_UNDEFINED;}
JSValue Synth(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double frequency = 440.0, duration = .05; if (!scope || !scope->api || argc < 1 || JS_ToFloat64(context, &frequency, argv[0])) return JS_EXCEPTION; if (argc > 1) JS_ToFloat64(context, &duration, argv[1]); SynthVoice voice; voice.frequency_hz = static_cast<float>(frequency); voice.duration_s = static_cast<float>(duration); scope->api->sound.synth(voice); return JS_UNDEFINED;}
JSValue Drum(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* name = JS_ToCString(context, argv[0]); if (!name) return JS_EXCEPTION; double velocity = 1.0, pan = 0.0; if (argc > 1 && JS_ToFloat64(context, &velocity, argv[1])) { JS_FreeCString(context, name); return JS_EXCEPTION; } if (argc > 2 && JS_ToFloat64(context, &pan, argv[2])) { JS_FreeCString(context, name); return JS_EXCEPTION; } const std::string drum(name); JS_FreeCString(context, name); static constexpr std::string_view allowed[] = {"kick", "snare", "clap", "hat", "block"}; if (std::find(std::begin(allowed), std::end(allowed), drum) == std::end(allowed)) return JS_ThrowRangeError(context, "unknown drum"); scope->api->sound.drum(drum, static_cast<float>(velocity), static_cast<float>(pan)); return JS_UNDEFINED;}
JSValue Oscillator(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double frequency = 220.0, volume = .12; if (!scope || !scope->api || argc < 1 || JS_ToFloat64(context, &frequency, argv[0])) return JS_EXCEPTION; if (argc > 1 && JS_ToFloat64(context, &volume, argv[1])) return JS_EXCEPTION; scope->api->sound.oscillator(static_cast<float>(frequency), static_cast<float>(volume)); return JS_UNDEFINED;}
JSValue OscillatorStop(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; scope->api->sound.oscillator_stop(); return JS_UNDEFINED;}
JSValue Write(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* value = JS_ToCString(context, argv[0]); if (!value) return JS_EXCEPTION; double x = 80, y = 80, size = 64; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); int32_t r = 255, g = 255, b = 255; if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.write({value, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, value); return JS_UNDEFINED;}
JSValue Box(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 0, height = 0; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 4 || JS_ToFloat64(context, &x, argv[0]) || JS_ToFloat64(context, &y, argv[1]) || JS_ToFloat64(context, &width, argv[2]) || JS_ToFloat64(context, &height, argv[3])) return JS_EXCEPTION; if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.box({static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue Line(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x1 = 0, y1 = 0, x2 = 0, y2 = 0, width = 1; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 4 || JS_ToFloat64(context, &x1, argv[0]) || JS_ToFloat64(context, &y1, argv[1]) || JS_ToFloat64(context, &x2, argv[2]) || JS_ToFloat64(context, &y2, argv[3])) return JS_EXCEPTION; if (argc > 4) JS_ToFloat64(context, &width, argv[4]); if (argc > 5) JS_ToInt32(context, &r, argv[5]); if (argc > 6) JS_ToInt32(context, &g, argv[6]); if (argc > 7) JS_ToInt32(context, &b, argv[7]); scope->api->graphics.line({static_cast<float>(x1), static_cast<float>(y1), static_cast<float>(x2), static_cast<float>(y2), static_cast<float>(width), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue TriangleFill(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x1 = 0, y1 = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0; int32_t r = 255, g = 255, b = 255; if (!scope || !scope->api || argc < 6 || JS_ToFloat64(context, &x1, argv[0]) || JS_ToFloat64(context, &y1, argv[1]) || JS_ToFloat64(context, &x2, argv[2]) || JS_ToFloat64(context, &y2, argv[3]) || JS_ToFloat64(context, &x3, argv[4]) || JS_ToFloat64(context, &y3, argv[5])) return JS_EXCEPTION; if (argc > 6) JS_ToInt32(context, &r, argv[6]); if (argc > 7) JS_ToInt32(context, &g, argv[7]); if (argc > 8) JS_ToInt32(context, &b, argv[8]); const std::array<double, 6> coordinates{x1, y1, x2, y2, x3, y3}; if (!std::all_of(coordinates.begin(), coordinates.end(), [](double value) { return std::isfinite(value) && std::abs(value) <= 32768.0; })) return JS_ThrowRangeError(context, "invalid triangle coordinates"); scope->api->graphics.triangle({static_cast<float>(x1), static_cast<float>(y1), 0, static_cast<float>(x2), static_cast<float>(y2), 0, static_cast<float>(x3), static_cast<float>(y3), 0, {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
JSValue Triangle3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); std::array<double, 9> value{}; if (!scope || !scope->api || argc < 9) return JS_EXCEPTION; for (int index = 0; index < 9; ++index) if (JS_ToFloat64(context, &value[index], argv[index])) return JS_EXCEPTION; if (!std::all_of(value.begin(), value.end(), [](double item) { return std::isfinite(item) && std::abs(item) <= 32768.0; })) return JS_ThrowRangeError(context, "invalid 3D triangle coordinates"); int32_t r = 255, g = 255, b = 255; if (argc > 9) JS_ToInt32(context, &r, argv[9]); if (argc > 10) JS_ToInt32(context, &g, argv[10]); if (argc > 11) JS_ToInt32(context, &b, argv[11]); scope->api->graphics.triangle({static_cast<float>(value[0]), static_cast<float>(value[1]), static_cast<float>(value[2]), static_cast<float>(value[3]), static_cast<float>(value[4]), static_cast<float>(value[5]), static_cast<float>(value[6]), static_cast<float>(value[7]), static_cast<float>(value[8]), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); return JS_UNDEFINED;}
// One native boundary crossing for an entire frame's triangle stream. The// Float32Array layout repeats x1,y1,z1,x2,y2,z2,x3,y3,z3,r,g,b. Keeping this// API deliberately flat lets QuickJS fill a reusable buffer without allocating// thousands of argument arrays or calling into C++ once per face.JSValue Triangles3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_ThrowTypeError(context, "triangles3d requires a Float32Array"); if (JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "triangles3d requires a Float32Array");
size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (12 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d length must be a multiple of 12 floats"); } const size_t capacity = byteLength / (12 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } const size_t count = requestedCount; if (count > capacity) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d count exceeds the buffer"); } if (count > 8192) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "triangles3d accepts at most 8192 triangles"); }
const float* values = reinterpret_cast<const float*>(bytes + byteOffset); for (size_t triangle = 0; triangle < count; ++triangle) { const float* value = values + triangle * 12; bool valid = true; for (int index = 0; index < 9; ++index) valid = valid && std::isfinite(value[index]) && std::abs(value[index]) <= 32768.f; if (!valid) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid 3D triangle coordinates"); } const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; scope->api->graphics.triangle({value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], {color(value[9]), color(value[10]), color(value[11]), 255}}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(count));}
// Screen-facing point-sampled sprites. Layout repeats x,y,z,size,r,g,b,frame.JSValue Sprites3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1 || JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "sprites3d requires a Float32Array"); size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (8 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "sprites3d length must be a multiple of 8 floats"); } const size_t capacity = byteLength / (8 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } if (requestedCount > capacity || requestedCount > 512) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "sprites3d count exceeds the buffer or 512 sprites"); } const float* values = reinterpret_cast<const float*>(bytes + byteOffset); const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; for (size_t sprite = 0; sprite < requestedCount; ++sprite) { const float* value = values + sprite * 8; bool valid = true; for (int index = 0; index < 4; ++index) valid = valid && std::isfinite(value[index]) && std::abs(value[index]) <= 32768.f; if (!valid || value[3] <= 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid sprite coordinates or size"); } scope->api->graphics.sprite({value[0], value[1], value[2], value[3], {color(value[4]), color(value[5]), color(value[6]), 255}, static_cast<uint8_t>((std::max)(0.f, (std::min)(1.f, value[7])))}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(requestedCount));}
// UV-mapped triangle stream. Layout repeats three x,y,z,u,v vertices followed// by an RGB light tint: 18 floats per triangle.JSValue TexturedTriangles3d(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1 || JS_GetTypedArrayType(argv[0]) != JS_TYPED_ARRAY_FLOAT32) return JS_ThrowTypeError(context, "texturedTriangles3d requires a Float32Array"); size_t byteOffset = 0, byteLength = 0, bytesPerElement = 0; JSValue arrayBuffer = JS_GetTypedArrayBuffer( context, argv[0], &byteOffset, &byteLength, &bytesPerElement); if (JS_IsException(arrayBuffer)) return arrayBuffer; size_t bufferLength = 0; uint8_t* bytes = JS_GetArrayBuffer(context, &bufferLength, arrayBuffer); if (!bytes || bytesPerElement != sizeof(float) || byteOffset > bufferLength || byteLength > bufferLength - byteOffset || byteLength % (18 * sizeof(float)) != 0) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "texturedTriangles3d length must be a multiple of 18 floats"); } const size_t capacity = byteLength / (18 * sizeof(float)); uint32_t requestedCount = static_cast<uint32_t>(capacity); if (argc > 1 && JS_ToUint32(context, &requestedCount, argv[1])) { JS_FreeValue(context, arrayBuffer); return JS_EXCEPTION; } if (requestedCount > capacity || requestedCount > 2048) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "texturedTriangles3d count exceeds the buffer or 2048 triangles"); } const float* values = reinterpret_cast<const float*>(bytes + byteOffset); const auto color = [](float channel) { return static_cast<uint8_t>((std::max)(0.f, (std::min)(255.f, channel))); }; for (size_t triangle = 0; triangle < requestedCount; ++triangle) { const float* value = values + triangle * 18; bool valid = true; for (int vertex = 0; vertex < 3; ++vertex) { const int base = vertex * 5; for (int index = 0; index < 5; ++index) valid = valid && std::isfinite(value[base + index]) && std::abs(value[base + index]) <= 32768.f; } if (!valid) { JS_FreeValue(context, arrayBuffer); return JS_ThrowRangeError(context, "invalid textured triangle coordinates"); } scope->api->graphics.textured_triangle({ value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], {color(value[15]), color(value[16]), color(value[17]), 255}}); } JS_FreeValue(context, arrayBuffer); return JS_NewInt32(context, static_cast<int32_t>(requestedCount));}
JSValue QueueSystemWrite(JSContext* context, int argc, JSValueConst* argv, const char* family) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* value = JS_ToCString(context, argv[0]); if (!value) return JS_EXCEPTION; double x = 80, y = 80, size = 48; int32_t r = 255, g = 255, b = 255; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.system_write({value, family, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, value); return JS_UNDEFINED;}
JSValue SystemWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "Segoe UI");}
JSValue YwftWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "YWFT Processing");}
JSValue ComicWrite(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { return QueueSystemWrite(context, argc, argv, "Comic Relief");}
JSValue SystemGlyph(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 1) return JS_EXCEPTION; const char* name = JS_ToCString(context, argv[0]); if (!name) return JS_EXCEPTION; double x = 80, y = 80, size = 64; int32_t r = 255, g = 255, b = 255; if (argc > 1) JS_ToFloat64(context, &x, argv[1]); if (argc > 2) JS_ToFloat64(context, &y, argv[2]); if (argc > 3) JS_ToFloat64(context, &size, argv[3]); if (argc > 4) JS_ToInt32(context, &r, argv[4]); if (argc > 5) JS_ToInt32(context, &g, argv[5]); if (argc > 6) JS_ToInt32(context, &b, argv[6]); scope->api->graphics.system_glyph({name, static_cast<float>(x), static_cast<float>(y), static_cast<float>(size), {static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), 255}}); JS_FreeCString(context, name); return JS_UNDEFINED;}
JSValue Painting(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 640, height = 480; if (!scope || !scope->api) return JS_EXCEPTION; if (argc > 0) JS_ToFloat64(context, &x, argv[0]); if (argc > 1) JS_ToFloat64(context, &y, argv[1]); if (argc > 2) JS_ToFloat64(context, &width, argv[2]); if (argc > 3) JS_ToFloat64(context, &height, argv[3]); scope->api->graphics.image({"latest-painting", static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height)}); return JS_UNDEFINED;}
JSValue StampPainting(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || argc < 3) return JS_EXCEPTION; const char* source = JS_ToCString(context, argv[0]); if (!source) return JS_EXCEPTION; std::string code(source); JS_FreeCString(context, source); const bool valid = code.size() >= 2 && code.size() <= 9 && code.front() == '#' && std::all_of(code.begin() + 1, code.end(), [](unsigned char character) { return (character >= '0' && character <= '9') || (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z'); }); if (!valid) return JS_ThrowTypeError(context, "painting code must match #[0-9A-Za-z]{1,8}"); double x = 0, y = 0, scale = 1; if (JS_ToFloat64(context, &x, argv[1]) || JS_ToFloat64(context, &y, argv[2]) || (argc > 3 && JS_ToFloat64(context, &scale, argv[3]))) return JS_EXCEPTION; if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(scale) || std::abs(scale) > 8.0) return JS_ThrowRangeError(context, "invalid painting transform"); scope->api->graphics.image({code, static_cast<float>(x), static_cast<float>(y), 0, 0, static_cast<float>(scale), true}); return JS_UNDEFINED;}
JSValue Blur(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); int32_t radius = 0; if (!scope || !scope->api || argc < 1 || JS_ToInt32(context, &radius, argv[0])) return JS_EXCEPTION; if (radius < 0 || radius > 16) return JS_ThrowRangeError(context, "blur radius must be between 0 and 16"); scope->api->graphics.blur(static_cast<unsigned>(radius)); return JS_UNDEFINED;}
JSValue Telemetry(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->telemetry || argc < 1) return JS_UNDEFINED; const char* event = JS_ToCString(context, argv[0]); const char* detail = argc > 1 ? JS_ToCString(context, argv[1]) : nullptr; if (!event) return JS_EXCEPTION; std::string line = "JS "; line += event; if (detail) { line += " "; line += detail; } scope->api->telemetry(line); if (detail) JS_FreeCString(context, detail); JS_FreeCString(context, event); return JS_UNDEFINED;}
JSValue GameSignal(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->game_signal || argc < 1) return JS_UNDEFINED; const char* rawEvent = JS_ToCString(context, argv[0]); if (!rawEvent) return JS_EXCEPTION; std::string event(rawEvent); JS_FreeCString(context, rawEvent); if (event.empty() || event.size() > 32 || !std::all_of(event.begin(), event.end(), [](unsigned char character) { return std::isalnum(character) || character == '_' || character == '-'; })) return JS_ThrowRangeError(context, "invalid game signal event"); int32_t player = -1; double value = 0, value2 = 0; if ((argc > 1 && JS_ToInt32(context, &player, argv[1])) || (argc > 2 && JS_ToFloat64(context, &value, argv[2])) || (argc > 3 && JS_ToFloat64(context, &value2, argv[3]))) return JS_EXCEPTION; if (player < -1 || player > 3 || !std::isfinite(value) || !std::isfinite(value2) || std::abs(value) > 1000000 || std::abs(value2) > 1000000) return JS_ThrowRangeError(context, "invalid game signal payload"); scope->api->game_signal(event, player, static_cast<float>(value), static_cast<float>(value2)); return JS_UNDEFINED;}
JSValue SaveReplay(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->replay_save || argc < 1) return JS_UNDEFINED; size_t length = 0; const char* raw = JS_ToCStringLen(context, &length, argv[0]); if (!raw) return JS_EXCEPTION; if (length < 2 || length > 524288 || raw[0] != '{') { JS_FreeCString(context, raw); return JS_ThrowRangeError(context, "replay must be a JSON object under 512 KiB"); } scope->api->replay_save(std::string_view(raw, length)); JS_FreeCString(context, raw); return JS_UNDEFINED;}
JSValue PublishLive(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api || !scope->api->live_publish || argc < 2) return JS_UNDEFINED; size_t matchLength = 0, stateLength = 0; const char* rawMatch = JS_ToCStringLen(context, &matchLength, argv[0]); if (!rawMatch) return JS_EXCEPTION; const char* rawState = JS_ToCStringLen(context, &stateLength, argv[1]); if (!rawState) { JS_FreeCString(context, rawMatch); return JS_EXCEPTION; } const bool valid = ValidOskiewarMatchId( std::string_view(rawMatch, matchLength)) && stateLength >= 2 && stateLength <= 7168 && rawState[0] == '{'; if (!valid) { JS_FreeCString(context, rawState); JS_FreeCString(context, rawMatch); return JS_ThrowRangeError(context, "invalid oskiewar live payload"); } scope->api->live_publish(std::string_view(rawMatch, matchLength), std::string_view(rawState, stateLength)); JS_FreeCString(context, rawState); JS_FreeCString(context, rawMatch); return JS_UNDEFINED;}
JSValue RuntimeInfo(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, scope->api->screen.width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, scope->api->screen.height)); JS_SetPropertyStr(context, result, "renderWidth", JS_NewInt32(context, scope->api->system.render_width)); JS_SetPropertyStr(context, result, "renderHeight", JS_NewInt32(context, scope->api->system.render_height)); JS_SetPropertyStr(context, result, "refreshHz", JS_NewFloat64(context, scope->api->system.refresh_hz)); JS_SetPropertyStr(context, result, "frameMs", JS_NewFloat64(context, scope->api->system.frame_ms)); JS_SetPropertyStr(context, result, "renderCpuMs", JS_NewFloat64(context, scope->api->system.render_cpu_ms)); JS_SetPropertyStr(context, result, "presentMs", JS_NewFloat64(context, scope->api->system.present_ms)); JS_SetPropertyStr(context, result, "antialiasingSamples", JS_NewInt32(context, scope->api->system.antialiasing_samples)); JS_SetPropertyStr(context, result, "antialiasingMode", JS_NewString(context, scope->api->system.antialiasing_mode.c_str())); JS_SetPropertyStr(context, result, "sampleRate", JS_NewInt32(context, scope->api->sound.sample_rate())); JS_SetPropertyStr(context, result, "simCount", JS_NewInt64(context, scope->api->sim_count)); JS_SetPropertyStr(context, result, "paintCount", JS_NewInt64(context, scope->api->paint_count)); JS_SetPropertyStr(context, result, "monotonicUs", JS_NewInt64(context, scope->api->clock.monotonic_us)); JS_SetPropertyStr(context, result, "unixMs", JS_NewInt64(context, scope->api->clock.unix_ms)); JS_SetPropertyStr(context, result, "clockSynced", JS_NewBool(context, scope->api->clock.network_synced)); JS_SetPropertyStr(context, result, "clockOffsetMs", JS_NewInt64(context, scope->api->clock.network_offset_ms)); JS_SetPropertyStr(context, result, "clockRttMs", JS_NewInt32(context, scope->api->clock.network_rtt_ms)); JS_SetPropertyStr(context, result, "clockSyncAgeMs", JS_NewInt64(context, scope->api->clock.network_sync_age_ms)); JS_SetPropertyStr(context, result, "audioLatencyMs", JS_NewFloat64(context, scope->api->audio.output_latency_ms)); JS_SetPropertyStr(context, result, "audioSubmitUs", JS_NewFloat64(context, scope->api->audio.submit_us)); JS_SetPropertyStr(context, result, "inputToAudioUs", JS_NewFloat64(context, scope->api->audio.input_to_submit_us)); JS_SetPropertyStr(context, result, "audioGlitches", JS_NewInt32(context, scope->api->audio.glitches)); JS_SetPropertyStr(context, result, "midiInputs", JS_NewInt32(context, scope->api->audio.midi_inputs)); JS_SetPropertyStr(context, result, "midiEvents", JS_NewInt64(context, scope->api->audio.midi_events)); JS_SetPropertyStr(context, result, "midiNote", JS_NewInt32(context, scope->api->audio.midi_note)); JS_SetPropertyStr(context, result, "midiVelocity", JS_NewInt32(context, scope->api->audio.midi_velocity)); JS_SetPropertyStr(context, result, "midiChannel", JS_NewInt32(context, scope->api->audio.midi_channel)); JS_SetPropertyStr(context, result, "midiPitchBend", JS_NewInt32(context, scope->api->audio.midi_pitch_bend)); JS_SetPropertyStr(context, result, "midiControl", JS_NewInt32(context, scope->api->audio.midi_control)); JS_SetPropertyStr(context, result, "midiControlValue", JS_NewInt32(context, scope->api->audio.midi_control_value)); JS_SetPropertyStr(context, result, "midiGate", JS_NewBool(context, scope->api->audio.midi_gate)); JS_SetPropertyStr(context, result, "midiStatus", JS_NewString(context, scope->api->audio.midi_status.c_str())); const auto clientErrorReportStatus = scope->api->client_error_report_status ? scope->api->client_error_report_status() : std::string{}; JS_SetPropertyStr(context, result, "clientErrorReportStatus", JS_NewString(context, clientErrorReportStatus.c_str())); return result;}
JSValue PadObject(JSContext* context, const PadState& pad, int index) { JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "index", JS_NewInt32(context, index)); JS_SetPropertyStr(context, result, "connected", JS_NewBool(context, pad.connected)); JS_SetPropertyStr(context, result, "leftX", JS_NewFloat64(context, pad.left_x)); JS_SetPropertyStr(context, result, "leftY", JS_NewFloat64(context, pad.left_y)); JS_SetPropertyStr(context, result, "rightX", JS_NewFloat64(context, pad.right_x)); JS_SetPropertyStr(context, result, "rightY", JS_NewFloat64(context, pad.right_y)); JS_SetPropertyStr(context, result, "leftTrigger", JS_NewFloat64(context, pad.left_trigger)); JS_SetPropertyStr(context, result, "rightTrigger", JS_NewFloat64(context, pad.right_trigger)); JSValue down = JS_NewArray(context); uint32_t downIndex = 0; for (const auto& button : pad.down) JS_SetPropertyUint32(context, down, downIndex++, JS_NewString(context, button.c_str())); JS_SetPropertyStr(context, result, "down", down); return result;}
JSValue GamepadState(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (argc < 1) return PadObject(context, scope->api->gamepad, 0); int32_t index = 0; if (JS_ToInt32(context, &index, argv[0])) return JS_EXCEPTION; if (index >= 0 && static_cast<std::size_t>(index) < scope->api->gamepad.pads.size()) return PadObject(context, scope->api->gamepad.pads[static_cast<std::size_t>(index)], index); return PadObject(context, PadState{}, index);}
JSValue Controllers(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewArray(context); uint32_t index = 0; for (const auto& controller : scope->api->gamepad.controllers) { JSValue item = JS_NewObject(context); JS_SetPropertyStr(context, item, "id", JS_NewString(context, controller.id.c_str())); JS_SetPropertyStr(context, item, "name", JS_NewString(context, controller.name.c_str())); JS_SetPropertyStr(context, item, "vendorId", JS_NewInt32(context, controller.vendor_id)); JS_SetPropertyStr(context, item, "productId", JS_NewInt32(context, controller.product_id)); JS_SetPropertyStr(context, item, "axes", JS_NewInt32(context, controller.axes)); JS_SetPropertyStr(context, item, "buttons", JS_NewInt32(context, controller.buttons)); JS_SetPropertyStr(context, item, "switches", JS_NewInt32(context, controller.switches)); JS_SetPropertyStr(context, item, "gamepad", JS_NewBool(context, controller.gamepad)); JS_SetPropertyUint32(context, result, index++, item); } return result;}
JSValue Capabilities(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; JSValue result = JS_NewObject(context); JS_SetPropertyStr(context, result, "platform", JS_NewString(context, "xbox-uwp")); JS_SetPropertyStr(context, result, "inputFamily", JS_NewString(context, "xbox")); JS_SetPropertyStr(context, result, "quickjs", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "direct3d11", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "xaudio2", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "internetClient", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "privateNetworkClientServer", JS_NewBool(context, true)); JS_SetPropertyStr(context, result, "online", JS_NewBool(context, scope->api->system.online)); JS_SetPropertyStr(context, result, "networkLevel", JS_NewString(context, scope->api->system.network_level.c_str())); JS_SetPropertyStr(context, result, "networkName", JS_NewString(context, scope->api->system.network_name.c_str())); JS_SetPropertyStr(context, result, "version", JS_NewString(context, scope->api->system.version.c_str())); JS_SetPropertyStr(context, result, "deviceFamily", JS_NewString(context, scope->api->system.device_family.c_str())); JS_SetPropertyStr(context, result, "deviceFamilyVersion", JS_NewString(context, scope->api->system.device_family_version.c_str())); JS_SetPropertyStr(context, result, "productName", JS_NewString(context, scope->api->system.product_name.c_str())); JS_SetPropertyStr(context, result, "memoryUsageBytes", JS_NewInt64(context, scope->api->system.memory_usage_bytes)); JS_SetPropertyStr(context, result, "memoryLimitBytes", JS_NewInt64(context, scope->api->system.memory_limit_bytes)); JS_SetPropertyStr(context, result, "expectedMemoryLimitBytes", JS_NewInt64(context, scope->api->system.expected_memory_limit_bytes)); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, scope->api->screen.width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, scope->api->screen.height)); JS_SetPropertyStr(context, result, "renderWidth", JS_NewInt32(context, scope->api->system.render_width)); JS_SetPropertyStr(context, result, "renderHeight", JS_NewInt32(context, scope->api->system.render_height)); JS_SetPropertyStr(context, result, "refreshHz", JS_NewFloat64(context, scope->api->system.refresh_hz)); JS_SetPropertyStr(context, result, "frameMs", JS_NewFloat64(context, scope->api->system.frame_ms)); JS_SetPropertyStr(context, result, "renderCpuMs", JS_NewFloat64(context, scope->api->system.render_cpu_ms)); JS_SetPropertyStr(context, result, "presentMs", JS_NewFloat64(context, scope->api->system.present_ms)); JS_SetPropertyStr(context, result, "antialiasingSamples", JS_NewInt32(context, scope->api->system.antialiasing_samples)); JS_SetPropertyStr(context, result, "antialiasingMode", JS_NewString(context, scope->api->system.antialiasing_mode.c_str())); JS_SetPropertyStr(context, result, "liveLocalState", JS_NewBool(context, true)); return result;}
JSValue AcData(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; const auto snapshot = std::atomic_load(&scope->api->ac); JSValue result = JS_NewObject(context); if (!snapshot) return result; JS_SetPropertyStr(context, result, "mood", JS_NewString(context, snapshot->mood.c_str())); JS_SetPropertyStr(context, result, "moodHandle", JS_NewString(context, snapshot->mood_handle.c_str())); JS_SetPropertyStr(context, result, "clockFrom", JS_NewString(context, snapshot->clock_from.c_str())); JS_SetPropertyStr(context, result, "clockText", JS_NewString(context, snapshot->clock_text.c_str())); JS_SetPropertyStr(context, result, "paintingUrl", JS_NewString(context, snapshot->painting_url.c_str())); JS_SetPropertyStr(context, result, "paintingHandle", JS_NewString(context, snapshot->painting_handle.c_str())); JS_SetPropertyStr(context, result, "status", JS_NewString(context, snapshot->status.c_str())); JS_SetPropertyStr(context, result, "refreshedUnixMs", JS_NewInt64(context, snapshot->refreshed_unix_ms)); JSValue fighters = JS_NewArray(context); for (uint32_t index = 0; index < snapshot->fighters.size(); ++index) { const auto& profile = snapshot->fighters[index]; JSValue fighter = JS_NewObject(context); JS_SetPropertyStr(context, fighter, "handle", JS_NewString(context, profile.handle.c_str())); JS_SetPropertyStr(context, fighter, "mood", JS_NewString(context, profile.mood.c_str())); JS_SetPropertyStr(context, fighter, "lastChat", JS_NewString(context, profile.last_chat.c_str())); JSValue colors = JS_NewArray(context); for (uint32_t colorIndex = 0; colorIndex < profile.colors.size(); ++colorIndex) { const auto& color = profile.colors[colorIndex]; JSValue value = JS_NewObject(context); JS_SetPropertyStr(context, value, "r", JS_NewInt32(context, color.r)); JS_SetPropertyStr(context, value, "g", JS_NewInt32(context, color.g)); JS_SetPropertyStr(context, value, "b", JS_NewInt32(context, color.b)); JS_SetPropertyUint32(context, colors, colorIndex, value); } JS_SetPropertyStr(context, fighter, "colors", colors); JS_SetPropertyUint32(context, fighters, index, fighter); } JS_SetPropertyStr(context, result, "fighters", fighters); return result;}
JSValue DiscState(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; const auto snapshot = std::atomic_load(&scope->api->disc.snapshot); JSValue result = JS_NewObject(context); if (!snapshot) return result; JS_SetPropertyStr(context, result, "status", JS_NewString(context, snapshot->status.c_str())); JS_SetPropertyStr(context, result, "volume", JS_NewString(context, snapshot->volume.c_str())); JS_SetPropertyStr(context, result, "name", JS_NewString(context, snapshot->name.c_str())); JS_SetPropertyStr(context, result, "count", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->count))); JS_SetPropertyStr(context, result, "index", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->index))); JS_SetPropertyStr(context, result, "width", JS_NewInt32(context, snapshot->width)); JS_SetPropertyStr(context, result, "height", JS_NewInt32(context, snapshot->height)); JS_SetPropertyStr(context, result, "currentReady", JS_NewBool(context, snapshot->current_ready)); JS_SetPropertyStr(context, result, "copyStatus", JS_NewString(context, snapshot->copy_status.c_str())); JS_SetPropertyStr(context, result, "copied", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->copied))); JS_SetPropertyStr(context, result, "copyFailed", JS_NewInt64(context, static_cast<std::int64_t>(snapshot->copy_failed))); return result;}
JSValue DiscScan(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (!scope->api->disc.scan) return JS_NewBool(context, false); scope->api->disc.scan(); return JS_NewBool(context, true);}
JSValue DiscShow(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); std::int64_t index = 0; if (!scope || !scope->api || argc < 1 || JS_ToInt64(context, &index, argv[0])) return JS_EXCEPTION; if (!scope->api->disc.show) return JS_NewBool(context, false); scope->api->disc.show(index); return JS_NewBool(context, true);}
JSValue DiscCopy(JSContext* context, JSValueConst, int, JSValueConst*) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); if (!scope || !scope->api) return JS_EXCEPTION; if (!scope->api->disc.copy) return JS_NewBool(context, false); scope->api->disc.copy(); return JS_NewBool(context, true);}
JSValue DiscPhoto(JSContext* context, JSValueConst, int argc, JSValueConst* argv) { auto* scope = static_cast<CallScope*>(JS_GetContextOpaque(context)); double x = 0, y = 0, width = 1920, height = 1080; if (!scope || !scope->api) return JS_EXCEPTION; if (argc > 0 && JS_ToFloat64(context, &x, argv[0])) return JS_EXCEPTION; if (argc > 1 && JS_ToFloat64(context, &y, argv[1])) return JS_EXCEPTION; if (argc > 2 && JS_ToFloat64(context, &width, argv[2])) return JS_EXCEPTION; if (argc > 3 && JS_ToFloat64(context, &height, argv[3])) return JS_EXCEPTION; if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) || !std::isfinite(height) || width <= 0 || height <= 0 || width > 3840 || height > 2160) return JS_ThrowRangeError(context, "invalid disc photo rectangle"); scope->api->graphics.image({"disc-photo", static_cast<float>(x), static_cast<float>(y), static_cast<float>(width), static_cast<float>(height)}); return JS_UNDEFINED;}
class QuickJsPiece final : public JsPiece { public: QuickJsPiece(const PieceBundle& bundle, const JsLimits& limits, std::string& error) : slug_(bundle.slug), version_(bundle.version) { runtime_ = JS_NewRuntime(); if (!runtime_) { error = "JS_NewRuntime failed"; return; } JS_SetMemoryLimit(runtime_, limits.max_heap_bytes); JS_SetMaxStackSize(runtime_, 1024 * 1024); context_ = JS_NewContext(runtime_); if (!context_) { error = "JS_NewContext failed"; return; } JS_SetContextOpaque(context_, &scope_); JSValue global = JS_GetGlobalObject(context_); JS_SetPropertyStr(context_, global, "wipe", JS_NewCFunction(context_, Wipe, "wipe", 3)); JS_SetPropertyStr(context_, global, "synth", JS_NewCFunction(context_, Synth, "synth", 2)); JS_SetPropertyStr(context_, global, "drum", JS_NewCFunction(context_, Drum, "drum", 3)); JS_SetPropertyStr(context_, global, "oscillator", JS_NewCFunction(context_, Oscillator, "oscillator", 2)); JS_SetPropertyStr(context_, global, "oscillatorStop", JS_NewCFunction(context_, OscillatorStop, "oscillatorStop", 0)); JS_SetPropertyStr(context_, global, "write", JS_NewCFunction(context_, Write, "write", 7)); JS_SetPropertyStr(context_, global, "box", JS_NewCFunction(context_, Box, "box", 7)); JS_SetPropertyStr(context_, global, "line", JS_NewCFunction(context_, Line, "line", 8)); JS_SetPropertyStr(context_, global, "triangle", JS_NewCFunction(context_, TriangleFill, "triangle", 9)); JS_SetPropertyStr(context_, global, "triangle3d", JS_NewCFunction(context_, Triangle3d, "triangle3d", 12)); JS_SetPropertyStr(context_, global, "triangles3d", JS_NewCFunction(context_, Triangles3d, "triangles3d", 2)); JS_SetPropertyStr(context_, global, "sprites3d", JS_NewCFunction(context_, Sprites3d, "sprites3d", 2)); JS_SetPropertyStr(context_, global, "texturedTriangles3d", JS_NewCFunction(context_, TexturedTriangles3d, "texturedTriangles3d", 2)); JS_SetPropertyStr(context_, global, "systemWrite", JS_NewCFunction(context_, SystemWrite, "systemWrite", 7)); JS_SetPropertyStr(context_, global, "ywftWrite", JS_NewCFunction(context_, YwftWrite, "ywftWrite", 7)); JS_SetPropertyStr(context_, global, "comicWrite", JS_NewCFunction(context_, ComicWrite, "comicWrite", 7)); JS_SetPropertyStr(context_, global, "systemGlyph", JS_NewCFunction(context_, SystemGlyph, "systemGlyph", 7)); JS_SetPropertyStr(context_, global, "painting", JS_NewCFunction(context_, Painting, "painting", 4)); JS_SetPropertyStr(context_, global, "stampPainting", JS_NewCFunction(context_, StampPainting, "stampPainting", 4)); JS_SetPropertyStr(context_, global, "blur", JS_NewCFunction(context_, Blur, "blur", 1)); JS_SetPropertyStr(context_, global, "telemetry", JS_NewCFunction(context_, Telemetry, "telemetry", 2)); JS_SetPropertyStr(context_, global, "gameSignal", JS_NewCFunction(context_, GameSignal, "gameSignal", 4)); JS_SetPropertyStr(context_, global, "saveReplay", JS_NewCFunction(context_, SaveReplay, "saveReplay", 1)); JS_SetPropertyStr(context_, global, "publishLive", JS_NewCFunction(context_, PublishLive, "publishLive", 2)); JS_SetPropertyStr(context_, global, "runtime", JS_NewCFunction(context_, RuntimeInfo, "runtime", 0)); JS_SetPropertyStr(context_, global, "gamepad", JS_NewCFunction(context_, GamepadState, "gamepad", 1)); JS_SetPropertyStr(context_, global, "controllers", JS_NewCFunction(context_, Controllers, "controllers", 0)); JS_SetPropertyStr(context_, global, "capabilities", JS_NewCFunction(context_, Capabilities, "capabilities", 0)); JS_SetPropertyStr(context_, global, "ac", JS_NewCFunction(context_, AcData, "ac", 0)); JS_SetPropertyStr(context_, global, "disc", JS_NewCFunction(context_, DiscState, "disc", 0)); JS_SetPropertyStr(context_, global, "discScan", JS_NewCFunction(context_, DiscScan, "discScan", 0)); JS_SetPropertyStr(context_, global, "discShow", JS_NewCFunction(context_, DiscShow, "discShow", 1)); JS_SetPropertyStr(context_, global, "discCopy", JS_NewCFunction(context_, DiscCopy, "discCopy", 0)); JS_SetPropertyStr(context_, global, "discPhoto", JS_NewCFunction(context_, DiscPhoto, "discPhoto", 4)); JS_FreeValue(context_, global); JSValue result = JS_Eval(context_, bundle.source.data(), bundle.source.size(), bundle.slug.c_str(), JS_EVAL_TYPE_GLOBAL); if (JS_IsException(result)) error = ExceptionText(); else valid_ = true; JS_FreeValue(context_, result); } ~QuickJsPiece() override { if (context_) JS_FreeContext(context_); if (runtime_) JS_FreeRuntime(runtime_); } bool valid() const { return valid_; } std::string_view slug() const noexcept override { return slug_; } std::string_view version() const noexcept override { return version_; } void boot(Api& api) override { Call("boot", api); } void sim(Api& api) override { Call("sim", api); } void paint(Api& api) override { Call("paint", api); } void leave(Api& api) override { Call("leave", api); } void act(Api& api, const Event& event) override { scope_.api = &api; JSValue global = JS_GetGlobalObject(context_); JSValue function = JS_GetPropertyStr(context_, global, "act"); if (JS_IsFunction(context_, function)) { JSValue argument = JS_NewString(context_, event.name.c_str()); JSValue result = JS_Call(context_, function, global, 1, &argument); JS_FreeValue(context_, argument); if (JS_IsException(result)) { JS_FreeValue(context_, result); JS_FreeValue(context_, function); JS_FreeValue(context_, global); throw std::runtime_error(ExceptionText()); } JS_FreeValue(context_, result); } JS_FreeValue(context_, function); JS_FreeValue(context_, global); scope_.api = nullptr; } private: void Call(const char* name, Api& api) { scope_.api = &api; JSValue global = JS_GetGlobalObject(context_); JSValue function = JS_GetPropertyStr(context_, global, name); if (JS_IsFunction(context_, function)) { JSValue result = JS_Call(context_, function, global, 0, nullptr); if (JS_IsException(result)) { JS_FreeValue(context_, result); JS_FreeValue(context_, function); JS_FreeValue(context_, global); throw std::runtime_error(ExceptionText()); } JS_FreeValue(context_, result); } JS_FreeValue(context_, function); JS_FreeValue(context_, global); scope_.api = nullptr; } std::string ExceptionText() { JSValue exception = JS_GetException(context_); const char* text = JS_ToCString(context_, exception); std::string result = text ? text : "JavaScript exception"; if (text) JS_FreeCString(context_, text); JS_FreeValue(context_, exception); return result; } std::string slug_, version_; JSRuntime* runtime_ = nullptr; JSContext* context_ = nullptr; CallScope scope_{nullptr}; bool valid_ = false;};}
std::unique_ptr<JsPiece> QuickJsEngine::compile(const PieceBundle& bundle, const JsLimits& limits, std::string& error) { auto piece = std::make_unique<QuickJsPiece>(bundle, limits, error); return piece->valid() ? std::move(piece) : nullptr;}} // namespace ac::xbox