From 76715a89bab0a648ce11f588b3449fed2e9fae8d Mon Sep 17 00:00:00 2001
From: theo-doree <95889170+theo-doree@users.noreply.github.com>
Date: Sat, 6 Jan 2024 17:55:03 -0500
Subject: [PATCH] Initial Commit
---
.env.example | 16 +
.gitignore | 3 +
README.md | 105 ++++
package-lock.json | 973 ++++++++++++++++++++++++++++++++++
package.json | 24 +
src/index.js | 40 ++
src/lib/RCCService.wsdl | 811 ++++++++++++++++++++++++++++
src/lib/classes/GameJob.js | 51 ++
src/lib/classes/Job.js | 56 ++
src/lib/classes/RCCService.js | 56 ++
src/lib/classes/RenderJob.js | 347 ++++++++++++
src/lib/logger.js | 12 +
src/lib/randport.js | 22 +
src/lua/bodyshot.lua | 29 +
src/lua/clothing.lua | 24 +
src/lua/gameserver.lua | 76 +++
src/lua/head.lua | 33 ++
src/lua/headshot.lua | 41 ++
src/lua/mesh.lua | 23 +
src/lua/place.lua | 23 +
src/lua/texture.lua | 15 +
src/lua/xml.lua | 23 +
src/routes/game/execute.js | 19 +
src/routes/game/renew.js | 14 +
src/routes/game/running.js | 19 +
src/routes/game/start.js | 21 +
src/routes/game/status.js | 14 +
src/routes/game/stop.js | 14 +
src/routes/index.js | 23 +
src/routes/render/asset.js | 26 +
src/routes/render/clothing.js | 16 +
src/routes/render/game.js | 16 +
src/routes/render/head.js | 16 +
src/routes/render/mesh.js | 16 +
src/routes/render/texture.js | 16 +
src/routes/render/user.js | 36 ++
36 files changed, 3069 insertions(+)
create mode 100644 .env.example
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 src/index.js
create mode 100644 src/lib/RCCService.wsdl
create mode 100644 src/lib/classes/GameJob.js
create mode 100644 src/lib/classes/Job.js
create mode 100644 src/lib/classes/RCCService.js
create mode 100644 src/lib/classes/RenderJob.js
create mode 100644 src/lib/logger.js
create mode 100644 src/lib/randport.js
create mode 100644 src/lua/bodyshot.lua
create mode 100644 src/lua/clothing.lua
create mode 100644 src/lua/gameserver.lua
create mode 100644 src/lua/head.lua
create mode 100644 src/lua/headshot.lua
create mode 100644 src/lua/mesh.lua
create mode 100644 src/lua/place.lua
create mode 100644 src/lua/texture.lua
create mode 100644 src/lua/xml.lua
create mode 100644 src/routes/game/execute.js
create mode 100644 src/routes/game/renew.js
create mode 100644 src/routes/game/running.js
create mode 100644 src/routes/game/start.js
create mode 100644 src/routes/game/status.js
create mode 100644 src/routes/game/stop.js
create mode 100644 src/routes/index.js
create mode 100644 src/routes/render/asset.js
create mode 100644 src/routes/render/clothing.js
create mode 100644 src/routes/render/game.js
create mode 100644 src/routes/render/head.js
create mode 100644 src/routes/render/mesh.js
create mode 100644 src/routes/render/texture.js
create mode 100644 src/routes/render/user.js
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..8bbbab3
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,16 @@
+PORT=64989
+RCCSERVICE=
+
+BASE_URL=http://www.warrior.rip
+
+ARBITER_TOKEN=
+ARBITER_KEY=
+
+RENDER_USER_WIDTH=720
+RENDER_USER_HEIGHT=720
+
+RENDER_ASSET_WIDTH=720
+RENDER_ASSET_HEIGHT=720
+
+RENDER_PLACE_WIDTH=854
+RENDER_PLACE_HEIGHT=480
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c27e9ba
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+build
+.env
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..632247f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,105 @@
+# bingle-arbiter
+
+The Bingle arbiter is designed to be used with almost any revival backend.
+
+It comes preloaded with some Lua scripts made by kinery and jackd900.
+
+You **will** have to replace/modify these scripts when implementing for your own projects.
+
+Set your desired settings in `.env.example`, then rename it to `.env`.
+
+## Routes
+
+### GET /render/asset/:id
+
+
+200 OK
+
+```
+iVBORw0KGgoAAAANSUhEUgAAAtAAAALQCAYAAAC...
+```
+
+
+
+### GET /render/asset/3d/:id
+
+
+200 OK
+
+```json
+{
+ "camera": {
+ "position": { "x": 0, "y": 0, "z": 0 },
+ "direction": { "x": 0, "y": 0, "z": 0 }
+ },
+ "AABB": {
+ "min": { "x": 0, "y": 0, "z": 0 },
+ "max": { "x": 0, "y": 0, "z": 0 }
+ },
+ "files": {
+ "scene.obj": { "content": "..." },
+ "scene.mtl": { "content": "..." },
+ "Handle1Tex.png": { "content": "..." }
+ }
+}
+```
+
+
+
+### GET /render/texture/:id
+
+
+200 OK
+
+```
+iVBORw0KGgoAAAANSUhEUgAAAtAAAALQCAYAAAC...
+```
+
+
+
+### GET /render/user/headshot/:id
+
+
+200 OK
+
+```
+iVBORw0KGgoAAAANSUhEUgAAAtAAAALQCAYAAAC...
+```
+
+
+
+### GET /render/user/bodyshot/:id
+
+
+200 OK
+
+```
+iVBORw0KGgoAAAANSUhEUgAAAtAAAALQCAYAAAC...
+```
+
+
+
+### GET /render/user/3d/:id
+
+
+200 OK
+
+```json
+{
+ "camera": {
+ "position": { "x": 0, "y": 0, "z": 0 },
+ "direction": { "x": 0, "y": 0, "z": 0 }
+ },
+ "AABB": {
+ "min": { "x": 0, "y": 0, "z": 0 },
+ "max": { "x": 0, "y": 0, "z": 0 }
+ },
+ "files": {
+ "scene.obj": { "content": "..." },
+ "scene.mtl": { "content": "..." },
+ "Handle1Tex.png": { "content": "..." }
+ }
+}
+```
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..5f640fa
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,973 @@
+{
+ "name": "bingle-arbiter",
+ "version": "0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "bingle-arbiter",
+ "version": "0.1",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^0.27.2",
+ "chalk": "^4.1.2",
+ "dotenv": "^16.0.3",
+ "express": "^4.18.2",
+ "soap": "^1.0.0",
+ "wait-port": "^1.0.4"
+ }
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.6",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz",
+ "integrity": "sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+ "dependencies": {
+ "follow-redirects": "^1.14.9",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/axios-ntlm": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/axios-ntlm/-/axios-ntlm-1.3.1.tgz",
+ "integrity": "sha512-YhjZj6UUzFzGirh7SiKbyvoXCWiZFMjjx2WJ8ouUUGNrqw/QgTc4H3M+7a6CTGENfLgXi2OiEhVeHmqoCffdYQ==",
+ "dependencies": {
+ "axios": "^1.2.0",
+ "dev-null": "^0.1.1"
+ }
+ },
+ "node_modules/axios-ntlm/node_modules/axios": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.3.tgz",
+ "integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+ "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+ "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+ "engines": {
+ "node": "^12.20.0 || >=14"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dev-null": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
+ "integrity": "sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ=="
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+ "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.18.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+ "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.1",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.5.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formidable": {
+ "version": "3.2.5",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.2.5.tgz",
+ "integrity": "sha512-GRGDJTWAZ3H+umZbF2bKcqjsTov25zgon1St05ziKdiSw3kxvI+meMJrXx3ylRmuSADOpviSakBuS4yvGCGnSg==",
+ "dependencies": {
+ "dezalgo": "1.0.3",
+ "hexoid": "1.0.0",
+ "once": "1.4.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hexoid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
+ "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/soap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/soap/-/soap-1.0.0.tgz",
+ "integrity": "sha512-GB5GuKjWFtAPP0IaM4tKUvdF4dFlaz4iujLPg0DsCy9qAAgm5/g+SI+P9e6igWWwXZ74CC5Uo0VEEz8lte0CJA==",
+ "dependencies": {
+ "axios-ntlm": "^1.2.0",
+ "debug": "^4.3.2",
+ "formidable": "^3.2.4",
+ "get-stream": "^6.0.1",
+ "lodash": "^4.17.21",
+ "sax": ">=0.6",
+ "strip-bom": "^3.0.0",
+ "uuid": "^8.3.2",
+ "whatwg-mimetype": "3.0.0",
+ "xml-crypto": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "axios": "^0.27.2"
+ }
+ },
+ "node_modules/soap/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/soap/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wait-port": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.0.4.tgz",
+ "integrity": "sha512-w8Ftna3h6XSFWWc2JC5gZEgp64nz8bnaTp5cvzbJSZ53j+omktWTDdwXxEF0jM8YveviLgFWvNGrSvRHnkyHyw==",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "commander": "^9.3.0",
+ "debug": "^4.3.4"
+ },
+ "bin": {
+ "wait-port": "bin/wait-port.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/wait-port/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wait-port/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/xml-crypto": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.0.1.tgz",
+ "integrity": "sha512-7XrwB3ujd95KCO6+u9fidb8ajvRJvIfGNWD0XLJoTWlBKz+tFpUzEYxsN+Il/6/gHtEs1RgRh2RH+TzhcWBZUw==",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.5",
+ "xpath": "0.0.32"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/xpath": {
+ "version": "0.0.32",
+ "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
+ "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..5092ecb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "bingle-arbiter",
+ "version": "0.1",
+ "main": "src/index.js",
+ "scripts": {
+ "start": "node .",
+ "dev": "nodemon . --exec \"clear; npm run start\""
+ },
+ "repository": {
+ "type": "git",
+ "url": "git@calones.xyz:Bingle/arbiter.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^0.27.2",
+ "chalk": "^4.1.2",
+ "dotenv": "^16.0.3",
+ "express": "^4.18.2",
+ "soap": "^1.0.0",
+ "wait-port": "^1.0.4"
+ }
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..e419a76
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,40 @@
+require("dotenv").config()
+const express = require("express")
+const app = express()
+
+const logger = require("./lib/logger.js")
+
+if (process.platform == "linux") logger.warn("Game hosting might not be fully compatible with Linux")
+
+app.use(({ query }, response, next) => {
+ if (!query.key) return response.status(400).json({ error: "Missing key in query" })
+ if (query.key !== process.env.ARBITER_KEY) return response.status(403).json({ error: "Incorrect key in query" })
+ next()
+})
+
+app.use("/game/start", require("./routes/game/start.js"))
+app.use("/game/stop", require("./routes/game/stop.js"))
+app.use("/game/running", require("./routes/game/running.js"))
+app.use("/game/renew", require("./routes/game/renew.js"))
+app.use("/game/status", require("./routes/game/status.js"))
+app.use("/game/execute", require("./routes/game/execute.js"))
+
+app.use("/render/asset", require("./routes/render/asset.js"))
+app.use("/render/game", require("./routes/render/game.js"))
+app.use("/render/texture", require("./routes/render/texture.js"))
+app.use("/render/user", require("./routes/render/user.js"))
+app.use("/render/clothing", require("./routes/render/clothing.js"))
+app.use("/render/head", require("./routes/render/head.js"))
+app.use("/render/mesh", require("./routes/render/mesh.js"))
+
+app.use("*", require("./routes/index.js"))
+
+process.on("uncaughtException", (err) => logger.error(err.message))
+global.games = new Map()
+setInterval(() => {
+ global.games.forEach(async (value, key) => {
+ if (!(await value.Running())) value.Stop()
+ })
+}, 15000)
+
+app.listen(process.env.PORT, () => logger.boot(`Listening on http://127.0.0.1:${process.env.PORT}/`))
\ No newline at end of file
diff --git a/src/lib/RCCService.wsdl b/src/lib/RCCService.wsdl
new file mode 100644
index 0000000..b58c3f0
--- /dev/null
+++ b/src/lib/RCCService.wsdl
@@ -0,0 +1,811 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/lib/classes/GameJob.js b/src/lib/classes/GameJob.js
new file mode 100644
index 0000000..1cc4528
--- /dev/null
+++ b/src/lib/classes/GameJob.js
@@ -0,0 +1,51 @@
+const axios = require("axios")
+const { readFile } = require("fs/promises")
+
+const Job = require("./Job.js")
+const logger = require("../logger.js")
+const randport = require("../randport.js")
+
+class GameJob extends Job {
+ constructor() {
+ super({ expirationInSeconds: 360 })
+ }
+
+ StartGame(id) {
+ return new Promise(async (resolve, reject) => {
+ this.placeId = id
+
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ if (!this.client) await this.CreateClient()
+
+ logger.info(`[${this.id}] GameJob started for ${id}`)
+
+ const port = await randport.udp()
+
+ this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/gameserver.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+ { type: "LUA_TSTRING", value: "Place" },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TNUMBER", value: port },
+ ],
+ },
+ }).catch((e) => reject(e))
+
+ resolve(port)
+ })
+ }
+
+ async Running() {
+ const result = await this.Execute("IsRunning", "return true").catch((_) => _)
+ return !result?.message
+ }
+}
+
+module.exports = GameJob
diff --git a/src/lib/classes/Job.js b/src/lib/classes/Job.js
new file mode 100644
index 0000000..4757f84
--- /dev/null
+++ b/src/lib/classes/Job.js
@@ -0,0 +1,56 @@
+const soap = require("soap")
+const { randomUUID } = require("crypto")
+
+const RCCService = require("./RCCService.js")
+const logger = require("../logger.js")
+
+class Job extends RCCService {
+ constructor({ id = randomUUID(), expirationInSeconds = 60, category = 0, cores = 1 } = {}) {
+ super()
+ this.id = id
+ this.expirationInSeconds = expirationInSeconds
+ this.category = category
+ this.cores = cores
+ }
+
+ async CreateClient() {
+ this.client = await soap.createClientAsync(__dirname + "/../RCCService.wsdl", {}, `http://127.0.0.1:${this.port}/`)
+ return this.client
+ }
+
+ async OpenJobEx(script) {
+ if (!this.client) throw new Error("There is no client")
+ return await this.client.OpenJobExAsync({
+ job: {
+ id: this.id,
+ expirationInSeconds: this.expirationInSeconds,
+ category: this.category,
+ cores: this.cores,
+ },
+ script,
+ })
+ }
+
+ async CloseJob() {
+ if (!this.client) return true
+ return await this.client.CloseJobAsync({ jobID: this.id })
+ }
+
+ async RenewLease(expirationInSeconds) {
+ if (!this.client) throw new Error("There is no client")
+ logger.info(`[${this.id}] Job renewed to ${expirationInSeconds} seconds`)
+ return await this.client.RenewLeaseAsync({ jobID: this.id, expirationInSeconds })
+ }
+
+ async Execute(name, script) {
+ if (!this.client) throw new Error("There is no client")
+ return await this.client.ExecuteAsync({ jobID: this.id, script: { name, script, arguments: {} } })
+ }
+
+ async GetStatus() {
+ if (!this.client) throw new Error("There is no client")
+ return await this.client.GetStatusAsync({})
+ }
+}
+
+module.exports = Job
diff --git a/src/lib/classes/RCCService.js b/src/lib/classes/RCCService.js
new file mode 100644
index 0000000..ee1cb40
--- /dev/null
+++ b/src/lib/classes/RCCService.js
@@ -0,0 +1,56 @@
+const EventEmitter = require("events")
+const child_process = require("child_process")
+const waitPort = require("wait-port")
+
+const logger = require("../../lib/logger.js")
+const randport = require("../../lib/randport.js")
+
+const chalk = require("chalk")
+
+class RCCService extends EventEmitter {
+ constructor() {
+ super()
+ }
+
+ Start() {
+ return new Promise(async (resolve, reject) => {
+ try {
+ this.port = await randport.tcp()
+
+ if (process.platform == "win32") {
+ this.proc = child_process.spawn("RCCService.exe", ["-Console", "-PlaceId:-1", `-Port`, this.port], { cwd: process.env.RCCSERVICE, stdio: "inherit" })
+ } else {
+ this.proc = child_process.spawn("wine", ["RCCService.exe", "-Console", "-PlaceId:-1", `-Port`, this.port], { cwd: process.env.RCCSERVICE, stdio: "inherit" })
+ }
+
+ this.proc.once("spawn", async () => {
+ logger.info(`${chalk.gray(`[${this.port}]`)} RCCService instance spawned`)
+ const { open } = await waitPort({ host: "127.0.0.1", port: this.port, timeout: 5000, output: "silent" }).catch((e) => console.log(e))
+ if (!open || this.proc.exitCode !== null) {
+ this.proc.kill()
+ logger.error(`${chalk.gray(`[${this.port}]`)} RCCService could not listen`)
+ return resolve(false)
+ }
+
+ this.started = true
+ return resolve(true)
+ })
+
+ this.proc.once("exit", () => {
+ this.proc.kill()
+ logger.info(`${chalk.gray(`[${this.port}]`)} RCCService instance exited`)
+ })
+ } catch (_) {
+ resolve(false)
+ }
+ })
+ }
+
+ // sigma ??
+ Stop(signal = "SIGTERM") {
+ if (!this.proc) return
+ return this.proc.kill(signal)
+ }
+}
+
+module.exports = RCCService
diff --git a/src/lib/classes/RenderJob.js b/src/lib/classes/RenderJob.js
new file mode 100644
index 0000000..49db58c
--- /dev/null
+++ b/src/lib/classes/RenderJob.js
@@ -0,0 +1,347 @@
+const { readFile } = require("fs/promises")
+const chalk = require("chalk")
+
+const Job = require("./Job.js")
+const logger = require("../logger.js")
+const { randomUUID } = require("crypto")
+
+class RenderJob extends Job {
+ constructor() {
+ super()
+ }
+
+ async RenderHeadshot(id) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Headshot RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/headshot.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Headshot" },
+ { type: "LUA_TSTRING", value: "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ ],
+ },
+ }).catch((e) => false)
+
+ logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Headshot RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderBodyshot(id, three_d = false) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Bodyshot RenderJob started for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Bodyshot RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/bodyshot.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Bodyshot" },
+ { type: "LUA_TSTRING", value: three_d ? "OBJ" : "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ ],
+ },
+ }).catch((e) => false)
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Bodyshot RenderJob finished for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Bodyshot RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderAsset(id, three_d = false) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Asset RenderJob started for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Asset RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/xml.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Asset" },
+ { type: "LUA_TSTRING", value: three_d ? "OBJ" : "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TBOOLEAN", value: "true" },
+ ],
+ },
+ }).catch((e) => false)
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Asset RenderJob finished for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Asset RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderPlace(id) {
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Place RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/place.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Place" },
+ { type: "LUA_TSTRING", value: "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_PLACE_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_PLACE_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TSTRING", value: process.env.ARBITER_TOKEN },
+ ],
+ },
+ }).catch((e) => false)
+
+ logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Place RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderTexture(id) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ logger.info(`[${this.id}] Texture RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/texture.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Texture" },
+ { type: "LUA_TSTRING", value: "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_USER_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ ],
+ },
+ }).catch((e) => false)
+
+ logger.info(`[${this.id}] Headshot RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderClothing(id, three_d = false) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Clothing RenderJob started for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Clothing RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/clothing.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Clothing" },
+ { type: "LUA_TSTRING", value: three_d ? "OBJ" : "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TBOOLEAN", value: "true" },
+ ],
+ },
+ }).catch((e) => false)
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Clothing RenderJob finished for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Clothing RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderHead(id, three_d = false) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Head RenderJob started for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Head RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/head.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Head" },
+ { type: "LUA_TSTRING", value: three_d ? "OBJ" : "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TBOOLEAN", value: "true" },
+ ],
+ },
+ }).catch((e) => false)
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Head RenderJob finished for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Head RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+
+ async RenderMesh(id, three_d = false) {
+ this.id = randomUUID()
+
+ const running = this.started
+ if (!running) {
+ const started = await this.Start()
+ if (!started) throw new Error("RCCService failed to start")
+ }
+
+ if (!this.client) await this.CreateClient()
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Mesh RenderJob started for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Mesh RenderJob started for ${id}`)
+
+ const result = await this.OpenJobEx({
+ name: this.id,
+ script: await readFile(__dirname + "/../../lua/mesh.lua", { encoding: "utf-8" }),
+ arguments: {
+ LuaValue: [
+ { type: "LUA_TSTRING", value: this.id },
+
+ { type: "LUA_TSTRING", value: "Mesh" },
+ { type: "LUA_TSTRING", value: three_d ? "OBJ" : "PNG" },
+
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_WIDTH },
+ { type: "LUA_TNUMBER", value: process.env.RENDER_ASSET_HEIGHT },
+
+ { type: "LUA_TSTRING", value: process.env.BASE_URL },
+ { type: "LUA_TNUMBER", value: id },
+ { type: "LUA_TBOOLEAN", value: "true" },
+ ],
+ },
+ }).catch((e) => false)
+
+ if (three_d) logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} 3D Mesh RenderJob finished for ${id}`)
+ else logger.info(`${chalk.gray(`${chalk.gray(`[${this.id}]`)}`)} Mesh RenderJob finished for ${id}`)
+
+ this.Stop()
+
+ if (!result) return false
+ return result[0]?.OpenJobExResult?.LuaValue[0]?.value
+ }
+}
+
+module.exports = RenderJob
diff --git a/src/lib/logger.js b/src/lib/logger.js
new file mode 100644
index 0000000..38adbe7
--- /dev/null
+++ b/src/lib/logger.js
@@ -0,0 +1,12 @@
+const chalk = require("chalk")
+
+const logger = {
+ // Omg just like tadah :D (Tadahjak face)
+ boot: (_) => console.log(chalk.greenBright("[BOOT]"), _),
+ info: (_) => console.log(chalk.blue("[INFO]"), _),
+ success: (_) => console.log(chalk.green("[SUCCESS]"), _),
+ warn: (_) => console.log(chalk.yellow("[WARN]"), _),
+ error: (_) => console.log(chalk.red("[ERROR]"), _),
+}
+
+module.exports = logger
diff --git a/src/lib/randport.js b/src/lib/randport.js
new file mode 100644
index 0000000..0da19fa
--- /dev/null
+++ b/src/lib/randport.js
@@ -0,0 +1,22 @@
+const net = require("net")
+const dgram = require("dgram")
+
+exports.tcp = () => {
+ return new Promise((resolve) => {
+ const server = net.createServer()
+ server.listen(0, () => {
+ const port = server.address().port
+ server.close((err) => resolve(port))
+ })
+ })
+}
+
+exports.udp = () => {
+ return new Promise((resolve) => {
+ const server = dgram.createSocket("udp4")
+ server.bind(Math.random() * (60_000 - 50_000) + 50_000, () => {
+ const port = server.address().port
+ server.close((err) => resolve(port))
+ })
+ })
+}
diff --git a/src/lua/bodyshot.lua b/src/lua/bodyshot.lua
new file mode 100644
index 0000000..092241c
--- /dev/null
+++ b/src/lua/bodyshot.lua
@@ -0,0 +1,29 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+print(("[%s] Started RenderJob for type '%s' with assetId %d ..."):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local Player = game.Players:CreateLocalPlayer(0)
+Player.CharacterAppearance = ("%s/Character/%d"):format(baseUrl, assetId)
+Player:LoadCharacter(false)
+
+game:GetService("RunService"):Run()
+
+Player.Character.Animate.Disabled = true
+Player.Character.Torso.Anchored = true
+
+local gear = Player.Backpack:GetChildren()[1]
+if gear then
+ gear.Parent = Player.Character
+ Player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
+end
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
diff --git a/src/lua/clothing.lua b/src/lua/clothing.lua
new file mode 100644
index 0000000..ffddfa8
--- /dev/null
+++ b/src/lua/clothing.lua
@@ -0,0 +1,24 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d"):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local Player = game.Players:CreateLocalPlayer(0)
+Player.CharacterAppearance = ("%s/clothing/%d"):format(baseUrl, assetId)
+Player:LoadCharacter(false)
+
+game:GetService("RunService"):Run()
+
+Player.Character.Animate.Disabled = true
+Player.Character.Torso.Anchored = true
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/gameserver.lua b/src/lua/gameserver.lua
new file mode 100644
index 0000000..a875969
--- /dev/null
+++ b/src/lua/gameserver.lua
@@ -0,0 +1,76 @@
+local jobId, type, baseUrl, placeId, port = ...
+
+------------------- UTILITY FUNCTIONS --------------------------
+function waitForChild(parent, childName)
+ while true do
+ local child = parent:findFirstChild(childName)
+ if child then return child end
+ parent.ChildAdded:wait()
+ end
+end
+
+-----------------------------------END UTILITY FUNCTIONS -------------------------
+
+-----------------------------------"CUSTOM" SHARED CODE----------------------------------
+
+pcall(function() settings().Network.UseInstancePacketCache = true end)
+pcall(function() settings().Network.UsePhysicsPacketCache = true end)
+pcall(function() settings()["Task Scheduler"].PriorityMethod = Enum.PriorityMethod.AccumulatedError end)
+
+settings().Network.PhysicsSend = Enum.PhysicsSendMethod.TopNErrors
+settings().Network.ExperimentalPhysicsEnabled = true
+settings().Network.WaitingForCharacterLogRate = 100
+pcall(function() settings().Diagnostics:LegacyScriptMode() end)
+
+-----------------------------------START GAME SHARED SCRIPT------------------------------
+
+local scriptContext = game:GetService("ScriptContext")
+pcall(function() scriptContext:AddStarterScript(37801172) end)
+scriptContext.ScriptsDisabled = true
+
+game:SetPlaceID(placeId, false)
+game:GetService("ChangeHistoryService"):SetEnabled(false)
+
+local ns = game:GetService("NetworkServer")
+
+if baseUrl ~= nil then
+ pcall(function() game:GetService("Players"):SetAbuseReportUrl(baseUrl .. "/AbuseReport/InGameChatHandler.ashx") end)
+ pcall(function() game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/Asset/") end)
+ pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl .. "/") end)
+ pcall(function() game:GetService("Players"):SetChatFilterUrl(baseUrl .. "/Game/ChatFilter.ashx") end)
+
+ game:GetService("BadgeService"):SetPlaceId(placeId)
+ game:GetService("BadgeService"):SetIsBadgeLegalUrl("")
+ game:GetService("InsertService"):SetBaseSetsUrl(baseUrl .. "/Game/Tools/InsertAsset.ashx?nsets=10&type=base")
+ game:GetService("InsertService"):SetUserSetsUrl(baseUrl .. "/Game/Tools/InsertAsset.ashx?nsets=20&type=user&userid=%d")
+ game:GetService("InsertService"):SetCollectionUrl(baseUrl .. "/Game/Tools/InsertAsset.ashx?sid=%d")
+ game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/Asset/?id=%d")
+ game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+
+ pcall(function() loadfile(baseUrl .. "/Game/LoadPlaceInfo.ashx?PlaceId=" .. placeId)() end)
+ pcall(function() game:GetService("NetworkServer"):SetIsPlayerAuthenticationRequired(true) end)
+end
+
+settings().Diagnostics.LuaRamLimit = 0
+
+game:GetService("Players").PlayerAdded:connect(function(player)
+ print("Player " .. player.userId .. " added")
+end)
+
+game:GetService("Players").PlayerRemoving:connect(function(player)
+ print("Player " .. player.userId .. " leaving")
+end)
+
+if placeId ~= nil and baseUrl ~= nil then
+ wait()
+ game:Load(baseUrl .. "/asset/?id=" .. placeId)
+end
+
+ns:Start(port)
+
+scriptContext:SetTimeout(10)
+scriptContext.ScriptsDisabled = false
+
+------------------------------END START GAME SHARED SCRIPT--------------------------
+
+game:GetService("RunService"):Run()
diff --git a/src/lua/head.lua b/src/lua/head.lua
new file mode 100644
index 0000000..59a36eb
--- /dev/null
+++ b/src/lua/head.lua
@@ -0,0 +1,33 @@
+local jobId, headType, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d"):format(jobId, headType, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local Player = game.Players:CreateLocalPlayer(0)
+Player.CharacterAppearance = ("%s/clothing/%d"):format(baseUrl, assetId)
+Player:LoadCharacter(false)
+
+local function removeBodyParts()
+ for _, part in pairs(Player.Character:GetChildren()) do
+ if part:IsA("BasePart") and part.Name ~= "Head" then
+ part:Destroy()
+ end
+ end
+end
+
+removeBodyParts()
+
+game:GetService("RunService"):Run()
+
+Player.Character.Animate.Disabled = true
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/headshot.lua b/src/lua/headshot.lua
new file mode 100644
index 0000000..d1c6457
--- /dev/null
+++ b/src/lua/headshot.lua
@@ -0,0 +1,41 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d ..."):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local Player = game.Players:CreateLocalPlayer(0)
+Player.CharacterAppearance = ("%s/Character/%d"):format(baseUrl, assetId)
+Player:LoadCharacter(false)
+
+game:GetService("RunService"):Run()
+
+Player.Character.Animate.Disabled = true
+Player.Character.Torso.Anchored = true
+
+-- Headshot Camera
+local FOV = 52.5
+local AngleOffsetX = 0
+local AngleOffsetY = 0
+local AngleOffsetZ = 0
+
+local CameraAngle = Player.Character.Head.CFrame * CFrame.new(AngleOffsetX, AngleOffsetY, AngleOffsetZ)
+local CameraPosition = Player.Character.Head.CFrame + Vector3.new(0, 0, 0) + (CFrame.Angles(0, -0.2, 0).lookVector.unit * 3)
+
+local Camera = Instance.new("Camera", Player.Character)
+Camera.Name = "ThumbnailCamera"
+Camera.CameraType = Enum.CameraType.Scriptable
+
+Camera.CoordinateFrame = CFrame.new(CameraPosition.p, CameraAngle.p)
+Camera.FieldOfView = FOV
+workspace.CurrentCamera = Camera
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/mesh.lua b/src/lua/mesh.lua
new file mode 100644
index 0000000..d7bf58c
--- /dev/null
+++ b/src/lua/mesh.lua
@@ -0,0 +1,23 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d ..."):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local meshPart = Instance.new("Part", workspace)
+meshPart.Anchored = true
+meshPart.Size = Vector3.new(10, 10, 10)
+
+local mesh = Instance.new("SpecialMesh", meshPart)
+mesh.MeshType = "FileMesh"
+mesh.MeshId = ("%s/asset?id=%d"):format(baseUrl, assetId)
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/place.lua b/src/lua/place.lua
new file mode 100644
index 0000000..5dee862
--- /dev/null
+++ b/src/lua/place.lua
@@ -0,0 +1,23 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d ..."):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+
+-- do this twice for security
+game:GetService("ScriptContext").ScriptsDisabled = true
+game:GetService("StarterGui").ShowDevelopmentGui = false
+
+game:Load(("%s/asset/?id=%d"):format(baseUrl, assetId))
+
+game:GetService("ScriptContext").ScriptsDisabled = true
+game:GetService("StarterGui").ShowDevelopmentGui = false
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, false)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/texture.lua b/src/lua/texture.lua
new file mode 100644
index 0000000..5aaf1b6
--- /dev/null
+++ b/src/lua/texture.lua
@@ -0,0 +1,15 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d"):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):ClickTexture("rbxassetid://" .. assetId, format, x, y)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/lua/xml.lua b/src/lua/xml.lua
new file mode 100644
index 0000000..e9a960d
--- /dev/null
+++ b/src/lua/xml.lua
@@ -0,0 +1,23 @@
+local jobId, type, format, x, y, baseUrl, assetId = ...
+
+print(("[%s] Started RenderJob for type '%s' with assetId %d ..."):format(jobId, type, assetId))
+
+game:GetService("ScriptInformationProvider"):SetAssetUrl(baseUrl .. "/asset/")
+game:GetService("InsertService"):SetAssetUrl(baseUrl .. "/asset/?id=%d")
+game:GetService("InsertService"):SetAssetVersionUrl(baseUrl .. "/Asset/?assetversionid=%d")
+game:GetService("ContentProvider"):SetBaseUrl(baseUrl)
+game:GetService("ScriptContext").ScriptsDisabled = true
+
+local asset = game:GetObjects(("%s/asset/?id=%d"):format(baseUrl, assetId))[1]
+asset.Parent = workspace
+
+local thumbnailCamera = asset:FindFirstChild("ThumbnailCamera")
+if thumbnailCamera ~= nil and thumbnailCamera.ClassName == "Camera" then
+ workspace.CurrentCamera = thumbnailCamera
+end
+
+print(("[%s] Rendering ..."):format(jobId))
+local result = game:GetService("ThumbnailGenerator"):Click(format, x, y, true)
+print(("[%s] Done!"):format(jobId))
+
+return result
\ No newline at end of file
diff --git a/src/routes/game/execute.js b/src/routes/game/execute.js
new file mode 100644
index 0000000..6ea0070
--- /dev/null
+++ b/src/routes/game/execute.js
@@ -0,0 +1,19 @@
+const { randomUUID } = require("crypto")
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.use(express.json())
+
+app.post("/:id", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (!game) return response.status(404).json({ error: "Game is not running" })
+
+ const { script } = request.body
+ const jobResponse = await game.Execute(randomUUID(), script)
+
+ return response.json({ response: jobResponse })
+})
+
+module.exports = app
diff --git a/src/routes/game/renew.js b/src/routes/game/renew.js
new file mode 100644
index 0000000..68e6538
--- /dev/null
+++ b/src/routes/game/renew.js
@@ -0,0 +1,14 @@
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.get("/:id/:expire", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (!game) return response.status(404).json({ error: "Game is not running" })
+
+ await game.RenewLease(request.params.expire)
+ return response.json({ success: true })
+})
+
+module.exports = app
diff --git a/src/routes/game/running.js b/src/routes/game/running.js
new file mode 100644
index 0000000..a1f97a0
--- /dev/null
+++ b/src/routes/game/running.js
@@ -0,0 +1,19 @@
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.get("/:id", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (!game) return response.json(false)
+
+ const running = await game.Running()
+ if (!running && game) {
+ game.Stop()
+ return response.json(false)
+ }
+
+ return response.json(true)
+})
+
+module.exports = app
diff --git a/src/routes/game/start.js b/src/routes/game/start.js
new file mode 100644
index 0000000..0049888
--- /dev/null
+++ b/src/routes/game/start.js
@@ -0,0 +1,21 @@
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.get("/:id", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (game) return response.status(400).json({ error: "Game is running" })
+
+ const job = new GameJob()
+ const result = await job.StartGame(request.params.id).catch((_) => _)
+
+ global.games.set(request.params.id, job)
+ job.proc.once("exit", () => {
+ global.games.delete(request.params.id)
+ })
+
+ return response.json({ success: true })
+})
+
+module.exports = app
diff --git a/src/routes/game/status.js b/src/routes/game/status.js
new file mode 100644
index 0000000..7c4aeed
--- /dev/null
+++ b/src/routes/game/status.js
@@ -0,0 +1,14 @@
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.get("/:id", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (!game) return response.status(404).json({ error: "Game is not running" })
+
+ const status = await game.GetStatus()
+ return response.json(status[0]?.GetStatusResult)
+})
+
+module.exports = app
diff --git a/src/routes/game/stop.js b/src/routes/game/stop.js
new file mode 100644
index 0000000..be676b5
--- /dev/null
+++ b/src/routes/game/stop.js
@@ -0,0 +1,14 @@
+const express = require("express")
+const app = express.Router()
+
+const GameJob = require("../../lib/classes/GameJob.js")
+
+app.get("/:id", async (request, response) => {
+ const game = global.games.get(request.params.id)
+ if (!game) return response.status(404).json({ error: "Game is not running" })
+
+ game.Stop()
+ return response.json({ success: true })
+})
+
+module.exports = app
diff --git a/src/routes/index.js b/src/routes/index.js
new file mode 100644
index 0000000..26804f0
--- /dev/null
+++ b/src/routes/index.js
@@ -0,0 +1,23 @@
+const express = require("express")
+const app = express.Router()
+
+function getGameIds() {
+ let gameIds = []
+
+ global.games.forEach((value, key) => {
+ gameIds.push(value.placeId)
+ })
+
+ return gameIds
+}
+
+app.get("/", (request, response) => {
+ return response.status(200).json({
+ runningGamesCount: global.games.size,
+ runningGames: getGameIds(),
+ })
+})
+
+app.all("*", (request, response) => response.status(404).json({ status: 404 }))
+
+module.exports = app
diff --git a/src/routes/render/asset.js b/src/routes/render/asset.js
new file mode 100644
index 0000000..7f0eca1
--- /dev/null
+++ b/src/routes/render/asset.js
@@ -0,0 +1,26 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const asset = await job.RenderAsset(params.id).catch((_) => _)
+ if (asset?.message) return response.status(500).json({ error: asset.message })
+
+ return response.end(asset);
+})
+
+app.get("/:id/3d", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const three_d = await job.RenderAsset(params.id, true).catch((_) => _)
+ if (three_d?.message) return response.status(500).json({ error: three_d.message })
+
+ return response.json(JSON.parse(three_d))
+})
+
+module.exports = app
diff --git a/src/routes/render/clothing.js b/src/routes/render/clothing.js
new file mode 100644
index 0000000..6111a49
--- /dev/null
+++ b/src/routes/render/clothing.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const clothing = await job.RenderClothing(params.id).catch((_) => _)
+ if (clothing?.message) return response.status(500).json({ error: clothing.message })
+
+ return response.end(clothing)
+})
+
+module.exports = app
diff --git a/src/routes/render/game.js b/src/routes/render/game.js
new file mode 100644
index 0000000..a2e4c51
--- /dev/null
+++ b/src/routes/render/game.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const game = await job.RenderPlace(params.id).catch((_) => _)
+ if (game?.message) return response.status(500).json({ error: game.message })
+
+ return response.end(game)
+})
+
+module.exports = app
diff --git a/src/routes/render/head.js b/src/routes/render/head.js
new file mode 100644
index 0000000..3abd12f
--- /dev/null
+++ b/src/routes/render/head.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const head = await job.RenderHead(params.id).catch((_) => _)
+ if (head?.message) return response.status(500).json({ error: head.message })
+
+ return response.end(head)
+})
+
+module.exports = app
diff --git a/src/routes/render/mesh.js b/src/routes/render/mesh.js
new file mode 100644
index 0000000..d2edc73
--- /dev/null
+++ b/src/routes/render/mesh.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const mesh = await job.RenderMesh(params.id).catch((_) => _)
+ if (mesh?.message) return response.status(500).json({ error: mesh.message })
+
+ return response.end(mesh)
+})
+
+module.exports = app
diff --git a/src/routes/render/texture.js b/src/routes/render/texture.js
new file mode 100644
index 0000000..6bd0fc7
--- /dev/null
+++ b/src/routes/render/texture.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const texture = await job.RenderTexture(params.id).catch((_) => _)
+ if (texture?.message) return response.status(500).json({ error: texture.message })
+
+ return response.end(texture)
+})
+
+module.exports = app
diff --git a/src/routes/render/user.js b/src/routes/render/user.js
new file mode 100644
index 0000000..f0a532e
--- /dev/null
+++ b/src/routes/render/user.js
@@ -0,0 +1,36 @@
+const express = require("express")
+const app = express.Router()
+
+const RenderJob = require("../../lib/classes/RenderJob.js")
+
+app.get("/:id/bodyshot", async (request, response) => {
+ const { params } = request
+ const job = new RenderJob()
+
+ const bodyshot = await job.RenderBodyshot(params.id).catch((_) => _)
+ if (bodyshot?.message) return response.status(500).json({ error: bodyshot.message })
+
+ return response.end(bodyshot);
+})
+
+app.get("/:id/headshot", async (request, response) => {
+ const { params } = request
+ const job = new RenderJob()
+
+ const headshot = await job.RenderHeadshot(params.id).catch((_) => _)
+ if (headshot?.message) return response.status(500).json({ error: headshot.message })
+
+ return response.end(headshot);
+})
+
+app.get("/:id/3d", async (request, response) => {
+ const { params, query } = request
+ const job = new RenderJob()
+
+ const three_d = await job.RenderBodyshot(params.id, true).catch((_) => _)
+ if (three_d?.message) return response.status(500).json({ error: three_d.message })
+
+ return response.json(JSON.parse(three_d))
+})
+
+module.exports = app