Want to host your own Rōblox LAN parties? Looking for a way to deploy your Rōblox experiences, new and old, on your own machine?
Rōblox Freedom Distribution is one such solution. It's a 'revival' launcher built on existing research for self-hosting Rōblox servers.
Using RFD, users can host their own server instances from compiled Rōblox binaries from 2018-07-25 or 2021-01-25.
Players can join existing servers.
Clients only need to keep track of which hosts and ports to connect to. That's because clients will automatically connect to a server of the same version.
If you worked with Python (3.10–3.12) before, initial setup is supposed to take less than a minute. Why initial? Freedom Distribution automatically downloads additional data (at most 90 MiB) for you.
Initial adaptation from the Rōblox Filtering Disabled project by Jetray, et al.
All the code is free-as-in-freedom software and is licensed under the GNU GPL v3.
This README is optimised for viewing on GitHub.
My use of Rōblox's binaries are prone to copyright-infringement issues. Be wary of any potential copyright takedowns.
In the event of a DMCA takedown, don't rely on forks of this repo on GitHub. Consider using other means. Also consult this document if you want to know why I believe I'm protected under fair-use law.
RFD is natively supported on Windows and somewhat works on GNU/Linux systems with wine
. No plans for native MacOS support so far.
This is good for if you want to deploy quickly on any machine with connection to the internet.
To install as an executable, run:
mkdir rfd
cd rfd
curl https://github.com/Windows81/Roblox-Freedom-Distribution/releases/latest/download/RFD.exe --output RFD.exe
To launch RFD, your command line will look something like this:
./RFD.exe player -h 172.88.194.43 -p 2005
Might not run as well as on Windows.
To bootstrap the compiled exe
, run:
mkdir rfd
cd rfd
curl https://github.com/Windows81/Roblox-Freedom-Distribution/releases/latest/download/RFD.exe --output RFD.exe
To launch RFD, your command line will look something like this:
wine RFD.exe player -h 172.88.194.43 -p 2005
This is good for if you already have Python installed on your machine. Do you want to help contribute to RFD? Use this.
To install from source, run:
git clone --depth 1 https://github.com/Windows81/Roblox-Freedom-Distribution rfd
cd rfd
pip install -r ./Source/requirements.txt
To launch RFD, your command line will look something like this:
py Source/_main.py player -h 172.88.194.43 -p 2005
server
Game-specific options are specified in the --config_path
argument, which defaults to ./GameConfig.toml
.
Please review each option in the config file before starting your server up.
Option | Type | Default |
---|---|---|
--config_path , -cp |
int |
./GameConfig.toml |
--rcc_port , -rp , -p |
int |
2005 |
--web_port , -wp |
int |
2005 |
--run_client , -rc |
store_true |
N/A |
--skip_rcc |
store_true |
N/A |
--skip_rcc_popen |
store_true |
N/A |
--skip_web |
store_true |
N/A |
player
Option | Type | Default |
---|---|---|
--rcc_host , -rh , -h |
str |
None |
--rcc_port , -rp , -p |
int |
2005 |
--web_host , -wh |
str |
N/A |
--web_port , -wp |
int |
2005 |
--user_code , -u |
str |
N/A |
Command syntaxes for studio
and download
also exists, but haven't been adequately documented yet.
To keep it simple: just open port 2005 on both TCP and UDP.
Anyone can host a server and must leave both a TCP and UDP network port of their choice accessible.
It's possible to connect to a webserver and an RCC server from different hosts. However, I wouldn't recommend it.
RCC is an acronym for 'Rōblox Cloud Compute', which is the exe
program we use to run the Rōblox servers. The UDP-based protocol it communicated with built under RakNet.
Host is specified by the -h
option (also by --rcc_host
or -rh
).
Port is specified by the -p
option (also by --rcc_port
or -rp
).
The webserver is responsible for facilitating player connections and loading in-game assets.
Host is optionally specified by the --webserver_host
or -wh
option, in case RCC is hosted elsewhere.
Port is specified by the --webserver_port
or -wp
option.
Assets are automatically cached server-side in directory ./AssetCache
. To manually add assets, place the raw data in a file named with the iden number or string without any extension.
The following are examples of asset idens resolving to cache files:
Asset Iden | File Name | Format |
---|---|---|
rbxassetid://1818 |
./00000001818 |
%11d |
rbxassetid://5950704 |
./00005950704 |
%11d |
rbxassetid://97646706196482 |
./97646706196482 |
%11d |
rbxassetid://custom-asset |
./custom-asset |
%s |
You can modify rbxl
file in current-day Studio as of September 2024. For compatibility with older clients, RFD comes with its own serialiser suite. Objects transformed include:
Some modern programs do weird things to client-sided scripts. They use Script
classs objects, but with a RunContext
property set to "Client"
. You will also need to manually convert these objects to LocalScripts
.
And, union operations done in current-day Studio (CSG v3) are not supported. This is because CSG v2 support was completely removed in late 2022.
In that case...
Sodikm has a functional Studio build from 2018.
Rōblox Filtering Disabled has a working Studio build from 2022.
You can also use this 2018M build whilst running the Rōblox Filtering Disabled webserver.
If you need any help, please shoot me an issue on GitHub or a message to an account with some form of 'VisualPlugin' elsewhere.
Where ...
is your command-line prefix,
... server -p 2005 --config ./GameConfig.toml
... player -rh 172.88.194.43 -p 2005
GameConfig.toml
StructureThis specification is current as of 0.53. Some options might be different in future versions.
Function-type options are very flexible in RFD. Way too flexible if you're asking me.
Look out for {OPTION}_call_mode
, where {OPTION}
is the name of the option you're modifying. If {OPTION}_call_mode
is not specified, RFD tries to assume on its own.
Following is a hypothetical option called skibidi_plugin
. The examples all do the same thing.
skibidi_plugin_call_mode = "lua"
skibidi_plugin = '''
function(toil_int, daf_qbool)
if toil_int == 666 then
return {
evil = 666,
}
elseif daf_qbool == true then
return {
owo = 7,
}
elseif toil_int == 420 and daf_qbool == false then
return {
uwu = 3,
}
end
return {
camera_man = 1,
ohio = 2,
}
end
'''
skibidi_plugin_call_mode = "python"
skibidi_plugin = '''
def f(toil_int: int, daf_qbool: bool):
if toil_int == 666:
return {
"evil": 666,
}
elif daf_qbool == True:
return {
"owo": 7,
}
elif toil_int == 420 and daf_qbool == False:
return {
"uwu": 3,
}
return {
"camera_man": 1,
"ohio": 2,
}
'''
Nobody cares what name you give the function. RFD should be smart enough to figure out what you're using.
In Python mode, RFD assigns global constants for your convenience.
Variable | Description |
---|---|
CONFIG_DIR |
the config file's directory path |
skibidi_plugin_call_mode = "dict"
skibidi_plugin.666 = {
"evil": 666,
}
skibidi_plugin.True = {
"owo": 7,
}
skibidi_plugin.420-False = {
"uwu": 3,
}
skibidi_plugin.default = {
"camera_man": 1,
"ohio": 2,
}
Dict keys are access in the following order of precedence:
_
, ,
, then ,
,default
.metadata.config_version_wildcard
Resolves to a wildcard; defaults to "*"
.
Matches against the GIT_RELEASE_VERSION
internal constant. Useful for protecting changes in config structure between RFD versions.
server_core.startup_script
Resolves to type str
.
Runs at the CoreScript security level whenever a new server is started.
startup_script = 'game.workspace.FilteringEnabled = false'
server_core.metadata.title
Resolves to type str
.
Shows up on the loading screen when a player joins the server.
server_core.metadata.description
Resolves to type str
.
Shows up on the loading screen when a player joins the server.
server_core.metadata.creator_name
Resolves to type str
.
Shows up on the loading screen when a player joins the server.
server_core.metadata.icon_uri
Resolves to internal type uri_obj
.
Can resolve to either a relative or absolute local path -- or extracted from a remote URL.
server_core.place_file.rbxl_uri
Resolves to internal type uri_obj
. Files must be encoded in the binary rbxl
format and not in the human-readable rbxlx
format.
Can resolve to either a relative or absolute local path -- or extracted from a remote URL.
rbxl_uri = 'c:\Users\USERNAME\Documents\Baseplate.rbxl'
rbxl_uri = 'https://archive.org/download/robloxBR1/RBR1/RBR1.rbxl'
server_core.place_file.enable_saveplace
Resolves to type bool
; defaults to false.
When game:SavePlace()
is called and enable_saveplace
is true, the file at rbxl_uri
is overwritten. It won't work if rbxl_uri
points to a remote resource.
game_setup.roblox_version
The following are valid version strings.
"v348" |
"v463" |
---|---|
"2018M" |
"2021E" |
"2018" |
"2021" |
All entries on the same column are aliases for the same version.
game_setup.asset_cache.dir_path
Resolves to type path_str
. Relative paths are traced from the directory where the config file is placed.
game_setup.asset_cache.clear_on_start
Resolves to type bool
; defaults to false.
If true, deletes cache from assets which should redirect so that the config file remains correct.
game_setup.persistence.clear_on_start
Resolves to type bool
; defaults to false.
If true, clears the sqlite
database before starting a new server.
game_setup.persistence.sqlite_path
Resolves to type path_str
. Relative paths are traced from the directory where the config file is placed.
server_core.chat_style
Corresponds to Rōblox Enum.ChatStyle
. Can either be "Classic"
, "Bubble"
, or "ClassicAndBubble"
.
server_core.retrieve_default_user_code
Resolves to function type (float) -> str
.
If the client doesn't include a -u
user code whilst connecting to the server, this function is called. Should be a randomly-generated value.
retrieve_default_user_code_call_mode = "lua"
retrieve_default_user_code = '''
function(tick) -- float -> str
return string.format('Player%d', tick)
end
server_core.check_user_allowed
Resolves to function type (int, str) -> bool
.
check_user_allowed_call_mode = "lua"
check_user_allowed = '''
function(user_id_num, user_code) -- string -> bool
return true
end
'''
server_core.check_user_has_admin
Resolves to function type (int, str) -> bool
.
check_user_has_admin_call_mode = "lua"
check_user_has_admin = '''
function(user_id_num, user_code) -- string -> bool
return true
end
'''
server_core.retrieve_username
Resolves to function type (int, str) -> str
.
Only gets called the first time a new user joins. Otherwise, RFD checks for a cached value in the sqlite
database.
retrieve_username_call_mode = "lua"
retrieve_username = '''
function(user_id_num, user_code)
return user_code
end
'''
server_core.retrieve_user_id
Resolves to function type (str) -> int
.
Only gets called the first time a new user joins. Otherwise, RFD checks for a cached value in the sqlite
database.
retrieve_user_id_call_mode = "lua"
retrieve_user_id = '''
function(user_code)
return math.random(1, 16777216)
end
'''
server_core.retrieve_avatar_type
Resolves to function type (int, str) -> Enum.HumanoidRigType
.
Where Rōblox Enum.HumanoidRigType
can either be "R6"
or "R15"
.
retrieve_avatar_type_call_mode = "lua"
retrieve_avatar_type = '''
function(user_id_num, user_code)
return 'R15'
end
'''
server_core.retrieve_avatar_items
Resolves to function type (int, str) -> [int]
.
The returned list contains asset idens from the Rōblox catalogue.
retrieve_avatar_items_call_mode = "lua"
retrieve_avatar_items = '''
function(user_id_num, user_code)
return {
10726856854,
9482991343,
9481782649,
9120251003,
4381817635,
6969309778,
5731052645,
2846257298,
121390054,
261826995,
154386348,
201733574,
48474294,
6340101,
192483960,
190245296,
183808364,
34247191,
}
end
'''
server_core.retrieve_avatar_scales
Resolves to function type (int, str) -> util.types.structs.avatar_scales
.
retrieve_avatar_scales_call_mode = "lua"
retrieve_avatar_scales = '''
function(user_id_num, user_code)
return {
height = 1,
width = 0.8,
head = 1,
depth = 0.8,
proportion = 0,
body_type = 0,
}
end
'''
server_core.retrieve_avatar_colors
Resolves to function type (int, str) -> util.types.structs.avatar_colors
.
retrieve_avatar_colors_call_mode = "lua"
retrieve_avatar_colors = '''
function(user_id_num, user_code)
return {
head = 315,
left_arm = 315,
left_leg = 315,
right_arm = 315,
right_leg = 315,
torso = 315,
}
end
'''
server_core.retrieve_groups
Resolves to function type (int, str) -> dict[str, int]
.
Key is the group iden number written as a string; value is the rank value from 0 to 255.
retrieve_groups_call_mode = "lua"
retrieve_groups = '''
function(user_id_num, user_code)
return {
['1200769'] = 255;
['2868472'] = 255;
['4199740'] = 255;
['4265462'] = 255;
['4265456'] = 255;
['4265443'] = 255;
['4265449'] = 255;
}
end
'''
server_core.retrieve_account_age
Resolves to function type (int, str) -> int
.
retrieve_account_age_call_mode = "lua"
retrieve_account_age = '''
function(user_id_num, user_code) -- str -> int
return 6969
end
'''
server_core.retrieve_default_funds
Resolves to function type (int, str) -> int
.
Established the amount of funds that a player receives when they join a server for the first time. These funds can only be spent on server-defined gamepasses.
retrieve_default_funds_call_mode = "lua"
retrieve_default_funds = '''
function(user_id_num, user_code)
return 6969
end
'''
server_core.filter_text
Resolves to function type (str, int, str) -> str
.
filter_text_call_mode = "lua"
filter_text = '''
function(text, user_id_num, user_code)
return text:gsub('oo','òó'):gsub('OO','ÒÓ'):gsub('ee','èé'):gsub('EE','ÈÉ'):gsub('Roblox','Rōblox'):gsub('ROBLOX','RŌBLOX')
end
'''
remote_data.gamepasses
Resolves to a data dictionary.
[[remote_data.gamepasses]]
id_num = 163231044
name = 'EnforcersPowers'
price = 100
remote_data.asset_redirects
Resolves to function type (int | str) -> asset_redirect
.
When an RFD server receives a request to load an asset by id, it does so from Roblox.com by default.
However, entries in asset_redirects
override that default.
Through the uri
field, assets can load either from a local or remote resource.
The following examples notate the structure into the dict mode syntax:
[remote_data.asset_redirects.13] # asset iden 13
uri = 'c:\Users\USERNAME\Pictures\Image.jpg'
[remote_data.asset_redirects.14] # asset iden 14
raw_data = 'https://archive.org/download/youtube-WmNfDXTnKMw/WmNfDXTnKMw.webm'
You can include a cmd_line
field if you want the loaded asset to literally come from the stdout
of a program.
[remote_data.asset_redirects.15] # asset iden 15
cmd_line = 'curl https://archive.org/download/youtube-WmNfDXTnKMw/WmNfDXTnKMw.webm -L --output -'
A raw_data
field works here too. That literally encapsuates the binary data that will be sent as an asset.
[remote_data.asset_redirects.16] # asset iden 15
raw_data = '\0'
This should also work. It redirects asset iden strings starting wtih time_music_
to static files on the internet.
remote_data.asset_redirects = "python"
remote_data.asset_redirects = '''
def f(asset_iden):
PREFIX = "time_music_"
if asset_iden.startswith(PREFIX):
h = int(asset_iden[len(PREFIX):])
return {
"uri": "https://github.com/Windows81/Time-Is-Musical/blob/main/hour_%02d.wav" % (h % 24)
}
return None
'''
remote_data.badges
Resolves to a data dictionary.
[[remote_data.badges]]
id_num = 757
name = 'Awardable Badge'
price = 1