Introduction
xModManager is a simple Roblox tool designed to make modding for games much simpler.
Under the hood, xModManager consists of a VFS (Virtual File System) and a basic VPM (Virtual Process Manager).
The modding programming language is Lua. Note it’s NOT Luau and lacks some features.
At a simple level, Mods are stored in the VFS as strings with a filename at a specific path. The process manager then reads the Lua code from there and returns an executable that is stored in the process table. When the mod is run, that executable is spawned as a new task with task.spawn() that doesn’t block execution of your current script.
Why use xModManager?
1. Simplicity
xModManager is very simple to use, and exposes a few easy-to-understand APIs. It’s codebase is very readable.
2. Error handling
xModManager returns an error code alongside the return values for almost every function. An error code of 0 means success, while any other return code is an error of some type.
3. Security
It may sound counterintutive that mods that can be written as Lua strings are secure, but they are!
First off, the Lua code doesn’t have access, by default, to any environemnt variable that can cause an escape from the environment.
Second off, if an user adds accidentally an environment variable that could cause an escape, xModManager warns in the console.
4. Easy manipulation of mods
A mod process can easily be killed with the SIGKILL signal.
You can signal to a mod using SIGCOM, and that mod can attach to that using a function provided in the environment and using a callback.
5. Configurability
There is a module named Configuration which is very configurable and has a lot of settings that affect how the mod manager works.
The structure of xModManager
xModManager comes with the following hierarchy
xModManager
├── Configuration
├── ModFileSystem
├── Signals
└── ModProcessManager
└── vLua
├── FiOne
└── Yueliang
xModManager
The root module of xModManager, requires all modules and returns them in a table, so one require can be done in your file instead of four.
Configuration
The settings of xModManager
ModFileSystem
The VFS component of the mod manager
Signals
Contains signals that correspond to a specific number. This is so you don’t have to write signals in numbers, but you can read them and memorize them.
ModProcessManager
The VPM component of the mod manager
vLua, FiOne and Yueliang
The bytecode compiler and interpreter of Lua that ModProcessManager uses behind the hood. You, the user do not need to care about these modules.
Using xModManager
This section will teach you how to use the mod manager at a very basic scale.
You should read the API section as well to understand how to use xModManager at its fullest
Information
Naming
There will be a few names that you may not be familiar with in this article, and I will explain as many of them over here
PID
PID stands for Process ID, and it is a unique identifier that is assigned to each process in xModManager
Environment
The environment is all of the external functions the Lua code gets. In Roblox, for example, your Lua code gets in its environment functions and tables like
Instance
UDim2
Vector3
These are part of the Luau environment and you can use them to do various actions across the Roblox engine.
VFS
VFS stands for Virtual File System. In xModManager, the VFS plays a crucial role by assigning each mod file a path and some attributes. In addition, it also provides a lot of additional features, such as creating directories, removing and listing.
PM
PM stands for Process Manager. In xModManager, the process manager saves all mod processes in a table, their identifier being their PID. It contains useful functions and information.
Terms used interchangeably
There will be a few terms that will be used interchangeably
Mod and File
In the file system, a mod is represented as a file entry. Therefore APIs that operate on mods return FileHandle objects.
Creating our first mod
Let’s create our first mod!
This mod will just print “Hello, World!” to the console.
The first step to use xModManager, is to… require it!
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
Next off, let’s write to the virtual file system our Lua code!
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_mod_name.mod", -- the file path
[[
print("Hello, world!")
]] -- the code
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
We have successfully written our mod to disk if there is no warning in the console! It is now time to spawn our mod as a process
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_mod_name.mod", -- we pass the mod path
{}, -- we can leave args as an empty table for now
{} -- we can leave extra_env as an empty table for now
)
-- We do the same here as we did for the file system
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
Check out Process Manager to understand what the functions and empty tables are doing exactly
You may notice the mod is not executing. That is because we just registered it, we still have to execute it!
ProcessManager.RunMod(pid)
Together, the code looks like this:
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_mod_name.mod", -- the file path
[[
print("Hello, world!")
]] -- the code
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_mod_name.mod", -- we pass the mod path
{}, -- we can leave args as an empty table for now
{} -- we can leave extra_env as an empty table for now
)
-- We do the same here as we did for the file system
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
If you run this, you should now see “Hello, World!” in the console!
Good job, you just wrote your first mod!
Using arguments in our mod
What about arguments? Let’s learn how to use them.
This mod will print all the arguments passed to it.
First, let’s require all our needed modules
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
Let’s write a new mod that reads all the args and prints them
local return_code, _ = FileSystem.WriteMod(
"Mods/even_cooler_mod.lua", -- the file path
[[
for i, v in ipairs(args()) do
print(v)
end
]] -- the code
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
Now let’s spawn and run our mod.
local return_code, pid = ProcessManager.SpawnMod(
"Mods/even_cooler_mod.lua", -- we pass the mod path
{"hello", "world", "coolest argument"}, -- the args
{} -- leave empty for now
)
-- Check return code
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
The full code:
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local return_code, _ = FileSystem.WriteMod(
"Mods/even_cooler_mod.lua", -- the file path
[[
for i, v in ipairs(args()) do
print(v)
end
]] -- the code
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/even_cooler_mod.lua", -- we pass the mod path
{"hello", "world", "coolest argument"}, -- the args
{} -- leave empty for now
)
-- Check return code
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
You should see in the console:
hello
world
coolest argument
If so, then you have just built your second mod!
Using a custom environment
Mods execute in a sandbox. They cannot automatically access your game’s functions or objects. Instead, you explicitly decide what a mod is allowed to use by passing an environment when spawning it.
We will give in the environment a function that simply creates a frame on the screen with the specified size (offset)
As always, first let’s require the needed modules
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
Let’s also get the Player and PlayerGui
local Player = game:GetService("Players").LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
Now, let’s create a ScreenGui inside of PlayerGui
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Parent = PlayerGui
We now make the cool function, the one which creates a frame and parents it to the ScreenGui
local function createFrame(size_x, size_y)
local frame = Instance.new("Frame")
frame.Size = UDim2.fromOffset(size_x, size_y)
frame.Position = UDim2.fromScale(0, 0)
frame.BackgroundTransparency = 0
frame.Parent = ScreenGui
end
Now, we will create a mod
-- NOTICE: we use the CreateFrame function
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_mod_name.mod", -- the file path
[[
CreateFrame(100, 100)
]]
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_mod_name.mod", -- we pass the mod path
{},
{CreateFrame = createFrame} -- we now pass the createFrame function with the name CreateFrame
)
-- We do the same here as we did for the file system
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
Together, it looks like:
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local Player = game:GetService("Players").LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Parent = PlayerGui
local function createFrame(size_x, size_y)
local frame = Instance.new("Frame")
frame.Size = UDim2.fromOffset(size_x, size_y)
frame.Position = UDim2.fromScale(0, 0)
frame.BackgroundTransparency = 0
frame.Parent = ScreenGui
end
-- NOTICE: we use the CreateFrame function
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_mod_name.mod", -- the file path
[[
CreateFrame(100, 100)
]]
)
-- We check if return code is success
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_mod_name.mod", -- we pass the mod path
{},
{CreateFrame = createFrame} -- we now pass the createFrame function with the name CreateFrame
)
-- We do the same here as we did for the file system
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
You should see a frame on the screen! If so, then we have successfully passed a function in the environment.
Communicating with a program using SIGCOM
Warning
This page assumes that
Configuration.Allow_SIGCOM = true,Configuration.SIGCOM_Name = "on_sigcom",Configuration.GiveModProcessInfo = trueandConfiguration.ProcessInfoName = "_process"
Let’s communicate with our mod using SIGCOM. First, we require everything that we need.
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local Signals = xModManager.Signals
Next, let’s create our mod
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_signals.mod",
[[
on_sigcom(function(msg)
print(msg)
_process.exit()
end)
while _process.should_run() do
wait(0.5)
end
]]
)
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_signals.mod",
{},
{}
)
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
Let’s analyze the mod source
[[
on_sigcom(function(msg)
print(msg)
_process.exit()
end)
while _process.should_run() do
wait(0.5)
end
]]
As you can see, on SIGCOM, we register a function that receives the message, and all it does is print the message, then exit the process.
Finally, let’s wait 1 second, then signal SIGCOM
task.wait(1)
ProcessManager.Signal(Signals.SIGCOM, pid, "Hello, world!")
Together, it looks like this:
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local Signals = xModManager.Signals
local return_code, _ = FileSystem.WriteMod(
"Mods/cool_signals.mod",
[[
on_sigcom(function(msg)
print(msg)
_process.exit()
end)
while _process.should_run() do
wait(0.5)
end
]]
)
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/cool_signals.mod",
{},
{}
)
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
task.wait(1)
ProcessManager.Signal(Signals.SIGCOM, pid, "Hello, world!")
You should see “Hello, world!” in the console. If so, congratulations!
Terminating a mod using SIGKILL
Terminating a mod is also easy using signals
SIGTERM
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local Signals = xModManager.Signals
local return_code, _ = FileSystem.WriteMod(
"Mods/signals_test_1.mod",
[[
while _process.should_run() do
print("Running")
wait(0.5)
end
]]
)
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/signals_test_1.mod",
{},
{}
)
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
task.wait(1)
ProcessManager.Signal(Signals.SIGTERM, pid)
The core line is:
ProcessManager.Signal(Signals.SIGTERM, pid)
This signal makes _process.should_run() return false, which will exit the loop.
SIGKILL
local xModManager = require(path_to_module)
local FileSystem = xModManager.FileSystem
local ProcessManager = xModManager.ProcessManager
local Signals = xModManager.Signals
local return_code, _ = FileSystem.WriteMod(
"Mods/signals_test_2.mod",
[[
while true do end
]]
)
if return_code ~= FileSystem.ReturnCodes.WriteMod.Success then
warn("An error happened!")
return
end
local return_code, pid = ProcessManager.SpawnMod(
"Mods/signals_test_2.mod",
{},
{}
)
if return_code ~= ProcessManager.ReturnCodes.SpawnMod.Success then
warn("An error happened!")
return
end
ProcessManager.RunMod(pid)
task.wait(1)
ProcessManager.Signal(Signals.SIGKILL, pid)
The important line here is:
ProcessManager.Signal(Signals.SIGKILL, pid)
SIGKILL terminates the program, without caring if the program cooperates or not.
API
Note
Did you read the information about all the names and definitions used in this article?
This section covers and talks about all of the APIs of xModManager in depth.
Configuration
Requiring
local Configuration = require(path_to_xModManager).Configuration
Items
| Name | Description |
|---|---|
| pm | The process manager instance that is initialized inside of ModProcessManager |
| GiveModProcessInfo | Whether mod should get process info |
| ProcessInfoName | The name of the table from which mods can get their information |
| GiveModDefaultEnvironment | Whether mod should have the implicit environment or not |
| DefaultEnvironment | The default environment the mod will get |
| Allow_SIGCOM | Whether mod should be able to give a callback for the sigcom signal |
| SIGCOM_Name | The name of the sigcom function |
| AllowImport | Whether mod should be able to import other modules |
| ImportAncestors | The import parents the mod will try importing from |
| ImportDefaultTargets | Default targets the mod can import without needing the file system |
| ImportName | The name of the import function |
| ExcludedInSafetyCheck | Names of fields in the environment that will be excluded in safety check |
pm
- Type: table
- This will be initialized by ModProcessManager
GiveModProcessInfo
- Type: boolean
- Whether mod gets process info using the process info table (the name of that table is defined in ProcessInfoName)
- A mod can access process information such as the pid
Default value:
Configuration.GiveModProcessInfo = true
ProcessInfoName
- Type: string
- The name of the process info table that the script uses to access important program information
- You can change the name of the table, so instead of “_process” it can be “ProcessInfo”
Default value:
Configuration.ProcessInfoName = "_process"
GiveModDefaultEnvironment
- Type: boolean
- Whether the mod gets the default environment passed or not
Default value:
Configuration.GiveModDefaultEnvironment = true
DefaultEnvironment
- Type: table
- The default environment the mod gets
Default value:
Configuration.DefaultEnvironment = {
math = math,
string = string,
table = table,
task = task,
next = next,
print = print,
unpack = unpack,
xpcall = xpcall,
pcall = pcall,
ipairs = ipairs,
pairs = pairs,
tostring = tostring,
tonumber = tonumber,
type = type,
typeof = typeof,
signals = Signals,
Vector2 = Vector2,
UDim = UDim,
UDim2 = UDim2,
Color3 = Color3,
}
Allow_SIGCOM
- Type: boolean
- Whether a process is allowed to register a callback which will be called on SIGCOM or not
- SIGCOM is useful for IPC, as you can pass a message alongside it
Default value:
Configuration.Allow_SIGCOM = true
SIGCOM_Name
- Type: string
- The name of the function that will register a callback to sigcom
Default value:
Configuration.SIGCOM_Name = "on_sigcom"
AllowImport
- Type: boolean
- Whether imports should be allowed or not
Default value:
Configuration.AllowImport = false
ImportAncestors
- Type: table
- All of the paths in the VFS that the mod can import from
- For example, if AllowImport has the path “Mods/Folder1”, and our mod tries to run import(“Folder2/file.mod”), the path of the import will be “Mods/Folder1/Folder2/file.mod”
Default value:
Configuration.ImportAncestors = {}
ImportDefaultTargets
- Type: table
- Similar to the environment table, but instead of the mod getting access to the target directly, it has to import it.
Default value:
Configuration.ImportDefaultTargets = {}
ImportName
- Type: string
- The name of the import function
Default value:
Configuration.ImportName = "import"
ExcludedInSafetyCheck
- Type: table
- If a table or an instance is passed in the environment, the process manager will warn about it. Excluding it will make it not warn for the specific value
Default value:
Configuration.ExcludedInSafetyCheck = {}
Configuration.ExcludedInSafetyCheck["math"] = true
Configuration.ExcludedInSafetyCheck["string"] = true
Configuration.ExcludedInSafetyCheck["table"] = true
Configuration.ExcludedInSafetyCheck["task"] = true
Configuration.ExcludedInSafetyCheck["next"] = true
Configuration.ExcludedInSafetyCheck["print"] = true
Configuration.ExcludedInSafetyCheck["unpack"] = true
Configuration.ExcludedInSafetyCheck["xpcall"] = true
Configuration.ExcludedInSafetyCheck["pcall"] = true
Configuration.ExcludedInSafetyCheck["ipairs"] = true
Configuration.ExcludedInSafetyCheck["pairs"] = true
Configuration.ExcludedInSafetyCheck["tostring"] = true
Configuration.ExcludedInSafetyCheck["tonumber"] = true
Configuration.ExcludedInSafetyCheck["type"] = true
Configuration.ExcludedInSafetyCheck["typeof"] = true
Configuration.ExcludedInSafetyCheck["signals"] = true
Configuration.ExcludedInSafetyCheck["Vector2"] = true
Configuration.ExcludedInSafetyCheck["UDim"] = true
Configuration.ExcludedInSafetyCheck["UDim2"] = true
Configuration.ExcludedInSafetyCheck["Color3"] = true
Signals
Warning
You are free to rename the fields in this module, as internally the checks are done with the numeric values. This could cause issues for readability.
Requiring
local Signals = require(path_to_xModManager).Signals
Items
| Name | Value | Description |
|---|---|---|
| SIGTERM | 1 | Sets the should_run field of a program to false |
| SIGKILL | 2 | Cancels a program thread and cleans up |
| SIGCOM | 3 | Communicates with a program; a message can be specified |
SIGTERM
- Type: number
- When this signal is sent, the should_run field of a program will be set to false.
SIGKILL
- Type: number
- When this signal is sent, the thread of the program is cancelled, and the process will be cleaned up from memory.
SIGCOM
- Type: number
- This signal can be sent with a message (string)
- When this signal is sent, if there is a callback registered, it will be called with the message as the argument.
FileSystem
Note
After reading the APIs from this page, make sure to look over the return codes so you can best understand how the APIs work
Overview
The FileSystem module provides a VFS used by xModManager to store directories and mod files.
Directories are represented by DirectoryHandle objects.
Files (Mods) are represented by FileHandle objects.
All methods return a return code that should be checked before using the returned handle.
Requiring
local ModFileSystem = require(path_to_xModManager).FileSystem
Items
EntryType
| Name | Value | Description |
|---|---|---|
| Invalid | 0 | An invalid entry |
| Directory | 1 | A valid directory entry |
| File | 2 | A valid file entry |
Invalid
- Type: number
- An invalid entry that shouldn’t be used
Directory
- Type: number
- A valid entry for a directory that can be used
File
- Type: number
- A valid entry for a file that can be used
ReturnCodes
Check out Return Codes for more information
Exported Types
| Name | Description |
|---|---|
| DirectoryHandle | A handle for a directory |
| FileHandle | A handle for a file |
DirectoryHandle
Fields:
Type: number
Path: string
- The path of the directory
- In format “PARENT/DIRECTORY”
Attributes: string
- Can be anything
- Used to give the directory additional information
Parent: string
- The parent of the directory
Name: string
- The name of the directory
Default value:
local entry: DirectoryHandle = {
Type = ModFileSystem.EntryType.Directory,
Path = "",
Attributes = "",
Parent = "",
Name = "",
}
FileHandle
Fields:
Type: number
Path: string
- The path of the file
- In format “PARENT/FILE.EXTENSION”
Attributes: string
- Can be anything
- Used for giving the file additional information
Parent: string
- The parent of the file
Data: string
- The data inside of the file
- This is most commonly the Lua code of the mods
Extension: string
- The extension of the file
Name: string
- The name of the file
Default value:
local entry: FileHandle = {
Type = ModFileSystem.EntryType.File,
Path = "",
Attributes = "",
Parent = "",
Data = "",
Extension = "",
Name = "",
}
Methods
| Name | Description |
|---|---|
| GetParent | Get the parent from a path |
| GetName | Get name from path |
| GetExtension | Get extension from path |
| CreateDirectory | Create a new directory in the file system |
| WriteMod | Write a mod to the file system |
| RemoveDirectory | Remove a directory from the file system |
| RemoveMod | Remove a mod from the file system |
| ReadModEntry | Get a copy of a mod entry in the file system |
| ReadDirectoryEntry | Get a copy of a directory entry in the file system |
GetParent
function ModFileSystem.GetParent(path: string): string
Arguments:
path: string
The path from which we should determine the parent
Returns:
parent: string
The parent determined from the path
Usage:
local path = "Mods/child.mod"
local parent = ModFileSystem.GetParent(path)
print(parent) -- "Mods"
GetName
function ModFileSystem.GetName(path: string): string
Arguments:
path: string
The path from which we should determine the name
Returns:
name: string
The name determined from the path
Usage:
local path = "Mods/file_name.lua"
local name = ModFileSystem.GetName(path)
print(name) -- "file_name.lua"
GetExtension
function ModFileSystem.GetExtension(path: string): string
Arguments:
path: string
The path OR name from which we should determine the extension
Returns:
extension: string
The extension determined from the string provided
Usage:
local filename = "coolname.exe"
local extension = ModFileSystem.GetExtension(filename)
print(extension) -- "exe"
CreateDirectory
function ModFileSystem.CreateDirectory(
path: string,
attributes: string?
): (number, DirectoryHandle)
Arguments:
path: string
The path of the directory that should be created
attributes: string?The optional attributes that should be added to the directory in the file system
Returns:
return_code: numberThe return code of the function
handle: DirectoryHandleThe handle of the directory
Usage:
local return_code, dhandle = ModFileSystem.CreateDirectory(
"Mods/CoolDirectory",
"Some cool attribute"
)
if return_code == ModFileSystem.ReturnCodes.CreateDirectory.Success then
-- our directory has been written successfully!
print(dhandle.Attributes) -- "Some cool attribute"
end
WriteMod
function ModFileSystem.WriteMod(
path: string,
data: string,
attributes: string?
): (number, FileHandle)
Arguments:
path: stringThe path of the mod that should be created
data: stringThe data of the mod that should be written, commonly Lua source, but anything can be written
attributes: string?The optional attributes that should be added to the file in the file system
Returns:
return_code: numberThe return code of the function
handle: FileHandleThe handle of the file
Usage:
local return_code, fhandle = ModFileSystem.WriteMod(
"Mods/my_cool_mod.lua",
[[
print("The mod code")
]],
-- attributes are optional
)
if return_code == ModFileSystem.ReturnCodes.WriteMod.Success then
-- the file has been written successfully
print(fhandle.Data) -- 'print("The mod code")'
end
RemoveDirectory
function ModFileSystem.RemoveDirectory(path: string): number
Arguments:
path: stringThe path of the directory that should be removed
Returns:
return_code: numberThe return code of the function
Usage:
-- create a directory "Mods/ToBeRemoved" using CreateDirectory
local return_code = ModFileSystem.RemoveDirectory("Mods/ToBeRemoved")
if return_code == ModFileSystem.ReturnCodes.RemoveDirectory.Success then
-- we have successfully removed the directory
end
RemoveMod
function ModFileSystem.RemoveMod(path: string): number
Arguments:
path: stringThe path of the mod that should be removed
Returns:
return_code: numberThe return code of the function
Usage:
-- create a mod "Mods/mod.lua" using WriteMod
local return_code = ModFileSystem.RemoveMod("Mods/mod.lua")
if return_code == ModFileSystem.ReturnCodes.RemoveMod.Success then
-- we have successfully removed the file
end
ReadModEntry
function ModFileSystem.ReadModEntry(path: string): (number, FileHandle)
Arguments:
path: stringThe path to the file which should be read
Returns:
return_code: numberThe return code of the function
fhandle: FileHandleThe copy of the file handle
Usage:
-- discard the second argument so we can read ourselves
local return_code = ModFileSystem.WriteMod(
"Mods/example.lua",
[[
this is example data
]],
)
-- if return code is not 0 then cancel the attempt
if return_code ~= ModFileSystem.ReturnCodes.WriteMod.Success then return end
return_code, fhandle = ModFileSystem.ReadModEntry("Mods/example.lua")
-- abort if read fails as well
if return_code ~= ModFileSystem.ReturnCodes.ReadModEntry.Success then return end
print(fhandle.Data) -- "this is example data"
ReadDirectoryEntry
function ModFileSystem.ReadDirectoryEntry(path: string): (
number,
DirectoryHandle,
{string}
)
Arguments:
path: stringThe path to the directory which should be read
Returns:
return_code: numberThe return code of the function
dhandle: DirectoryHandleThe copy of the directory handle
children: {string}The children of the directory
Usage:
-- create a directory "Mods/FinalTest" using CreateDirectory
local return_code, dhandle, _ = ModFileSystem.ReadDirectoryEntry(
"Mods/FinalTest"
)
if return_code == ModFileSystem.ReturnCodes.ReadDirectoryEntry.Success then
-- success
print(dhandle.Name) -- "FinalTest"
end
Process Manager
Note
Also read:
Signals
Return Codes
Caution
The process manager does not reuse PIDs. This should be fine, as Lua numbers lose precision around 9 quadrillion.
Requiring
local ModProcessManager = require(path_to_xModManager).ProcessManager
Items
env
This item is just initialized at runtime with the default environment, or {} if a default environment is disabled
ReturnCodes
Check out Return Codes for more information
MasterHandler
The MasterHandler is the core component of the Process Manager which makes it a secure process manager. It is documented on another page and not this one.
Exported Types
| Name | Description |
|---|---|
| ProcessHandle | A handle to a process |
ProcessHandle
Fields:
pid: number
- The PID of the process
exec_path: string
- The path at which the program started executing
cwd: string
- The current working directory
- This can be changed
running: boolean
- Whether the program is running or not
should_run: boolean
- Whether the program should run or not
args: {string}
- All of the arguments passed to the program
- Can be an empty table
proc_func: () -> any
- The function of the process
- Determined from the Lua code
sigcom_func: ((msg: string) -> any)?
- The SIGCOM callback registered by the process
- If not registered, this will be nil and won’t be called on SIGCOM
Warning
This field is initialized when the callback is registered, not during process creation.
thread: thread?
- The thread that is handling the process
- Spawned with
task.spawn
Warning
This field is initialized once the process is run, not when it is created.
sigcom_thread: thread?
- The thread that is handling SIGCOM
- Spawned with
task.spawn
Warning
This field is initialized every SIGCOM signal, and is then cancelled.
This can cause race conditions! Use with caution.
Methods
| Name | Description |
|---|---|
| SpawnMod | Spawns a process from specified path |
| RunMod | Runs a process with the specified PID |
| Signal | Sends a signal to the process |
| ListProcesses | Lists all processes’ PID |
| IsRunning | Checks if a process with specified PID is running or not |
SpawnMod
function ModProcessManager.SpawnMod(
path: string,
args: {string},
extra_env: {[string]: any}
): (number, number | string | nil)
Arguments:
path: string
The path to the lua source to be spawned
args: {string}
An array of arguments the program will use
extra_env: {[string]: any}
Additional environment for the process
Returns:
return_code: number
The return code of the function
pid: number | error_message: string | nil
If this function succeeds and the process is run, the return value will be - pid: number (the PID of the process)
If this function fails at compile time, the return value will be - error_message: string (the error message)
If this function fails at the file system level, the return value will be - nil
Usage:
local return_code, pid = ModProcessManager.SpawnMod(
"Mods/path_to_your_mod.lua",
{},
{}
)
if return_code == ModProcessManager.ReturnCodes.SpawnMod.Success then
-- we can now use the PID!
end
RunMod
function ModProcessManager.RunMod(pid: number): number
Arguments:
pid: numberThe PID of the spawned process to be executed
Returns:
return_code: numberThe return code of the function
Usage:
-- we first spawn a mod with SpawnMod()
local return_code, pid = ModProcessManager.SpawnMod(
"Mods/path_to_your_mod.lua",
{},
{}
)
if return_code == ModProcessManager.ReturnCodes.SpawnMod.Success then
return_code = ModProcessManager.RunMod(pid)
if return_code == ModProcessManager.ReturnCodes.RunMod.Success then
-- it is guaranteed that the mod is now running successfully
-- this does NOT guarantee that errors won't happen at runtime.
end
end
Signal
function ModProcessManager.Signal(signal: number, pid: number, msg: string?): nil
Arguments:
signal: numberThe signal which will be sent to the program
pid: numberThe PID of the target program
msg: string?In case of SIGCOM, the message to be passed to the program
Returns:
nil
Usage:
local Signals = require(path_to_xModManager).Signals
-- Spawn and run a mod using SpawnMod and RunMod
-- This will kill the process!
ModProcessManager.Signal(Signals.SIGKILL, process_pid)
ListProcesses
function ModProcessManager.ListProcesses(): {number}
Arguments:
nil
Returns:
processes: {number} An array of all currently running processes
Usage:
local processes = ModProcessManager.ListProcesses()
for i, v in ipairs(processes) do
print(v) -- print the PID of each running process
end
IsRunning
function ModProcessManager.IsRunning(pid: number): boolean
Arguments:
pid: numberThe PID of the process which should be checked if running
Returns:
is_running: booleanWhether the process is running or not
Usage:
if ModProcessManager.IsRunning(6) then
print("PID 6 is running!")
else
print("PID 6 is not running!")
end
Master Handler
Return Codes
File System
CreateDirectory
| Name | Value | Description |
|---|---|---|
| Success | 0 | The directory has been created successfully |
| DirectoryAlreadyExists | 1 | Another directory at the same path exists |
| ParentNotFound | 2 | The parent of the target directory path doesn’t exist |
| InvalidParentType | 3 | The parent of the path is not a directory |
| InvalidPathCharacter | 4 | The path contains an invalid character |
WriteMod
| Name | Value | Description |
|---|---|---|
| Success | 0 | The file has been created successfully |
| DirectoryWithSamePathExists | 1 | A directory at the same path already exists |
| ParentNotFound | 2 | The parent of the target file path doesn’t exist |
| InvalidParentType | 3 | The parent of the path is not a directory |
RemoveDirectory
| Name | Value | Description |
|---|---|---|
| Success | 0 | The directory has been removed successfully |
| CanNotRemoveRoot | 1 | The root (“Mods”) folder can not be removed |
| DirectoryNotFound | 2 | The directory doesn’t exist |
| CanNotRemoveFiles | 3 | RemoveDirectory doesn’t work with files |
RemoveMod
| Name | Value | Description |
|---|---|---|
| Success | 0 | The file has been removed sucessfully |
| FileNotFound | 1 | The file doesn’t exist |
| CanNotRemoveDirectories | 2 | RemoveMod doesn’t work with directories |
ReadModEntry
| Name | Value | Description |
|---|---|---|
| Success | 0 | The file has been read successfully |
| FileNotFound | 1 | The file doesn’t exist |
| CanNotReadDirectory | 2 | ReadModEntry doesn’t work with directories |
ReadDirectoryEntry
| Name | Value | Description |
|---|---|---|
| Success | 0 | The directory has been read successfully |
| DirectoryNotFound | 1 | The directory doesn’t exist |
| CanNotReadFile | 2 | ReadDirectoryEntry doesn’t work with files |
Process Manager
SpawnMod
| Name | Value | Description |
|---|---|---|
| Success | 0 | The process has been spawned successfully |
| FileNotFound | 1 | The file was not found in the VFS |
| CanNotExecuteDirectory | 2 | The data at specified path was a directory |
| CompileError | 3 | An error happened at compile time |
RunMod
| Name | Value | Description |
|---|---|---|
| Success | 0 | The process started running succesfully |
| PIDNotFound | 1 | The specified PID doesn’t exist |
| PIDAlreadyRunning | 2 | A process with the specified PID is already running |