Skip to content

Instantly share code, notes, and snippets.

@MrChickenRocket
Last active January 22, 2023 01:03
Show Gist options
  • Save MrChickenRocket/d272579179adc94314f78c135580a700 to your computer and use it in GitHub Desktop.
Save MrChickenRocket/d272579179adc94314f78c135580a700 to your computer and use it in GitHub Desktop.
Roblox proxy server for writing files
local module = {}
local HttpService = game:GetService("HttpService")
function to_base64(data)
local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
return ((data:gsub('.', function(x)
local r,b='',x:byte()
for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data%3+1])
end
function module:WriteFile(filePath, fileData)
local url = "http://localhost:3050/file"
local chunks = {}
local chunkSize = 512 * 1024 -- 512 KB
local chunksCount = math.ceil(#fileData / chunkSize)
for i = 1, chunksCount do
local start = (i - 1) * chunkSize + 1
local finish = i * chunkSize
chunks[i] = fileData:sub(start, finish)
end
for i = 1, chunksCount do
local jsonData = HttpService:JSONEncode({path = filePath, data = to_base64(chunks[i]), index = i, total = chunksCount})
local success, response = pcall(function()
return HttpService:PostAsync(url, jsonData)
end)
if not success then
warn("Failed to write file: " .. response)
return false
end
end
return true
end
return module
const express = require('express');
const fs = require('fs');
const path = require("path");
const bodyParser = require("body-parser");
var base64 = require('base64-arraybuffer');
const app = express();
const { check, validationResult } = require('express-validator');
const port = process.argv[2] || 3050; //Default port of 3050
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
function GetMostRecentRobloxFolder()
{
const appData = process.env.appData;
let systemFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/' : process.env.HOME + "/.local/")
const robloxFolder = path.join(systemFolder, "..", "Local", "Roblox", "Versions");
try {
const files = fs.readdirSync(robloxFolder);
let mostRecent;
let mostRecentTime = 0;
files.forEach(file => {
const filePath = path.join(robloxFolder, file);
const fileStat = fs.statSync(filePath);
if (fileStat.isDirectory() && fileStat.ctimeMs > mostRecentTime) {
mostRecent = filePath;
mostRecentTime = fileStat.ctimeMs;
}
});
return mostRecent;
} catch (err) {
throw err;
}
}
function GetContentFolder(robloxPath)
{
const contentFolder = path.join(robloxPath, "content", "procmesh");
try {
if (!fs.existsSync(contentFolder)) {
fs.mkdirSync(contentFolder);
console.log(`'${contentFolder}' folder created.`);
} else {
console.log(`'${contentFolder}' folder already exists.`);
}
} catch (err) {
console.error(err);
}
return contentFolder;
}
let robloxFolder = '';
function Setup()
{
console.log("Checking folders...");
robloxFolder = GetContentFolder(GetMostRecentRobloxFolder())
console.log(`Most recent roblox path is ${robloxFolder}`);
}
/*
app.post('/file', [
check('path').not().isEmpty().withMessage('File path is required'),
check('path').isString().withMessage('File path must be a string'),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Handle file writing
const fileName = req.body.path;
console.log('writing ' + fileName);
let finalPath = path.join(robloxFolder, fileName);
const data = base64.decode(req.body.data);
const bufferData = Buffer.from(data);
fs.writeFile(finalPath, bufferData, err => {
if (err) {
res.status(500).json({ message: `Error writing file: ${err}` });
} else {
res.status(200).json({ message: 'File written successfully' });
}
});
});
*/
let chunks = {}
app.post('/file', [
check('path').not().isEmpty().withMessage('File path is required'),
check('path').isString().withMessage('File path must be a string'),
check('index').not().isEmpty().withMessage('Chunk index is required'),
check('total').not().isEmpty().withMessage('Total chunks count is required'),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Handle file writing
const fileName = req.body.path;
console.log('writing ' + fileName);
const index = req.body.index;
const total = req.body.total;
let finalPath = path.join(robloxFolder, fileName);
const data = base64.decode(req.body.data);
const bufferData = Buffer.from(data);
chunks[index] = bufferData;
if (Object.keys(chunks).length === total) {
let finalData = Buffer.concat(Object.values(chunks));
fs.writeFile(finalPath, finalData, err => {
if (err) {
res.status(500).json({ message: `Error writing file: ${err}` });
} else {
res.status(200).json({ message: 'File written successfully' });
}
chunks = {}
});
} else {
res.status(200).json({ message: 'Chunk received' });
}
});
Setup();
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
@MrChickenRocket
Copy link
Author

This writes to somewhere accessible to rbxasset://

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment