5
Unreal Engine offsets dealing
k4sh44 edited this page 2026-03-01 20:59:03 +00:00

Finding offsets opcode access signature

As soon as we have generated a SDK with Dumper-7, we have to open the Basic.hpp file generated in order to get the offsets.
They look like this :

/*
* Disclaimer:
*	- The 'GNames' is only a fallback and null by default, FName::AppendString is used
*	- THe 'GWorld' offset is not used by the SDK, it's just there for "decoration", use the provided 'UWorld::GetWorld()' function instead
*/
namespace Offsets

{
	constexpr int32 GObjects          = 0x092F8D70;
	constexpr int32 AppendString      = 0x01273B00;
	constexpr int32 GNames            = 0x092155C0;
	constexpr int32 GWorld            = 0x090C7008;
	constexpr int32 ProcessEvent      = 0x0149DC80;
	constexpr int32 ProcessEventIdx   = 0x0000004F;
}

These offsets will vary with game' updates and thus can't be used as static in our code because SDK functions call will make the game to crash sooner or later.

So the workaround here is to search for opcode that access them (load the address pointed by the offset).
Typical opcode address loading by offset looks like these :
lea rax,["HellIsUs-Win64-Shipping.exe"+92155C0]
mov rax, ["HellIsUs-Win64-Shipping.exe"+92155C0]
mov eax, ["HellIsUs-Win64-Shipping.exe"+92155C0]

We will need a Cheat Engine lua script in order to find an opcode accessing the offset.
local MODNAME = "HellIsUs-Win64-Shipping.exe" has to be modified with the game executable name
local OFFSET_HEX has to be modified with the Unreal Engine offset we look for found in Basic.hpp
As soon as the script has pointed an address with the opcode accessing the offset, we can generate a unique AOB signature.
For code to be used see below :

-- Lua script for Cheat Engine
-- Version multi-match paramétrable basée sur ton script d'origine

local MODNAME = "Styx3-Win64-Shipping.exe"
local OFFSET_HEX = 0x0928EAF0
local SIG_SCAN_FLAGS = "+X-C-W"
local MAX_VALID_MATCHES = 50  -- 🔥 PARAMÉTRABLE

local SIGNATURES = {
  {pattern="48 8D 05 ?? ?? ?? ??", dispOffset=3, instrLen=7},
  {pattern="48 8B 05 ?? ?? ?? ??", dispOffset=3, instrLen=7},
  {pattern="8B 05 ?? ?? ?? ??",    dispOffset=2, instrLen=6},
  {pattern="48 89 1D ?? ?? ?? ??", dispOffset=3, instrLen=7}
}

-- Helper: lit un int32 little-endian safe
local function safeReadInt32(addr)
  local ok, b = pcall(readBytes, addr, 4, true)
  if not ok or not b or #b < 4 then return nil end
  local val = b[1] | (b[2] << 8) | (b[3] << 16) | (b[4] << 24)
  if val >= 0x80000000 then val = val - 0x100000000 end
  return val
end

-- Helper: trouve module
local function findModule(name)
  local mods = enumModules()
  if not mods then return nil end
  for i, m in ipairs(mods) do
    if m.Name == name then
      return {base = m.Address, size = m.Size}
    end
  end
  return nil
end

-- Récup module
local mod = findModule(MODNAME)
if not mod then
  print("Module non trouvé : " .. MODNAME)
  return
end

local moduleBase = mod.base
local targetAddr = moduleBase + OFFSET_HEX

print(string.format("Module trouvé: %s base=0x%X size=0x%X", MODNAME, moduleBase, mod.size))
print(string.format("Recherche offset cible = 0x%X", targetAddr))

local validCount = 0

for _, sig in ipairs(SIGNATURES) do
  local matches = AOBScan(sig.pattern, SIG_SCAN_FLAGS)
  if matches then
    for i=0, matches.Count-1 do
      if validCount >= MAX_VALID_MATCHES then break end

      local addr = tonumber(matches[i],16)
      if addr then
        local disp32 = safeReadInt32(addr + sig.dispOffset)
        if disp32 then
          local resolved = (addr + sig.instrLen) + disp32
          if resolved == targetAddr then
            validCount = validCount + 1

            print(string.format(
              "Match trouvé à 0x%X -> résolu = 0x%X",
              addr,
              resolved
            ))

            -- Ajoute à la table
            local al = getAddressList()
            local mr = al.createMemoryRecord()
            mr.Description = string.format("Offset pointer [%d]", validCount)
            mr.Address = string.format("%X", resolved)
            mr.Type = vtQword
            mr.Active = true

            -- Enregistre symbole
            pcall(function()
              registerSymbol(
                string.format("Offset_pointer_%d", validCount),
                resolved,
                true
              )
            end)
          end
        end
      end
    end

    matches.destroy()
  end

  if validCount >= MAX_VALID_MATCHES then break end
end

if validCount == 0 then
  print("Aucun match valide trouvé pour Offset pointer.")
else
  print(string.format("Total correspondances valides trouvées : %d", validCount))
end