Jump to content

loadstring


IIYAMA

Recommended Posts

loadstring just acts like a normal code execution, it first loads a string and returns a function that executes the code on call.

E.g:

loadedString = loadstring ( "outputChatBox ( \"Hello!\" )" ) -- Here's our loaded code that will be executed when we call 'loadedString' function 
loadedString ( ) -- And here we call it 
-- Result: Hello! 

Note: I'm not sure about this explaination though, i might be wrong.

Link to comment

Its just a string. I use loadString for loading map scripts. I insert functions into tables and I didn't touch chatbox functions since its a string and loadString just executes it and its not saved in memory or something. <-- This is how much I know. :)

btw if you have event handlers in the string then you'll need to insert it into tables and after that you'll need to use removeEventHandler same goes for timers. This might be helpful.

i just created another resource which handles that. The code looks like:
  
local objects = { } 
local _createObject = createObject -- save the original function 
  
function createObject(id,x,y,z,rx,ry,rz,islod) -- replace the original one 
  if not id or not x or not y or not z then 
    return false 
  end 
  local rx = rx or 0 
  local ry = ry or 0 
  local rz = rz or 0 
  local object = _createObject(id,x,y,z,rx,ry,rz,islod) -- use the old function 
  if object then 
    table.insert(objects,object) 
    return object -- Don't forget to return the info of the original one 
  end 
end 
  
function loadCode(content) 
  local loaded = loadstring(content) 
  local ex = pcall(loaded) 
  if ex then outputChatBox("loaded the code") end 
end 
  
function unloadCode() 
  for i,v in ipairs(objects) do 
    if isElement(v) then 
      destroyElement(v) 
    end 
  end 
end 

Edited by Guest
Link to comment
  • Moderators
you can just assign 'thisFunction' to nil
loadstring("thisFunction = nil")() --Destroy the function 'thisFunction' 

EDIT: But you will get errors if the rest of the script is still calling it somewhere so just do this instead:

loadstring("thisFunction = function () end")() 

Link to comment
  • Moderators
This is what I do:
local func = loadstring("function thisFunction() end") 
pcall(func) 
  
-- and then later when I want to unload... 
func = nil 

There is no need to use pcall function just call func like any other function:

local func = loadstring("function thisFunction() end") 
func() --like this 

By the way as I said, setting the variable to nil will throw errors if the rest of the script is still trying to call that function.

Link to comment
  • Moderators

So I must use it like this?

dataForTheFunctionInside = {"my name is IIYAMA","apple","what ever"}-- must be global 
local callThisFunction = "function myFunction(data) outputChatBox(next(data)) end myFunction(dataForTheFunctionInside)" 
  
local callLoadString = function () 
    local loaded = loadstring(callThisFunction) 
    local ex = pcall(loaded) 
    if ex then outputChatBox("loaded the code") end 
end 
addCommandHandler("loadstring",callLoadString) 

(no tested)

Link to comment

There is no need to use pcall function just call func like any other function:

local func = loadstring("function thisFunction() end") 
func() --like this 

By the way as I said, setting the variable to nil will throw errors if the rest of the script is still trying to call that function.

Does anybody knows how I can unload a loaded string?

I assume he wants to unload the whole code loaded with the string; and why would there be any reason to only "unload" some parts of it?

Also it is better to use pcall if you want to have the same affect as mta's script loader, like there are hundreds of maps that try to call a misspelled function (SetCloudsEnabled), and mta script loader ignores that and makes other scripts in the map work, and that is only possible with pcall.

So I must use it like this?
dataForTheFunctionInside = {"my name is IIYAMA","apple","what ever"}-- must be global 
local callThisFunction = "function myFunction(data) outputChatBox(next(data)) end myFunction(dataForTheFunctionInside)" 
  
local callLoadString = function () 
    local loaded = loadstring(callThisFunction) 
    local ex = pcall(loaded) 
    if ex then outputChatBox("loaded the code") end 
end 
addCommandHandler("loadstring",callLoadString) 

(no tested)

Yes, that is the right way. It is recommended to use a "sandbox", but you can add later.

Link to comment
  • Moderators

aha,

Well I am not going to save the scripts I load via loadstring on the players their harddrive.

More like loading modus per map, which is send from the server.

But if I do save them, how can a sandbox prevent this?

Link to comment
aha,

Well I am not going to save the scripts I load via loadstring on the players their harddrive.

More like loading modus per map, which is send from the server.

But if I do save them, how can a sandbox prevent this?

You got it wrong, saving to harddrive or not doesn't matter, it's the fact that you are loading scripts from a map that is not made by you. Using a sandbox will prevent the script from calling functions that you dont want it to be able to call.

Link to comment
  • Moderators

ok,

local env = {} 
setfenv(user_script, env) 
pcall(user_script) 
  

Using setfenv I can prevent this?

But after I set this environment. Will the code inside the environment able to access global variables (read or/and write)?

Or just calling other functions disabled?

Note:

All the code that I am going to load is written by myself and doesn't come from the maps but from the gamemode.

The gamemode sends the code which fits with the map.

Link to comment

Note:

All the code that I am going to load is written by myself and doesn't come from the maps but from the gamemode.

The gamemode sends the code which fits with the map.

If you are only sending your own code to the player and loading it, then you don't need a sandbox.

Anyways if you want to know, what you wrote is not enough, setmetatable is also needed, but you can read on how to do that somewhere else as there are already good tutorials for that (and i've also answered on how to do it properly on mta forum previously, so you can use search).

Link to comment

A very useful post by azeru. :)

Using
setmetatable(env, {__index = _G}) 

means if a variable/function doesn't exist in the environment, then search in the global environment (of the resource), which means if you are trying to disallow triggerServerEvent, then a map can simply have script that does this:

triggerServerEvent = nil 

and the map will have access to the real triggerServerEvent function.

To fix this problem, you have to copy only the functions and variables to the new environment; you can do that by doing this:

  
-- env is a empty table 
-- Make sure to use pairs, not ipairs. 
for k, v in pairs(_G) do 
    if(v ~= _G)then 
        env[k] = v 
    end 
end 
env._G = env 
  

The only problem left is that source, resourceRoot, localPlayer, resource etc are global variables and are only set in the default environment (resource global environment), so you need to do to so that if you are trying to get/set the value of any of those variables, then you should access the global environment (of the resource) instead, but at the same time, your environment should not have access to that environment.

This can be done by taking advantage of 'variable capturing', which means refrences to variables are copied into functions that are defined in a scope (for example if a function is defined in another function, then that function will have access to variables that the function that created it, has access to too), for example:

  
function f(v) 
    local d = 23 
    local f2 =  
        function() 
            outputChatBox(d) -- will output 23 
            outputChatBox(v) -- will output the value of 'v' from the function v 
        end 
end 
  

We can take advantage of this method to give metamethod __index and __newindex of the new environment access to global environment without giving the actual environment access to it.

An example of how the metamethod looks like (this should be called in the global environment before copying variables/functions):

  
setmetatable(env, 
{ 
    __index =  
        function(t, key) 
            local r = rawget(_G, "_GET_GLOBAL")(key) 
            if(r)then 
                return r 
            end 
            return rawget(t, key) 
        end, 
    __newindex =  
        function(t, k, value) 
            local r = rawget(_G, "_SET_GLOBAL")(key, value) 
            if(r)then 
                return t 
            end 
            return rawset(t, k, value) 
        end 
}) 
  

_GET_GLOBAL and _SET_GLOBAL are functions defined in the global environment, and what they simply do is check if the key you called it with is "source", "resource", "resourceRoot" etc, then return the value or set it and return true; otherwise get the value from the new environment or set it in the new environment.

The reason you need __newindex as well is because __index is only called when trying to access a variable that doesn't exist (we dont want source, resourceRoot etc to exist in the new environment), and if a script in a map for example has:

source = aValue 

and you dont have __newindex set, then source will be saved as a new variable in the environment, and __index will not be called when trying to access source, and source will always have the same value until you manually override it again.

The order you should do everything in is:

Create a empty table (env in this case).

Set the metamethods for __index and __newindex for it, to get/set values from global environment instead.

Loop the global environment and copy variables.

Set the environments _G table to itself (env._G = env in this case).

(Set the environments debug table to nil, in this case env.debug = nil, to prevent a user from doing stuff that they should not able to do).

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...