Ability to pass functions through remote events to client

As a Roblox Developer, It’s currently too hard to write code in the way I want, because I cannot use :FireAllClients/:FireClient to run arbitrary functions on the client-side.

If this issue is addressed, It would improve my development experience because I wouldn’t always have to use premade functions on the client, I could just send a function for the client to call.

I know that Client to Server would present security issues, But if we were to allow Server to Client it would present 0 security issues (From what I know…) and make lives easier and give ROBLOX some more flexibility than before.

If there is a reason this could present issues I’d like to know. Because I feel like this would make developers live’s easier, Albeit a small change it would still have quite a big impact.

Furthermore: Scripts like RemoteSpy doesn’t even detect :FireClient/:FireAllClients so I do not believe the exploiter could easily modify/rewrite any functions that are passed through.

A good example one why this would be incredibly useful (also pertains to my issue which prompted this) is as follows:

-- What we would be able to do if we could pass functions:
-- Server

local ReplicationEvent = Instance.new("RemoteEvent", ReplicatedStorage)
ReplicationEvent.Name = "ReplicationEvent"

function Replicate(Func)
	ReplicationEvent:FireAllClients(Func)
end

Replicate(function()
	local Part = Instance.new("Part", workspace)
end)

-- Client

local ReplicationEvent = ReplicatedStorage:WaitForChild("ReplicationEvent")

ReplicationEvent.OnClientEvent:Connect(function(Func)
	Func() -- Would create a part for all players in the game, Good for replicating stuff without having to script a premade function for it.
end)

---------------

-- What we currently have to do
-- Server

local ReplicationEvent = Instance.new("RemoteEvent", ReplicatedStorage)
ReplicationEvent.Name = "ReplicationEvent"

function Replicate(Request)
	ReplicationEvent:FireAllClients(Request)
end)

-- Client

local ReplicatedStorage = game.ReplicatedStorage
local ReplicationEvent = ReplicatedStorage:WaitForChild("ReplicationEvent")

ReplicationEvent.OnClientEvent:Connect(function(Request, Other)
	if Request == "MakePart" then -- So on and so forth for every little replication we want to make.
		local Part = Instance.new("Part", workspace)
	end
end)
1 Like

This isn’t feasible, at all, unless you’re willing to compromise a lot. First, you should consider using RemoteFunctions or RemoteEvents with handlers on the client already present for whatever you’re trying to achieve.

Next, the biggest issue itself, is actually security. Roblox’s server and client might not be the same in terms of their bytecode or function format and such. I can’t speak much for Luau, but I know they had differing encryption keys for their functions back when they still used Lua 5.1. You would need some kind of layer to translate server->client code, and it’d make it easier for exploiters to inspect how Luau internals work to develop better execution methods.

On top of that, someone with a bad remote model could find themselves open to people abusing this to run arbitrary code on all clients, or at least limited-ish code with potential side effects.

The second issue you’ll encounter is upvalues. You simply can’t sync upvalues between the client and server and expect it to be smooth. Consider the following:

local a = 1
local function b(x)
    a = x
end

What happens if you send b over the network to one or many clients? When they call b, there’s 2 possible things that can happen:

  1. Upvalues are copied and dropped, which means each client has a different copy of a and b, and would be weird and outlandish for how Lua already works, because suddenly you have variables and functions that aren’t coupled to each other when passed over the network.
  2. Upvalues are linked over the network somehow, which means the GC also has to sync, and issues with multiple clients reading/writing to the variable must be addressed. This is very messy and slow if not impossible.

As for your point about RemoteSpy, I don’t really get it. It takes ~2 minutes to write a script to hook any incoming traffic from the server and swap out any of the arguments, regardless of how it’s sent.

Your example is already not very clear: what if the global variables Instance and workspace need to be or are different on the client, because of user action for example? Globals are an interesting issue:

var = {}
local function b()
    print(var)
end

This code would only print table: {hex} on the server, and everywhere else it would print nil, because unlike locals that can be made into upvalues, globals aren’t bound to any function. And as such, it’s a lot harder to copy or “link up” globals being used in functions that aren’t already present on the receiving end.

Lastly, I feel it’s deceptively basic. Your counter example of “this is how it’s done right now” is not scalable, but that doesn’t mean there aren’t better ways to make it so. Instead of a long elseif chain, you could use a lookup table for example. You can have a well defined protocol for clients to follow, and it can certainly be more complex and scalable than string comparisons.

8 Likes

Very direct answer: functions can’t be sent to other clients because you can’t serialize them.

Serialization is turning objects and things to a form that can be understanded by any other machine, thus you can send those serialized objects over the network (which what interests us) and save them without meeting any problems.

Functions can’t be serialized (for reasons that might be better to search online). So sending functions over the network isn’t just something roblox can do, it’s a problem to all of the computing world.

2 Likes

As noted above this isn’t really great.
Right now you can send a RemoteFunction to the client and wrap it on both sides (this is just an example, it’s nowhere near perfect):

-- Server
local function serializeFunction(ownerPlayer, func)
    if not func then -- No ownerPlayer passed, so use it as the function
        func = ownerPlayer
        ownerPlayer = nil
    end

    local remoteFunc = Instance.new("RemoteFunction")
    remoteFunc.OnServerInvoke = function(player, ...)
        if ownerPlayer and player ~= ownerPlayer then
            -- Do some stuff
            error("You are not the owner of this remote.")
        end
        return func(player, ...)
    end
    remoteFunc.Parent = workspace
    return remoteFunc
end
remote:FireClient(player, serializeFunction(player, function(player, someArgs, ...)
    print(player, someArgs, ...)
    return "someValue", ...
end)

-- Client
remote.OnClientEvent:Connect(function(func)
    if typeof(func) == "Instance" and func:IsA("RemoteFunction") then
        local remoteFunc = func
        func = function(...)
            return remoteFunc:InvokeServer(...)
        end
    end

    print(func("arg1", "arg2"))
end)
3 Likes

This isn’t really the case; functions can be serialized, and that’s what bytecode is, serialized functions. The issues stem from the fact that you not only need to serialize the functions, but also their tied data like upvalues. You also need to deal with cases where the environments might differ from client to server.

5 Likes

Not to mention that simply just allowing the client to run bytecode over the network introduces a HUGE gateway for executing code on the client. Simply proxying the client (it’s not particularly hard, even on Windows, and even without exploiting) and manipulate the bytecode from there, or even forge your own bytecode packets and run any code you want without exploiting.

1 Like

How would I achieve something like this? Because I really need to be able to replicate code across all clients so it seems server sided but it’s not and I just find it very hard to do that where I prescript every small function that I need to be executed on the client.

Also thanks to everyone who replied with constructive criticism, Really opens my eyes to the fact that I actually don’t know much, Makes me want to learn more!

Example of a ‘winner is $x with $y total score ($z in this round)’ thing:

-- Server
local function reportWinner(winner, theirScore, theirNameColour)
    -- this can even be simplied down to FireAllClients(...)
    -- you'd probably want to make winner a table of attributes rather
    -- than a player instance for reasons:tm:
    winnerRemoteEvent:FireAllClients(winner, theirScore, theirNameColour)
end
-- Client
winnerRemoteEvent.OnClientEvent:Connect(function(winner, theirScore, theirNameColour)
    guiHandler.WinnerComponent:ShowWinnerOrWhatever({
        winnerName = winner.name, -- something like coexperience
        winnerTotalScore = winner.totalScore, -- something like 1,005
        winningScore = theirScore, -- something like 5
        winnerNameColour = theirNameColour -- something like RGB(255, 0, 0)
    })
end)

It’s not super difficult; basically all games (both on and off Roblox) seem to have mastered this.

1 Like

But my goal is to replicate small code without having to prewrite said small code, It’d be very tedious if I had to make an elseif statement for each and every small replication, Idk, Maybe I’m over complicating something that isn’t that big of a deal, I’m just constantly looking for a way to make things easier.