Add launchable

This commit is contained in:
usernames122
2025-10-23 20:58:19 +02:00
parent 300bb16e5e
commit d9d5460856
27 changed files with 1743 additions and 85 deletions

102
authserver/index.js Normal file
View File

@@ -0,0 +1,102 @@
import express from "express";
import bodyParser from "body-parser";
import crypto from "crypto";
const app = express();
app.use(bodyParser.json());
// In-memory stores
const users = {}; // { userId: username }
const sessions = {}; // { token: userId }
// Basic HTML UI
const HTML_PAGE = `
<!DOCTYPE html>
<html>
<head>
<title>Fake Auth API (Express)</title>
<style>
body { font-family: sans-serif; max-width: 500px; margin: 2rem auto; }
input, button { margin: .3rem 0; padding: .3rem; }
pre { background: #f5f5f5; padding: 1rem; border-radius: 5px; }
</style>
</head>
<body>
<h2>Fake Auth API</h2>
<form id="userForm">
<input id="uid" placeholder="User ID" required><br>
<input id="uname" placeholder="Username" required><br>
<button type="submit">Add User</button>
</form>
<h3>Users</h3>
<div id="users"></div>
<script>
async function refresh() {
const res = await fetch('/api/users');
const data = await res.json();
const div = document.getElementById('users');
div.innerHTML = '';
for (const [id, name] of Object.entries(data)) {
const btn = document.createElement('button');
btn.textContent = 'Create token';
btn.onclick = async () => {
const res = await fetch('/api/create_token/' + id, {method:'POST'});
const tok = await res.json();
alert('Token for ' + name + ': ' + tok.token);
};
div.append(\`\${id}: \${name} \`, btn, document.createElement('br'));
}
}
document.getElementById('userForm').onsubmit = async (e) => {
e.preventDefault();
const id = uid.value.trim();
const name = uname.value.trim();
await fetch('/api/users', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({id, name})
});
uid.value = uname.value = '';
refresh();
};
refresh();
</script>
</body>
</html>
`;
// === Routes ===
// UI page
app.get("/", (req, res) => res.send(HTML_PAGE));
// Get users
app.get("/api/users", (req, res) => res.json(users));
// Add user
app.post("/api/users", (req, res) => {
const { id, name } = req.body;
if (!id || !name) return res.status(400).json({ error: "Missing id or name" });
users[id] = name;
res.json({ status: "ok" });
});
// Create token
app.post("/api/create_token/:uid", (req, res) => {
const uid = req.params.uid;
if (!users[uid]) return res.status(404).json({ error: "User not found" });
const token = crypto.randomBytes(8).toString("hex");
sessions[token] = uid;
res.json({ token });
});
// Verify token
app.get("/api/verify/:token", (req, res) => {
const uid = sessions[req.params.token];
if (!uid) return res.status(404).json({ error: "Invalid token" });
res.json({ id: uid, name: users[uid] });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`[FakeAuth] Running on http://localhost:${PORT}`));

842
authserver/package-lock.json generated Normal file
View File

@@ -0,0 +1,842 @@
{
"name": "authserver",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "authserver",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"express": "^5.1.0"
}
},
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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==",
"license": "MIT",
"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/http-errors/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"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==",
"license": "ISC"
},
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"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==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"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==",
"license": "MIT",
"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==",
"license": "ISC",
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"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==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.7.0",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"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"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.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==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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==",
"license": "ISC"
}
}
}

14
authserver/package.json Normal file
View File

@@ -0,0 +1,14 @@
{
"name": "authserver",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^5.1.0"
}
}

View File

@@ -5,7 +5,7 @@ import { ReplicatorService } from "./instances/ReplicatorService.js";
import { RenderService } from "./instances/RenderService.js"; import { RenderService } from "./instances/RenderService.js";
import { Workspace } from "./instances/Workspace.js"; import { Workspace } from "./instances/Workspace.js";
import { DebugTextService } from "./instances/DebugTextService.js"; // Client-only debug overlay manager import { DebugTextService } from "./instances/DebugTextService.js"; // Client-only debug overlay manager
import { Players } from "./instances/Players.js";
const dm = new DataModel(); const dm = new DataModel();
dm.SetParent(null); // root dm.SetParent(null); // root
@@ -16,6 +16,11 @@ ws.SetParent(dm);
const net = new NetworkService(); const net = new NetworkService();
net.SetParent(dm); net.SetParent(dm);
net.isServer = false; // client mode net.isServer = false; // client mode
// Create a players container (client-side crippled version)
const players = new Players(net, true);
players.SetParent(dm);
await net.connect("ws://localhost:8080"); // connect to server await net.connect("ws://localhost:8080"); // connect to server
const render = new RenderService(dm); const render = new RenderService(dm);
@@ -27,4 +32,17 @@ const replication = new ReplicatorService(dm, net); // Automatically parents to
// Create DebugTextService // Create DebugTextService
const debugText = new DebugTextService(); const debugText = new DebugTextService();
debugText.SetParent(dm); debugText.SetParent(dm);
setTimeout(() => {
console.log("Prompting for token...");
let sessionToken = prompt("Enter your session token: (any string will do for this test except 'fake')");
console.log("Got token:", sessionToken);
if (sessionToken) {
const encoder = new TextEncoder();
const payload = encoder.encode(sessionToken);
net.send(0x01, 0x03, payload);
} else {
console.warn("No token entered — not sending packet.");
}
}, 3000);

36
js/core/Auth.js Normal file
View File

@@ -0,0 +1,36 @@
export class Auth {
constructor(baseUrl = "http://localhost:5000") {
this.baseUrl = baseUrl;
}
/**
* Verify a session token against the fake auth API.
* Returns the user ID if valid, or null if invalid.
*/
async verifySessionToken(token) {
try {
const res = await fetch(`${this.baseUrl}/api/verify/${encodeURIComponent(token)}`);
if (!res.ok) return null;
const data = await res.json();
return data.id ?? null;
} catch (err) {
console.warn("[Auth] verifySessionToken error:", err);
return null;
}
}
/**
* Look up the user's display name by ID.
*/
async getUserNameById(id) {
try {
const res = await fetch(`${this.baseUrl}/api/users`);
if (!res.ok) return "Unknown";
const data = await res.json();
return data[id] ?? `User${id}`;
} catch (err) {
console.warn("[Auth] getUserNameById error:", err);
return `User${id}`;
}
}
}

28
js/core/AuthStub.js Normal file
View File

@@ -0,0 +1,28 @@
// For whoever is going to use this engine later, please modify this file to actually do authentication.
export class Auth {
/**
* Verifies a session token and returns a user ID.
* In this dummy version, it just returns a random numeric ID.
*
* @param {string} token - The player's session token.
* @returns {number|null} - The verified user ID, or null if invalid.
*/
verifySessionToken(token) {
// Dummy implementation: always "verifies" successfully.
if (token === "fake") return null; // Simulate invalid token
return Math.floor(Math.random() * 1000);
}
/**
* Gets a user's display name by their ID.
* Replace this with a real lookup (e.g., database or API call).
*
* @param {number|string} userId - The player's user ID.
* @returns {string} - The player's display name.
*/
getUserNameById(userId) {
// Dummy implementation
return `User${userId}`;
}
}

View File

@@ -10,6 +10,16 @@ export class DataModel extends Instance {
return this.Children.find(c => c.ClassName === name) || null; return this.Children.find(c => c.ClassName === name) || null;
} }
GetDataModel() {return this;} // Override to return self GetDataModel() {return this;} // Override to return self
LuaBridge () {
const obj = super.LuaBridge();
obj.GetService = (name) => {
if (name === "RunService") name = "RenderService"; // Alias
const service = this.GetService(name);
return service ? service.LuaBridge() : null;
}
}
} // Just a container for the whole instance tree } // Just a container for the whole instance tree
// cuz this is an Entity Component System, we need a root entity // cuz this is an Entity Component System, we need a root entity
// Assume all children of DataModel are services or top-level game objects // Assume all children of DataModel are services or top-level game objects

View File

@@ -134,6 +134,7 @@ function eulerDecoder(arr) { // Float32Array(3) to THREE.Euler
const encoders = { const encoders = {
Vector3: { encode: v3Encoder, decode: v3Decoder }, Vector3: { encode: v3Encoder, decode: v3Decoder },
Euler: { encode: eulerEncoder, decode: eulerDecoder }, Euler: { encode: eulerEncoder, decode: eulerDecoder },
bool: { encode: (bool)=>bool, decode:(bool)=>bool}
// Add more as needed // Add more as needed
}; };

View File

@@ -0,0 +1,13 @@
import { Instance } from "./Instance.js";
export class BaseService extends Instance {
constructor(name) {
super(name);
}
SetParent(parent) {
if (this.Parent === null) {
super.SetParent(parent);
return;
} // Only accept it once
throw new Error("Cannot reparent a service instance.");
}
}

View File

@@ -1,7 +1,8 @@
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
import { BaseService } from "./BaseService.js";
import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js"; import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js";
export class DebugTextService extends Instance { export class DebugTextService extends BaseService {
constructor() { constructor() {
super("DebugTextService"); super("DebugTextService");

View File

@@ -1,5 +1,6 @@
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
export class ExampleService extends Instance { import { BaseService } from "./BaseService.js";
export class ExampleService extends BaseService {
constructor() { constructor() {
super("ExampleService"); super("ExampleService");
} }

View File

@@ -5,7 +5,7 @@ let instanceCounter = 0;
export class Instance { export class Instance {
constructor(className = "Instance") { constructor(className = "Instance") {
this.ClassName = className; this.ClassName = className;
this.Name = `${className}${instanceCounter++}`; this.Name = `${className}`;
this.Parent = null; this.Parent = null;
this.Children = []; this.Children = [];
this.InstanceId = guidGenerator(); // Unique identifier, used for replication this.InstanceId = guidGenerator(); // Unique identifier, used for replication
@@ -120,4 +120,21 @@ export class Instance {
console.warn("Destroy: No DataModel found in ancestry. Are you destroying an unparented instance?"); console.warn("Destroy: No DataModel found in ancestry. Are you destroying an unparented instance?");
} }
} // Garbage collected after this } // Garbage collected after this
LuaBridge(exclude, visited = new Set()) { // Convert to a plain object for LuaBridge. Should be overridden in subclasses to add properties.
if (visited.has(this.InstanceId)) {
// Already visited, avoid circular reference
return null;
}
visited.add(this.InstanceId);
const obj = {
ClassName: this.ClassName,
Name: this.Name,
InstanceId: this.InstanceId,
Properties: {},
Children: this.Children.filter(c => c !== exclude).map(c => c.LuaBridge(this, visited)).filter(Boolean),
Parent: this.Parent && this.Parent !== exclude ? this.Parent.LuaBridge(this, visited) : null // Avoid circular reference by excluding self
};
return obj;
}
} }

View File

@@ -9,6 +9,7 @@ if (typeof window !== "undefined") {
WebSocketServer = wsModule.WebSocketServer; WebSocketServer = wsModule.WebSocketServer;
} }
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
import { BaseService } from "./BaseService.js";
import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js"; import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js";
/** /**
@@ -17,7 +18,7 @@ import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js";
* - Binary framed packets: [uint32 length][ptype][psub][payload] * - Binary framed packets: [uint32 length][ptype][psub][payload]
* - Fires PacketReceived signal for incoming packets * - Fires PacketReceived signal for incoming packets
*/ */
export class NetworkService extends Instance { export class NetworkService extends BaseService {
constructor() { constructor() {
super("NetworkService"); super("NetworkService");
@@ -132,6 +133,17 @@ export class NetworkService extends Instance {
console.warn("[NetworkService] Disconnected from server.") console.warn("[NetworkService] Disconnected from server.")
alert("Disconnected from server! If this was unintentional, please refresh the page. You might have been kicked by the server.\n\n(If you are the server host, check if the servers running.)"); alert("Disconnected from server! If this was unintentional, please refresh the page. You might have been kicked by the server.\n\n(If you are the server host, check if the servers running.)");
}; };
// Register 0xFF kick handler
this.registerHandler(0x00, 0xFF, (payload) => {
let reason = "Kicked by server.";
try {
reason = new TextDecoder().decode(payload);
} catch (e) {
// ignore
}
console.warn("[NetworkService] Kicked by server:", reason);
alert("You have been kicked by the server:\n\n" + reason);
});
}); });
} }
@@ -151,6 +163,7 @@ export class NetworkService extends Instance {
this.wss.on("connection", (ws) => { this.wss.on("connection", (ws) => {
this.clients.set(ws, {}); this.clients.set(ws, {});
ws.isAuthed = false; // Determine whether to send any data except auth packets
console.log("[NetworkService] New client connected. Total clients:", this.clients.size); console.log("[NetworkService] New client connected. Total clients:", this.clients.size);
ws.binaryType = "arraybuffer"; ws.binaryType = "arraybuffer";
@@ -167,10 +180,12 @@ export class NetworkService extends Instance {
}); });
} }
broadcast(ptype, psub, payload) { broadcast(ptype, psub, payload,ignoreUnauthed,ignoreCheck) {
if (!this.isServer || !this.wss) return; if (!this.isServer || !this.wss) return;
const packet = this.encodePacket(ptype, psub, payload); const packet = this.encodePacket(ptype, psub, payload);
for (const ws of this.clients.keys()) { for (const ws of this.clients.keys()) {
if(ignoreUnauthed && !ws.isAuthed) continue;
if (ignoreCheck && ignoreCheck(ws)) continue;
if (ws.readyState === WebSocket.OPEN) ws.send(packet); if (ws.readyState === WebSocket.OPEN) ws.send(packet);
} }
} }
@@ -181,6 +196,17 @@ export class NetworkService extends Instance {
if (ws.readyState === WebSocket.OPEN) ws.send(packet); if (ws.readyState === WebSocket.OPEN) ws.send(packet);
} }
KickClientLater(ws,code=4000,reason="") {
// Send a kick packet
this.sendToClient(ws, 0x00, 0xFF, new TextEncoder().encode(reason));
setTimeout(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.close(code,reason);
}
}, 500); // Slight delay to ensure any pending packets are sent
}
Broadcast(ptype, psub, payload) { Broadcast(ptype, psub, payload) {
console.warn("[NetworkService] Broadcast is deprecated, use broadcast() instead."); console.warn("[NetworkService] Broadcast is deprecated, use broadcast() instead.");
this.broadcast(ptype, psub, payload); this.broadcast(ptype, psub, payload);

11
js/instances/Player.js Normal file
View File

@@ -0,0 +1,11 @@
import { Instance } from "./Instance.js";
export class Player extends Instance {
constructor( id, name, ws ) {
super("Player");
this.InstanceId = id; // To fix jank with replication system
this.UserId = id; // For compatibility
this.Name = name;
this.ws = ws; // WebSocket or client reference
}
}

105
js/instances/Players.js Normal file
View File

@@ -0,0 +1,105 @@
import { NetworkService } from "./NetworkService.js";
import { Player } from "./Player.js";
import { BaseService } from "./BaseService.js";
import { Auth } from "../core/Auth.js";
/**
* Players manager
* - Tracks connected players after successful auth (0x01 0x03)
*/
export class Players extends BaseService {
constructor(networkService,isClient) {
super("Players");
this.InstanceId = "Players"; // For replication system
if (isClient) {
this.networkService = networkService;
this.networkService.registerHandler(0x00, 0x04, (payload) => {
// Auth response from server, parse it
const decoder = new TextDecoder();
const msg = decoder.decode(payload);
console.debug("[Players] Received auth response from server:", msg);
if (msg.startsWith("AUTH_SUCCESS|")) {
const playerName = msg.split("|")[1];
const id = msg.split("|")[2];
console.debug(`[Players] You have logged in as ${playerName}`);
// Create a local Player instance
const player = new Player( id, playerName, null );
this.players = new Map();
this.players.set(null, player); // single local player
this.LocalPlayer = player;
player.SetParent(this);
} else {
console.warn("[Players] Auth failed or unknown response:", msg);
}
});
return;
}
this.networkService = networkService;
this.players = new Map(); // id -> Player
this.authenticator = new Auth();
// Listen for auth packets
this.networkService.registerHandler(0x01, 0x03, async (payload, ws) => {
if (ws.isAuthed) return; // Already authed, ignore additional auth attempts
console.debug("[Players] Received auth packet:", "CENSORED");
// Assume payload is a string (player name)
let token;
try {
// Decode bytes
const decoder = new TextDecoder();
token = decoder.decode(payload);
} catch (e) {
console.warn("[Players] Invalid auth payload:", payload);
networkService.KickClientLater(ws, 4000, "Invalid auth payload, please try again.");
return;
}
// Verify token (dummy implementation)
const id = await this.authenticator.verifySessionToken(token);
if (id === null) {
// Kick off the client
networkService.KickClientLater(ws, 4001, "Authentication failed; are you logged in?");
// Reason must be understandable as its shown to the user
return;
}
console.debug(`[Players] Client authenticated as ${id}`);
ws.isAuthed = true; // Mark ws as authed
// Create Player instance
const name = await this.authenticator.getUserNameById(id);
const player = new Player(id, name, ws );
this.players.set(ws, player);
player.SetParent(this);
// Listen for disconnection
ws.on("close", () => {
this.players.delete(ws);
player.Destroy();
console.debug(`[Players] Player ${name} (${id}) disconnected.`);
});
console.debug(`[Players] Player ${name} (${id}) connected.`);
// Send auth success packet
const encoder = new TextEncoder();
const successPayload = encoder.encode("AUTH_SUCCESS|" + name + "|" + id);
this.networkService.sendToClient(ws, 0x00, 0x04, successPayload);
});
}
/**
* Get all connected players
*/
getAll() {
return Array.from(this.players.values());
}
/**
* Remove player by ws
*/
removeByWs(ws) {
this.players.delete(ws);
}
/**
* Get player by ws
*/
getByWs(ws) {
return this.players.get(ws);
}
}

View File

@@ -4,7 +4,8 @@ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js"; import { BO3ScriptSignal } from "../core/BO3ScriptSignal.js";
import * as CANNON from "cannon-es"; import * as CANNON from "cannon-es";
export class RenderService extends Instance { import { BaseService } from "./BaseService.js";
export class RenderService extends BaseService {
constructor() { constructor() {
super("RenderService"); super("RenderService");

View File

@@ -1,6 +1,7 @@
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
import { BaseService } from "./BaseService.js";
import { sha256, encoders } from "../core/util.js"; // Since SubtleCrypto isnt available in http only, we use a JS implementation import { sha256, encoders } from "../core/util.js"; // Since SubtleCrypto isnt available in http only, we use a JS implementation
export class ReplicatorService extends Instance { export class ReplicatorService extends BaseService {
constructor(datamodel, network) { constructor(datamodel, network) {
super("ReplicatorService"); super("ReplicatorService");
// Get tickloop from RenderService // Get tickloop from RenderService
@@ -98,6 +99,9 @@ export class ReplicatorService extends Instance {
}); });
} else { } else {
this._renderService.Heartbeat.Connect((dt) => this._onHeartbeat(dt)); this._renderService.Heartbeat.Connect((dt) => this._onHeartbeat(dt));
this._networkService.registerHandler(0x01, 0x02, (payload,ws) => {
}); // Server side replicator reciever, will be used later for client to server replication
// aka what roblox does for certain instances like under the player character
} }
this.SetParent(datamodel); this.SetParent(datamodel);
this.lastClearedStatCheck = 0; // If above 1 second, clear stats this.lastClearedStatCheck = 0; // If above 1 second, clear stats
@@ -132,9 +136,14 @@ export class ReplicatorService extends Instance {
const toReplicate = []; const toReplicate = [];
// Get workspace and its children // Get workspace and its children
const workspace = this.Parent.GetService("Workspace"); const workspace = this.Parent.GetService("Workspace");
const players = this.Parent.GetService("Players");
if (workspace) { if (workspace) {
toReplicate.push(...workspace.Children); toReplicate.push(...workspace.Children);
} }
if (players) {
toReplicate.push(...players.Children); // Replicate player instances too
// (so players can see each other)
}
// Send over the pipe // Send over the pipe
for (const inst of toReplicate) { for (const inst of toReplicate) {
// Check property hashes to see if anything changed // Check property hashes to see if anything changed
@@ -175,7 +184,8 @@ export class ReplicatorService extends Instance {
key === "scene" || key === "scene" ||
key === "controls" || key === "controls" ||
key === "physicsWorld" || key === "physicsWorld" ||
key === "loader" key === "loader" ||
key === "LuaState" // Script Lua state
) { ) {
continue; continue;
} }
@@ -199,7 +209,14 @@ export class ReplicatorService extends Instance {
// Send to all clients // Send to all clients
let outpay = JSON.stringify(payload); let outpay = JSON.stringify(payload);
this.bytesSent += outpay.length + 2; // +2 for ptype and psub this.bytesSent += outpay.length + 2; // +2 for ptype and psub
this._networkService.broadcast(0x00, 0x02, outpay); // Refer to ptype and psub definitions to see what these mean this._networkService.broadcast(0x00, 0x02, outpay,true,(ws) => {
const player = players ? players.getByWs(ws) : null;
// Dont send to the player if its their own character
if (player && inst.InstanceId === player.InstanceId) {
return true;
}
return false;
}); // Refer to ptype and psub definitions to see what these mean
// Update the _lastHash property to avoid re-sending unchanged data // Update the _lastHash property to avoid re-sending unchanged data
inst._lastHash = hash; inst._lastHash = hash;
//console.log(`Replicated instance ${inst.Name} (${inst.InstanceId}) to clients.`); //console.log(`Replicated instance ${inst.Name} (${inst.InstanceId}) to clients.`);

63
js/instances/Script.js Normal file
View File

@@ -0,0 +1,63 @@
import { lua, lauxlib, lualib, to_luastring } from "fengari";
import { ScriptSchedulerService } from "./ScriptSchedulerService.js";
import { Instance } from "./Instance.js"; // your engine's instance class
// Import interop from fengari-interop
import { push, luaopen_js } from "fengari-interop";
export class Script extends Instance {
constructor(sourceCode) {
super("Script");
this.Source = sourceCode;
this.LuaState = null;
}
Run() {
const L = lauxlib.luaL_newstate();
luaopen_js(L);
lualib.luaL_openlibs(L);
// Bridge all engine APIs
this._registerGlobals(L);
// Compile the source
const status = lauxlib.luaL_loadstring(L, to_luastring(this.Source));
if (status !== lua.LUA_OK) {
const err = lua.lua_tojsstring(L, -1);
throw new Error("Lua load error: " + err);
}
// Create a coroutine thread
const thread = lua.lua_newthread(L);
// Schedule with scheduler
this._getScheduler().scheduleFengariCoroutine(thread, L);
this.LuaState = L;
}
_registerGlobals(L) {
// game global
push(L, globalThis.game);
lua.lua_setglobal(L, to_luastring("game"));
// task + wait bridge
lua.lua_pushjsfunction(L, () => {
const secs = lua.lua_isnumber(L, 1) ? lua.lua_tonumber(L, 1) : 0;
const yieldVal = JSON.stringify({ wait: secs });
lua.lua_pushstring(L, to_luastring(yieldVal));
return lua.lua_yield(L, 1);
});
lua.lua_setglobal(L, to_luastring("task_wait"));
// Lua API helpers
lua.lua_pushjsfunction(L, () => {
const inst = new Instance("Part"); // example
return inst.LuaBridge(); // converts to Lua table/proxy
});
lua.lua_setglobal(L, to_luastring("Instance_new"));
}
_getScheduler() {
return game.GetService("ScriptSchedulerService");
}
}

View File

@@ -0,0 +1,111 @@
import { lua, lauxlib, lualib, to_luastring } from "fengari";
const lua_resume = lua.lua_resume;
const LUA_OK = lua.LUA_OK;
const LUA_YIELD = lua.LUA_YIELD;
import { BaseService } from "./BaseService.js";
export class ScriptSchedulerService extends BaseService {
constructor() {
super("ScriptSchedulerService");
this._scheduled = [];
this._waiting = [];
this._running = false;
}
// Add a script (generator function) to the scheduler
schedule(scriptGen) {
if (typeof scriptGen === "function") {
const gen = scriptGen();
this._scheduled.push({ gen, wakeTime: 0 });
} else if (scriptGen && typeof scriptGen.next === "function") {
this._scheduled.push({ gen: scriptGen, wakeTime: 0 });
} else {
throw new Error("Script must be a generator or generator function");
}
}
// Add a Fengari coroutine (thread) to the scheduler
scheduleFengariCoroutine(luaThread, luaState) {
// luaThread: the coroutine (thread) object from Fengari
// luaState: the Lua state (L) from Fengari
console.debug("Scheduling Fengari coroutine");
this._scheduled.push({ fengari: true, thread: luaThread, state: luaState, wakeTime: 0 });
}
// Main update loop, call this every frame with dt (delta time in seconds)
step(dt) {
const now = Date.now();
// Wake up any waiting scripts whose time has come
for (let i = this._waiting.length - 1; i >= 0; i--) {
const item = this._waiting[i];
if (now >= item.wakeTime) {
this._scheduled.push(item);
this._waiting.splice(i, 1);
console.log("Woke up routine",i,"from waiting.");
}
}
// Run scheduled scripts
for (let i = this._scheduled.length - 1; i >= 0; i--) {
const item = this._scheduled[i];
if (item.fengari) {
// Fengari coroutine
const L = item.state;
const thread = item.thread;
// Resume the coroutine
const status = lua_resume(thread, L, 0);
if (status === LUA_OK) {
// Coroutine finished
this._scheduled.splice(i, 1);
console.log("Woke up routine",i,"from fengari, finished.");
} else if (status === LUA_YIELD) {
// Check for yield value (wait)
// You must implement a way for Lua to yield with a wait time, e.g. coroutine.yield({wait=seconds})
// For now, we assume the top of the stack is a table with a 'wait' field
const luaTable = lua.lua_tojsstring(L, -1);
let waitSeconds = 0;
try {
// Try to parse the yielded value as JSON (if you yield a JSON string)
const val = JSON.parse(luaTable);
if (val && val.wait) waitSeconds = val.wait;
} catch (e) { }
if (waitSeconds > 0) {
this._waiting.push({ ...item, wakeTime: now + waitSeconds * 1000 });
this._scheduled.splice(i, 1);
}
// Otherwise, just continue next frame
} else {
// Error or unknown status
this._scheduled.splice(i, 1);
}
} else {
// JavaScript generator
const { value, done } = item.gen.next();
if (done) {
this._scheduled.splice(i, 1);
console.log("Woke up routine",i,"from JS generator, finished.");
} else if (value && value.wait) {
// Script yielded with {wait: seconds}
const ms = value.wait * 1000;
this._waiting.push({ gen: item.gen, wakeTime: now + ms });
this._scheduled.splice(i, 1);
}
}
}
}
// Utility for scripts to yield for a certain time (in seconds)
static wait(seconds) {
return { wait: seconds };
}
}
// Example usage:
// function* myScript() {
// console.log("Script start");
// yield ScriptSchedulerService.wait(1);
// console.log("1 second later");
// yield ScriptSchedulerService.wait(2);
// console.log("2 more seconds later");
// }
// const scheduler = new ScriptSchedulerService();
// scheduler.schedule(myScript);
// setInterval(() => scheduler.step(1/60), 1000/60);

View File

@@ -0,0 +1,117 @@
// ServiceService.js
import { BaseService } from "./BaseService.js";
import { NetworkService } from "./NetworkService.js";
import { RenderService } from "./RenderService.js";
import { ReplicatorService } from "./ReplicatorService.js";
import { ScriptSchedulerService } from "./ScriptSchedulerService.js";
import { Players } from "./Players.js";
import { Workspace } from "./Workspace.js";
import { DataModel } from "../core/DataModel.js";
import * as THREE from "three";
export class ServiceService extends BaseService {
constructor() {
super("ServiceService");
this._services = new Map();
}
register(name, ctor, ...args) {
if (this._services.has(name)) return this._services.get(name);
const instance = new ctor(...args);
this._services.set(name, instance);
return instance;
}
GetService(name) {
return this._services.get(name);
}
GetDataModel() {
return globalThis.game;
}
initAll(port) {
// === Core DataModel ===
const dm = this.register("DataModel", DataModel);
dm.SetParent(null);
this.SetParent(dm); // Parent myself to the DataModel
globalThis.game = dm;
// === Workspace ===
const ws = this.register("Workspace", Workspace);
ws.SetParent(dm);
// === RenderService ===
const render = this.register("RenderService", RenderService, dm);
render.isServer = true;
render.start();
render.SetParent(dm);
// === Networking ===
const net = this.register("NetworkService", NetworkService);
net.listen(port);
net.SetParent(dm);
// === Players ===
const players = this.register("Players", Players, net);
players.SetParent(dm);
// === ReplicatorService ===
const replication = this.register("ReplicatorService", ReplicatorService, dm, net);
// === ScriptSchedulerService ===
const scriptScheduler = this.register(
"ScriptSchedulerService",
ScriptSchedulerService,
dm
);
scriptScheduler.SetParent(dm);
// Connect to RenderService for update loop
render.Stepped.Connect((dt) => {
scriptScheduler.step(dt);
});
// Cross-link
replication.Players = players;
players.Replicator = replication;
// === Example echo handler ===
net.registerHandler(0x01, 0x00, (payload, client) => {
console.log("Echo:", new TextDecoder().decode(payload));
net.sendToClient(client, 0x01, 0x01, payload);
});
// === Example test parts ===
//this._spawnDemoScene(ws);
console.log("[ServiceService] Initialized all services.");
}
async _spawnDemoScene(ws) {
const { LerpedBasePart } = await import("./LerpedBasePart.js");
const ground = new LerpedBasePart();
ground.Size = new THREE.Vector3(10, 1, 10);
ground.Position.set(0, -0.5, 0);
ground.setAnchored(true);
ground.SetParent(ws);
ground.updateSizes();
let angle = 0;
setInterval(() => {
angle += 0.01;
ground.Orientation.set(0, angle, 0);
ground.Position.set(
5 * Math.sin(angle * 2),
-0.5,
5 * Math.cos(angle * 2)
);
}, 16);
const part = new LerpedBasePart();
part.Size = new THREE.Vector3(1, 1, 1);
part.Position.set(10, 5, 0);
part.setAnchored(false);
part.SetParent(ws);
part.updateSizes();
}
}

View File

@@ -1,5 +1,6 @@
import { BaseService } from "./BaseService.js";
import { Instance } from "./Instance.js"; import { Instance } from "./Instance.js";
export class Workspace extends Instance { export class Workspace extends BaseService {
constructor() { constructor() {
super("Workspace"); super("Workspace");
this.InstanceId = "WorkspaceRoot"; // Fixed ID for Workspace (Bad idea to replicate services over network?) this.InstanceId = "WorkspaceRoot"; // Fixed ID for Workspace (Bad idea to replicate services over network?)

40
js/place.json Normal file
View File

@@ -0,0 +1,40 @@
{
"objects": [
{
"InstanceId": "2",
"ClassName": "BasePart",
"Name": "Ground",
"ParentId": "WorkspaceRoot",
"properties": {
"Color": { "_type": "Color", "value": 65280 },
"Size": { "_type": "Vector3", "value": { "x": 50, "y": 1, "z": 50 } },
"Position": { "_type": "Vector3", "value": { "x": 0, "y": -0.5, "z": 0 } },
"Anchored": { "_type": "bool", "value": true }
}
},
{
"InstanceId": "3",
"ClassName": "BasePart",
"Name": "FallingCube",
"ParentId": "WorkspaceRoot",
"properties": {
"Color": { "_type": "Color", "value": 16711680 },
"Size": { "_type": "Vector3", "value": { "x": 2, "y": 2, "z": 2 } },
"Position": { "_type": "Vector3", "value": { "x": 0, "y": 15, "z": 0 } },
"Anchored": { "_type": "bool", "value": false }
}
},
{
"InstanceId": "4",
"ClassName": "BasePart",
"Name": "Wall",
"ParentId": "WorkspaceRoot",
"properties": {
"Color": { "_type": "Color", "value": 255 },
"Size": { "_type": "Vector3", "value": { "x": 1, "y": 10, "z": 20 } },
"Position": { "_type": "Vector3", "value": { "x": 10, "y": 5, "z": 0 } },
"Anchored": { "_type": "bool", "value": true }
}
}
]
}

56
js/serverParams.js Normal file
View File

@@ -0,0 +1,56 @@
import { ServiceService } from "./instances/ServiceService.js";
import fs from 'fs';
import { program } from "commander";
import { sha256, encoders } from "./core/util.js";
import { exit } from "process";
program
.option("-p, --port <number>", "Port to listen on", "8080")
.option("-a, --place <url>", "Place JSON file", "https://example.com/asset.php?id=1818");
program.parse();
const options = program.opts();
options.port = parseInt(options.port, 10); // optional, if you need a number
const Services = new ServiceService(options.port);
Services.initAll();
globalThis.Services = Services; // optional global shortcut
// Load from place.json
// import FS
(async () => {
const placeReq = await fetch(options.place);
if (!placeData.ok) {
console.error("Failed to fetch place data!");
exit(1);
}
const placeData = await placeReq.json();
// Create instances off this
for (let obj of placeData.objects) {
const parent = Services.GetDataModel().FindInstanceRecursively(obj.ParentId)
if (parent) {
import(`./instances/${obj.ClassName}.js`).then((module) => {
const NewClass = module[obj.ClassName];
if (!NewClass) {
console.warn(`Init: Class ${obj.ClassName} not found for startup. Is this server up to date?`);
return;
}
const newInst = new NewClass();
newInst.InstanceId = obj.InstanceId; // Set the same InstanceId
newInst.Name = obj.Name;
for (let prop in obj.properties) {
const type = prop._type
const value = prop.value
if (encoders[type]) obj.value = encoders[type].decode(value);
else obj.value = value;
};
newInst.SetParent(parent)
});
}
}
console.log("Server initialized and listening on port " + options.port.toString() + "!");
})();

View File

@@ -1,72 +1,22 @@
import { NetworkService } from "./instances/NetworkService.js"; import { ServiceService } from "./instances/ServiceService.js";
import { RenderService } from "./instances/RenderService.js";
import { ReplicatorService } from "./instances/ReplicatorService.js";
import { DataModel } from "./core/DataModel.js";
import { Workspace } from "./instances/Workspace.js";
import * as THREE from "three";
const dm = new DataModel(); const Services = new ServiceService();
dm.SetParent(null); Services.initAll();
// === Workspace === globalThis.Services = Services; // optional global shortcut
const ws = new Workspace();
ws.SetParent(dm);
// === RenderService (headless physics) === console.log("Server initialized and listening on port 8080.");
const render = new RenderService(dm);
render.isServer = true;
render.start();
render.SetParent(dm);
// === Networking === // Create a test script instance
const net = new NetworkService(); import { Script } from "./instances/Script.js";
net.listen(8080); const testScript = new Script(`print("Hello from Lua script!")
net.SetParent(dm); for i=1,5 do
print("Waiting...", i)
// Simple echo handler task_wait(1) -- wait 1 second
net.registerHandler(0x01, 0x00, (payload, client) => { end
console.log("Received from client:", new TextDecoder().decode(payload)); print("Lua script finished!")`);
net.sendToClient(client, 0x01, 0x01, payload);
});
// === Replication Service ===
const replication = new ReplicatorService(dm, net);
// === Test parts ===
const { LerpedBasePart } = await import("./instances/LerpedBasePart.js");
// Falling cube
// Anchored ground
const ground = new LerpedBasePart();
ground.Size = new THREE.Vector3(10, 1, 10);
ground.Position.set(0, -0.5, 0);
ground.setAnchored(true);
ground.SetParent(ws);
ground.updateSizes();
// Spin ground
let angle = 0;
setInterval(() => {
angle += 0.01;
ground.Orientation.set(0, angle, 0);
ground.Position.set(5 * Math.sin(angle * 2), -0.5, 5 * Math.cos(angle * 2));
}, 16);
/*setInterval(() => {
const part = new LerpedBasePart();
part.Position.set(0, 35, 0);
part.SetParent(ws);
part.setAnchored(true);
part.updateVisual(1 / 60); // Ensure physics is initialized
setTimeout(() => {
part.updateBodyPosition(); // Reset physics to new position
part.setAnchored(false);
}, 2000);
part.updateSizes();
}, 1000);*/
// Falling cube singularity
const part = new LerpedBasePart();
part.Size = new THREE.Vector3(1, 1, 1);
part.Position.set(10, 5, 0);
part.setAnchored(false);
part.SetParent(ws);
part.updateSizes();
// Parent to Workspace
const workspace = Services.GetService("Workspace");
testScript.SetParent(workspace);
testScript.Run();

View File

@@ -8,7 +8,13 @@ Byte 2 is PSub (packet subtype), which is the actual packet name. Each ptype can
0x00 = Clientbound (should never be sent by a client) 0x00 = Clientbound (should never be sent by a client)
0x01 = Serverbound (should never be recieved by a client) 0x01 = Serverbound (should never be recieved by a client)
## PSub ## PSub for client
0x02 = Replication channel 0x02 = Replication channel
0x01 = Echo (Server will return what you send) 0x01 = Echo (Server will return what you send)
0x04 = Recv auth validness
0xFF = kick
## PSub for server
0x03 = Recv auth session
0x02 = Same as client

74
package-lock.json generated
View File

@@ -10,6 +10,9 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cannon-es": "^0.20.0", "cannon-es": "^0.20.0",
"commander": "^14.0.1",
"fengari": "^0.1.4",
"fengari-interop": "^0.1.3",
"three": "^0.180.0", "three": "^0.180.0",
"ws": "^8.18.3" "ws": "^8.18.3"
}, },
@@ -780,6 +783,15 @@
"integrity": "sha512-eZhWTZIkFOnMAJOgfXJa9+b3kVlvG+FX4mdkpePev/w/rP5V8NRquGyEozcjPfEoXUlb+p7d9SUcmDSn14prOA==", "integrity": "sha512-eZhWTZIkFOnMAJOgfXJa9+b3kVlvG+FX4mdkpePev/w/rP5V8NRquGyEozcjPfEoXUlb+p7d9SUcmDSn14prOA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/commander": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz",
"integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.25.10", "version": "0.25.10",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
@@ -840,6 +852,26 @@
} }
} }
}, },
"node_modules/fengari": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz",
"integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==",
"license": "MIT",
"dependencies": {
"readline-sync": "^1.4.9",
"sprintf-js": "^1.1.1",
"tmp": "^0.0.33"
}
},
"node_modules/fengari-interop": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.3.tgz",
"integrity": "sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==",
"license": "MIT",
"peerDependencies": {
"fengari": "^0.1.0"
}
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -874,6 +906,15 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -923,6 +964,15 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/readline-sync": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
"license": "MIT",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.52.4", "version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
@@ -975,6 +1025,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
"node_modules/three": { "node_modules/three": {
"version": "0.180.0", "version": "0.180.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz",
@@ -998,10 +1054,22 @@
"url": "https://github.com/sponsors/SuperchupuDev" "url": "https://github.com/sponsors/SuperchupuDev"
} }
}, },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"license": "MIT",
"dependencies": {
"os-tmpdir": "~1.0.2"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.1.9", "version": "7.1.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -10,11 +10,14 @@
"description": "", "description": "",
"dependencies": { "dependencies": {
"cannon-es": "^0.20.0", "cannon-es": "^0.20.0",
"commander": "^14.0.1",
"fengari": "^0.1.4",
"fengari-interop": "^0.1.3",
"three": "^0.180.0", "three": "^0.180.0",
"ws": "^8.18.3" "ws": "^8.18.3"
}, },
"devDependencies": { "devDependencies": {
"vite": "^7.1.9" "vite": "^7.1.9"
}, },
"type":"module" "type": "module"
} }