Difference between revisions of "Tutorials:Lua:ObjectOriented"

From Cheat Engine
Jump to navigation Jump to search
(→‎Offsets)
(Replaced content with '<span style="font-size:25px;color:red">Sorry! Content not available.</span>')
Line 1: Line 1:
−
== Calling Notation ==
+
<span style="font-size:25px;color:red">Sorry! Content not available.</span>
−
 
 
−
The first thing to understand is that you can create functions on your tables.  Think of a simple table
 
−
for a rectangle with a length and width and a function that calculates the area.  You can go ahead and
 
−
run this script in CE (go to Memory Viewer and hit `CTRL+L` to open the Lua Engine)
 
−
 
 
−
<pre>local rect = { length = 7, width = 9 }
 
−
function calculateArea(tbl)
 
−
  return tbl.length * tbl.width
 
−
end
 
−
print('Width: '..tostring(calculateArea(rect)))</pre>
 
−
 
 
−
 
 
−
Let's look at creating the function *on the table*.  This helps avoid naming
 
−
collisions and keeps the global namespace clean.  Here's an example up front
 
−
showing how that works:
 
−
 
 
−
<pre>
 
−
local rect = { length = 7, width = 9 }
 
−
 
 
−
function rect.calculateArea(tbl) -- `.` specifies object as argument
 
−
  return tbl.length * tbl.width
 
−
end
 
−
 
 
−
function rect:calculateArea2() -- `:` creates assumed 'self' first argument
 
−
  return self.length * self.width
 
−
end
 
−
 
 
−
rect.calculateArea3 = function(tbl)
 
−
  return tbl.length * tbl.width
 
−
end
 
−
 
 
−
 
 
−
-- need to pass table with `.`
 
−
print('Area: '..tostring(rect.calculateArea(rect)))
 
−
print('Area2: '..tostring(rect.calculateArea2(rect)))
 
−
print('Area3: '..tostring(rect.calculateArea3(rect)))
 
−
 
 
−
-- `:` passes rect as first arg
 
−
print(':Area: '..tostring(rect:calculateArea()))
 
−
print(':Area2: '..tostring(rect:calculateArea2()))
 
−
print(':Area3: '..tostring(rect:calculateArea3()))
 
−
 
 
−
-- you can pass other tables as arguments even though the function is
 
−
-- on the `rect` table
 
−
print('Other Area:'..tostring(rect.calculateArea({ length = 4, width = 3 }))) -- p
 
−
print('Other Area2:'..tostring(rect.calculateArea2({ length = 4, width = 3 })))
 
−
print('Other Area3:'..tostring(rect.calculateArea3({ length = 4, width = 3 }
 
−
</pre>
 
−
 
 
−
Using the `.` notation, you can see that the function exists as a property on the
 
−
table like any other table value like `length` and `width`.  Since it is a simple
 
−
function, it's not really tied to operating on the table, it takes a parameter that we called
 
−
`tbl` which should be the table to operate on.  When calling it with a `.`, we
 
−
also need to pass the object to operate on because it is just a simple function.
 
−
Although it exists on our table with length and width specified, there is no
 
−
real relation between it and the table.
 
−
 
 
−
Now look at `calculateArea2()`.  Here we use a `:` when specifying that the function
 
−
exists on our `rect` table.  There are no arguments, but we use `self` inside of the
 
−
function.  By using a `:` in the function declaration, LUA assumes there is an
 
−
unspecified argument named `self` before any others.
 
−
 
 
−
And with `calculateArea3` we directly assign the function as a property on the
 
−
table.  All three of these declarations are functionally the same.  The functions
 
−
are simple properties on the `rect` table, they each take one argument, and they
 
−
return the value of multiplying the `length` and `width` properties on that
 
−
argument.
 
−
 
 
−
Now look at the function calls where we use a `:`.  These don't pass any arguments.
 
−
That is because when using a `:` when *calling* a function property, the table
 
−
the function resides on is passed as the first argument.  This is the basis for
 
−
creating methods on objects.
 
−
 
 
−
== Metatables ==
 
−
 
 
−
Here are some useful links for reference:
 
−
 
 
−
* [http://lua-users.org/wiki/MetamethodsTutorial MetamethodsTutorial]
 
−
* [http://nova-fusion.com/2011/06/30/lua-metatables-tutorial/ metatables tutorial]
 
−
 
 
−
Metatables allow you to specify a second table that will handle certain actions performed
 
−
on your table.  This is a mostly hidden reference, though you can access it with `getmetatable(tbl)`.
 
−
This lets you define a table like a class with methods and default properties and assign
 
−
it as a metatable to instances without setting the functions as properties on each instance.
 
−
 
 
−
It also allows you to do things like intercept calls to your table as a function and to
 
−
overload operators.
 
−
 
 
−
=== __index ===
 
−
 
 
−
The most useful element of a metatable is the `__index` property.  If specified as a table, then any
 
−
calls to properties that don't exist on your instance will attempt to be accessed on that table.
 
−
Here's an example where we setup `Rectangle` as a class with the `calculateArea` method.  By setting
 
−
`Rectangle` as the metatable on our `rect` table, we can call `rect:calculateArea` and it will call
 
−
`Rectangle.calculateArea(rect)`:
 
−
 
 
−
<pre>
 
−
Rectangle = {}
 
−
 
 
−
function Rectangle:calculateArea()
 
−
  return self.length * self.width
 
−
end
 
−
 
 
−
local rect = { length = 7, width = 9 }
 
−
setmetatable(rect, { __index = Rectangle })
 
−
 
 
−
print('Area: '..tostring(rect:calculateArea()))
 
−
</pre>
 
−
 
 
−
Another way to do it is to set it to a function.  This function will take the table instance
 
−
you are accessing a property on and the property key as arguments and should return the value.
 
−
This is only called when a key does not exist directly on the table itself.  A different way
 
−
to accomplish the same thing as above would be to use a function that just returns the
 
−
property value on the Rectangle table:
 
−
 
 
−
<pre>setmetatable(rect, { __index = function(tbl, key) return Rectangle[key] end })</pre>
 
−
 
 
−
=== __newindex ===
 
−
 
 
−
Similar is the metamethod `__newindex` which is called when setting a property on your table
 
−
that doesn't already exist.  This is much like a property write accessor in object oriented
 
−
languages.  It takes the table it's called on, the property key, and the value that is
 
−
being set.  For instance, this would log any attempts to access non-existing properties on
 
−
the rect object and not actually set them, preventing code from adding properties that
 
−
don't exist:
 
−
 
 
−
<pre>
 
−
Rectangle = {}
 
−
 
 
−
function Rectangle:calculateArea()
 
−
  return self.length * self.width
 
−
end
 
−
 
 
−
local rect = { length = 7, width = 9 }
 
−
setmetatable(rect, {
 
−
  __index = Rectangle,
 
−
  __newindex =
 
−
    function(tbl, key, value)
 
−
      print('Rectangle: Attempt to set "'..key..'" property to '..tostring(value))
 
−
    end
 
−
  })
 
−
 
 
−
rect.length = 3
 
−
print('length changed: '..tostring(rect:calculateArea()))
 
−
 
 
−
rect.nothing = 5
 
−
print('rect.nothing was not set: '..tostring(rect.nothing))
 
−
</pre>
 
−
 
 
−
=== __call ===
 
−
 
 
−
`__call` can be set to a function when your table is used like a function.  This
 
−
lets you write clean code for creating a new instance of your `class` instead of
 
−
manually setting the metatable for an object each time.  Here's an example that
 
−
lets us create a new Rectangle by either calling `Rectangle.new(length, width)` or
 
−
simply `Rectangle(length, width)`:
 
−
 
 
−
<pre>
 
−
Rectangle = {}
 
−
 
 
−
function Rectangle:calculateArea() return self.length * self.width end
 
−
 
 
−
-- create a new instance and assign Rectangle as the metatable (like a class)
 
−
function Rectangle.new(length, width)
 
−
  local obj = { length = length, width = width }
 
−
  setmetatable(obj, { __index = Rectangle })
 
−
  return obj
 
−
end
 
−
 
 
−
-- allow Rectangle table to be called like a function
 
−
setmetatable(Rectangle, {
 
−
    __call = function(tbl, length, width)
 
−
        -- tbl will be Rectangle
 
−
        return Rectangle.new(length, width)
 
−
      end
 
−
  })
 
−
 
 
−
local rect1 = Rectangle.new(7, 9)
 
−
local rect2 = Rectangle(3, 4)
 
−
 
 
−
print('rect1: '..tostring(rect1:calculateArea()))
 
−
print('rect2: '..tostring(rect2:calculateArea()))
 
−
</pre>
 
−
 
 
−
=== __lt ===
 
−
 
 
−
'''__lt''' is a function used for less than comparison, and for greater than comparison by
 
−
reversing the arguments.  It is really useful for sorting arrays.  Here we sort the
 
−
rectangles array based on their area:
 
−
 
 
−
<pre>
 
−
Rectangle = {}
 
−
 
 
−
function Rectangle:calculateArea() return self.length * self.width end
 
−
 
 
−
function Rectangle.new(length, width)
 
−
  local obj = { length = length, width = width }
 
−
  setmetatable(obj, {
 
−
      __index = Rectangle,
 
−
      __lt = function(a, b) return a:calculateArea() < b:calculateArea() end
 
−
    })
 
−
  return obj
 
−
end
 
−
 
 
−
setmetatable(Rectangle, {
 
−
    __call = function(tbl, length, width)
 
−
        -- tbl will be Rectangle
 
−
        return Rectangle.new(length, width)
 
−
      end
 
−
  })
 
−
 
 
−
local rectangles = { Rectangle(2, 5), Rectangle(7, 9), Rectangle(1, 3),
 
−
  Rectangle(3, 4), Rectangle(9, 10), Rectangle(2, 1) }
 
−
table.sort(rectangles)
 
−
for i,v in ipairs(rectangles) do
 
−
  print(tostring(i)..": "..tostring(v:calculateArea()).." in "..tostring(v.length).."x"..tostring(v.width))
 
−
end
 
−
</pre>
 
−
 
 
−
=== other operators ===
 
−
 
 
−
* '''__add''' - Changes the behavior of the '+' operator, i.e. 'rect1 + rect2'
 
−
* '''__sub''' - Changes the behavior of the '-' operator, i.e. 'rect1 - rect2'
 
−
* '''__mul''' - Changes the behavior of the '*' operator, i.e. 'rect1 * rect2'
 
−
* '''__div''' - Changes the behavior of the '/' operator, i.e. 'rect1 / rect2'
 
−
* '''__mod''' - Changes the behavior of the '%' operator, i.e. 'rect1 % rect2'
 
−
* '''__unm''' - Changes the behavior of the unary minus '-' operator, i.e. '-rect2'
 
−
* '''__le''' - Changes the behavior of the '<=' operator (and '>=' by reversing args)
 
−
* '''__concat''' - Changes the behavior of the '..' operator, i.e. 'rect1..rect2' or 'rect1..string'
 
−
* '''__eq''' - Changes the behavior of the '==' operator (and '~='), i.e. 'rect1 == rect2'
 
−
* '''__tostring''' - Not an operator, but can change the results when using print() or tostring(), i.e. 'print(rect1)'
 
−
 
 
−
For example you may want to override '''__add''' and return a new Rectangle object with the length and width of
 
−
two rectangles added together, or to expand both the length and width when you add a number to it.
 
−
You may want to override '''__tostring''' and the concat operator '''..''' so that you can easily print out your
 
−
objects:
 
−
 
 
−
<pre>
 
−
Rectangle = {}
 
−
 
 
−
function Rectangle:calculateArea() return self.length * self.width end
 
−
 
 
−
function Rectangle.new(length, width)
 
−
  local obj = { length = length, width = width }
 
−
  setmetatable(obj, {
 
−
      __index = Rectangle,
 
−
      __add = function(a, b)
 
−
        if type(b) == 'number' then return Rectangle(a.length + b, a.width + b) end
 
−
        return Rectangle(a.length + b.length, a.width + b.width)
 
−
      end,
 
−
      __concat = function(a, b)
 
−
        return tostring(a)..tostring(b)
 
−
      end,
 
−
      __tostring = function(a)
 
−
        return "[Rect("..tostring(a.length)..","..tostring(a.width)..")]"
 
−
      end
 
−
    })
 
−
  return obj
 
−
end
 
−
 
 
−
setmetatable(Rectangle, {
 
−
    __call = function(tbl, length, width)
 
−
        -- tbl will be Rectangle
 
−
        return Rectangle.new(length, width)
 
−
      end
 
−
  })
 
−
 
 
−
local rect1 = Rectangle(3, 4)
 
−
local rect2 = Rectangle(7, 9)
 
−
local rectAdded = rect1 + rect2 -- add lengths and widths
 
−
local rectExpanded = rect2 + 5 -- add 5 to length and width
 
−
print('rectAdded is '..rectAdded..' and rectExpanded is '..rectExpanded)
 
−
</pre>
 
−
 
 
−
== Address Example ==
 
−
 
 
−
Code: https://gist.github.com/JasonGoemaat/6e879ba3cd46b79a2208093541bae331
 
−
 
 
−
I've been frustrated with addresses in Cheat Engine so I created a class that helps me out.
 
−
It makes it easy to handle reading memory values and adding offsets without having to remember
 
−
what is a string and what is a number.  You can do something like this to change
 
−
the health at offset 0x48 to a pointer stored in the global symbol 'playerptr' by a script:
 
−
 
 
−
  local player = Address('[playerptr]')
 
−
  player:offset(0x48).integer = 100
 
−
 
 
−
First we have code to create the class and allow you to create an instance with
 
−
'''Address.new(value)''' or '''Address(value)''':
 
−
 
 
−
<pre>
 
−
local Address = {}
 
−
 
 
−
--[[  Return either a number or null for an address.  If the address is
 
−
      already a number, return it.  If a string, do a symbol lookup.
 
−
--]]
 
−
local ValueToNumber = function(value)
 
−
  if type(value) == 'string' then value = getAddressSafe(value) end
 
−
  if type(value) ~= 'number' then return nil end
 
−
  return value
 
−
end
 
−
 
 
−
--[[  Constructor to return a new Address object given a value.
 
−
      Usage:
 
−
        local address1 = Address.new('[modifiers_ptr]+80')
 
−
        local address2 = Address.new(0x717335200)
 
−
--]]
 
−
function Address.new(value)
 
−
  local obj = {}
 
−
  if type(value) == 'string' then
 
−
    obj.symbol = value
 
−
    value = getAddressSafe(value)
 
−
  end
 
−
  if type(value) ~= 'number' then return nil end
 
−
  obj.address = value
 
−
  obj.hex = string.format('%X', value)
 
−
  return setmetatable(obj, Address)
 
−
end
 
−
 
 
−
--[[ Alternate constructor can be called just by Address()
 
−
    Usage:
 
−
        local address1 = Address('[modifiers_ptr]+80')
 
−
        local address2 = Address(0x717335200)
 
−
--]]
 
−
setmetatable(Address, { __call = function(tbl, value) return Address.new(value) end })
 
−
</pre>
 
−
 
 
−
These will create an object with numeric '''address''' and string '''hex''' properties,
 
−
along with '''symbol''' for the original string passed if it was created using CE's
 
−
string address interpretation.
 
−
 
 
−
=== String helpers ===
 
−
 
 
−
Next we override '''___tostring''' and '''__concat''' to make debuging easier:
 
−
 
 
−
<pre>
 
−
--[[ For output as a string, just output hex value
 
−
--]]
 
−
function Address:__tostring()
 
−
  return self.hex
 
−
end
 
−
 
 
−
--[[ For concatenation, mostly for debug purposes use hex value
 
−
--]]
 
−
function Address:__concat(a)
 
−
  return tostring(self)..tostring(a)
 
−
end
 
−
</pre>
 
−
 
 
−
So you can do this:
 
−
 
 
−
  local player = Address('[playerptr]')
 
−
  print('player is address '..player)
 
−
  -- output something like 'player is address 74821030'
 
−
 
 
−
=== Offsets ===
 
−
 
 
−
Next I create an '''offset''' function and override '''__add''' to do the same thing.  These
 
−
work with strings (converted to Address instances), Addresse instances, or just numbers.
 
−
 
 
−
<pre>
 
−
--[[ Add an offset (number, Address, or string evaluated by CE) to the address
 
−
    and return a new instance.
 
−
--]]
 
−
function Address:offset(value)
 
−
  if type(value) == 'string' then value = Address(value) end
 
−
  if type(value) == 'table' and value.address ~= nil then value = value.address end
 
−
  if type(value) ~= 'number' then return nil end
 
−
  return Address(self.address + value)
 
−
end
 
−
 
 
−
--[[ __add is an operator so you can do the following:
 
−
      local address = Address('modifiers_ptr')
 
−
      address = address + 0x1c
 
−
--]]
 
−
function Address:__add(value)
 
−
  -- handle if we are the second operand, i.e. '0x18 + address'
 
−
  if getmetatable(value) == Address then return value:offset(self) end
 
−
  return self:offset(value)
 
−
end
 
−
</pre>
 
−
 
 
−
Sample usage:
 
−
 
 
−
  local a1 = Address.new('[playerptr]')
 
−
  print('health is at '..(a1 + 0x48))
 
−
 
 
−
=== Reading and Writing memory ===
 
−
 
 
−
Next I set '''__index''' and '''__newindex''' on the metatables to handle reading and
 
−
writing simple types to the memory address:
 
−
 
 
−
<pre>
 
−
--[[ __index method to access properties that aren't there
 
−
--]]
 
−
function Address:__index(key)
 
−
  local original = key
 
−
  key = key:lower()
 
−
  if key == 'byte' then
 
−
    local b = readBytes(self.address, 1)
 
−
    return b
 
−
  end
 
−
  if key == 'smallinteger' then return readSmallInteger(self.address) end
 
−
  if key == 'integer' then return readInteger(self.address) end
 
−
  if key == 'qword' then return readQword(self.address) end
 
−
  if key == 'pointer' then return readPointer(self.address) end
 
−
  if key == 'float' then return readFloat(self.address) end
 
−
  if key == 'double' then return readDouble(self.address) end
 
−
  if key == 'string' then return readString(self.address, MAX_STRING_LENGTH) end
 
−
  return getmetatable(self)[original]
 
−
end
 
−
 
 
−
--[[ __newindex method to write properties that aren't there, for writing
 
−
    values to memory at the address
 
−
--]]
 
−
function Address:__newindex(key, value)
 
−
  local original = key
 
−
  key = key:lower()
 
−
  if key == 'byte' then return writeBytes(self.address, 1, value) end
 
−
  if key == 'smallinteger' then return writeSmallInteger(self.address, value) end
 
−
  if key == 'integer' then return writeInteger(self.address, value) end
 
−
  if key == 'qword' then return writeQword(self.address, value) end
 
−
  if key == 'pointer' then return writePointer(self.address, value) end
 
−
  if key == 'float' then return writeFloat(self.address, value) end
 
−
  if key == 'double' then return writeDouble(self.address, value) end
 
−
  if key == 'string' then return writeString(self.address, value) end
 
−
  self[key] = value
 
−
  return value
 
−
end
 
−
</pre>
 
−
 
 
−
Notice I use '''key:lower()''' so you can use any case with the property names, and you can access them like properties.
 
−
For example to add 10 to the player's health you could do this:
 
−
 
 
−
  local a1 = Address.new('[playerptr]')
 
−
  local health = a1 + 0x48 -- will be an Address
 
−
  health.integer = health.integer + 10
 
−
 
 
−
I also added some standard functions for arrays of bytes because they require the
 
−
count as a parameter:
 
−
 
 
−
<pre>
 
−
 
 
−
--[[ Function to read a number of bytes
 
−
--]]
 
−
function Address:readBytes(count)
 
−
  return readBytes(self.address, count, true)
 
−
end
 
−
 
 
−
--[[ Function to write a number of bytes
 
−
--]]
 
−
function Address:writeBytes(bytes)
 
−
  return readBytes(self.address, bytes)
 
−
end
 
−
 
 
−
--[[ Function to read a number of bytes as an AOB string
 
−
--]]
 
−
function Address:readAOB(count)
 
−
  local bytes = readBytes(self.address, count, true)
 
−
  local strings = {}
 
−
  for i,b in ipairs(bytes) do
 
−
    table.insert(strings, string.format('%02X', b))
 
−
  end
 
−
  return table.concat(strings, ' ')
 
−
end
 
−
</pre>
 

Revision as of 16:14, 16 March 2019

Sorry! Content not available.