diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d46f235 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +RCCSERVICE= + +BASE_URL= + +RENDER_FORMAT= + +RENDER_USER_WIDTH= +RENDER_USER_HEIGHT= + +RENDER_ASSET_WIDTH= +RENDER_ASSET_HEIGHT= + +RENDER_PLACE_WIDTH= +RENDER_PLACE_HEIGHT= diff --git a/.gitignore b/.gitignore index b7dab5e..c27e9ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -build \ No newline at end of file +build +.env \ No newline at end of file diff --git a/README.md b/README.md index 04e76ca..6310435 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # 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. + +Set your desired settings in `.env.example`, then rename it to `.env`. diff --git a/dev.bat b/dev.bat deleted file mode 100644 index 940ff1b..0000000 --- a/dev.bat +++ /dev/null @@ -1,2 +0,0 @@ -set RCCSERVICE_PATH=D:\\Projects\\Roblox\\Source\\UploadBits\\Win32-Release-RCCService\\ -npm run dev \ No newline at end of file diff --git a/dev.sh b/dev.sh deleted file mode 100644 index 38dd7f5..0000000 --- a/dev.sh +++ /dev/null @@ -1,2 +0,0 @@ -export RCCSERVICE_PATH=/run/media/calones/32531f99-d721-4297-90c7-f91e56851060/ICCService/ -npm run dev \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9605e32..aae7bcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "chalk": "^4.1.2", + "dotenv": "^16.0.3", "express": "^4.18.2", "soap": "^1.0.0", "wait-port": "^1.0.4" @@ -305,6 +306,14 @@ "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", diff --git a/package.json b/package.json index 0ac2d50..d4d7433 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "ISC", "dependencies": { "chalk": "^4.1.2", + "dotenv": "^16.0.3", "express": "^4.18.2", "soap": "^1.0.0", "wait-port": "^1.0.4" diff --git a/prod.bat b/prod.bat deleted file mode 100644 index f82489d..0000000 --- a/prod.bat +++ /dev/null @@ -1,2 +0,0 @@ -set RCCSERVICE_PATH=C:\\RCCService\\ -npm run start \ No newline at end of file diff --git a/src/index.js b/src/index.js index 972a50d..e674af3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,18 @@ -const logger = require("./lib/logger.js") +require("dotenv").config() const express = require("express") const app = express() -app.use("/game/start", require("./routes/game/start.js")) -app.use("/game/stop", require("./routes/game/stop.js")) -app.use("/game/execute", require("./routes/game/execute.js")) +const logger = require("./lib/logger.js") + +if (process.platform == "linux") logger.warn("Game hosting might not fully compatible with Linux") + +app.use("/place/start", require("./routes/place/start.js")) +app.use("/place/stop", require("./routes/place/stop.js")) +app.use("/place/execute", require("./routes/place/execute.js")) +app.use("/place/renew", require("./routes/place/renew.js")) app.use("/render/asset", require("./routes/render/asset.js")) -app.use("/render/game", require("./routes/render/game.js")) +app.use("/render/place", require("./routes/render/place.js")) app.use("/render/headshot", require("./routes/render/headshot.js")) app.use("/render/bodyshot", require("./routes/render/bodyshot.js")) @@ -18,5 +23,5 @@ app.listen(process.env.PORT || 64989, () => { }) process.on("uncaughtException", (err) => { - logger.error(err) + logger.error(err.message) }) diff --git a/src/lib/classes/Job.js b/src/lib/classes/Job.js index 02cf8bb..f59340e 100644 --- a/src/lib/classes/Job.js +++ b/src/lib/classes/Job.js @@ -1,9 +1,11 @@ -const RCCService = require("./RCCService.js") const soap = require("soap") +const { randomUUID } = require("crypto") + +const RCCService = require("./RCCService.js") class Job extends RCCService { - constructor(id, port, expirationInSeconds = 10, category = 0, cores = 1) { - super(port) + constructor({ id = randomUUID(), expirationInSeconds = 10, category = 0, cores = 1 } = {}) { + super() this.id = id this.expirationInSeconds = expirationInSeconds this.category = category @@ -15,7 +17,7 @@ class Job extends RCCService { return this.client } - async Open(script) { + async OpenJobEx(script) { if (!this.client) throw new Error("There is no client") return await this.client.OpenJobExAsync({ job: { @@ -28,16 +30,19 @@ class Job extends RCCService { }) } - async Close() { + async CloseJob() { if (!this.client) return true - return await this.client.CloseAllJobsAsync({}) + return await this.client.CloseJobAsync({ jobID: this.id }) } async RenewLease(expirationInSeconds) { - return await this.client.RenewLeaseAsync({ - jobID: this.id, - expirationInSeconds, - }) + if (!this.client) throw new Error("There is no client") + 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: {} } }) } } diff --git a/src/lib/classes/RCCService.js b/src/lib/classes/RCCService.js index 0a755b0..b96e29d 100644 --- a/src/lib/classes/RCCService.js +++ b/src/lib/classes/RCCService.js @@ -1,30 +1,31 @@ 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") class RCCService extends EventEmitter { - constructor(port, path = process.env.RCCSERVICE_PATH) { + constructor() { super() - this.path = path - this.port = port } - Start(options = { cwd: this.path }) { - return new Promise((resolve, reject) => { + 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], options) + this.proc = child_process.spawn("RCCService.exe", ["-Console", "-PlaceId:-1", `-Port`, this.port], { cwd: process.env.RCCSERVICE }) } else { - this.proc = child_process.spawn("wine", ["RCCService.exe", "-Console", "-PlaceId:-1", `-Port`, this.port], options) + this.proc = child_process.spawn("wine", ["RCCService.exe", "-Console", "-PlaceId:-1", `-Port`, this.port], { cwd: process.env.RCCSERVICE }) } this.proc.once("spawn", async () => { logger.info(`[${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) { - logger.error(`[${this.port}] RCCService could not listen, exiting`) - this.Close() + logger.error(`[${this.port}] RCCService could not listen`) return resolve(false) } diff --git a/src/lib/classes/RenderJob.js b/src/lib/classes/RenderJob.js index 62a6e69..43d540e 100644 --- a/src/lib/classes/RenderJob.js +++ b/src/lib/classes/RenderJob.js @@ -1,7 +1,152 @@ +const { readFileSync } = require("fs") + const Job = require("./Job.js") +const logger = require("../logger.js") class RenderJob extends Job { - constructor() {} + constructor() { + super() + } + + async RenderHeadshot(id, base64 = false) { + const started = await this.Start() + if (!started) throw new Error("RCCService failed to start") + if (!this.client) await this.CreateClient() + + logger.info(`[${this.id}] Headshot RenderJob started for ${id}`) + + const result = await this.OpenJobEx({ + name: this.id, + script: readFileSync(__dirname + "/../../lua/headshot.lua", { encoding: "utf-8" }), + arguments: { + LuaValue: [ + { type: "LUA_TSTRING", value: this.id }, + + { type: "LUA_TSTRING", value: "Headshot" }, + { type: "LUA_TSTRING", value: process.env.RENDER_FORMAT }, + + { 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}`) + + await this.Stop() + + if (!result) return false + if (base64) return result[0].OpenJobExResult.LuaValue[0].value + return Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64") + } + + async RenderBodyshot(id, base64 = false) { + const started = await this.Start() + if (!started) throw new Error("RCCService failed to start") + if (!this.client) await this.CreateClient() + + logger.info(`[${this.id}] Bodyshot RenderJob started for ${id}`) + + const result = await this.OpenJobEx({ + name: this.id, + script: readFileSync(__dirname + "/../../lua/bodyshot.lua", { encoding: "utf-8" }), + arguments: { + LuaValue: [ + { type: "LUA_TSTRING", value: this.id }, + + { type: "LUA_TSTRING", value: "Bodyshot" }, + { type: "LUA_TSTRING", value: process.env.RENDER_FORMAT }, + + { 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}] Bodyshot RenderJob finished for ${id}`) + + await this.Stop() + + if (!result) return false + if (base64) return result[0].OpenJobExResult.LuaValue[0].value + return Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64") + } + + async RenderAsset(id, base64 = false) { + const started = await this.Start() + if (!started) throw new Error("RCCService failed to start") + if (!this.client) await this.CreateClient() + + logger.info(`[${this.id}] Asset RenderJob started for ${id}`) + + const result = await this.OpenJobEx({ + name: this.id, + script: readFileSync(__dirname + "/../../lua/xml.lua", { encoding: "utf-8" }), + arguments: { + LuaValue: [ + { type: "LUA_TSTRING", value: this.id }, + + { type: "LUA_TSTRING", value: "Asset" }, + { type: "LUA_TSTRING", value: process.env.RENDER_FORMAT }, + + { 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 }, + ], + }, + }).catch((e) => false) + + logger.info(`[${this.id}] Asset RenderJob finished for ${id}`) + + await this.Stop() + + if (!result) return false + if (base64) return result[0].OpenJobExResult.LuaValue[0].value + return Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64") + } + + async RenderPlace(id, base64 = false) { + const started = await this.Start() + if (!started) throw new Error("RCCService failed to start") + if (!this.client) await this.CreateClient() + + logger.info(`[${this.id}] Place RenderJob started for ${id}`) + + const result = await this.OpenJobEx({ + name: this.id, + script: readFileSync(__dirname + "/../../lua/place.lua", { encoding: "utf-8" }), + arguments: { + LuaValue: [ + { type: "LUA_TSTRING", value: this.id }, + + { type: "LUA_TSTRING", value: "Place" }, + { type: "LUA_TSTRING", value: process.env.RENDER_FORMAT }, + + { 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 }, + ], + }, + }).catch((e) => false) + + logger.info(`[${this.id}] Place RenderJob finished for ${id}`) + + await this.Stop() + + if (!result) return false + if (base64) return result[0].OpenJobExResult.LuaValue[0].value + return Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64") + } } module.exports = RenderJob diff --git a/src/lib/randport.js b/src/lib/randport.js index c96c965..23bd478 100644 --- a/src/lib/randport.js +++ b/src/lib/randport.js @@ -1,6 +1,7 @@ const net = require("net") +const dgram = require("dgram") -module.exports = () => { +exports.tcp = () => { return new Promise((resolve) => { const server = net.createServer() server.listen(0, () => { @@ -9,3 +10,13 @@ module.exports = () => { }) }) } + +exports.udp = () => { + return new Promise((resolve) => { + const server = dgram.createSocket() + server.bind(0, () => { + const port = server.address().port + server.close((err) => resolve(port)) + }) + }) +} diff --git a/src/lib/wait.js b/src/lib/wait.js deleted file mode 100644 index 93e4399..0000000 --- a/src/lib/wait.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = (ms) => { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) -} diff --git a/src/lua/xml.lua b/src/lua/xml.lua index 855b6b5..a893b6a 100644 --- a/src/lua/xml.lua +++ b/src/lua/xml.lua @@ -13,7 +13,7 @@ asset.Parent = workspace local thumbnailCamera = asset:FindFirstChild("ThumbnailCamera") if thumbnailCamera ~= nil and thumbnailCamera.ClassName == "Camera" then - workspace.CurrentCamera = ModelCamera + workspace.CurrentCamera = thumbnailCamera end print(("[%s] Rendering ..."):format(jobId)) diff --git a/src/routes/game/execute.js b/src/routes/place/execute.js similarity index 100% rename from src/routes/game/execute.js rename to src/routes/place/execute.js diff --git a/src/routes/game/start.js b/src/routes/place/renew.js similarity index 100% rename from src/routes/game/start.js rename to src/routes/place/renew.js diff --git a/src/routes/game/stop.js b/src/routes/place/start.js similarity index 100% rename from src/routes/game/stop.js rename to src/routes/place/start.js diff --git a/src/routes/render/game.js b/src/routes/place/stop.js similarity index 100% rename from src/routes/render/game.js rename to src/routes/place/stop.js diff --git a/src/routes/render/asset.js b/src/routes/render/asset.js index 7411169..15fb3b3 100644 --- a/src/routes/render/asset.js +++ b/src/routes/render/asset.js @@ -1,45 +1,14 @@ -const { readFileSync } = require("fs") -const { randomUUID } = require("crypto") -const logger = require("../../lib/logger.js") -const randport = require("../../lib/randport.js") -const Job = require("../../lib/classes/Job.js") - const express = require("express") const app = express.Router() +const RenderJob = require("../../lib/classes/RenderJob.js") + app.get("/:id", async (request, response) => { - const job = new Job(randomUUID(), await randport()) - const started = await job.Start() - if (!started) return response.status(500).json({ error: "RCCService failed to start" }) + const job = new RenderJob() + const result = await job.RenderAsset(request.params.id).catch((_) => _) - logger.info(`[${job.id}] Job opened`) - await job.CreateClient() - const result = await job - .Open({ - name: job.id, - script: readFileSync(__dirname + "/../../lua/xml.lua", { encoding: "utf-8" }), - arguments: { - LuaValue: [ - { type: "LUA_TSTRING", value: job.id }, - { type: "LUA_TSTRING", value: "XML" }, - { type: "LUA_TSTRING", value: "PNG" }, - - // change this to 1920x when we finish the arbiter - { type: "LUA_TNUMBER", value: "420" }, - { type: "LUA_TNUMBER", value: "420" }, - - { type: "LUA_TSTRING", value: "https://economy.ittblox.gay" }, - { type: "LUA_TNUMBER", value: request.params.id }, - ], - }, - }) - .catch((e) => false) - - logger.info(`[${job.id}] Job closed`) - await job.Stop() - - if (result) return response.end(Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64")) - else return response.status(500).end() + if (result?.message) return response.status(500).json({ error: result.message }) + else return response.end(result) }) module.exports = app diff --git a/src/routes/render/bodyshot.js b/src/routes/render/bodyshot.js index ad6a884..557f3cc 100644 --- a/src/routes/render/bodyshot.js +++ b/src/routes/render/bodyshot.js @@ -1,45 +1,14 @@ -const { readFileSync } = require("fs") -const { randomUUID } = require("crypto") -const logger = require("../../lib/logger.js") -const randport = require("../../lib/randport.js") -const Job = require("../../lib/classes/Job.js") - const express = require("express") const app = express.Router() +const RenderJob = require("../../lib/classes/RenderJob.js") + app.get("/:id", async (request, response) => { - const job = new Job(randomUUID(), await randport()) - const started = await job.Start() - if (!started) return response.status(500).json({ error: "RCCService failed to start" }) + const job = new RenderJob() + const result = await job.RenderBodyshot(request.params.id).catch((_) => _) - logger.info(`[${job.id}] Job opened`) - await job.CreateClient() - const result = await job - .Open({ - name: job.id, - script: readFileSync(__dirname + "/../../lua/bodyshot.lua", { encoding: "utf-8" }), - arguments: { - LuaValue: [ - { type: "LUA_TSTRING", value: job.id }, - { type: "LUA_TSTRING", value: "Bodyshot" }, - { type: "LUA_TSTRING", value: "PNG" }, - - // change this to 1920x when we finish the arbiter - { type: "LUA_TNUMBER", value: "420" }, - { type: "LUA_TNUMBER", value: "420" }, - - { type: "LUA_TSTRING", value: "https://economy.ittblox.gay" }, - { type: "LUA_TNUMBER", value: request.params.id }, - ], - }, - }) - .catch((e) => false) - - logger.info(`[${job.id}] Job closed`) - await job.Stop() - - if (result) return response.end(Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64")) - else return response.status(500).end() + if (result?.message) return response.status(500).json({ error: result.message }) + else return response.end(result) }) module.exports = app diff --git a/src/routes/render/headshot.js b/src/routes/render/headshot.js index fa55aee..31b4fd7 100644 --- a/src/routes/render/headshot.js +++ b/src/routes/render/headshot.js @@ -1,45 +1,14 @@ -const { readFileSync } = require("fs") -const { randomUUID } = require("crypto") -const logger = require("../../lib/logger.js") -const randport = require("../../lib/randport.js") -const Job = require("../../lib/classes/Job.js") - const express = require("express") const app = express.Router() +const RenderJob = require("../../lib/classes/RenderJob.js") + app.get("/:id", async (request, response) => { - const job = new Job(randomUUID(), await randport()) - const started = await job.Start() - if (!started) return response.status(500).json({ error: "RCCService failed to start" }) + const job = new RenderJob() + const result = await job.RenderHeadshot(request.params.id).catch((_) => _) - logger.info(`[${job.id}] Job opened`) - await job.CreateClient() - const result = await job - .Open({ - name: job.id, - script: readFileSync(__dirname + "/../../lua/headshot.lua", { encoding: "utf-8" }), - arguments: { - LuaValue: [ - { type: "LUA_TSTRING", value: job.id }, - { type: "LUA_TSTRING", value: "Headshot" }, - { type: "LUA_TSTRING", value: "PNG" }, - - // change this to 1920x when we finish the arbiter - { type: "LUA_TNUMBER", value: "420" }, - { type: "LUA_TNUMBER", value: "420" }, - - { type: "LUA_TSTRING", value: "https://economy.ittblox.gay" }, - { type: "LUA_TNUMBER", value: request.params.id }, - ], - }, - }) - .catch((e) => false) - - logger.info(`[${job.id}] Job closed`) - await job.Stop() - - if (result) return response.end(Buffer.from(result[0]?.OpenJobExResult?.LuaValue[0]?.value, "base64")) - else return response.status(500).end() + if (result?.message) return response.status(500).json({ error: result.message }) + else return response.end(result) }) module.exports = app diff --git a/src/routes/render/place.js b/src/routes/render/place.js new file mode 100644 index 0000000..88822f0 --- /dev/null +++ b/src/routes/render/place.js @@ -0,0 +1,6 @@ +const express = require("express") +const app = express.Router() + +app.all("*", (request, response) => response.status(404).json({ status: 404 })) + +module.exports = app