Introduction
This page serves as the official documentation for the Uranium project, a pure Luau environment powered by the FiU virtual machine.
The purpose of this documentation is to provide a complete reference for the extended functionality of the virtual machine, including its features, behavior, and available APIs.
It is intended for developers who want to understand, use, or build on top of the Uranium environment. This guide already assumes you have the needed experience.
Basics
Basic information covering the environment
What is the purpose?
Uranium is a multi-purpose pure luau environment, which uses the FiU bytecode interpreter to execute code. It’s sole purpose is to closely replicate the environment that seen in executors, as well as giving the user full control over the VM, allowing to control most things that occur in the VM.
The VM can also be used as a sandbox or to monitor and inspect obfuscated code during run-time. Some examples of use are listed below: - Reverse engineering deeply obfuscated code during run-time - Sandboxing - Monitoring or controlling script behavior
How can we properly utilize this tool?
Uranium by default offers a modified environment, which includes custom functions. These functions often mimic popular functions that are seen in executors as mentioned before, these are tools provided to you as default to allow you to achieve just that.
How do we use them?
This page serves as the documentation page, as mentioned before, this will allow you to help understand concepts you do not understand.
VM
What do you mean by VM?
Well we first must understand how Lua works and what happens when you code in Lua, the CPU executes your code, but how?
All roads lead to the CPU!
Whenever you write code in Lua, Python, C, C++, Swift, whatever it maybe, the CPU is the one who acts upon your code! but how?
Well lua is an interpreted language, meaning unlike languages like C, C++ it is not directly compiled to machine-code, which is what the CPU executes.
Lua, since its an interpreted language has what is known as the Virtual Machine.
What is the Virtual Machine?
The Virtual Machine is what executes your code, the compiler turns your code into bytecode which is what the Virtual Machine reads and executes, we do not write Lua code in bytecode because it will be very hard to understand and write.
You can think of it like this: Roblox takes my code I write and tells the compiler to compile our code to bytecode, then the compiler sends the bytecode to the VM (Virtual Machine) which will read the bytecode and execute them.
What is bytecode?
As I said previously bytecode is what the VM executes, bytecode is your compiled code. When the VM disassembles the bytecode, it becomes more understand and readable, bytecode is what the VM will read from and execute as I said before, and disassembled bytecode is the direct representation of the bytecode, so everything is more understandable to humans you can think of disassembled bytecode as “a list of tasks to do”.
I’ll give you an example, take a look at this source code:
local function add(a, b)
return (a+b);
end;
add(1, 2);
Very simple, just a function to add two numbers together, but what does this look like under the hood?
Well when bytecode isn’t disassembled it becomes very hard to read and understand, so we will be looking at what disassembled bytecode is, the disassembled bytecode will look like this:
Function 0 (add)
ADD R2 R0 R1
RETURN R2 1
Function 1 ()
DUPCLOSURE R0 K0 [add]
MOVE R1 R0
LOADN R2 1
LOADN R3 2
CALL R1 2 0
RETURN R0 0
So, when bytecode gets compiled they wrap your code in everything that is called the MAIN_PROTO, we can best visualize it by showing what it looks like in lua
This is our source:
print("Hello, world!");
When its compiled:
local function main(...)
print("Hello, world!");
return;
end;
The VM will start execution at main, if we look at the bytecode, we can piece things that are happening.
If we take a look at our ‘Function 1’ that is our main proto we talked about, below that we see what we call instructions.
What are instructions?
Instructions basically tell the VM what to do, at a very high-level. There are all kind of instructions that we see in Lua, keep in note Luau has their own instructions that are not seen in Lua.
A thing you may notice is the repetitive R like you see ‘R1’ in the disassembled bytecode, you may ask what are those?
Those are known as something as a register. Registers are slots of data that the VM works with, registers contain and store data, you can think of them as the arrays in lua which is an oversimplified analogy but whatever registers are limited in space!, there is more than just one register.
To help you get a better idea, lets look at this example, the provided code below is our source code
print("Hello, world!");
The disassembled bytecode looks like so:
GETIMPORT R0 1 [print] ; This will load the "print" function into register 0
LOADK R1 K2 ['Hello, world!'] ; This will load our hello world string in the source into register one
CALL R0 1 0 ; We invoke the print function specifying register 0, we specify the number one because that is the amount of arguments we called print which is our "hello world" and we specify the number zero because in our source code we ignored what print returns
Keep in mind, code while in –!native | @native (Native Code Generation) is LuauJIT (Luau Just In Time), meaning your source is directly translated into machine-code that CPUs execute rather than bytecode
What are Function Environments?
The best way I can explain this is how functions in your script get functions from, and the only way you can learn this is by playing with code your self.
Try to run this code in studio and see what happens!
local function test_func()
return print("Hello, world!");
end;
setfenv(test_func, {print = warn});
test_func();
In this example, we created a new function test_func which will print ‘Hello, world!’ when called, then we called a function called setfenv (Set Function Environment) to our defined array {print = warn}.
If you ran it, you would notice that print actually called warn!, because we defined a new function environment for test_func which is an array which set the print index to the warn function!
Setting a new environment for the function clears the old one!
local function test_func()
if (workspace ~= nil) then
print("Workspace exists");
else
print("Workspace doesnt exist!", workspace);
print(typeof(workspace));
end;
return;
end;
setfenv(test_func, {print = warn; typeof = typeof});
test_func();
As you see, print was warn again because like last time we defined a new array with the key string of print as warn, and you noticed in that function the workspace global was nil, because we set the environment to our new one
local function test_func(): ()
print("game?", game);
print(typeof(game));
print(game.Name);
return;
end;
setfenv(test_func, {game = workspace; print = print; typeof = typeof});
test_func();
If you ran it, we would see the game global is swapped out with workspace, because we defined that in our function environment {game = workspace; print = print; typeof = typeof}
We can also get the function environments of functions, we can do this with the function getfenv (get function environment)
local function test_func(): ()
print("Called");
return;
end;
local our_new_env: {[string]: any} = {print = warn};
setfenv(test_func, our_new_env);
print(getfenv(test_func) == our_new_env); --> true
getfenv(test_func).print = print;
test_func();
If you ran the script, you could observe that print actually made a call too print even when we defined {print = warn} as the new environment for the function, this is because we later GOT the function environment with getfenv and set getfenv(test_func).print = print back to the real print, keep in note we set ONLY test_func environment, so anything outside test_func still retains the old print
Why does this matter at all for this project?
As I’ve said the project uses a pure LuaU compiler and Virtual Machine Fiu to execute and run custom code, while in luau, this project is a modified | fork of the VM.
Since we can control the VM, we can add some pretty cool stuff to it.
Libraries
Libraries
Currently we have the following:
- Debug
- Signals
- Table
- Closure
- Instance
- VM
What is a library?
Libraries categorize a group of functions we provide (ex. Signals will provide functions that work with Signals and Connections)!
MustRead
Be aware of getrawmetatable, setrawmetatable, hookmetamethod functions
They will fail if a provided object’s metatable was created OUTSIDE the VM, if the metatable was created INSIDE the VM then the function will behave as normal.
Be aware of hooking
Make sure to use newcclosure because they open up detection vectors.
local hook: (...any) -> (...any); hook = hookfunction(math.clamp, function(...: any): ...: any
return hook(...);
end);
-- could be detected by:
xpcall(function(): never
return math.clamp();
end, function(error_message_str: string): ()
if (getfenv(2) ~= getfenv() or debug.info(2, "s") ~= "[C]") then
print("detected");
end;
return
end);
However you do not need to worry about this risk, when hooking because we do it for you automatically if you did not, they still open detections via stack-overflow and such.
However you are safe from more simple checks:
local hook: (...any) -> (...any); hook = hookfunction(tick, function(...: any): ...any
return hook(...);
end);
if (getfenv(tick) ~= getfenv() or debug.info(tick, "f") ~= tick or pcall(setfenv, tick, {}) or debug.info(tick, "s") ~= "[C]") then
print("detected");
end;
-- fails
There is NO ARGUMENT GUARD! it is up to you as the developer to sanitize and check arguments passed to hooks.
Detections
There’s plenty of detections an anti-tamper can use, I have no plans to fix them and I only care about a sandbox escape, if you use a script that has an anti-tamper prepare to be obliterated by it, however we offer you plenty of tools to reverse engineer the anti-tamper from the VM level (ex. hookopcode, many many functions in the debug lib)
Functions
newcclosure
The newcclosure function will take the user’s inputted function and creates and returns a C closure that invokes the user’s original inputted function.
function newcclosure(func: (...any) -> (...any)): (...any) -> (...any)
local function test_func(arg: string): ()
print(arg);
return;
end;
return newcclosure(test_func)("Hello, world!");
This is useful when doing L-C hooking.
hookfunction
The hookfunction function will invoke the user’s hook when the target is called, and provide a clone of the target.
function hookfunction(target: (...any) -> (...any), hook: (...any) -> (...any), debug_name_str: string?): -> (...) ->(...any)
local hook: (...any) -> (...any); hook = hookfunction(warn, function(...: any): ...: any
print("warn was called");
return hook(...);
end);
print(hook == warn);
warn("Test");
This function is useful when wanting to block calls or modify return values from function calls
isnewcclosure
The isnewcclosure function will return a boolean indicating if the provided function was wrapped using newcclosure
function isnewcclosure(func: (...any) -> (...any)): boolean
print(isnewcclosure(print)); --> false
print(newcclosure(print)); --> true
This function is useful for determining fake C closures
clonefunction
The clonefunction function will clone the target func.
function clonefunction(func: (...any) -> (...any)): (...any) -> (...any);
local cloned_print = clonefunction(print);
print(cloned_print == print);
cloned_print("Hello, world!");
This function is useful when you do not want to call the target func
isexecutorclosure
The isexecutorclosure function will determine if the function’s source came from the executor
function isexecutorclosure(func: (...any) -> (...any)): boolean
print(isexecutorclosure(print)); --> false
print(isexecutorclosure(function() end)); --> true
print(isexecutorclosure(newcclosure(function() end))); --> false
print(isexecutorclosure(isexecutorclosure)); --> true
This function is useful if you need to identify where a specific function came from.
iscclosure
The iscclosure function will determine if the function is a C Closure
function iscclosure(func: (...any) -> (...any)): boolean
print(iscclosure(print)); --> true
print(iscclosure(function() end)); --> false
print(iscclosure(hookfunction)); --> true
print(iscclosure(function() end)); --> false
islclosure
The islclosure will determine if the function is a Lua Closure
function islclosure(func: (...any) -> (...any)): boolean
print(islclosure(print)); --> false
print(islclosure(function() end)); --> true
print(islclosure(newcclosure(function() end))); --> false
print(islclosure(hookfunction)); --> false;
The islclosure function will determine if the function is a Lua Closure
restorefunction
The restorefunction will unhook a function that has been hooked by hookfunction or hookmetamethod
function restorefunction(func: (...any) -> (...any)): ()
local function can_use()
return false;
end;
hookfunction(can_use, function()
return true;
end);
print(can_use()); --> true
restorefunction(can_use);
print(can_use()) --> false
This function is useful when you no longer need a function to be hooked.
isfunctionhooked
The isfunctionhooked function will determine if the function has been hooked by hookfunction or hookmetamethod
function isfunctionhooked(func: (...any) -> (...any)): boolean
print(isfunctionhooked(print)); --> false
hookfunction(print, function(...)
return warn(...);
end);
print(isfunctionhooked(print)); --> true
restorefunction(print);
print(isfunctionhooked(print)); --> false
This function is useful when you need to tell if a function is hooked.
setstackhidden
The setstackhidden will hide or unhide based on the hide parameter, the provided function or the provided level from the call-stack
function setstackhidden(func_or_lvl: (...any) -> (...any) | number, hide: boolean?): ()
setstackhidden(error, true);
-- now 'error' will be hidden from results like debug.info
xpcall(error, function(...)
print(debug.info(2, "f")); --> will not return `error`
end);
This function is useful when you need specific functions to be hidden from stack related functions, like debug.info and debug.traceback
__bytecode__
The __bytecode__ function will take in user provided bytecode and execute it.
function __bytecode__(bytecode_str: string) -> ()
__bytecode__(".global __main __main: GETIMPORT R0 1 [print] LOADK R1 K2 ['Hello, world!'] CALL R0 2 1 RETURN R0 1");
The bytecode is parased and executed as a new proto, keep in note a RETURN instruction is always required at the end of your bytecode.
Instances
getscripts
The getscripts function will return all scripts located under DataModel, use getnilinstances if you want to retrieve scripts or objects that are not under the DataModel that might’ve been destroyed via :Destroy or .Parent = nil operations.
function getscripts(): {[number]: LuaSourceContainer}
for _: unknown, v: LuaSourceContainer in getscripts() do
print(v.ClassName);
end;
This function is useful if you want to retrieve scripts in game that are actively reparenting.
getinstances
The getinstances function will return all Instances that are located under DataModel, refer to getnilinstances if you wish to locate Instances that are not under the DataModel.
return getinstances(): {[number]: Instance}
for _: unknown, v: Instance in getinstances() do
print(v.Name);
end;
This function is useful if you want to retrieve instances in game that are actively reparenting.
compareinstances
The compareinstances will compare two Instances if they are equal to each-other, bypassing cloneref
function compareinstances(a: Instance, b: Instance): boolean
print(cloneref(workspace) == workspace) --> false
print(compareinstances(cloneref(workspace), workspace)); --> true
This function is useful if you want to compare two Instances when a == b is false when a or b is a product of cloneref
cloneref
The cloneref will clone the userdata reference of b, this is useful for bypassing reference detections, keep in note cloneref will NOT :Clone() the object.
function cloneref(obj: Instance): Instance
print(cloneref(game) == game); --> false
print(cloneref(game).GetService) --> function: 0x...
This function is useful for bypassing GC (Garbage Collection) detections.
getnilinstances
The getnilinstances function will retrieve all instances that are NOT parented under DataModel
function getnilinstances(): {[number]: Instance}
do
local p: Part = Instance.new("Part");
p.Parent = workspace;
p.Name = "secret_part";
p:Destroy();
end;
for _: unknown, v: Instance in getnilinstances() do
if (v:Is("Part") and tostring(v) == "secret_part") then
print(v.Parent); --> nil
end;
end;
This function is useful if you want to retrieve an instance that has been destroyed to avoid getting accessed.
setscriptable
The setscriptable will block any script from trying to access Property of the provided Target to Toggle and will return the object’s previous scriptable state.
function setscriptable(object: Instance, property: string, toggle: boolean): boolean
local obj: Part = Instance.new("Part");
setscriptable(obj, "Name", false);
print(obj.Name); --> Name is not a valid Member of Part "Part"
This function is useful if you want to get detected
getcallbackmember
The getcallbackmember will return the function callback that is binded to Instance
function getcallbackmember(obj: Instance, name: string): (...any) -> (...any)
do -- pretend this is in another script
local remote_function: RemoteFunction = game:GetService("ReplicatedStorage").RemoteFunction;
remote_function.OnServerInvoke = function(...)
return "hi";
end;
end;
-- now our script
local callback: (...any) -> (...any) = getcallbackvalue(game:GetService("ReplicatedStorage").RemoteFunction, "OnServerInvoke");
print(callback); --> function: 0x...
This function is useful if you want to get a function binded to a method of an object without having said function.
Signal
getconnections
The getconnections function is used to get connections that are connected to the provided Signal
function getconnections(signal: RbxScriptSignal): {[number]: ConnectionProxy}
do -- in another script
local workspace: Workspace = workspace;
workspace:GetPropertyChangedSignal("Gravity"):Connect(function()
print("gravity was changed");
end);
end;
-- our script:
for _: unknown, v: ConnectionProxy in getconnections(workspace:GetPropertyChangedSignal("Gravity")) do
print(v.Function); --> function that is bound
v:Fire(); --> fire this connection
v:Disable(); --> disable this connection
hookfunction(v.Function, function() end); --> disable the function from getting called
end;
This function is useful if you want to have control over connections.
firesignal
The firesignal will fire every connection that is bound to signal
function firesignal(signal: RbxScriptSignal, ...: any): ()
firesignal(workspace.Changed, "Name");
This function is useful if you want to fire a signal
getsignalfunctions
The getsignalfunctions will return function that is connected to Signal
function getsignalfunctions(signal: RbxScriptSignal): {[number]: (...any) -> (...any)}
Table
getnamecallmethod
The getnamecallmethod function will return the current method that invoked the latest NAMECALL opcode
function getnamecallmethod(): string
game:GetService("Workspace");
print(getnamecallmethod()) --> GetService
game:FindService("Workspace");
print(getnamecallmethod()) --> FindService
This function is used mainly in __namecall hooks, but it has other use cases.
setnamecallmethod
The setnamecallmethod function will set the method that invoked NAMECALl opcode
function setnamecallmethod(new_method_str: string): ()
local hook: (...any) -> (...any); hook = hookmetamethod(game, "__namecall", function(...: any): ...any
local self: Instance, second_arg: string = ...;
if (typeof(self) == "Instance" and self == game and getnamecallmethod() == "GetService" and type(second_arg) == "string" and second_arg == "Workspace") then
setnamecallmethod("FindService");
return hook(...);
end;
return hook(...);
end);
This function is useful for detouring specific methods that you do not want to be called.
setreadonly
The setreadonly function will set the table’s read-only state to the specified boolean
function setreadonly(arg: {[any]: any}, state: boolean): ()
local silly_table: {[string]: any} = {};
silly_table.hi = "hi";
setreadonly(silly_table, true);
silly_table.hi = "no"; --> errors: Attempt to modify a read only table
This function is useful for being able to protect or unprotect tables from being read-only
makewriteable
The makewriteable is a short-cut to setreadonly(table, false), this makes the table writable
function makewriteable(arg: {[any]: any}): ()
local wutever: {[any]: any} = {""};
setreadonly(wutever, true);
-- if we modify the table without using `makewriteable` or `setreadonly` the script will error
makewriteable(wutever); --> this is the same as doing setreadonly(wutever, false);
-- we can make changes again
wutever.whatever = ""; --> no error
This function is useful for setting readonly tables to writable states
makereadonly
The makereadonly is a short-cut to setreadonly(table, true) this makes the table read-only
function makereadonly(arg: {[any]: any}): ()
local wutever: {[any]: any} = {""};
makereadonly(wutever);
-- now we can't make changes without the script erroring
wutever.noo = ""; --> errors: Attempt to modify a read only table
This function is useful for setting a table’s read-only state to true
getrawmetatable
The getrawmetatable function will get an object’s metatable similar to getmetatable but will bypass the __metatable protection field.
function getrawmetatable(arg: any): {[string]: any}
local tbl: {[any]: any} = setmetatable({}, {
__index = function(self: any, index: any): never
return nil;
end;
__metatable = "Locked";
});
print(getmetatable(tbl)) --> prints: "Locked"
print(getrawmetatable(tbl)) --> prints the metatable table
This function is useful if you want to get a metatable of an object that has the __metatable field set
Note this function can return __metatable IF the object came from OUTSIDE the VM
setrawmetatable
The setrawmetatable function will set an object’s metatable to the provided table, similar to setmetatable but will bypass the __metatable protection field
function setrawmetatable(arg: any, meta_table: {[string]: any}): {[string]: any}
local tbl: {[any]: any} = setmetatable({}, {
__index = function(self: any, index: any): never
return nil;
end;
__metatable = "Locked";
});
setrawmetatable(tbl, {});
print(getmetatable(tbl)); --> we can now get the table with getmetatable because we set the metatable to a new metatable without __metatable
This function is useful if you want to set a metatable of an object that has the __metatable field set
Note this function can error IF the object came from OUTSIDE the VM
hookmetamethod
The hookmetamethod function will call hookfunction on an object’s metatable with the provided metamethod, if its a function, returning the old function.
function hookmetamethod(arg: any, name_str: string, hook: (...any) -> (...any)): (...any) -> (...any)
local hook: (...any) -> (...any); hook = hookmetamethod(game, "__index", function(...: any): any
local self: Instance, index: string = ...;
local result: any = hook(...);
print(self, index);
return result;
end);
print(game.Workspace); -- > output: Game, Workspace
This function is useful if you want to hook a metamethod of an object’s metatable, if the metamethod of said object is a function.
Note this function will error if it is called with an object who’s metatable came OUTSIDE of the VM
getgc
The getgc function will return all functions that is stored within the Lua-Garbage-Collector, with an optional argument to include userdatas and tables
function getgc(include_tables_and_userdatas: boolean): {[number]: any}
local function funny_function(): ()
return;
end;
for _: unknown, v: (...any) -> (...any) in getgc(false) do
if (type(v) == "function" and islclosure(v) and debug.info(v, "n") == "funny_function") then
print("found", v);
break;
end;
end;
This function is useful if you want to retrieve functions or tables that you do not directly have access to.
getgenv
The getgenv will return the Executor’s Global Environment.
function getgenv(): {[string]: any}
getgenv().hi = true;
print(getgenv().hi); --> true
print(hi) --> true
print(getfenv().hi) --> true
This function is useful for setting constants | states globally from other executed scripts.
getrenv
The ‘getrenv’ function will return Roblox Global Environment, which contain’s task debug os spawn and other globals.
function getrenv(): {[string]: any}
print(debug == getrenv().debug);
This function is useful for resolving name conflicts, (ex. debug in getgenv is not the same as getrenv().debug)
isreadonly
The isreadonly function will return a boolean determining a table’s read-only state.
function isreadonly(arg: {[any]: any}): boolean
local tbl = {};
print(isreadonly(tbl)); --> false
setreadonly(tbl, true);
print(isreadonly(tbl)); --> true
This function is useful if you want to tell if a table is read-only safely.
Debug
debug.getconstants
The debug.getconstants function will retrive the constants of a Lua Closure
function getconstants(func: (...any) -> (...any)): {[number]: any}
local function test_func(): ()
local h: string = "Hello";
return;
end;
print(debug.getconstants(test_func)[1]); -- "Hello"
This function is useful for retriving constants to help with reverse engineering
debug.setconstant
The debug.setconstant function will set the constant of func at index with value
function setconstant(func: (...any) -> (...any), index: number, value: any): ()
local function test_func(): ()
local h: string = "Hello";
print(h);
return;
end;
test_func(); --> prints "Hello"
debug.setconstant(test_func, 1, "Bye");
test_func(); --> prints "Bye"
This function is useful for modifying internals of a function
debug.getconstant
The debug.getconstant function will retrive the constants of a Lua Closure at index
function getconstant(func: (...any) -> (...any), index: number): any
local function test_func(): ()
local h: string = "Hello";
return;
end;
print(debug.getconstant(test_func, 1)); -- "Hello"
debug.getupvalues
The debug.getupvalues function will retrive the upvalues of a Lua Closure
function getupvalues(func: (...any) -> (...any)): {[number]: any}
local tbl: {[number]: string} = {"hi"};
local function test_func(): ()
print(tbl); --> 'tbl' is now an 'upvalue' of function 'test_func'
return;
end;
print(debug.getupvalues(tbl)[1] == tbl); --> true
This function is useful for retriving and modifying upvalues to help with reverse engineering
debug.getupvalue
The debug.getupvalue function will retrive the upvalue of a Lua Closure at index
function getupvalue(func: (...any) -> (...any), index: number): any
local tbl: {[number]: string} = {"hi"};
local function test_func(): ()
print(tbl); --> 'tbl' is now an 'upvalue' of function 'test_func'
return;
end;
print(debug.getupvalue(tbl, 1) == tbl); --> true
debug.setupvalue
The debug.setupvalue function will set the upvalue of a Lua Closure at index with value
function getupvalue(func: (...any) -> (...any), index: number, value: any): ()
local tbl: {[number]: string} = {"hi"};
local function test_func(): ()
print(tbl); --> 'tbl' is now an 'upvalue' of function 'test_func'
return;
end;
debug.setupvalue(test_func, 1, {});
print(debug.getupvalue(test_func, 1) == tbl); --> false
This function is useful for modifying internals of a function
debug.getprotos
The debug.getprotos function will retrive protos (functions inside the functions) of the provided functions.
function debug.getprotos(func: (...any) -> (...any)): {[number]: (...any) -> (...any)}
local function test_func()
local function hi() --> function 'hi' is a proto of 'test_func'
return;
end;
return;
end;
print(debug.getprotos(test_func)[1]) --> function: 0x..;
This function is useful for retriving and modifying upvalues to help with reverse engineering
debug.getproto
The debug.getproto function will retrive the proto (functions inside the functions) at index of the specified function
function debug.getproto(func: (...any) -> (...any), index: number): (...any) -> (...any)
local function test_func()
local function hi() --> function 'hi' is a proto of 'test_func'
return;
end;
return;
end;
print(debug.getupvalue(test_func, 1)); --> function: 0x..
debug.setproto
The debug.setproto function will set the proto (functions inside the functions) at index of the specified function
function debug.getproto(func: (...any) -> (...any), index: number, func: (...any) -> (...any)): ()
local function test_func()
local function hi() --> function 'hi' is a proto of 'test_func'
return;
end;
return;
end;
debug.setproto(test_func, 1, function(): ()
return;
end);
This function is useful for modifying internals of a function
debug.getstack
The debug.getstack function will all values or values specified by an index of the level
function getstack(level: number, index: number?): {[number]: any}
local function test_function(): ()
local workspace: Workspace = workspace;
local game: DataModel = game;
local hi: string = "wow";
print(debug.getstack(1, 1)) --> Workspace
return;
end;
test_function();
This function is useful for retriving values from the stack to help with reverse engineering
debug.setstack
The debug.setstack function sets values onto the stack with the specified index and value
function setstack(level: number, index: number, value: any): ()
local outer_value = 10
local function inner_function()
outer_value += 9
debug.setstack(2, 1, 100)
end
inner_function()
print(outer_value) -- Output: 100
This function is useful for modifying internals of a function
debug.setname
The debug.setname (or setname) function set the debug name of the func with name_str
function setname(func: (...any) -> (...any), name_str: string): ()
local hi = function() -- created a new function with NO debug name
return;
end;
print(debug.info(hi, "n")); --> ""
debug.setname(hi, "gay");
print(debug.info(hi, "n")) --> "hi"
This function is useful for modifying internals of a function
debug.setinfo
The debug.setinfo function sets the provided debug fields of func
function setinfo(func: (...any) -> (...any), options: {[string]: any})
This function is useful for modifying internals of a function
debug.getcode
The debug.getcode function retrives instructions from func or the instruction at index
function getcode(func: (...any) -> (...any), index: number?): {[number]: any} | {[string]: any}
local function add(a: number, b: number): number
return (a+b);
end;
for _: unknown, v: {[string]: any} in debug.getcode(add) do
if (v.opname == "ADD") then
print(v);
break;
end;
end;
debug.setcode
The debug.setcode function is used to swap the instruction at index with the user-provided instruction
function setcode(func: (...any) -> (...any), index: number, instruction: {[string]: any}): ()
debug.getinstance
The debug.getinstance function is used to retrive the real instance of proxy all Instances in the VM are emulated
function getinstance(proxy: any): Instance