diff --git a/resources/runcode/__resource.lua b/resources/runcode/__resource.lua new file mode 100644 index 0000000..308f2c9 --- /dev/null +++ b/resources/runcode/__resource.lua @@ -0,0 +1,8 @@ +client_script 'runcode_cl.lua' +server_script 'runcode_sv.lua' +server_script 'runcode_web.lua' + +client_script 'runcode_shared.lua' +server_script 'runcode_shared.lua' + +resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' \ No newline at end of file diff --git a/resources/runcode/runcode.lua b/resources/runcode/runcode.lua new file mode 100644 index 0000000..0456b37 --- /dev/null +++ b/resources/runcode/runcode.lua @@ -0,0 +1,3 @@ +RegisterCommand('runcode', function(source, args, rawCommand) + RunCode('return ' .. rawCommand:sub(8)) +end, true) \ No newline at end of file diff --git a/resources/runcode/runcode_cl.lua b/resources/runcode/runcode_cl.lua new file mode 100644 index 0000000..3dd5253 --- /dev/null +++ b/resources/runcode/runcode_cl.lua @@ -0,0 +1,15 @@ +RegisterNetEvent('runcode:gotSnippet') + +AddEventHandler('runcode:gotSnippet', function(id, code) + local res, err = RunCode(code) + + if not err then + if type(res) == 'vector3' then + res = json.encode({ table.unpack(res) }) + elseif type(res) == 'table' then + res = json.encode(res) + end + end + + TriggerServerEvent('runcode:gotResult', id, res, err) +end) \ No newline at end of file diff --git a/resources/runcode/runcode_shared.lua b/resources/runcode/runcode_shared.lua new file mode 100644 index 0000000..b22c388 --- /dev/null +++ b/resources/runcode/runcode_shared.lua @@ -0,0 +1,17 @@ +function RunCode(code) + local code, err = load(code, '@runcode') + + if err then + print(err) + return nil, err + end + + local status, result = pcall(code) + print(result) + + if status then + return result + else + return nil, result + end +end \ No newline at end of file diff --git a/resources/runcode/runcode_sv.lua b/resources/runcode/runcode_sv.lua new file mode 100644 index 0000000..9fd9e8c --- /dev/null +++ b/resources/runcode/runcode_sv.lua @@ -0,0 +1,7 @@ +RegisterCommand('run', function(source, args, rawCommand) + local res, err = RunCode('return ' .. rawCommand:sub(4)) +end, true) + +RegisterCommand('crun', function(source, args, rawCommand) + TriggerClientEvent('runcode:gotSnippet', source, -1, 'return ' .. rawCommand:sub(5)) +end, true) \ No newline at end of file diff --git a/resources/runcode/runcode_web.lua b/resources/runcode/runcode_web.lua new file mode 100644 index 0000000..55506dc --- /dev/null +++ b/resources/runcode/runcode_web.lua @@ -0,0 +1,133 @@ +local cachedFiles = {} + +local function sendFile(res, fileName) + if cachedFiles[fileName] then + res.send(cachedFiles[fileName]) + return + end + + local fileData = LoadResourceFile(GetCurrentResourceName(), 'web/' .. fileName) + + if not fileData then + res.writeHead(404) + res.send('Not found.') + return + end + + cachedFiles[fileName] = fileData + res.send(fileData) +end + +local codeId = 1 +local codes = {} + +local function handlePost(req, res) + req.setDataHandler(function(body) + local data = json.decode(body) + + if not data or not data.password or not data.code then + res.send(json.encode({ error = 'Bad request.'})) + return + end + + if GetConvar('rcon_password', '') == '' then + res.send(json.encode({ error = 'The server has an empty rcon_password.'})) + return + end + + if data.password ~= GetConvar('rcon_password', '') then + res.send(json.encode({ error = 'Bad password.'})) + return + end + + if not data.client or data.client == '' then + CreateThread(function() + local result, err = RunCode(data.code) + + res.send(json.encode({ + result = result, + error = err + })) + end) + else + codes[codeId] = { + timeout = GetGameTimer() + 1000, + res = res + } + + TriggerClientEvent('runcode:gotSnippet', tonumber(data.client), codeId, data.code) + + codeId = codeId + 1 + end + end) +end + +local function returnCode(id, res, err) + if not codes[id] then + return + end + + local code = codes[id] + codes[id] = nil + + local gotFrom + + if source then + gotFrom = GetPlayerName(source) .. ' [' .. tostring(source) .. ']' + end + + code.res.send(json.encode({ + result = res, + error = err, + from = gotFrom + })) +end + +CreateThread(function() + while true do + Wait(100) + + for k, v in ipairs(codes) do + if GetGameTimer() > v.timeout then + source = nil + returnCode(k, '', 'Timed out waiting on the target client.') + end + end + end +end) + +RegisterNetEvent('runcode:gotResult') +AddEventHandler('runcode:gotResult', returnCode) + +SetHttpHandler(function(req, res) + local path = req.path + + if req.method == 'POST' then + return handlePost(req, res) + end + + -- client shortcuts + if req.path == '/clients' then + local clientList = {} + + for _, id in ipairs(GetPlayers()) do + table.insert(clientList, { GetPlayerName(id), id }) + end + + res.send(json.encode({ + clients = clientList + })) + + return + end + + -- should this be the index? + if req.path == '/' then + path = 'index.html' + end + + -- remove any '..' from the path + path = path:gsub("%.%.", "") + + return sendFile(res, path) +end) \ No newline at end of file diff --git a/resources/runcode/web/index.html b/resources/runcode/web/index.html new file mode 100644 index 0000000..8e52368 --- /dev/null +++ b/resources/runcode/web/index.html @@ -0,0 +1,97 @@ + + + + fivem runcode + + + + + + + +

+ Password: (use your rcon password)
+ Client ID: (leave blank for server)
+
+ +
+
+ + + + + + + + + \ No newline at end of file