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.