rezecib

upvaluehacker.lua

Jun 13th, 2016
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 5.12 KB | None | 0 0
  1. --[[
  2. What is this for?
  3.     This is a way to access local variables in files. You should always, always, always, try to find
  4.     another way to do this first. However, there are some cases where there's really no other way to do it.
  5.     For example: you want to remove an event listener, but there's no way to access the local function.
  6.  
  7. How do I use this?
  8.     The basic idea is that you find a starting function first (usually a prefab's constructor), and then
  9.     you have to trace the variables downward through the stack to get at the particular variable you want.
  10.     First, load it into your modmain (assuming you put it in util/upvaluehacker.lua):
  11.    
  12.     local UpvalueHacker = GLOBAL.require("util/upvaluehacker")
  13.    
  14.     There are two main things you can do with this:
  15.         UpvalueHacker.FindUpvalue: get a reference to a local variable to use it
  16.         UpvalueHacker.SetUpvalue: set the value of the local variable to change it
  17.  
  18. Usually you'll want to be making these changes from an AddPrefabPostInit("world", function(inst) end),
  19. to make sure that all prefabs/mods have loaded first.
  20.    
  21. Example 1: A normal prefab
  22.     Let's say you want to change the IsCrazyGuy function in prefabs/bunnyman.
  23.     IsCrazyGuy is referenced in two other local functions, CalcSanityAura and LootSetupFunction
  24.     Let's go with CalcSanityAura; this is referenced in the bunnyman constructor ("fn()")
  25.     We can get a reference to the constructor from the Prefabs table,
  26.     so first we define our own IsCrazyGuy, and then we can set it like this:
  27.    
  28.     UpvalueHacker.SetUpvalue(GLOBAL.Prefabs.bunnyman.fn, IsCrazyGuy, "CalcSanityAura", "IsCrazyGuy")
  29.    
  30.     So the first argument there is the function we start from. Then, we give it our own IsCrazyGuy.
  31.     Then, we give it the series of functions we're following to finally get to the default IsCrazyGuy.
  32.     From looking before, we found that it went Constructor -> CalcSanityAura -> IsCrazyGuy
  33.    
  34. Example 2: A player prefab, with a secondary reference
  35.     Players are a little more complicated because if you look at their file, you might think that you
  36.     can get at stuff there directly, but you can't, because you have to go through the function that
  37.     MakePlayerCharacter generates in player_common. So, let's say we want to make WX-78 drop more gears.
  38.     Gears get dropped by his ondeath function, which is referenced in his master_postinit. However, ondeath
  39.     also references the local function applyupgrades, which we need as well if we just want to replace
  40.     ondeath with a small change to dropgears. First, how do we get to master_postinit?
  41.     We can go through GLOBAL.Prefabs.wx78.fn, which brings us to the fn defined in MakePlayerCharacter.
  42.     From there, we can get at master_postinit, and then ondeath, and then applyupgrades. So first we grab it:
  43.    
  44.     local applyupgrades = UpvalueHacker.FindUpvalue(GLOBAL.Prefabs.wx78.fn, "master_postinit", "ondeath", "applyupgrades")
  45.    
  46.     Then, we can define our own ondeath, starting by copy-pasting theirs... maybe need to fix some GLOBAL references...
  47.     And then, we can set the ondeath:
  48.    
  49.     UpvalueHacker.SetUpvalue(GLOBAL.Prefabs.wx78.fn, ondeath, "master_postinit", "ondeath")
  50.    
  51. Example 3: Local variables in components
  52.     Let's say we want to change what birds spawn on what turf, like make the deciduous turf spawn
  53.     the same birds as forest turf. Right now this is a local variable, BIRD_TYPES, in components/birdspawner.
  54.     We can start by doing an AddClassPostConstruct("components/birdspawner", function(self) <code> end)
  55.     Now that we have the class, we have to figure out where BIRD_TYPES is referenced.
  56.     Looks like BIRD_TYPES is only referenced in the local function PickBird. Where's that referenced?
  57.     PickBird is referenced in self:SpawnBird. This means that in our PostConstruct, we can find it at
  58.     self.SpawnBird -> PickBird -> BIRD_TYPES. So we can now use FindUpvalue and make the change:
  59.    
  60.     local BIRD_TYPES = UpvalueHacker.FindUpvalue(self.SpawnBird, "PickBird", "BIRD_TYPES")
  61.     BIRD_TYPES[GLOBAL.GROUND.DECIDUOUS] = BIRD_TYPES[GLOBAL.GROUND.FOREST]
  62.    
  63.     Because BIRD_TYPES is a table, we don't need to use SetUpvalue on it, because we got a reference to the
  64.     actual table and can change it. If it were a string, number, or function, then we'd have to use
  65.     SetUpvalue to replace it instead.
  66.    
  67. Good luck and happy upvalue hacking!
  68. ]]
  69.  
  70. UpvalueHacker = {}
  71. local function FindUpvalueHelper(fn, name)
  72.     local i = 1
  73.     while debug.getupvalue(fn, i) and debug.getupvalue(fn, i) ~= name do
  74.         i = i + 1
  75.     end
  76.     local name, value = debug.getupvalue(fn, i)
  77.     return value, i
  78. end
  79.  
  80. function UpvalueHacker.FindUpvalue(fn, ...)
  81.     local prv, i, prv_var = nil, nil, "(the starting point)"
  82.     for j,var in ipairs({...}) do
  83.         assert(type(fn) == "function", "We were looking for "..var..", but the value before it, "
  84.             ..prv_var..", wasn't a function (it was a "..type(fn)
  85.             .."). Here's the full chain: "..table.concat({"(the starting point)", ...}, ", "))
  86.         prv = fn
  87.         prv_var = var
  88.         fn, i = FindUpvalueHelper(fn, var)
  89.     end
  90.     return fn, i, prv
  91. end
  92.  
  93. function UpvalueHacker.SetUpvalue(start_fn, new_fn, ...)
  94.     local _fn, _fn_i, scope_fn = UpvalueHacker.FindUpvalue(start_fn, ...)
  95.     debug.setupvalue(scope_fn, _fn_i, new_fn)
  96. end
  97.  
  98. return UpvalueHacker
Add Comment
Please, Sign In to add comment