Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
2

Is it possible to get the arguments of the print function?

Asked by
LuaQuest 450 Moderation Voter
8 years ago

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.

3 answers

Log in to vote
2
Answered by 8 years ago

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.

0
YES, THANK YOU! LuaQuest 450 — 8y
Ad
Log in to vote
0
Answered by
Rhyles 80
8 years ago

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

0
String manipulation won't return the result of operations preformed inside the parameters of the function. For example: print(2+2). String manipulation would return "2+2", not "4". LuaQuest 450 — 8y
0
I am confused. So you want the arguments of the print function or the results? Or do you wish to take each argument specifically and obtain the result? DigitalVeer 1473 — 8y
0
Yes, so whenever the "print" function is indexed in whatever function environment it's in, instead of printing the arguments it was given, it stores them in a table. LuaQuest 450 — 8y
0
There is no way to make this work. Besides simple things like f=print; f(5), you would have to capture all the varied syntax that Lua has. It would be a waste of effort. BlueTaslem 18071 — 8y
Log in to vote
0
Answered by
BlueTaslem 18071 Moderation Voter Administrator Community Moderator Super Administrator
8 years ago

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.

0
Yeah i already thought of that, the problem is making this happen based off user input. Meaning the code I'm running is inside a string, so loadstring needs to be involved LuaQuest 450 — 8y
0
Just define print to be the above. Whether or not you're using loadstring doesn't matter. The goal is to make the "real" print go away and be replaced with the above. If you wanted to you could just tack this definition on to the beginning of the code being loadstringed. BlueTaslem 18071 — 8y

Answer this question