Okay so I'm trying to make something that gets the arguments used in a print function, and stores them. For example, if a user inputs: "print(2+2)", the client sends that code to the server for the server to use loadstring() on, and then returns that print result to the client (since loadstring() can't be used in a local script). Is this possible? If so, how? Here's a visual on how I'd picture doing this:
Server script
-- *some stuff that happens in the server script*
Local script
-- The rest of this would be in a local script -- This is the code that will be ran on the client local Code = [[ print("2+2="..tostring(2+2)) ]] local Remote = game:GetService("ReplicatedStorage").Event -- the server will return the arguments from the print function (again, i'm using a server connection because i think "loadstring" will be involved somehow, and that can't be done locally) local function GetPrintArgs(fromCode) local args = Remote:InvokeServer(fromCode) return args -- maybe returns a table of all the print arguments? end print("From client: "..(unpack(GetPrintArgs(Code)))) -- prints the arguments from the "Code" string.
So basically, passing a string of code for the server to use loadstring() on, and then returning a table to the client of every argument used in a print function from that code string. Can this be done? Thank you for your time.
Actually, what you're looking for is possible. A simple amount of function environment manipulation and sandboxing should do the job. I've tested some things, and ended up with a pleasing result. Now i don't know how much you know about function environments, so here's a quick review:
Function environments
A function environment is a global table consisting of varying keys and values, that can be accessed while the code is executed. Thanks to some functions given to us by Lua, we can actually change these environments, and quite drastically. To change these environments, and monitor them, we can use setfenv(function, table). The first argument "function", can be a function, or a number. If it's a number, that number represents it's stack level (like getfenv). It's second argument "table", is the environment itself. The setfenv function all together, returns the function given in it's first parameter.
Metatables
Again, I don't know how much you already understand, so here's a quick review on metatables. A metatable is a table consisting of functions (called metamethods) that execute when a table is changed, or referenced in any and every way. Now, because environments are tables, this is a very easy concept to implement in our code.
Server script
-- This can be the function that the client invokes from a remote function. -- Obviously, the remote name / parent may be different for you. But for demonstration purposes, this is what I'm going with. local RemoteOutput = game:GetService("ReplicatedStorage").RemoutOutput -- Err will be our error message, in case something goes wrong local function GetPrintArgs(CodeSource, Err) local Args = {} -- the table containing our print arguments -- if the code is faulty, this will be false instead of a function if CodeSource then -- Now i set the function environment of "CodeSource" (since it's the Lua function our loadstring returned), to a metatable that monitors everything being indexed in that code. setfenv(CodeSource, setmetatable({ -- Set up out index metamethod (if you don't know what this is, use the wiki to search metamethods) __index = function(t,k) -- If they indexed the environment for print, then... if k == "print" then -- Return this function instead of print. return function(...) -- Here i insert in the "Args" table, another table consisting of the arguments the user tried to pass to the print function. Now considering this is all happening in a function, these arguments will be "executed" (i.e, saying 2+2 will return 4, and use of variables is supported) Args[#Args+1] = {...} end else -- If the index wasn't print, return this string. According to your request, we just want them to be able to print things, not actually make complex code. This is pretty much a sandbox. return 'Not allowed' end end }))() -- execute the function -- If CodeSource isn't a function, then... else -- Return this error message return Err end return Args -- return the table end -- Create the remote function event Remote.OnServerInvoke = function(Client, Request, Value) -- I always make a request variable Request = Request:lower() -- Simple key to tell us what to do, can be anything. if Request == "getprint" then -- Remember, loadstring returns 2 values. First one being the function, or false, and the second being the error message, or false. local CodeFunc, Err = loadstring(Value) return GetPrintArgs(CodeFunc, Err) end end
And now we can make a simple function to "decode" this table, from a local script. Like this:
LocalScript
-- Again, assuming this is the name of the remote function... local RemoteOutput = game:GetService("ReplicatedStorage").RemoteOutput local function GetPrintArgs(CodeSource) -- Remember, we made "getprint" our key. local Args = RemoteOutput:InvokeServer("getprint", CodeSource) -- If it's a table, then print everything in it if type(Args) == "table" then -- Iterate through the table of tables, containing the arguments... for i,v in pairs(Args)do print(unpack(v)) -- "v" should be a table. end else -- If it's not a table, then print it's single value. print(Args) end end -- Now, let's try it: local CodeToRun = [[ local Test = "Hello world" print(Test) print(workspace) print(print) print("2+2="..tostring(2+2)) ]] GetPrintArgs(CodeToRun) --------------- -- Output -- --------------- -- > Hello world -- > Not allowed -- > function -- > 2+2=4
All of this code will run client-sided, as i think you were trying to accomplish. But anyway, hope this helped, I don't know how well i explained this, but i tested it and it works. Let me know if you have any questions.
Would string manipulation suffice for something like this, since you start with the code in string form? It would just involve searching through the code to find "print(" then searching for ')' after, then find the string that appears between them. This seems like an obvious approach to me, so perhaps I don't fully understand. I hope this helps anyway! :D
The simplest solution is to redefine the print
function.
For example, you could make it fire a remote event:
function print(...) local t = {...} for i, v in pairs(t) do t[i] = tostring(v) end game.ReplicatedStorage.Output:FireClient(target player, unpack(t)) end
There is also the LogService which has events that are fired whenever things appear in the output. You could use the normal print
and just connect to that, though it might be more difficult to determine which player the output belongs to.