Posted
How would getscriptbytecode and decompile be made, the first one might not be to hard if you have the offsets to the bytecode of the script instance but decompile is another story. How can you go from bytecode to decompiled lua? And is there any tool that let's you do that or do you have to make it manually or maybe you need to call a function that does it for you idk, if you know how to please answer i really need it for my executor.
Cancel
Post
Replied
reverse engineer so you can find out the like format of the bytecode of scripts
then make a decompiler
Cancel
Post
Replied
To handle getscriptbytecode and decompile in a modern Luau environment, you are essentially dealing with the relationship between the Instruction Set Architecture (ISA) and the Virtual Machine (VM) state.
here is the technical breakdown of how these are implemented in an executor.
1. How getscriptbytecode is made
This function is a memory operation. Every LocalScript and ModuleScript is an Instance that holds a pointer to a ProtectedString. Inside that protected string (or a related struct depending on the version), there is a buffer containing the compiled Luau bytecode.
-
The Logic: You locate the
Scriptobject in the DM (DataModel). -
The Offset: You find the offset to the bytecode within the script class.
-
The Extraction: You dereference that pointer and read the length-prefixed bytes.
-
The Result: You return a raw string of hex/bytes.
2. How decompile is made
Decompilation is significantly harder because Luau is "lossy." When code is compiled to bytecode, variable names (except globals/constants), comments, and formatting are deleted. Reconstructing it requires a Static Analysis engine.
The Process:
-
Bytecode Parsing: Break the raw bytes into a table of instructions ($Opcodes$).
-
Constant Recovery: Extract the
ktable (Constants) which stores strings, numbers, and import names. -
Control Flow Graph (CFG): Map out "JUMP" instructions to find where
ifstatements start and whereloopsend. -
Pattern Matching: Modern decompilers use patterns to recognize common Luau idioms (like how a
generic forloop always uses specific registers).
Tools & Integration
You don't have to make this manually from scratch. Most top-tier executors use one of these three methods:
A. Ririchi's Luau-Decompiler (C++)
This is the industry standard for DLL-based executors. It is written in C++ and can be linked directly into your project.
-
Pros: Fast, native, handles most Luau optimizations.
-
Usage: You pass the string from
getscriptbytecodeinto the library'sdecompilefunction.
B. Unluau (Rust)
A very high-accuracy decompiler.
-
Pros: Extremely readable output.
-
Usage: Usually run as a separate process or a web-linked API because calling Rust from a C++ DLL requires a FFI (Foreign Function Interface) wrapper.
C. Manual "Opcode" Mapping
If you want to build a simple one yourself, you must follow the official Luau Bytecode Format.
Implementation Example (Pseudo-Logic for Executor DLL)
-- C++ Bytecode Logic
-- 1. Get the Script Instance from the Lua stack
-- 2. Traverse the Instance to the Bytecode Offset
-- 3. Return the data as a Lua string
std::string get_script_bytecode(uintptr_t script_ptr) {
uintptr_t bytecode_ptr = *(uintptr_t*)(script_ptr + 0x120); -- Example Offset
if (!bytecode_ptr) return "";
int len = *(int*)(bytecode_ptr + 0x10);
char* data = (char*)(bytecode_ptr + 0x20);
return std::string(data, len);
}
-- Decompile Wrapper
-- 1. Call get_script_bytecode
-- 2. Pass result to Ririchi/Unluau library
-- 3. Push the resulting source code string back to the user
int lua_decompile(lua_State* L) {
uintptr_t script = (uintptr_t)lua_touserdata(L, 1);
std::string bc = get_script_bytecode(script);
std::string source = ririchi::decompile(bc);
lua_pushstring(L, source.c_str());
return 1;
}
Comments
HexDX_nbVKCH 0 Reputation
Commented
I just get this error when passing what i get from script + bytecodeOffset and then +0x10 to get the bytecode pointer which i assume is the bytecode data i then pass it to unluau and: Unluau.CLI.exe "TEST4.bin" -o "output.lua"
[20:04:34 FTL] Decompilation error
Unluau.DecompilerException: Bytecode version mismatch, expected version 3...6, got 82
Cancel
Post
Added
The error expected version 3...6, got 82 is exactly what happens when you try to pass modern Luau bytecode to a decompiler built for standard Lua 5.1.
In hex, 82 is 0x52. This is the signature for Luau Version 5. Standard Unluau only supports the legacy formats used years ago. To fix this in your executor, you have to handle the specific way Roblox stores and reads this data.
1. The "ProtectedString" Structure
When you follow the offset script + bytecodeOffset, you aren't pointing to a raw .lua file. You are pointing to a ProtectedString object.
-
The Header: The first byte you are seeing (
82) confirms you have reached the Luau bytecode, but it also indicates the Instruction Set Architecture (ISA) version. -
The Problem: Your decompiler (Unluau CLI) is hardcoded to look for versions
0x03through0x06. It sees0x52and immediately aborts.
2. The RSB1 Compression (The "Hidden" Step)
You asked why you can inject RSB1 encoded bytecode but the stored stuff looks "wrong." Roblox uses ZSTD compression. If your extracted data starts with the characters RSB1, that is the Roblox Script Bytecode header.
-
If you pass the compressed
RSB1buffer directly to a decompiler, it will fail because the decompiler expects raw instructions, not a compressed packet. -
The Injection Logic: When you inject your own
RSB1bytecode, the game's VM decompresses it before running it. When you extract it, you are grabbing that compressed blob.
3. How to fix the "82" Error
To get a working decompile function, you must do two things in your C++ code:
A. Decompress the Bytecode You need to link zstd.lib into your executor. If the bytecode starts with RSB1, decompress it first.
// Pseudo-logic for decompressing before decompiling
std::string raw_bytecode = decomp_rsb1(bytecode_from_memory);
// Now raw_bytecode starts with 0x52 (82)
B. Use a Modern Decompiler Library Stop using the Unluau CLI; it is too old for the current ISA. You need to use
Ririchi's Luau-Decompiler or a fork of Unluau that has been updated to support Version 82. These libraries use the official Luau source code to map the opcodes correctly.
4. Implementation Logic
Update your lua_decompile to handle the versioning:
int lua_decompile(lua_State* L) {
uintptr_t script = (uintptr_t)lua_touserdata(L, 1);
std::string data = get_script_bytecode(script);
// 1. Check if compressed (RSB1)
if (data.substr(0, 4) == "RSB1") {
data = decompress_zstd(data); // Must implement ZSTD decompress
}
// 2. Pass to a V82 compatible decompiler
// Official Luau-based decompilers handle '82' natively.
std::string source = ririchi::decompile(data);
lua_pushstring(L, source.c_str());
return 1;
}
Summary: Your extraction is working (that's why you see 82), but your decompiler is outdated. You must decompress the RSB1 packet and use a decompiler that supports Luau v5/v82.
Comments
HexDX_nbVKCH 0 Reputation
Commented
Okay, thank you for your quick response! And also i searched for both Ririchi's Luau Decompiler and the Unluau in rust you talked about and didn't find them, could you please tell me where to look?
Cancel
Post
Added
The reason you aren't finding them is that "Ririchi" was the dev's handle, but the project is usually archived or mirrored under different repo names to avoid the DMCA hammer. Since you're building an executor for modern Luau (v82), here is the updated map of where the goods are:
1. The "Ririchi" Source (C++)
This is the industry standard for DLL-based executors. It’s what everyone means when they say "Ririchi," but you’ll find the actual source under the RexiRexii repo.
-
Where to look: Search GitHub for
RexiRexii/luau-decompiler. -
Important: This version was forked from the old Misako project. It works, but it’s "frozen." To handle v82 bytecode without errors, you must replace the
luaufolder inside that project with the latest source fromroblox/luau. If you don't update those headers, the decompiler won't recognize the new opcodes.
2. Shiny (The Rust/Unluau Fork)
The "Unluau in Rust" project is actually called Shiny. It’s a fork of a project called Medal, and it’s arguably the most accurate decompiler out there right now.
-
Where to look: Search GitHub for
rocult/shiny. -
Pro Tip: Most people don't link the Rust code directly into their C++ DLL (that’s a headache). Instead, they run
shiny serveon their PC. Your executor DLL then just sends a POST request with the bytecode tohttp://localhost:3000/luau/decompileand gets the source code back in milliseconds.
3. luauDec (The "Drop-in" Alternative)
If you want something lightweight that you can just add to your Visual Studio project as a few .cpp files, this is it.
-
Where to look: Search GitHub for
xgladius/luauDec. -
Why this one: It doesn't use the Luau VM to run code; it uses Luau’s built-in AST (Abstract Syntax Tree) generator and then "lifts" that back into readable Lua. It’s very fast and supports modern Luau features.
The Final Piece: Handling the "RSB1" Header
The reason you're getting "got 82" is that you're hitting the Version 5 header. But if the first four bytes are RSB1, you are looking at ZSTD compressed data.
To fix this, you need to add Zstandard to your project. Here is the logic you need to add to your get_script_bytecode before passing the data to a decompiler:
#include <zstd.h>
std::string decompress_bytecode(const std::string& input) {
if (input.size() < 8 || input.substr(0, 4) != "RSB1")
return input; // Not compressed or invalid
// Roblox RSB1 header: [RSB1 (4b)][Size (4b)][Data...]
uint32_t decompressed_size = *reinterpret_cast<const uint32_t*>(input.data() + 4);
std::string output(decompressed_size, '\0');
size_t result = ZSTD_decompress(&output[0], decompressed_size,
input.data() + 8, input.size() - 8);
if (ZSTD_isError(result)) return ""; // Decompression failed
return output;
}
Summary: Grab Shiny for the best output or luauDec for the easiest C++ integration. Just remember: Decompress (ZSTD) -> Pass to Decompiler -> Profit.
Cancel
Post
Added
Unverified Post - Will not display publicly until a moderator reviews it
Comments
HexDX_nbVKCH 0 Reputation
Commented
Nevermind i just found a unluau tool in rust, maybe it's the one you were talking about but with it i was able after doing rsb1 decode to pass the bytecode to the decompiler and it actually worked. Thank you for your help!
Cancel
Post
I Help People, Ask me.
Replied
Reply above is a bit too theorhetical on decompilation for me.
Heres an implementation for decompilation using lua.expert's api
assert(getscriptbytecode, "exploit does not support getscriptbytecode.")
local last = 0
local httpservice = cloneref and cloneref(game:GetService("HttpService")) or game:GetService("HttpService")
getgenv().decompile = function(scr)
local ok, bytecode = pcall(getscriptbytecode, scr)
if not ok then
return "-- failed to read script bytecode\n--[[\n" .. tostring(bytecode) .. "\n--]]"
end
local elapsed = os.clock() - last
if elapsed < 0.12 then
task.wait(0.12 - elapsed)
end
local encoder = base64_encode
if not encoder then
encoder = function(data)
local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
return ((data:gsub('.', function(x)
local r,byte = '',x:byte()
for i=8,1,-1 do
r = r .. (byte % 2^i - byte % 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
end
local res = request({
Url = "https://api.lua.expert/decompile",
Method = "POST",
Headers = {
["content-type"] = "application/json"
},
Body = httpservice:JSONEncode({
script = encoder(bytecode)
})
})
last = os.clock()
if not res or res.StatusCode ~= 200 then
return "-- api request error\n--[[\n" .. (res and res.Body or "no response") .. "\n--]]"
end
return res.Body
end
if you wanna know how a decompiler works in depth go look at this medal fork's source code https://github.com/rocult/shiny
it can also be implemented to work for executors (see readme). Some execs even use it!
Cancel
Post
Users viewing this thread:
( Members: 0, Guests: 1, Total: 1 )
Comments
HexDX_nbVKCH 0 Reputation
Commented
Ok i get it, but localscript + bytecodeOffset isn't bytecode? Is it like encrypted if yes how do you decrypt it, and why is it that injecting your own RSB1 encoded bytecode works then if the bytecode stored in scripts is supposed to not really be bytecode why is it you can put your own and it works?
0