<?xml version="1.0" encoding="utf-8"?>
<CheatTable CheatEngineTableVersion="45">
  <CheatEntries>
    <CheatEntry>
      <ID>1196</ID>
      <Description>"Master Script"</Description>
      <Options moHideChildren="1"/>
      <Color>FF8000</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript Async="1">[ENABLE]
{$lua}
if syntaxcheck then return end

local DB_STR = '64 61 74 61 62 61 73 65 5C 67 61 6D 65 5C 47 61 6D 65 2E 67 61 6D 65 64 62 00'
local CAT_STRIDE, REC_STRIDE = 0x58, 0x30

local U_FACTION, U_NAME, U_ROW, U_ROLE, U_ROWPOS, U_LEVEL = 0x58, 0x50, 0x88, 0x2B8, 0x3A0, 0x340

local DD_EXACT = {
  ['Faction'] = 'Faction/Definition',
  ['Class (AI tree)'] = 'Combat/Tree',
  ['Preset member'] = 'Faction/PresetMembers',
  ['Row'] = 'Faction/Rows',
  ['Personality'] = 'Faction/Personalities',
  ['Purpose'] = 'Faction/Purpose/Definitions',
  ['Role / Title'] = 'Faction/Roles/Definitions',
  ['Home location'] = 'AI/SpawnAmbient/Locations',
  ['Where he spawns (home location)'] = 'AI/SpawnAmbient/Locations',
  ['Queued ambush (+2C8, 0 = none)'] = 'Faction/Traits/SpawnerHijack',
  ['Trait to add / remove'] = 'Faction/Traits/Definitions',
  ['current pick (re-rolled 4x a second)'] = 'Faction/Resurrection',
  ['Killed by (character type)'] = 'Combat/CharacterType',
  ['Resurrection style'] = 'Faction/Resurrection',
  ['head'] = 'Model/SimpleModelHeads',
  ['scar setup'] = 'Model/Scar/Setup',
  ['armor set'] = 'Model/SimpleModelArmorSet',
  ['ResurrectionScarType'] = 'Model/Scar/Types',
  ['AppliedScarType'] = 'Model/Scar/Types',
  ['Brand scar (+298, 0 = not branded)'] = {'Model/Scar/Scars', '_Brand$'},
}
local DD_PATTERN = {
  {'^trait %d+$', 'Faction/Traits/Definitions'},
  {'^flair %d+$', 'Faction/Flair/Definitions'},
  {'^tag %d+$', 'Faction/AppearanceTags/Tags'},
  {'^model: ', 'Model'},
}

local function gamedb()
  return getAddress('p_gamedb')
end

local function scan(input, vtype, flags, align, alignParam, startA, stopA)
  local ms = createMemScan()
  ms.firstScan(soExactValue, vtype, rtRounded, input, nil, startA, stopA, flags,
               align, alignParam, true, true, false, false)
  ms.waitTillDone()
  local fl = createFoundList(ms)
  fl.initialize()
  local res = {}
  for i = 0, fl.Count - 1 do res[#res + 1] = tonumber(fl.Address[i], 16) end
  fl.destroy()
  ms.destroy()
  return res
end

local function findGameDB()
  local base = getAddress(process)
  local hits = scan(DB_STR, vtByteArray, '*X*W*C', fsmNotAligned, '1', base, base + getModuleSize(process))
  for _, h in ipairs(hits) do
    local db = readQword(h - 8)
    if db and db ~= 0 and readString(readQword(db) or 0, 20) == 'GameDatabase' then return db end
  end
  return nil
end

local function category(name)
  local db = gamedb()
  local base, cnt = readQword(db + 0x38), readInteger(db + 0x30)
  for i = 0, cnt - 1 do
    local c = base + i * CAT_STRIDE
    if readString(readQword(c), 100) == name then
      return readQword(c + 0x38), readInteger(c + 0x28)
    end
  end
  return nil, 0
end

local function records(name)
  local first, n = category(name)
  local list = {}
  if first then
    for i = 0, n - 1 do
      local r = first + i * REC_STRIDE
      local s = readString(readQword(r + 0x20), 100)
      if s then list[#list + 1] = {r, s} end
    end
  end
  return list
end

local function recName(r)
  if not r or r == 0 then return nil end
  return readString(readQword(r + 0x20) or 0, 100)
end

local function listToString(list)
  table.sort(list, function(a, b) return a[2]:lower() &lt; b[2]:lower() end)
  local t = {'0000000000000000:&lt;None&gt;'}
  for _, e in ipairs(list) do t[#t + 1] = string.format('%016X:%s', e[1], e[2]) end
  return table.concat(t, '\n')
end

local function addDD(mr, str)
  mr.DropDownList.Text = str
  mr.DropDownReadOnly = false
  mr.DropDownDescriptionOnly = true
  mr.DisplayAsDropDownListItem = true
end

local function remDD(mr)
  mr.DropDownList.Text = ''
  mr.DisplayAsDropDownListItem = false
end

local function eachChild(mr, fn)
  for i = 0, mr.Count - 1 do
    local c = mr.Child[i]
    fn(c)
    eachChild(c, fn)
  end
end

local function urukGroup()
  return getAddressList().getMemoryRecordByDescription('Uruk Pointer')
end

local function categoryFor(desc)
  if DD_EXACT[desc] then return DD_EXACT[desc] end
  for _, p in ipairs(DD_PATTERN) do
    if desc:find(p[1]) then return p[2] end
  end
  return nil
end

local function filtered(cat)
  if type(cat) ~= 'table' then return records(cat) end
  local out = {}
  for _, r in ipairs(records(cat[1])) do
    if r[2]:find(cat[2]) then out[#out + 1] = r end
  end
  return out
end

local function fillDropdowns()
  local cache = {}
  eachChild(urukGroup(), function(mr)
    local cat = categoryFor(mr.Description)
    if cat then
      local key = type(cat) == 'table' and table.concat(cat, '|') or cat
      cache[key] = cache[key] or listToString(filtered(cat))
      addDD(mr, cache[key])
    end
  end)
end

local function clearDropdowns()
  local g = urukGroup()
  if not g then return end
  eachChild(g, function(mr)
    if categoryFor(mr.Description) or mr.Description == 'Select Uruk' then remDD(mr) end
  end)
end

local function findArmies()
  local armies = {}
  for _, f in ipairs(records('Faction/Definition')) do
    local r = f[1]
    for _, h in ipairs(scan(string.format('%X', r), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
      local a = h - 0x30
      local b, e = readQword(a + 0x38), readQword(a + 0x40)
      if b and e and e &gt; b and (e - b) % 8 == 0 and (e - b) // 8 &lt;= 512
         and readQword((readQword(b) or 0) + U_FACTION) == r
         and readQword((readQword(e - 8) or 0) + U_FACTION) == r then
        armies[#armies + 1] = {a, f[2]}
      end
    end
  end
  return armies
end

local function urukNames()
  local names = {}
  local mr = getAddressList().getMemoryRecordByDescription('Name')
  if mr then
    for line in mr.DropDownList.Text:gmatch('[^\r\n]+') do
      local id, nm = line:match('^(%x+):(.+)$')
      if id then names[tonumber(id, 16)] = nm end
    end
  end
  return names
end

SoM_slots = {}

function SoM_refresh()
  SoM_armies = findArmies()
  SoM_slots = {}
  local names = urukNames()
  local lines = {}
  for _, ar in ipairs(SoM_armies) do
    local a = ar[1]
    local b, e = readQword(a + 0x38), readQword(a + 0x40)
    for i = 0, (e - b) // 8 - 1 do
      local u = readQword(b + i * 8)
      local nid = readInteger(u + U_NAME) &amp; 0xFFFFFFFF
      local nm = names[nid] or string.format('%08X', nid)
      local role = (recName(readQword(u + U_ROLE)) or ''):gsub('^Role_', '')
      local row = (recName(readQword(u + U_ROW)) or ''):gsub('^Orc_', '')
      local pos = readBytes(u + U_ROWPOS, 1)
      local where = (row ~= '' and pos ~= 255) and string.format('  [%s pos %d]', row, pos) or ''

      local fl = readInteger(u + 0x3A4) or 0
      local state = (fl &amp; 0x14) == 0x14 and '  [returning]' or (fl &amp; 4 ~= 0 and '  [dead]' or '')
      lines[#lines + 1] = string.format('%d:%s the %s  Lv%d%s%s  {%s}', #SoM_slots, nm, role,
                                        readInteger(u + U_LEVEL) + 1, where, state, ar[2])
      SoM_slots[#SoM_slots + 1] = {a, i}
    end
  end
  local sel = getAddressList().getMemoryRecordByDescription('Select Uruk')
  if sel then addDD(sel, table.concat(lines, '\n')) end
  return #SoM_slots
end

local function selIndex()
  local v = readInteger('u_index') or -1
  if v &gt;= 0x80000000 then v = v - 0x100000000 end
  return v
end

local lastHover = 0
local function slotOf(h)
  for i, slot in ipairs(SoM_slots) do
    local b = readQword(slot[1] + 0x38)
    if readQword(b + slot[2] * 8) == h then return i - 1 end
  end
  return nil
end

local function followHover()
  if not getAddressSafe('p_hover') then lastHover = 0; return nil end
  local h = readQword('p_hover') or 0
  if h == 0 or h == lastHover then return nil end
  lastHover = h
  local i = slotOf(h)
  if not i then
    SoM_refresh()
    i = slotOf(h)
  end
  if i then
    writeInteger('u_index', i)
    return nil
  end
  return h
end

local lvlU, lvlA, lvlB
local function syncLevel(u)
  if u == 0 or readBytes('u_sync', 1) ~= 1 then lvlU = nil; return end
  local a, b = readInteger(u + U_LEVEL), readInteger(u + U_LEVEL + 4)
  if not a or not b then return end
  if u ~= lvlU then lvlU, lvlA, lvlB = u, a, b; return end
  if a ~= lvlA and b == lvlB then writeInteger(u + U_LEVEL + 4, a); b = a
  elseif b ~= lvlB and a == lvlA then writeInteger(u + U_LEVEL, b); a = b end
  lvlA, lvlB = a, b
end

local function updatePointer()
  local direct = followHover()
  if direct then
    writeQword('p_uruk', direct)
    writeInteger('u_index', -1)
    return
  end
  if selIndex() &lt; 0 then return end
  local slot = SoM_slots[selIndex() + 1]
  local u = 0
  if slot then
    local b, e = readQword(slot[1] + 0x38), readQword(slot[1] + 0x40)
    if b and e and slot[2] &lt; (e - b) // 8 then u = readQword(b + slot[2] * 8) or 0 end
  end
  if readQword('p_uruk') ~= u then writeQword('p_uruk', u) end
end

local cdU, cdV, cdP
local function syncCheatDeath(u)
  if not getAddressSafe('cd_list') then cdU = nil; return end
  if (readQword('cd_scar') or 0) == 0 and SoM_scarMelee then writeQword('cd_scar', SoM_scarMelee) end
  if u == 0 then cdU = nil; return end
  local list = getAddress('cd_list')
  local pct = list + 0x200
  local idx, free
  for i = 0, 63 do
    local v = readQword(list + i * 8)
    if v == u then idx = i elseif v == 0 and not free then free = i end
  end
  local inList = idx and 1 or 0
  if u ~= cdU then
    cdU, cdV = u, inList
    cdP = idx and readFloat(pct + idx * 4) or -1
    writeBytes('u_cd', inList)
    writeFloat('u_cdp', cdP)
    return
  end
  local flag = readBytes('u_cd', 1)
  if flag ~= cdV then
    if flag == 1 and not idx and free then
      idx = free
      writeQword(list + idx * 8, u)
      writeFloat(pct + idx * 4, cdP or -1)
    elseif flag ~= 1 and idx then
      writeQword(list + idx * 8, 0)
      idx = nil
    end
    cdV = flag
  end
  local p = readFloat('u_cdp')
  if p and p ~= cdP then
    cdP = p
    if idx then writeFloat(pct + idx * 4, p) end
  end
end

local U_HEAD, U_BRAND = 0x1B0, 0x298
function SoM_brandFor(u)
  local b = SoM_brands or {}
  local head = recName(readQword(u + U_HEAD)) or ''
  if head:find('Ratbag') and b['Ratbag_Brand'] then return b['Ratbag_Brand'] end
  if head:find('Tumor') and b['Tumor_Head_Brand'] then return b['Tumor_Head_Brand'] end
  if head:find('Sack') and b['Sack_Head_Brand'] then return b['Sack_Head_Brand'] end
  local num, ver = head:match('Head(%d+)[Vv]?(%d*)')
  if num then
    if ver ~= '' and b['Orc_Head' .. num .. 'v' .. ver .. '_Brand'] then return b['Orc_Head' .. num .. 'v' .. ver .. '_Brand'] end
    if b['Orc_Head' .. num .. '_Brand'] then return b['Orc_Head' .. num .. '_Brand'] end
  end
  return b['Orc_Head01_Brand']
end

local brU, brV
local function syncBrand(u)
  if u == 0 then brU = nil; return end
  local actual = (readQword(u + U_BRAND) or 0) ~= 0 and 1 or 0
  if u ~= brU then
    brU, brV = u, actual
    writeBytes('u_brand', actual)
    return
  end
  local flag = readBytes('u_brand', 1)
  if flag ~= brV then
    writeQword(u + U_BRAND, flag == 1 and (SoM_brandFor(u) or 0) or 0)
    brV = flag
  elseif actual ~= brV then
    writeBytes('u_brand', actual)
    brV = actual
  end
end

function SoM_returnNow()
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local f = readInteger(u + 0x3A4) or 0
  if f &amp; 0x14 ~= 0x14 then return 'selected uruk is not [returning] (needs IsDead + CanResurrect)' end
  f = ((f &amp; ~0x10 &amp; ~0x40 &amp; ~4) | 1 | 0x10000) &amp; 0xFFFFFFFF
  writeInteger(u + 0x3A4, f)
  writeBytes(u + 0x2FC, 1)
  return 'back from the dead'
end

function SoM_reveal()
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local f = readInteger(u + 0x3A4) or 0
  if f &amp; 0x800000 == 0 then return 'selected uruk is not mock-dead' end
  if f &amp; 4 ~= 0 then return 'selected uruk is really dead (IsDead) - use Bring back now first' end
  writeInteger(u + 0x3A4, f &amp; ~0x800000 &amp; 0xFFFFFFFF)
  writeBytes(u + 0x2FC, 1)
  return 'revealed: no longer shown as dead'
end

local function ambushListed(u, rec)
  local g = readQword(readQword('rr_G') or 0) or 0
  local m = g ~= 0 and readQword(g + 0x2B0) or 0
  if m == 0 then return true end
  local idx
  for _, ar in ipairs(SoM_armies or {}) do
    local b, e = readQword(ar[1] + 0x38), readQword(ar[1] + 0x40)
    for i = 0, (e - b) // 8 - 1 do
      if readQword(b + i * 8) == u then idx = i end
    end
  end
  local b, e = readQword(m + 0x18), readQword(m + 0x20)
  for i = 0, (e - b) // 8 - 1 do
    local r = readQword(b + i * 8)
    if r ~= 0 and readQword(r + 0x10) == rec and (not idx or readQword(r + 8) == idx) then return true end
  end
  return false
end

local function hijackResurrected()
  if not SoM_hijackRes then
    for _, r in ipairs(records('Faction/Traits/SpawnerHijack')) do
      if r[2] == 'Resurrected' then SoM_hijackRes = r[1] end
    end
  end
  return SoM_hijackRes
end

local function queueAmbush(u)
  local f = readInteger(u + 0x3A4) or 0
  if f &amp; 4 ~= 0 then return nil, 'dead - use Bring back now first' end
  if f &amp; 1 == 0 then return nil, 'no pending comeback (IsResurrected is 0)' end
  if f &amp; 2 ~= 0 then return nil, 'he is in the world right now - go find him' end
  local rec = hijackResurrected()
  if not rec then return nil, 'SpawnerHijack "Resurrected" not found in the game database' end
  if (readQword(u + 0x2C8) or 0) ~= 0 then

    if ambushListed(u, rec) then return nil, 'his ambush is already queued - roam and wait for it' end
    writeQword(u + 0x2C8, 0)
  end
  writeQword('rr_rec', rec)
  writeQword('rr_uruk', u)
  return true
end

function SoM_rearm()
  if not getAddressSafe('rr_uruk') then return 'enable "Comeback ambush hook (for re-arm)" first' end
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local ok, why = queueAmbush(u)
  if not ok then return 'selected uruk: ' .. why end
  return 'ambush requested - it is queued the next time the game checks spawns (watch "ambushes queued")'
end

local function traitList(u)
  local b, e = readQword(u + 0x70) or 0, readQword(u + 0x78) or 0
  local t = {}
  if b ~= 0 and e &gt;= b and e - b &lt;= 0x2000 then
    for a = b, e - 8, 8 do t[#t + 1] = readQword(a) end
  end
  return t, readInteger(u + 0x80) or 0
end

local function gameTraitCall(u, rec, op)
  local done = readInteger('ta_done')
  writeBytes('ta_op', op)
  writeQword('ta_rec', rec)
  writeQword('ta_uruk', u)
  for _ = 1, 50 do
    if readInteger('ta_done') ~= done then break end
    sleep(20)
  end
  if readInteger('ta_done') == done then return nil end
  return readBytes('ta_result', 1) ~= 0
end

local function traitOp(op)
  if not getAddressSafe('ta_uruk') then return 'enable "Add / remove traits (hook)" first' end
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local rec = readQword('ta_pick') or 0
  local nm = recName(rec)
  if not nm then return 'pick a trait in "Trait to add / remove" first' end
  if (readQword('ta_uruk') or 0) ~= 0 then return 'the previous request is still waiting for the game' end
  local has = false
  for _, r in ipairs(traitList(u)) do
    if r == rec then has = true end
  end
  if op == 1 and has then return 'he already has ' .. nm end
  if op == 2 and not has then return 'he doesn\'t have ' .. nm end
  local before = traitList(u)
  local ok = gameTraitCall(u, rec, op)
  if ok == nil then
    return 'requested - the game does it on its next frame (is it paused or minimized?)'
  end
  if not ok then
    if op == 1 then
      return nm .. ' refused by the game rules (his preset blocks it) - set "Game rules" to Force to add it anyway'
    end
    return 'the game could not remove ' .. nm
  end
  local t, cap = traitList(u)

  local now, gone = {}, {}
  for _, r in ipairs(t) do now[r] = true end
  for _, r in ipairs(before) do
    if not now[r] and r ~= rec then gone[#gone + 1] = recName(r) or string.format('%X', r) end
  end
  local msg = string.format('%s %s - %d traits now, room for %d', op == 1 and 'added' or 'removed', nm, #t, cap)
  if #gone &gt; 0 then
    msg = msg .. '. The game rules removed: ' .. table.concat(gone, ', ') .. ' (set "Game rules" to Force to keep them)'
  end
  return msg
end

local TRAITS_GROUP = 'Traits (strengths / weaknesses)'
SoM_traitRows = {}

local function dropTraitRows()
  for i = #SoM_traitRows, 1, -1 do SoM_traitRows[i].destroy() end
  SoM_traitRows = {}

  local grp = getAddressList().getMemoryRecordByDescription(TRAITS_GROUP)
  if not grp then return end
  for i = grp.Count - 1, 0, -1 do
    local c = grp.Child[i]
    if c.Description:find('^trait %d+$') then c.destroy() end
  end
end

function SoM_syncTraitRows(u)
  local n = 0
  if u ~= 0 then
    local b, e = readQword(u + 0x70) or 0, readQword(u + 0x78) or 0
    if b ~= 0 and e &gt;= b and e - b &lt;= 0x2000 then n = (e - b) // 8 end
  end
  if n == #SoM_traitRows then return end
  local grp = getAddressList().getMemoryRecordByDescription(TRAITS_GROUP)
  if not grp then return end
  while #SoM_traitRows &gt; n do table.remove(SoM_traitRows).destroy() end
  for i = #SoM_traitRows + 1, n do
    local mr = getAddressList().createMemoryRecord()
    mr.Description = string.format('trait %02d', i)
    mr.Type = vtQword
    mr.ShowAsHex = true
    mr.Address = 'p_uruk'
    mr.OffsetCount = 2
    mr.Offset[1] = 0x70
    mr.Offset[0] = (i - 1) * 8
    mr.appendToEntry(grp)
    if SoM_traitDD then addDD(mr, SoM_traitDD) end
    SoM_traitRows[i] = mr
  end
end

local function urukLabel(u)
  local nid = (readInteger(u + U_NAME) or 0) &amp; 0xFFFFFFFF
  return urukNames()[nid] or string.format('uruk %X', u)
end

function SoM_copyTraits()
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local t = traitList(u)
  if #t == 0 then return 'the selected uruk has no traits to copy' end
  SoM_traitClip = {from = urukLabel(u), list = t}
  local names = {}
  for _, r in ipairs(t) do names[#names + 1] = recName(r) or string.format('%X', r) end
  return string.format('copied %d traits from %s: %s', #t, SoM_traitClip.from, table.concat(names, ', '))
end

function SoM_pasteTraits()
  if not getAddressSafe('ta_uruk') then return 'enable "Add / remove traits (hook)" first' end
  if not SoM_traitClip then return 'nothing copied yet - select an uruk and use "Copy traits" first' end
  local u = readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  if (readQword('ta_uruk') or 0) ~= 0 then return 'the previous request is still waiting for the game' end
  local markers = readBytes('ta_markers', 1) == 1
  local function wanted(r)
    return markers or not (recName(r) or ''):find('^Marker_')
  end
  local want, wantSet = {}, {}
  for _, r in ipairs(SoM_traitClip.list) do
    if wanted(r) then want[#want + 1] = r; wantSet[r] = true end
  end
  local removed, added, refused = 0, 0, {}
  if readBytes('ta_pmode', 1) == 1 then
    for _, r in ipairs(traitList(u)) do
      if not wantSet[r] and wanted(r) then
        local ok = gameTraitCall(u, r, 2)
        if ok == nil then return 'the game isn\'t running frames (paused or minimized?) - stopped' end
        if ok then removed = removed + 1 end
      end
    end
  end
  for _, r in ipairs(want) do
    local has = false
    for _, x in ipairs(traitList(u)) do
      if x == r then has = true end
    end
    if not has then
      local ok = gameTraitCall(u, r, 1)
      if ok == nil then return 'the game isn\'t running frames (paused or minimized?) - stopped' end
      if ok then added = added + 1 else refused[#refused + 1] = recName(r) or string.format('%X', r) end
    end
  end

  local now = {}
  for _, r in ipairs(traitList(u)) do now[r] = true end
  local lost = {}
  for _, r in ipairs(want) do
    if not now[r] then
      local nm = recName(r) or string.format('%X', r)
      local isRefused = false
      for _, x in ipairs(refused) do
        if x == nm then isRefused = true end
      end
      if not isRefused then lost[#lost + 1] = nm end
    end
  end
  local t, cap = traitList(u)
  local msg = string.format('pasted traits from %s into %s: %d added, %d removed - %d traits now, room for %d',
                            SoM_traitClip.from, urukLabel(u), added, removed, #t, cap)
  if #refused &gt; 0 then msg = msg .. '. Refused by the game rules: ' .. table.concat(refused, ', ') end
  if #lost &gt; 0 then msg = msg .. '. Replaced by later traits (game rules): ' .. table.concat(lost, ', ') end
  if #refused + #lost &gt; 0 then msg = msg .. ' - set "Game rules" to Force for an exact copy' end
  return msg
end

function SoM_addTrait() return traitOp(1) end
function SoM_removeTrait() return traitOp(2) end

local PLAYER_COMP_VT = 0x14B45E0
do
  local s = AOBScanModuleUnique('ShadowOfMordor.exe', '4C 8D 1D ?? ?? ?? ?? 48 8D 5F 30 48 89 47 28 4C')
  if s then PLAYER_COMP_VT = s + 7 + readInteger(s + 3, true) - getAddress(process) end
end
local function playerNode()
  if SoM_playerComp and readQword(SoM_playerComp) == getAddress(process) + PLAYER_COMP_VT then
    return readQword(SoM_playerComp + 8)
  end
  SoM_playerComp = nil
  local al
  for _, r in ipairs(records('Character/Alignments')) do
    if r[2] == 'Player' then al = r[1] end
  end
  if not al then return nil end
  local vt = getAddress(process) + PLAYER_COMP_VT
  for _, h in ipairs(scan(string.format('%X', vt), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
    if readQword(h + 0x78) == al then SoM_playerComp = h; return readQword(h + 8) end
  end
  return nil
end

function SoM_teleportNear(u, offset)
  if not getAddressSafe('rr_tpcomp') then return 'enable "Comeback ambush hook (for re-arm)" first' end
  u = u or readQword('p_uruk') or 0
  if u == 0 then return 'no uruk selected' end
  local f = readInteger(u + 0x3A4) or 0
  if f &amp; 2 == 0 then return 'he is not in the world right now (status bit 1 is 0)' end
  local node, comp = readQword(u + 0x30) or 0, readQword(u + 0x40) or 0
  if node == 0 or comp == 0 or readQword(comp + 8) ~= node then return 'his world object was not found' end
  local pn = playerNode()
  if not pn or pn == 0 then return 'Talion was not found' end
  local px, py, pz = readFloat(pn + 8), readFloat(pn + 0xC), readFloat(pn + 0x10)
  offset = offset or 300
  local x, z = px + offset, pz + offset
  local ox, oz = readFloat(node + 8), readFloat(node + 0x10)
  writeFloat('rr_tpx', x); writeFloat('rr_tpy', py); writeFloat('rr_tpz', z)
  writeQword('rr_tpvt', readQword(comp))
  writeQword('rr_tpcomp', comp)
  return string.format('teleport requested from (%.0f, %.0f) to (%.0f, %.0f), next to Talion', ox, oz, x, z)
end

local autoN = 0
local function autoRearm()
  autoN = autoN + 1
  if autoN % 8 ~= 0 then return end
  if not getAddressSafe('rr_auto') or readBytes('rr_auto', 1) ~= 1 then return end
  if (readQword('rr_uruk') or 0) ~= 0 then return end
  for _, ar in ipairs(SoM_armies or {}) do
    local b, e = readQword(ar[1] + 0x38), readQword(ar[1] + 0x40)
    for i = 0, (e - b) // 8 - 1 do
      local u = readQword(b + i * 8)
      if u and (readInteger(u + 0x3A4) or 0) &amp; 7 == 1 and queueAmbush(u) then return end
    end
  end
end

local TP_FAR = 5000
local tpN, tpDone, tpTotal = 0, {}, 0
local function autoTeleport()
  tpN = tpN + 1
  if tpN % 8 ~= 4 then return end
  if readBytes('u_tp', 1) ~= 1 or not getAddressSafe('rr_tpcomp') then return end
  if (readQword('rr_tpcomp') or 0) ~= 0 then return end
  local pn
  for _, ar in ipairs(SoM_armies or {}) do
    local b, e = readQword(ar[1] + 0x38), readQword(ar[1] + 0x40)
    for i = 0, (e - b) // 8 - 1 do
      local u = readQword(b + i * 8)
      local node = u and readQword(u + 0x30) or 0
      if u and (readInteger(u + 0x3A4) or 0) &amp; 7 == 3 and node ~= 0 and (tpDone[node] or 0) &lt; 3 then
        pn = pn or playerNode()
        if not pn then return end
        local dx = readFloat(node + 8) - readFloat(pn + 8)
        local dz = readFloat(node + 0x10) - readFloat(pn + 0x10)
        if dx * dx + dz * dz &gt; TP_FAR * TP_FAR then
          tpDone[node] = (tpDone[node] or 0) + 1
          tpTotal = tpTotal + 1
          print('SoM Uruk Editor: returning orc #' .. i .. ' spawned far away - ' ..
                SoM_teleportNear(u, 300 + 200 * (tpTotal % 4)))
          return
        end
      end
    end
  end
end

local function catOf(r)
  local c = readQword(r + 0x18) or 0
  return c ~= 0 and readString(readQword(c) or 0, 60) or nil
end

local function usableStyle(r, size)
  local v = readQword(r + 0x10) or 0
  local tbl, ev
  for o = 0, size - 8, 8 do
    local q = readQword(v + o) or 0
    if q &gt; 0x10000 and q &lt; 0x7fffffffffff then
      local c = catOf(q)
      if c == 'Faction/Resurrection/Tables' then tbl = true end
      if c == 'Faction/Events/Events' and (recName(q) or ''):find('^WillResurrect_') then ev = true end
    end
  end
  return tbl and ev
end

local hsN, hsStyles = 0, nil
local function syncHeadSwap()
  if not getAddressSafe('hs_from') then hsStyles = nil; return end
  if not hsStyles then
    hsStyles = {}
    local from
    local recs = records('Faction/Resurrection')
    local vals = {}
    for _, r in ipairs(recs) do vals[#vals + 1] = readQword(r[1] + 0x10) or 0 end
    for _, r in ipairs(recs) do
      local v, size = readQword(r[1] + 0x10) or 0, 0x60
      for _, w in ipairs(vals) do
        if w &gt; v and w - v &lt; size then size = w - v end
      end
      if r[2] == 'Resurrection_HeadRemoved' then from = r[1]
      elseif usableStyle(r[1], size) then hsStyles[#hsStyles + 1] = r[1] end
    end
    if not from or #hsStyles == 0 then return end
    writeQword('hs_from', from)
    local names = {}
    for _, r in ipairs(hsStyles) do names[#names + 1] = (recName(r) or '?'):gsub('^Resurrection_', '') end
    print('SoM Uruk Editor: beheadings pick from: ' .. table.concat(names, ', '))
  end
  if #hsStyles &gt; 0 then writeQword('hs_to', hsStyles[math.random(#hsStyles)]) end
  hsN = hsN + 1
  if hsN % 8 ~= 2 then return end
  for _, ar in ipairs(SoM_armies or {}) do
    local b, e = readQword(ar[1] + 0x38), readQword(ar[1] + 0x40)
    for i = 0, (e - b) // 8 - 1 do
      local u = readQword(b + i * 8)
      local f = u and readInteger(u + 0x3A4) or 0
      if f &amp; 0x20005 == 0x20001 then writeInteger(u + 0x3A4, f &amp; ~0x20000 &amp; 0xFFFFFFFF) end
    end
  end
end

local function tick()
  updatePointer()
  local u = readQword('p_uruk') or 0
  syncLevel(u)
  syncCheatDeath(u)
  syncBrand(u)
  SoM_syncTraitRows(u)
  autoRearm()
  autoTeleport()
  syncHeadSwap()
end

function SoM_enable()
  autoAssemble([[
    globalalloc(p_uruk,8)
    globalalloc(u_index,4)
    globalalloc(u_sync,1)
    globalalloc(u_cd,1)
    globalalloc(u_cdp,4)
    globalalloc(u_brand,1)
    globalalloc(u_tp,1)
  ]])
  writeBytes('u_sync', 1)
  writeBytes('u_tp', 0)
  local db = findGameDB()
  if not db then
    messageDialog('Game database not found. Load a save first, then enable again.', mtError, mbOK)
    return false
  end
  unregisterSymbol('p_gamedb')
  registerSymbol('p_gamedb', db, true)
  local ok, err = pcall(fillDropdowns)
  if not ok then print('fillDropdowns: ' .. tostring(err)) end
  SoM_traitDD = listToString(records('Faction/Traits/Definitions'))
  synchronize(dropTraitRows)
  SoM_scarMelee, SoM_hijackRes = nil, nil
  for _, r in ipairs(records('Model/Scar/Types')) do
    if r[2] == 'Melee' then SoM_scarMelee = r[1] end
  end
  SoM_brands = {}
  for _, r in ipairs(filtered({'Model/Scar/Scars', '_Brand$'})) do SoM_brands[r[2]] = r[1] end
  local n = SoM_refresh()
  if n == 0 then
    messageDialog('No uruk army found yet. Load a save, then use "Refresh uruk list".', mtWarning, mbOK)
  end

  local lastErr
  synchronize(function()
    if SoM_timer then SoM_timer.destroy() end
    SoM_timer = createTimer(nil, false)
    SoM_timer.Interval = 250
    SoM_timer.OnTimer = function()
      local ok, err = pcall(tick)
      if not ok and err ~= lastErr then
        lastErr = err
        print('SoM Uruk Editor timer error: ' .. tostring(err))
      end
    end
    SoM_timer.Enabled = true
  end)
  pcall(tick)
  print(string.format('SoM Uruk Editor: gamedb %X, %d uruks, p_uruk %X, u_index %X',
                      db, n, getAddress('p_uruk'), getAddress('u_index')))
  return true
end

function SoM_disable()
  synchronize(function()
    if SoM_timer then SoM_timer.destroy(); SoM_timer = nil end
  end)
  clearDropdowns()
  synchronize(dropTraitRows)
  unregisterSymbol('p_gamedb')
  SoM_slots = {}
end

if not SoM_enable() then error('SoM Uruk Editor: enable failed') end
[DISABLE]
{$lua}
if syntaxcheck then return end
if SoM_disable then SoM_disable() end
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>1150</ID>
          <Description>"Uruk Pointer"</Description>
          <Options moHideChildren="1"/>
          <Color>4080FF</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>1107</ID>
              <Description>"Select Uruk"</Description>
              <Color>FF00FF</Color>
              <VariableType>4 Bytes</VariableType>
              <Address>u_index</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1108</ID>
              <Description>"Hover select (army screen)"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
aobscanmodule(aob_hover,ShadowOfMordor.exe,33 F6 45 0F B6 E0 48 8B DA 48 8B E9 8B FE 48 85 D2)
alloc(newmem_hover,$100,aob_hover)
label(return_hover)
label(skip_hover)
label(p_hover)
registersymbol(p_hover)

newmem_hover:
  mov rbx,rdx
  mov rbp,rcx
  test rdx,rdx
  je skip_hover
  mov [p_hover],rdx
skip_hover:
  jmp return_hover

p_hover:
  dq 0

aob_hover+6:
  jmp newmem_hover
  nop
return_hover:
registersymbol(aob_hover)

[DISABLE]
aob_hover+6:
  db 48 8B DA 48 8B E9

unregistersymbol(aob_hover)
unregistersymbol(p_hover)
dealloc(newmem_hover)
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1109</ID>
              <Description>"Refresh uruk list"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_refresh then error('Enable the Master Script first') end
print(string.format('SoM Uruk Editor: %d uruks listed', SoM_refresh()))
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1110</ID>
              <Description>"Base"</Description>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>8 Bytes</VariableType>
              <Address>p_uruk</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1111</ID>
              <Description>"Name"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">00000000:&lt;None&gt;
2252009D:Akoth
225207CB:Amûg
22521D55:Ashgarn
22521627:Azdûsh
225207CC:Azgrom
2252009E:Bagabûg
225207CD:Barfa
22521D56:Blorg
225207CE:Bolg
225207CF:Borgu
2252009F:Brogg
45504BE5:Brogg (twin, unique)
225207D0:Bûbol
22521628:Bûth
225207D1:Dharg
225EE745:Dûgza
225EE746:Dûsh
225200A0:Dûshrat
22521D57:Feldûsh
225207D2:Felgrat
2179E12B:Fifth Talon
C94A04FA:First Talon
225200A1:Flak
225207D3:Folgûm
C0BA7067:Fourth Talon
225200A2:Ghâm
22521D58:Ghûra
225EE74D:Gimûb
03D64966:Gimûb (unique)
225207D4:Glûk
22520B62:Golm
225200A3:Gorfel
22520B63:Gorgûm
225EE74E:Goroth
1F3F0053:Goroth (unique)
22521D59:Grisha
22520B64:Grûblik
22521629:Gûndza
225200A4:Horhog
22520B65:Hork
2252162A:Horza
2252162B:Hoshgrish
225EE747:Hoshû
22521D5A:Hûmgrat
22520B66:Hûra
225EE748:Ishgha
22521D5B:Ishmoz
22520B67:Khrosh
225200A5:Kothûg
22521D5C:Krakhorn
22521D5D:Krímp
22520B68:Krûk
22520B69:Kugáluga
2252162C:Kâka
225200A6:Kûga
22520B6A:Lamlûg
2252162D:Latbag
22520434:Lorm
22520B6B:Lûga
225EE749:Lûgdash
22521D5E:Lûgnak
22520435:Malmûg
22520436:Mogg
27FC6734:Mogg (twin, unique)
22520EFB:Mormog
225EE74A:Mozfel
2252162F:Mozû
225220EC:Mâku
22520EF9:Mûggrish
22520EFA:Mûglûk
2252162E:Mûzglob
225220EE:Narûg
225220EF:Nazdûg
225220F0:Nazû
225220F1:Norsko
225220F2:Norûk
225220ED:Nákra
225EE74B:Názkûga
22520EFC:Ogbur
225220F3:Ogthrak
22520EFD:Olgoth
225220F4:Olrok
22520437:Orthog
22521630:Otha
22520EFE:Prâk
22520EFF:Pushkrimp
225219BE:Pâsh
225220F5:Pígug
225219BF:Pûg
22520438:Pûgrish
225219C0:Ratanák
96B5769D:Ratbag (unique)
22520439:Ratlûg
22520F01:Ronk
22520F00:Râsh
225EE3AE:Rûg
22520F02:Rûkdûg
18EA4C98:Second Talon
225219C1:Shaká
2252043A:Shágflak
22521290:Skak
22521291:Skog
225219C2:Skoth
22521292:Skûn
225219C3:Snagog
22521293:Stakûga
225219C4:Takra
22521294:Thakrak
711A28C9:Third Talon
22521295:Thrak
22521296:Torz
2252043B:Târz
225EE3AF:Tûgog
225EE3B0:Tûkâ
225219C5:Tûmhorn
225EE3B1:Tûmûg
22521299:Zog
225EE3B5:Zogdûsh
225EE3B7:Zunn
2252043D:Zâthra
225EE74C:Zûgor
225EE3B6:Zûmug
2252043C:Ûgakûga
22521297:Ûggû
225EE3B2:Ûkbûk
22521298:Ûkrom
225EE3B3:Ûkshak
225EE3B4:Ûshbaka
225219C6:Ûshgol
225219C7:Ûthûg
</DropDownList>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>4 Bytes</VariableType>
              <Address>p_uruk</Address>
              <Offsets>
                <Offset>50</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1112</ID>
              <Description>"Level-1 (19 = Lv20)"</Description>
              <VariableType>4 Bytes</VariableType>
              <Address>p_uruk</Address>
              <Offsets>
                <Offset>340</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1113</ID>
              <Description>"TraitLevel (+344, trait strength)"</Description>
              <VariableType>4 Bytes</VariableType>
              <Address>p_uruk</Address>
              <Offsets>
                <Offset>344</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1114</ID>
              <Description>"Keep TraitLevel = Level"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:On
</DropDownList>
              <VariableType>Byte</VariableType>
              <Address>u_sync</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1128</ID>
              <Description>"Cheat death (hook)"</Description>
              <Options moHideChildren="1"/>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
aobscanmodule(aob_dice,ShadowOfMordor.exe,49 8B CC E8 ?? ?? ?? ?? 0F 28 F0 F3 0F 10 05 ?? ?? ?? ?? E8 ?? ?? ?? ?? 0F 2F F0 0F 28 74 24 40 76)
aobscanmodule(aob_score,ShadowOfMordor.exe,4C 89 6C 24 ?? 45 33 C0 48 8B D0 4C 89 7C 24 ?? 44 8B BF 84 03 00 00 E8)
alloc(newmem_dice,$100,aob_dice)
alloc(newmem_score,$100,aob_score)
aobscanmodule(aob_sd,ShadowOfMordor.exe,4C 8B 83 D8 02 00 00 48 8B 53 58 48 8B C8 4C 8B CE E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84)
alloc(newmem_sd,$100,aob_sd)
alloc(cd_list,$400,aob_dice)
label(return_sd)
label(sd_ok)
label(cd_diag)
registersymbol(cd_diag)
label(return_score)
label(score_loop)
label(score_force)
label(score_done)
label(return_dice)
label(dice_loop)
label(dice_force)
label(dice_orig)
label(dice_marked)
label(dice_apply)
label(cd_pmark)
label(cd_pall)
label(cd_100)
label(cd_hund)
registersymbol(cd_pmark)
registersymbol(cd_pall)
label(cd_mode)
label(cd_scar)
registersymbol(cd_list)
registersymbol(cd_mode)
registersymbol(cd_scar)

newmem_score:
  inc dword ptr [cd_diag]
  mov r15d,[rdi+00000384]
  cmp byte ptr [cd_mode],2
  je score_force
  cmp byte ptr [cd_mode],1
  jne score_done
  lea r10,[cd_list]
  mov r11d,#64
score_loop:
  cmp [r10],rdi
  je score_force
  add r10,8
  dec r11d
  jne score_loop
  jmp score_done
score_force:
  cmp r15d,#300
  jge score_done
  mov r15d,#300
score_done:
  jmp return_score

aob_score+10:
  jmp newmem_score
  nop 2
return_score:
registersymbol(aob_score)

newmem_dice:
  inc dword ptr [cd_diag+4]
  cmp byte ptr [cd_mode],0
  je dice_orig
  lea r10,[cd_list]
  xor r11d,r11d
dice_loop:
  cmp [r10+r11*8],rdi
  je dice_marked
  inc r11d
  cmp r11d,#64
  jb dice_loop
  cmp byte ptr [cd_mode],2
  jne dice_orig
  lea r10,[cd_pall]
  jmp dice_apply
dice_marked:
  lea r10,[r10+r11*4+200]
  cmp dword ptr [r10],0
  jge dice_apply
  lea r10,[cd_pmark]
dice_apply:
  cmp dword ptr [r10],0
  jl dice_orig
  movss xmm6,[r10]
  comiss xmm6,[cd_100]
  jae dice_force
  mulss xmm6,[cd_hund]
dice_orig:
  comiss xmm6,xmm0
  movaps xmm6,[rsp+40]
  jmp return_dice
dice_force:
  movaps xmm6,[rsp+40]
  test rsp,rsp
  jmp return_dice

newmem_sd:
  inc dword ptr [cd_diag+8]
  test rax,rax
  jne sd_ok
  inc dword ptr [cd_diag+C]
sd_ok:
  mov rdi,rax
  test rax,rax
  jmp return_sd

cd_mode:
  db 1
  db 0 0 0 0 0 0 0
cd_scar:
  dq 0
cd_diag:
  dd 0 0 0 0
cd_pmark:
  dd (float)100
cd_pall:
  dd (float)100
cd_100:
  dd (float)100
cd_hund:
  dd (float)0.01

aob_sd+16:
  jmp newmem_sd
  nop
return_sd:
registersymbol(aob_sd)

aob_dice+18:
  jmp newmem_dice
  nop 3
return_dice:
registersymbol(aob_dice)

[DISABLE]
aob_dice+18:
  db 0F 2F F0 0F 28 74 24 40
aob_score+10:
  db 44 8B BF 84 03 00 00
aob_sd+16:
  db 48 8B F8 48 85 C0

unregistersymbol(aob_dice)
unregistersymbol(aob_sd)
unregistersymbol(cd_diag)
dealloc(newmem_sd)
unregistersymbol(aob_score)
dealloc(newmem_score)
unregistersymbol(cd_list)
unregistersymbol(cd_mode)
unregistersymbol(cd_scar)
unregistersymbol(cd_pmark)
unregistersymbol(cd_pall)
dealloc(newmem_dice)
dealloc(cd_list)
</AssemblerScript>
              <CheatEntries>
                <CheatEntry>
                  <ID>1115</ID>
                  <Description>"Mode"</Description>
                  <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:Marked uruks only
2:Every uruk
</DropDownList>
                  <VariableType>Byte</VariableType>
                  <Address>cd_mode</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1116</ID>
                  <Description>"Dice chance %: marked uruks (100 always, 0 never, -1 game's own)"</Description>
                  <VariableType>Float</VariableType>
                  <Address>cd_pmark</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1117</ID>
                  <Description>"Dice chance %: every other uruk (mode 2)"</Description>
                  <VariableType>Float</VariableType>
                  <Address>cd_pall</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1122</ID>
                  <Description>"Diagnostics (counters since enabled)"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1118</ID>
                      <Description>"1 reached chance step"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>cd_diag</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1119</ID>
                      <Description>"2 dice rolled"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>cd_diag+4</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1120</ID>
                      <Description>"3 comeback asked for scar"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>cd_diag+8</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1121</ID>
                      <Description>"4 no scar -&gt; comeback cancelled"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>cd_diag+C</Address>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1123</ID>
                  <Description>"Branded orcs can cheat death"</Description>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
aobscanmodule(aob_brandrule,ShadowOfMordor.exe,48 83 B9 98 02 00 00 00 40 88 AC 24 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? F7 81 A4 03 00 00 00 00 02 00)
alloc(brandrule_orig,8,aob_brandrule)
registersymbol(aob_brandrule)
registersymbol(brandrule_orig)
brandrule_orig:
  readmem(aob_brandrule+10,6)
aob_brandrule+10:
  db 90 90 90 90 90 90

[DISABLE]
aob_brandrule+10:
  readmem(brandrule_orig,6)
unregistersymbol(aob_brandrule)
unregistersymbol(brandrule_orig)
dealloc(brandrule_orig)
</AssemblerScript>
                </CheatEntry>
                <CheatEntry>
                  <ID>1126</ID>
                  <Description>"Beheaded orcs can cheat death (random scar)"</Description>
                  <Options moHideChildren="1"/>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
aobscanmodule(aob_sevrule,ShadowOfMordor.exe,48 83 B9 98 02 00 00 00 40 88 AC 24 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? F7 81 A4 03 00 00 00 00 02 00)
alloc(sevrule_orig,8,aob_sevrule)
registersymbol(aob_sevrule)
registersymbol(sevrule_orig)
sevrule_orig:
  readmem(aob_sevrule+20,6)
aob_sevrule+20:
  db 90 90 90 90 90 90
aobscanmodule(aob_headswap,ShadowOfMordor.exe,E8 ?? ?? ?? ?? 48 8B F0 48 85 C0 74 ?? 80 3D ?? ?? ?? ?? 00 74 ?? 48 8B 0D)
aobscanmodule(aob_sevcancel,ShadowOfMordor.exe,F6 87 A4 03 00 00 10 74 ?? 48 83 BF D0 02 00 00 00 74 ?? 48 C7 87 D0 02 00 00 00 00 00 00)
registersymbol(aob_sevcancel)
aob_sevcancel+7:
  db EB
alloc(newmem_hs,$100,aob_headswap)
label(return_hs)
label(orig_hs)
label(hs_from)
label(hs_to)
label(hs_count)
registersymbol(aob_headswap)
registersymbol(hs_from)
registersymbol(hs_to)
registersymbol(hs_count)

newmem_hs:
  test rax,rax
  je orig_hs
  cmp rax,[hs_from]
  jne orig_hs
  cmp qword ptr [hs_to],0
  je orig_hs
  mov rax,[hs_to]
  inc dword ptr [hs_count]
orig_hs:
  mov rsi,rax
  test rax,rax
  jmp return_hs

hs_from:
  dq 0
hs_to:
  dq 0
hs_count:
  dd 0

aob_headswap+5:
  jmp newmem_hs
  nop
return_hs:

[DISABLE]
aob_headswap+5:
  db 48 8B F0 48 85 C0
aob_sevcancel+7:
  db 74
aob_sevrule+20:
  readmem(sevrule_orig,6)
unregistersymbol(aob_sevrule)
unregistersymbol(sevrule_orig)
dealloc(sevrule_orig)
unregistersymbol(aob_sevcancel)
unregistersymbol(aob_headswap)
unregistersymbol(hs_from)
unregistersymbol(hs_to)
unregistersymbol(hs_count)
dealloc(newmem_hs)
</AssemblerScript>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1124</ID>
                      <Description>"beheadings swapped (times)"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>hs_count</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1125</ID>
                      <Description>"current pick (re-rolled 4x a second)"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>hs_to</Address>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1127</ID>
                  <Description>"Ignore scar limit (no 1-orc-per-scar-type rule)"</Description>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
aobscanmodule(aob_scarlim,ShadowOfMordor.exe,48 C1 F8 05 41 8B FD 44 89 6D 50 41 8B DD 4C 8D 25 ?? ?? ?? ?? 85 C0 0F 84)
aobscanmodule(aob_scarmax,ShadowOfMordor.exe,48 8B CF E8 ?? ?? ?? ?? 85 C0 74 ?? 41 39 45 00 72 ?? 80 3D)
registersymbol(aob_scarlim)
registersymbol(aob_scarmax)
aob_scarlim+15:
  db 31 C0
aob_scarmax+10:
  db EB

[DISABLE]
aob_scarlim+15:
  db 85 C0
aob_scarmax+10:
  db 72
unregistersymbol(aob_scarlim)
unregistersymbol(aob_scarmax)
</AssemblerScript>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1129</ID>
              <Description>"Always cheat death (this uruk)"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
              <VariableType>Byte</VariableType>
              <Address>u_cd</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1130</ID>
              <Description>"Dice chance % (this uruk, when marked; -1 = use the marked-uruks value)"</Description>
              <VariableType>Float</VariableType>
              <Address>u_cdp</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1131</ID>
              <Description>"Bring back now (selected [returning] uruk)"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_returnNow then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_returnNow())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1132</ID>
              <Description>"Reveal: show as alive in army screen (clears IsMockDead, may skip the comeback scene)"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_reveal then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_reveal())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1133</ID>
              <Description>"IsMockDead (shown dead until you meet him)"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
              <VariableType>Binary</VariableType>
              <BitStart>7</BitStart>
              <BitLength>1</BitLength>
              <ShowAsBinary>0</ShowAsBinary>
              <Address>p_uruk</Address>
              <Offsets>
                <Offset>3A6</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1134</ID>
              <Description>"Where he spawns (home location)"</Description>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>8 Bytes</VariableType>
              <Address>p_uruk</Address>
              <Offsets>
                <Offset>2C0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1135</ID>
              <Description>"Teleport selected uruk next to me (needs Comeback ambush hook on; he must be in the world)"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_teleportNear then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_teleportNear())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1136</ID>
              <Description>"Auto-teleport cheated-death orcs that spawn far away (only IsResurrected orcs)"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:On
</DropDownList>
              <VariableType>Byte</VariableType>
              <Address>u_tp</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1148</ID>
              <Description>"Comeback ambush hook (for re-arm)"</Description>
              <Options moHideChildren="1"/>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return 'define(AMB_G,0)\ndefine(AMB_ADD,0)' end
local s = AOBScanModuleUnique('ShadowOfMordor.exe', '48 8B 0D ?? ?? ?? ?? 48 85 C9 74 ?? 4C 8B 87 C8 02 00 00 4D 85 C0 74 ?? 48 8B 89 B0 02 00 00 48 8B D7 E8 ?? ?? ?? ?? 48 89 B7 C8 02 00 00 48 8B 0D ?? ?? ?? ?? 4C 8B C6 48 8B 89 B0 02 00 00 48 8B D7 E8')
if not s then error('Comeback ambush: queue code not found (game version?)') end
local function rel(a) local v = readInteger(a); if v &gt;= 0x80000000 then v = v - 0x100000000 end; return v end
return string.format('define(AMB_G,%X)\ndefine(AMB_ADD,%X)', s + 7 + rel(s + 3), s + 71 + rel(s + 67))
{$asm}
aobscanmodule(aob_amb,ShadowOfMordor.exe,48 8B C4 48 89 58 10 55 56 57 41 54 41 55 41 56 41 57 48 81 EC 80 01 00 00)
aobscanmodule(aob_ambpick,ShadowOfMordor.exe,48 8B 45 10 49 89 07 48 8B C6 E9 ?? ?? ?? ?? CC)
alloc(newmem_amb,$300,aob_amb)
label(return_amb)
label(pick_amb)
label(return_pick)
label(rr_dist)
label(rr_picks)
label(rr_pick)
label(check_amb)
label(tp_amb)
label(tpdone_amb)
label(rr_tpcomp)
label(rr_tpvt)
label(rr_tpx)
label(rr_tpy)
label(rr_tpz)
label(rr_tpdone)
registersymbol(rr_tpcomp)
registersymbol(rr_tpvt)
registersymbol(rr_tpx)
registersymbol(rr_tpy)
registersymbol(rr_tpz)
registersymbol(rr_tpdone)
registersymbol(rr_dist)
registersymbol(rr_picks)
registersymbol(rr_pick)
label(orig_amb)
label(done_amb)
label(rr_uruk)
label(rr_rec)
label(rr_calls)
label(rr_done)
label(rr_G)
label(rr_auto)
registersymbol(rr_G)
registersymbol(rr_auto)
registersymbol(rr_uruk)
registersymbol(rr_rec)
registersymbol(rr_calls)
registersymbol(rr_done)

newmem_amb:
  inc dword ptr [rr_calls]
  movss [rr_dist],xmm1
  cmp qword ptr [rr_tpcomp],0
  jne tp_amb
check_amb:
  cmp qword ptr [rr_uruk],0
  je orig_amb
  push rcx
  push rdx
  push r8
  push r9
  sub rsp,68
  movdqu [rsp+20],xmm0
  movdqu [rsp+30],xmm1
  movdqu [rsp+40],xmm2
  movdqu [rsp+50],xmm3
  mov rax,AMB_G
  mov rax,[rax]
  test rax,rax
  je done_amb
  cmp rcx,[rax+2B0]
  jne done_amb
  mov rdx,[rr_uruk]
  cmp qword ptr [rdx+2C8],0
  jne done_amb
  mov r8,[rr_rec]
  mov [rdx+2C8],r8
  mov rax,AMB_ADD
  call rax
  inc dword ptr [rr_done]
done_amb:
  mov qword ptr [rr_uruk],0
  movdqu xmm0,[rsp+20]
  movdqu xmm1,[rsp+30]
  movdqu xmm2,[rsp+40]
  movdqu xmm3,[rsp+50]
  add rsp,68
  pop r9
  pop r8
  pop rdx
  pop rcx
orig_amb:
  mov rax,rsp
  mov [rax+10],rbx
  jmp return_amb

tp_amb:
  push rcx
  push rdx
  push r8
  push r9
  sub rsp,68
  movdqu [rsp+20],xmm0
  movdqu [rsp+30],xmm1
  movdqu [rsp+40],xmm2
  movdqu [rsp+50],xmm3
  mov rcx,[rr_tpcomp]
  mov rax,[rcx]
  cmp rax,[rr_tpvt]
  jne tpdone_amb
  mov rax,[rax+268]
  movss xmm1,[rr_tpx]
  movss xmm2,[rr_tpy]
  movss xmm3,[rr_tpz]
  call rax
  inc dword ptr [rr_tpdone]
tpdone_amb:
  mov qword ptr [rr_tpcomp],0
  movdqu xmm0,[rsp+20]
  movdqu xmm1,[rsp+30]
  movdqu xmm2,[rsp+40]
  movdqu xmm3,[rsp+50]
  add rsp,68
  pop r9
  pop r8
  pop rdx
  pop rcx
  jmp check_amb

rr_uruk:
  dq 0
rr_rec:
  dq 0
rr_calls:
  dd 0
rr_done:
  dd 0
rr_G:
  dq AMB_G
rr_auto:
  db 1
rr_dist:
  dd 0
rr_picks:
  dd 0
rr_pick:
  dq 0
rr_tpcomp:
  dq 0
rr_tpvt:
  dq 0
rr_tpx:
  dd 0
rr_tpy:
  dd 0
rr_tpz:
  dd 0
rr_tpdone:
  dd 0

pick_amb:
  mov rax,[rbp+10]
  mov [r15],rax
  mov [rr_pick],rsi
  inc dword ptr [rr_picks]
  jmp return_pick

aob_ambpick:
  jmp pick_amb
  nop 2
return_pick:
registersymbol(aob_ambpick)

aob_amb:
  jmp newmem_amb
  nop 2
return_amb:
registersymbol(aob_amb)

[DISABLE]
aob_amb:
  db 48 8B C4 48 89 58 10
unregistersymbol(aob_amb)
unregistersymbol(rr_uruk)
unregistersymbol(rr_rec)
unregistersymbol(rr_calls)
unregistersymbol(rr_done)
unregistersymbol(rr_G)
unregistersymbol(rr_auto)
aob_ambpick:
  db 48 8B 45 10 49 89 07
unregistersymbol(aob_ambpick)
unregistersymbol(rr_dist)
unregistersymbol(rr_picks)
unregistersymbol(rr_pick)
unregistersymbol(rr_tpcomp)
unregistersymbol(rr_tpvt)
unregistersymbol(rr_tpx)
unregistersymbol(rr_tpy)
unregistersymbol(rr_tpz)
unregistersymbol(rr_tpdone)
dealloc(newmem_amb)
</AssemblerScript>
              <CheatEntries>
                <CheatEntry>
                  <ID>1137</ID>
                  <Description>"Re-arm comeback ambush (selected uruk)"</Description>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_rearm then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_rearm())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
                </CheatEntry>
                <CheatEntry>
                  <ID>1138</ID>
                  <Description>"Keep comeback ambushes queued (auto re-arm every 2 s)"</Description>
                  <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:On
</DropDownList>
                  <VariableType>Byte</VariableType>
                  <Address>rr_auto</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1140</ID>
                  <Description>"Raise ambush distance limit (game: 5000)"</Description>
                  <Options moHideChildren="1"/>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
aobscanmodule(aob_ambdist,ShadowOfMordor.exe,F3 0F 10 44 24 ?? 41 0F 2F C0 77 ?? 44 0F 2F 44 24 44 77)
alloc(newmem_ambdist,$80,aob_ambdist)
label(return_ambdist)
label(rr_maxd)
registersymbol(aob_ambdist)
registersymbol(rr_maxd)

newmem_ambdist:
  comiss xmm8,[rr_maxd]
  ja aob_ambdist+49
  jmp return_ambdist
rr_maxd:
  dd (float)15000

aob_ambdist+A:
  db 66 90
aob_ambdist+C:
  jmp newmem_ambdist
  nop 3
return_ambdist:

[DISABLE]
aob_ambdist+A:
  db 77 3D 44 0F 2F 44 24 44 77 35
unregistersymbol(aob_ambdist)
unregistersymbol(rr_maxd)
dealloc(newmem_ambdist)
</AssemblerScript>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1139</ID>
                      <Description>"max distance to spawner"</Description>
                      <VariableType>Float</VariableType>
                      <Address>rr_maxd</Address>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1141</ID>
                  <Description>"Queued ambush (+2C8, 0 = none)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>2C8</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1142</ID>
                  <Description>"hook ran (times)"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>rr_calls</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1143</ID>
                  <Description>"ambushes queued"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>rr_done</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1144</ID>
                  <Description>"picker: last distance to spawner"</Description>
                  <VariableType>Float</VariableType>
                  <Address>rr_dist</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1145</ID>
                  <Description>"picker: orcs picked (times)"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>rr_picks</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1146</ID>
                  <Description>"picker: last orc picked"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>rr_pick</Address>
                </CheatEntry>
                <CheatEntry>
                  <ID>1147</ID>
                  <Description>"teleports done"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>rr_tpdone</Address>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1149</ID>
              <Description>"Branded (this uruk)"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
              <VariableType>Byte</VariableType>
              <Address>u_brand</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1032</ID>
              <Description>"Status"</Description>
              <Options moHideChildren="1"/>
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>1019</ID>
                  <Description>"Row position (0-based, 255 = not in a row)"</Description>
                  <VariableType>Byte</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>3A0</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1020</ID>
                  <Description>"Army screen detail (7F = show, 4F = hide)"</Description>
                  <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">7F:Show
4F:Hide
</DropDownList>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>Byte</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>3A9</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1021</ID>
                  <Description>"IntelDataLevel (+3AA)"</Description>
                  <VariableType>Byte</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>3AA</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1022</ID>
                  <Description>"Brand scar (+298, 0 = not branded)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>298</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1023</ID>
                  <Description>"ResurrectionCount"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>394</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1024</ID>
                  <Description>"InteractionScoreTotal (cheat-death chance needs 150+)"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>384</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1025</ID>
                  <Description>"InteractionScoreCurrent"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>388</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1026</ID>
                  <Description>"PlayerKills"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>348</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1028</ID>
                  <Description>"Status flags (+3A4)"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1027</ID>
                      <Description>"all flags (hex) - don't tick, ticking freezes"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1001</ID>
                      <Description>"IsResurrected  (bit 0)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>0</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1002</ID>
                      <Description>"IsDead  (bit 2)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>2</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1003</ID>
                      <Description>"KilledByPlayer  (bit 3)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>3</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1004</ID>
                      <Description>"CanResurrect (will cheat death)  (bit 4)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>4</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1005</ID>
                      <Description>"PresentedToPlayer  (bit 5)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>5</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A4</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1006</ID>
                      <Description>"SpawnConditionComplete  (bit 9)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>1</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A5</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1007</ID>
                      <Description>"SpawnConditionCompletePrevious  (bit 10)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>2</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A5</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1008</ID>
                      <Description>"HasVow  (bit 12)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>4</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A5</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1009</ID>
                      <Description>"IsSevered (beheaded)  (bit 17)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>1</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1010</ID>
                      <Description>"IsSquealer  (bit 18)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>2</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1011</ID>
                      <Description>"GangLeader  (bit 19)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>3</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1012</ID>
                      <Description>"GangLeaderMatchClass  (bit 20)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>4</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1013</ID>
                      <Description>"HasNemesisTrait  (bit 21)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>5</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1014</ID>
                      <Description>"IsMockDead  (bit 23)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>7</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A6</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1015</ID>
                      <Description>"CanInterrogateAbout  (bit 24)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>0</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A7</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1016</ID>
                      <Description>"RevengeTarget  (bit 26)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>2</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A7</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1017</ID>
                      <Description>"WasEverPreset  (bit 27)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>3</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A7</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1018</ID>
                      <Description>"OnlySpawnWithBoss  (bit 29)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Binary</VariableType>
                      <BitStart>5</BitStart>
                      <BitLength>1</BitLength>
                      <ShowAsBinary>0</ShowAsBinary>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>3A7</Offset>
                      </Offsets>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1029</ID>
                  <Description>"PurposeTimeOut (+398)"</Description>
                  <VariableType>2 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>398</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1030</ID>
                  <Description>"CurEncounterIndex (+39A)"</Description>
                  <VariableType>2 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>39A</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1031</ID>
                  <Description>"PrevEncounterIndex (+39C)"</Description>
                  <VariableType>2 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>39C</Offset>
                  </Offsets>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1041</ID>
              <Description>"Additional Properties"</Description>
              <Options moHideChildren="1"/>
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>1033</ID>
                  <Description>"Faction"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>58</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1034</ID>
                  <Description>"Class (AI tree)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>60</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1035</ID>
                  <Description>"Preset member"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>68</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1036</ID>
                  <Description>"Row"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>88</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1037</ID>
                  <Description>"Personality"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>90</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1038</ID>
                  <Description>"Purpose"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>A8</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1039</ID>
                  <Description>"Role / Title"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>2B8</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1040</ID>
                  <Description>"Home location"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>2C0</Offset>
                  </Offsets>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1055</ID>
              <Description>"Traits (strengths / weaknesses)"</Description>
              <Options moHideChildren="1"/>
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>1051</ID>
                  <Description>"Add / remove traits (hook - tick, then use the buttons)"</Description>
                  <Options moHideChildren="1"/>
                  <Color>4080FF</Color>
                  <VariableType>Auto Assembler Script</VariableType>
                  <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return 'define(TA_ADD,0)\ndefine(TA_REM,0)' end
local add = AOBScanModuleUnique('ShadowOfMordor.exe', '44 88 44 24 ?? 48 89 54 24 ?? 55 53 57 48 8D 6C 24 ?? 48 81 EC ?? ?? ?? ?? 32 C0 48 8B DA 48 8B F9')
local rem = AOBScanModuleUnique('ShadowOfMordor.exe', '40 53 57 48 83 EC ?? 4C 8B 41 78 48 8B 41 70 48 8B DA 48 8B F9')
if not add or not rem then error('Add / remove traits: game code not found (game version?)') end
return string.format('define(TA_ADD,%X)\ndefine(TA_REM,%X)', add, rem)
{$asm}
aobscanmodule(aob_taframe,ShadowOfMordor.exe,48 8D 8D E0 00 00 00 E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B CB 48 8B D0 E8)
alloc(newmem_ta,$100,aob_taframe)
label(return_ta)
label(orig_ta)
label(rem_ta)
label(res_ta)
label(ta_uruk)
label(ta_rec)
label(ta_pick)
label(ta_done)
label(ta_op)
label(ta_rules)
label(ta_result)
label(ta_pmode)
label(ta_markers)
registersymbol(aob_taframe)
registersymbol(ta_uruk)
registersymbol(ta_rec)
registersymbol(ta_pick)
registersymbol(ta_done)
registersymbol(ta_op)
registersymbol(ta_rules)
registersymbol(ta_result)
registersymbol(ta_pmode)
registersymbol(ta_markers)

newmem_ta:
  cmp qword ptr [ta_uruk],0
  je orig_ta
  sub rsp,20
  mov rcx,[ta_uruk]
  mov rdx,[ta_rec]
  cmp byte ptr [ta_op],2
  je rem_ta
  movzx r8d,byte ptr [ta_rules]
  mov rax,TA_ADD
  call rax
  jmp res_ta
rem_ta:
  mov rax,TA_REM
  call rax
res_ta:
  add rsp,20
  mov [ta_result],al
  inc dword ptr [ta_done]
  mov qword ptr [ta_uruk],0
orig_ta:
  lea rcx,[rbp+E0]
  jmp return_ta

ta_uruk:
  dq 0
ta_rec:
  dq 0
ta_pick:
  dq 0
ta_done:
  dd 0
ta_op:
  db 0
ta_rules:
  db 1
ta_result:
  db 0
ta_pmode:
  db 0
ta_markers:
  db 0

aob_taframe:
  jmp newmem_ta
  nop 2
return_ta:

[DISABLE]
aob_taframe:
  db 48 8D 8D E0 00 00 00
unregistersymbol(aob_taframe)
unregistersymbol(ta_uruk)
unregistersymbol(ta_rec)
unregistersymbol(ta_pick)
unregistersymbol(ta_done)
unregistersymbol(ta_op)
unregistersymbol(ta_rules)
unregistersymbol(ta_result)
unregistersymbol(ta_pmode)
unregistersymbol(ta_markers)
dealloc(newmem_ta)
</AssemblerScript>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1042</ID>
                      <Description>"Trait to add / remove"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>ta_pick</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1043</ID>
                      <Description>"Game rules (1 = drop opposite traits, may refuse; 0 = force)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Force
1:Game rules
</DropDownList>
                      <VariableType>Byte</VariableType>
                      <Address>ta_rules</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1044</ID>
                      <Description>"Add trait to selected uruk"</Description>
                      <Color>4080FF</Color>
                      <VariableType>Auto Assembler Script</VariableType>
                      <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_addTrait then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_addTrait())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1045</ID>
                      <Description>"Remove trait from selected uruk"</Description>
                      <Color>4080FF</Color>
                      <VariableType>Auto Assembler Script</VariableType>
                      <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_removeTrait then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_removeTrait())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1046</ID>
                      <Description>"Copy traits (selected uruk)"</Description>
                      <Color>4080FF</Color>
                      <VariableType>Auto Assembler Script</VariableType>
                      <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_copyTraits then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_copyTraits())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1047</ID>
                      <Description>"Paste traits into selected uruk"</Description>
                      <Color>4080FF</Color>
                      <VariableType>Auto Assembler Script</VariableType>
                      <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_pasteTraits then error('Enable the Master Script first') end
print('SoM Uruk Editor: ' .. SoM_pasteTraits())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1048</ID>
                      <Description>"Paste mode"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Add missing (keep his own)
1:Make identical (remove his others)
</DropDownList>
                      <VariableType>Byte</VariableType>
                      <Address>ta_pmode</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1049</ID>
                      <Description>"Paste Marker_ traits too (rank / class flags)"</Description>
                      <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
                      <VariableType>Byte</VariableType>
                      <Address>ta_markers</Address>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1050</ID>
                      <Description>"requests done (times)"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>ta_done</Address>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1052</ID>
                  <Description>"list_begin"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>70</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1053</ID>
                  <Description>"list_end (begin + count*8; use Add / remove, it grows the list)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>78</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1054</ID>
                  <Description>"list_maxlength (capacity: slots allocated; the game raises it when full - don't edit)"</Description>
                  <VariableType>4 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>80</Offset>
                  </Offsets>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1061</ID>
              <Description>"Last Death (nemesis memory)"</Description>
              <Options moHideChildren="1"/>
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>1056</ID>
                  <Description>"Killed by (character type)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>C0</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1057</ID>
                  <Description>"Finishing attack (Combat/Attack, raw)"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>C8</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1058</ID>
                  <Description>"Resurrection style"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>D8</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1059</ID>
                  <Description>"ResurrectionScarType"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>2D0</Offset>
                  </Offsets>
                </CheatEntry>
                <CheatEntry>
                  <ID>1060</ID>
                  <Description>"AppliedScarType"</Description>
                  <ShowAsHex>1</ShowAsHex>
                  <VariableType>8 Bytes</VariableType>
                  <Address>p_uruk</Address>
                  <Offsets>
                    <Offset>2D8</Offset>
                  </Offsets>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
            <CheatEntry>
              <ID>1106</ID>
              <Description>"Equipment/Looks (applies when the uruk respawns)"</Description>
              <Options moHideChildren="1"/>
              <GroupHeader>1</GroupHeader>
              <CheatEntries>
                <CheatEntry>
                  <ID>1102</ID>
                  <Description>"Flair (gear / weapons)"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1062</ID>
                      <Description>"flair 1"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>110</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1063</ID>
                      <Description>"flair 2"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>118</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1064</ID>
                      <Description>"flair 3"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>120</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1065</ID>
                      <Description>"flair 4"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>128</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1066</ID>
                      <Description>"flair 5"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>130</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1067</ID>
                      <Description>"flair 6"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>138</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1068</ID>
                      <Description>"flair 7"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>140</Offset>
                      </Offsets>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1103</ID>
                  <Description>"Tags"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1069</ID>
                      <Description>"tag count"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>150</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1070</ID>
                      <Description>"tag 1"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>158</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1071</ID>
                      <Description>"tag 2"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>160</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1072</ID>
                      <Description>"tag 3"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>168</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1073</ID>
                      <Description>"tag 4"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>170</Offset>
                      </Offsets>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1104</ID>
                  <Description>"Models"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1074</ID>
                      <Description>"model count (+178)"</Description>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>178</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1075</ID>
                      <Description>"model: anim set"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>180</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1076</ID>
                      <Description>"model: body"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>188</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1077</ID>
                      <Description>"model: body (2)"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>190</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1078</ID>
                      <Description>"head"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1A8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1079</ID>
                      <Description>"model: head"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1B0</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1080</ID>
                      <Description>"model: legs"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1B8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1081</ID>
                      <Description>"model: helmet"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1C0</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1082</ID>
                      <Description>"model: slot +1C8"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1C8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1083</ID>
                      <Description>"model: hair"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1D0</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1084</ID>
                      <Description>"model: slot +1D8"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1D8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1085</ID>
                      <Description>"model: bracers / quiver"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1E0</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1086</ID>
                      <Description>"model: extra"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1E8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1087</ID>
                      <Description>"scar setup"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1F0</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1088</ID>
                      <Description>"armor set"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>1F8</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1089</ID>
                      <Description>"model: chest armor"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>200</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1090</ID>
                      <Description>"model: shoulders"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>208</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1091</ID>
                      <Description>"model: arm armor"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>210</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1092</ID>
                      <Description>"model: leg armor"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>8 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>218</Offset>
                      </Offsets>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
                <CheatEntry>
                  <ID>1105</ID>
                  <Description>"Colors (ARGB)"</Description>
                  <Options moHideChildren="1"/>
                  <GroupHeader>1</GroupHeader>
                  <CheatEntries>
                    <CheatEntry>
                      <ID>1093</ID>
                      <Description>"color 1"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>220</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1094</ID>
                      <Description>"color 2"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>224</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1095</ID>
                      <Description>"color 3"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>228</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1096</ID>
                      <Description>"color 4"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>22C</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1097</ID>
                      <Description>"color 5"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>230</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1098</ID>
                      <Description>"color 6"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>234</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1099</ID>
                      <Description>"color 7"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>238</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1100</ID>
                      <Description>"color 8"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>23C</Offset>
                      </Offsets>
                    </CheatEntry>
                    <CheatEntry>
                      <ID>1101</ID>
                      <Description>"color 9"</Description>
                      <ShowAsHex>1</ShowAsHex>
                      <VariableType>4 Bytes</VariableType>
                      <Address>p_uruk</Address>
                      <Offsets>
                        <Offset>240</Offset>
                      </Offsets>
                    </CheatEntry>
                  </CheatEntries>
                </CheatEntry>
              </CheatEntries>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>1175</ID>
      <Description>"Rune Editor"</Description>
      <Options moHideChildren="1"/>
      <Color>40C040</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript Async="1">[ENABLE]
{$lua}
if syntaxcheck then return end

local DB_STR = '64 61 74 61 62 61 73 65 5C 67 61 6D 65 5C 47 61 6D 65 2E 67 61 6D 65 64 62 00'
local CAT_STRIDE, REC_STRIDE = 0x58, 0x30
local runeDB

local function scan(input, vtype, flags, align, alignParam, startA, stopA)
  local ms = createMemScan()
  ms.firstScan(soExactValue, vtype, rtRounded, input, nil, startA, stopA, flags,
               align, alignParam, true, true, false, false)
  ms.waitTillDone()
  local fl = createFoundList(ms)
  fl.initialize()
  local res = {}
  for i = 0, fl.Count - 1 do res[#res + 1] = tonumber(fl.Address[i], 16) end
  fl.destroy()
  ms.destroy()
  return res
end

local function findGameDB()
  local base = getAddress(process)
  local hits = scan(DB_STR, vtByteArray, '*X*W*C', fsmNotAligned, '1', base, base + getModuleSize(process))
  for _, h in ipairs(hits) do
    local db = readQword(h - 8)
    if db and db ~= 0 and readString(readQword(db) or 0, 20) == 'GameDatabase' then return db end
  end
  return nil
end

local function records(name)
  local base, cnt = readQword(runeDB + 0x38), readInteger(runeDB + 0x30)
  local list = {}
  for i = 0, cnt - 1 do
    local c = base + i * CAT_STRIDE
    if readString(readQword(c), 100) == name then
      local first, n = readQword(c + 0x38), readInteger(c + 0x28)
      for k = 0, n - 1 do
        local r = first + k * REC_STRIDE
        local s = readString(readQword(r + 0x20), 100)
        if s then list[#list + 1] = {r, s} end
      end
      break
    end
  end
  return list
end

local function filtered(cat)
  local out = {}
  for _, r in ipairs(records(cat[1])) do
    if r[2]:find(cat[2]) then out[#out + 1] = r end
  end
  return out
end

local function recName(r)
  if not r or r == 0 then return nil end
  return readString(readQword(r + 0x20) or 0, 100)
end

local function catOf(r)
  local c = readQword(r + 0x18) or 0
  return c ~= 0 and readString(readQword(c) or 0, 60) or nil
end

local function addDD(mr, str)
  mr.DropDownList.Text = str
  mr.DropDownReadOnly = false
  mr.DropDownDescriptionOnly = true
  mr.DisplayAsDropDownListItem = true
end

local function remDD(mr)
  mr.DropDownList.Text = ''
  mr.DisplayAsDropDownListItem = false
end

local function eachChild(mr, fn)
  for i = 0, mr.Count - 1 do
    local c = mr.Child[i]
    fn(c)
    eachChild(c, fn)
  end
end

local function fieldValue(rec, idx)
  local dsc = readQword(rec + 8) + idx * 16
  if (readSmallInteger(dsc + 0xC) or 0) == 0 then return nil end
  return readQword(readQword(rec + 0x10) + readSmallInteger(dsc + 0xE) * 8)
end

local function findField(rec, test)
  for idx = 0, 63 do
    local v = fieldValue(rec, idx)
    if v and v &gt; 0x10000 and v &lt; 0x7fffffffffff and test(v) then return idx end
  end
  return nil
end

local uiIdxBySchema, nameIdxBySchema = {}, {}
local function hasName(item)
  local s = readQword(item + 8)
  if uiIdxBySchema[s] == nil then
    uiIdxBySchema[s] = findField(item, function(v) return catOf(v) == 'Inventory/UIItemData' end) or false
  end
  if not uiIdxBySchema[s] then return false end
  local ui = fieldValue(item, uiIdxBySchema[s])
  if not ui or ui == 0 then return false end
  local us = readQword(ui + 8)
  if not nameIdxBySchema[us] then
    nameIdxBySchema[us] = findField(ui, function(v) return (readString(v, 8) or ''):sub(1, 7) == 'InvUIN_' end)
    if not nameIdxBySchema[us] then return false end
  end
  local key = fieldValue(ui, nameIdxBySchema[us])
  return key ~= nil and (readString(key, 8) or '') ~= ''
end

local function runeEffects()
  if SoM_runeFx then return SoM_runeFx end
  local fx, byAddr = {}, {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_'})) do
    local v = readQword(r[1] + 0x10) or 0
    local look = ''
    for o = 0x20, 0x38, 8 do
      local q = readQword(v + o) or 0
      if q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/WorldItemData' then
        look = recName(q) or ''; break
      end
    end
    local weapon = look:match('^(Sword)') or look:match('^(Dagger)') or look:match('^(Bow)')
    if weapon then
      local e = {r[1], r[2], weapon, look:find('Gold') ~= nil, tonumber(r[2]:match('_(%d+)$')) or 0, hasName(r[1])}
      fx[#fx + 1] = e
      byAddr[r[1]] = e
    end
  end
  table.sort(fx, function(a, b)
    if a[3] ~= b[3] then return a[3] &gt; b[3] end
    if a[4] ~= b[4] then return a[4] end
    return a[2] &lt; b[2]
  end)
  SoM_runeFx, SoM_runeFxBy = fx, byAddr
  return fx
end

local function fxLabel(e)
  return string.format('[%s%s] %s%s', e[3], e[4] and ' EPIC' or '', e[2]:gsub('^BD_', ''), e[6] and '' or ' ?')
end

local RUNE = 0x28

local function isItem(q) return q and q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/Items' end

local function isRuneEntry(e)
  local f = readQword(e) or -1
  if f ~= 0 and not (f &gt; 0x10000 and f &lt; 0x7fffffffffff and catOf(f) == 'Faction/Definition') then return false end
  local id = readInteger(e + 0x20)
  return isItem(readQword(e + 0x18)) and id and id &gt; 0 and id &lt; 0x100000
end

function SoM_findRunes()
  SoM_runeHdr = nil
  local anchors = {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Dag_Fear$'})) do anchors[#anchors + 1] = r[1] end
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Brutal_Terror$'})) do anchors[#anchors + 1] = r[1] end
  for _, a in ipairs(anchors) do
    for _, h in ipairs(scan(string.format('%X', a), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
      local e = h - 0x18
      if isRuneEntry(e) then
        local b = e
        while isRuneEntry(b - RUNE) do b = b - RUNE end
        for _, hd in ipairs(scan(string.format('%X', b), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
          local en = readQword(hd + 8) or 0
          if en &gt; b and (en - b) % RUNE == 0 and (en - b) // RUNE &lt;= (readInteger(hd + 0x10) or 0) then
            SoM_runeHdr = hd
            return hd
          end
        end
      end
    end
  end
  return nil
end

local ITEM = 0x10
SoM_itemObj = nil

function SoM_findItemList()
  SoM_itemObj = nil
  if not SoM_runeHdr then SoM_findRunes() end
  if not SoM_runeHdr then return nil end
  local inv = SoM_runeHdr - 0x30
  for _, h in ipairs(scan(string.format('%X', inv), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
    local obj = h - 0x230
    local b, e = readQword(obj + 0x298) or 0, readQword(obj + 0x2A0) or 0
    local cap = readInteger(obj + 0x2A8) or 0
    if b ~= 0 and e &gt;= b and (e - b) % ITEM == 0 and (e - b) // ITEM &lt;= cap and cap &lt; 100000 then
      SoM_itemObj = obj
      return obj
    end
  end
  return nil
end

local function itemEntry(item)
  local obj = SoM_itemObj
  local b, e = readQword(obj + 0x298), readQword(obj + 0x2A0)
  for a = b, e - ITEM, ITEM do
    if readQword(a) == item then return a end
  end
  return nil
end

function SoM_itemAdd(item, template)
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a = itemEntry(item)
  if a then
    writeSmallInteger(a + 8, (readSmallInteger(a + 8) or 0) + 1)
    return true
  end
  local obj = SoM_itemObj
  local b, e, cap = readQword(obj + 0x298), readQword(obj + 0x2A0), readInteger(obj + 0x2A8) or 0
  if (e - b) // ITEM &gt;= cap then
    return nil, string.format('the saved item list is full (%d of %d) - pick up any item so the game grows it, then try again', (e - b) // ITEM, cap)
  end
  writeQword(e, item)
  writeSmallInteger(e + 8, 1)
  writeSmallInteger(e + 0xA, 0)
  writeInteger(e + 0xC, template and readInteger(template + 0xC) or 7)
  writeQword(obj + 0x2A0, e + ITEM)
  return true
end

function SoM_itemSwap(old, new)
  if old == new then return true end
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a, bnew = itemEntry(old), itemEntry(new)
  if a and (readSmallInteger(a + 8) or 0) &lt;= 1 and not bnew then
    writeQword(a, new)
    return true
  end
  local ok, why = SoM_itemAdd(new, a)
  if not ok then return nil, why end
  if a then
    local c = readSmallInteger(a + 8) or 0
    if c &gt; 1 then
      writeSmallInteger(a + 8, c - 1)
    else
      local obj = SoM_itemObj
      local e = readQword(obj + 0x2A0)
      local n = (e - (a + ITEM))
      if n &gt; 0 then writeBytes(a, readBytes(a + ITEM, n, true)) end
      writeQword(obj + 0x2A0, e - ITEM)
    end
  end
  return true
end

local function runeCount()
  if not SoM_runeHdr then return 0 end
  local b, e = readQword(SoM_runeHdr) or 0, readQword(SoM_runeHdr + 8) or 0
  if b == 0 or e &lt; b then return 0 end
  return (e - b) // RUNE
end

local function runeGroup() return getAddressList().getMemoryRecordByDescription('Rune Editor') end

local function factionNames()
  local t = {}
  for _, f in ipairs(records('Faction/Definition')) do t[f[1]] = f[2] end
  return t
end

function SoM_runeRefresh()
  if not SoM_runeHdr then SoM_findRunes() end
  local g = runeGroup()
  if not g then return 0 end
  local fx = runeEffects()
  local fac = factionNames()
  local lines = {}
  local b = SoM_runeHdr and readQword(SoM_runeHdr) or 0
  for i = 0, runeCount() - 1 do
    local e = b + i * RUNE
    local it = readQword(e + 0x18) or 0
    local d = SoM_runeFxBy[it]
    local src = fac[readQword(e) or 0]
    lines[#lines + 1] = string.format('%d:%s  #%X%s', i, d and fxLabel(d) or (recName(it) or '?'),
                                      readInteger(e + 0x20) or 0, src and ('  {' .. src:gsub('^Orc_Faction_', '') .. '}') or '')
  end
  local fxList = {}
  for _, e in ipairs(fx) do fxList[#fxList + 1] = string.format('%016X:%s', e[1], fxLabel(e)) end
  local fxText = table.concat(fxList, '\n')
  local facList = {'0000000000000000:&lt;built-in / none&gt;'}
  for _, f in ipairs(records('Faction/Definition')) do facList[#facList + 1] = string.format('%016X:%s', f[1], f[2]) end
  eachChild(g, function(mr)
    local dsc = mr.Description
    if dsc == 'Select rune' then addDD(mr, table.concat(lines, '\n'))
    elseif dsc == 'Effect (changes the whole rune)' or dsc == 'Give: effect' then addDD(mr, fxText)
    elseif dsc == 'Source captain faction' then addDD(mr, table.concat(facList, '\n')) end
  end)
  SoM_runeListed = #lines
  return #lines
end

local lastRuneHover = 0
local function followRuneHover()
  if not getAddressSafe('rune_hover') then lastRuneHover = 0; return end
  local h = readQword('rune_hover') or 0
  if h == 0 or h == lastRuneHover then return end
  lastRuneHover = h
  if not SoM_runeHdr then return end
  local b = readQword(SoM_runeHdr) or 0
  if h &gt;= b and (h - b) % RUNE == 0 then
    local i = (h - b) // RUNE
    if i &gt;= runeCount() then return end
    if i &gt;= (SoM_runeListed or 0) then SoM_runeRefresh() end
    writeInteger('r_index', i)
  end
end

local function updateRunePointer()
  if not getAddressSafe('p_rune') then return end
  followRuneHover()
  local i = readInteger('r_index') or -1
  if i &gt;= 0x80000000 then i = i - 0x100000000 end
  local a = 0
  if SoM_runeHdr and i &gt;= 0 and i &lt; runeCount() then a = readQword(SoM_runeHdr) + i * RUNE end
  if readQword('p_rune') ~= a then writeQword('p_rune', a) end
end

SoM_runeEffectById = nil
local function mirrorEdits()
  if not SoM_runeHdr then return end
  local b, n = readQword(SoM_runeHdr) or 0, runeCount()
  local now = {}
  for i = 0, n - 1 do
    now[readInteger(b + i * RUNE + 0x20) or 0] = readQword(b + i * RUNE + 0x18) or 0
  end
  local old = SoM_runeEffectById
  SoM_runeEffectById = now
  if not old then return end
  for id, fx in pairs(now) do
    local was = old[id]
    if was and was ~= 0 and fx ~= 0 and was ~= fx then
      local ok, why = SoM_itemSwap(was, fx)
      print(string.format('SoM Rune Editor: rune #%X effect changed - %s', id,
            ok and 'saved item list updated' or ('NOT saved: ' .. tostring(why))))
    end
  end
end

function SoM_giveRune()
  if not SoM_runeHdr and not SoM_findRunes() then return 'rune list not found' end
  local it = readQword('g_effect') or 0
  runeEffects()
  local d = SoM_runeFxBy[it]
  if not d then return 'pick an effect in "Give: effect" first' end
  local hd = SoM_runeHdr
  local b, e, cap = readQword(hd), readQword(hd + 8), readInteger(hd + 0x10) or 0
  local n = (e - b) // RUNE
  if n &gt;= cap then return string.format('rune list is full (%d of %d)', n, cap) end

  local ok, why = SoM_itemAdd(it)
  if not ok then return 'not given: ' .. why end
  local flags = 0x39
  for i = 0, n - 1 do
    local o = SoM_runeFxBy[readQword(b + i * RUNE + 0x18) or 0]
    if o and o[3] == d[3] and o[4] == d[4] then flags = readBytes(b + i * RUNE + 0x10, 1); break end
  end
  local id = readInteger(hd + 0x20) or 0
  for i = 0, n - 1 do
    local x = readInteger(b + i * RUNE + 0x20) or 0
    if x &gt;= id then id = x + 1 end
  end
  writeQword(e, 0)
  writeQword(e + 8, 0xFFFFFFFF)
  writeQword(e + 0x10, flags | ((d[5] &amp; 0xFF) &lt;&lt; 8))
  writeQword(e + 0x18, it)
  writeQword(e + 0x20, id)
  writeInteger(hd + 0x20, id + 1)
  writeQword(hd + 8, e + RUNE)
  SoM_runeEffectById = nil
  SoM_runeRefresh()
  return string.format('added %s as rune #%X (%d runes now)', fxLabel(d), id, n + 1)
end

function SoM_runeSyncSave()
  if not SoM_runeHdr and not SoM_findRunes() then return 'rune list not found' end
  if not SoM_findItemList() then return 'saved item list not found' end
  runeEffects()
  local want = {}
  local b, n = readQword(SoM_runeHdr), runeCount()
  for i = 0, n - 1 do
    local it = readQword(b + i * RUNE + 0x18) or 0
    want[it] = (want[it] or 0) + 1
  end
  local obj = SoM_itemObj
  local have, fixed, failed = {}, 0, 0
  local lb, le = readQword(obj + 0x298), readQword(obj + 0x2A0)
  for a = lb, le - 0x10, 0x10 do
    local it = readQword(a) or 0
    if SoM_runeFxBy[it] or want[it] then have[it] = a end
  end
  for it, a in pairs(have) do
    local w = want[it] or 0
    if (readSmallInteger(a + 8) or 0) ~= w and w &gt; 0 then writeSmallInteger(a + 8, w); fixed = fixed + 1 end
  end
  for it, w in pairs(want) do
    if not have[it] then
      for _ = 1, w do
        if SoM_itemAdd(it) then fixed = fixed + 1 else failed = failed + 1 end
      end
    end
  end
  return string.format('saved item list synced: %d changes%s', fixed,
                       failed &gt; 0 and string.format(', %d not added (list full)', failed) or '')
end

function SoM_runeEnable()
  autoAssemble([[
    globalalloc(p_rune,8)
    globalalloc(r_index,4)
    globalalloc(g_effect,8)
    globalalloc(ev_effect,8)
    globalalloc(ev_pchance,8)
    globalalloc(ev_pdisplay,8)
    globalalloc(ev_pgain,8)
    globalalloc(ev_pflat,8)
    globalalloc(ev_ppct,8)
    globalalloc(ev_pres,8)
    globalalloc(ev_sync,1)
  ]])
  writeInteger('r_index', 0xFFFFFFFF)
  writeBytes('ev_sync', 1)
  runeDB = findGameDB()
  if not runeDB then
    messageDialog('Game database not found. Load a save first, then enable again.', mtError, mbOK)
    return false
  end
  SoM_runeHdr, SoM_runeFx = nil, nil
  local n = SoM_runeRefresh()
  if not SoM_runeHdr then
    messageDialog('Rune list not found. Load a save, then use "Refresh rune list".', mtWarning, mbOK)
  end
  local lastErr
  synchronize(function()
    if SoM_runeTimer then SoM_runeTimer.destroy() end
    SoM_runeTimer = createTimer(nil, false)
    SoM_runeTimer.Interval = 250
    SoM_runeTimer.OnTimer = function()
      local ok, err = pcall(function()
        updateRunePointer()
        mirrorEdits()
        if SoM_evTick then SoM_evTick() end
      end)
      if not ok and err ~= lastErr then
        lastErr = err
        print('SoM Rune Editor timer error: ' .. tostring(err))
      end
    end
    SoM_runeTimer.Enabled = true
  end)
  if SoM_evEnable then SoM_evEnable() end
  print(string.format('SoM Rune Editor: %d runes listed, list header %X', n, SoM_runeHdr or 0))
  return true
end

function SoM_runeDisable()
  synchronize(function()
    if SoM_runeTimer then SoM_runeTimer.destroy(); SoM_runeTimer = nil end
  end)
  if SoM_evDisable then SoM_evDisable() end
  local g = runeGroup()
  if g then
    eachChild(g, function(mr)
      local dsc = mr.Description
      if dsc == 'Select rune' or dsc == 'Effect (changes the whole rune)' or dsc == 'Give: effect'
         or dsc == 'Source captain faction' then remDD(mr) end
    end)
  end
  SoM_runeHdr, SoM_runeFx = nil, nil
end

local evLast, evChance, evProc = nil, nil, nil
local IDX = {}

local function fieldWhere(rec, key, test)
  local k = key .. string.format('%X', readQword(rec + 8) or 0)
  if not IDX[k] then IDX[k] = findField(rec, test) end
  if IDX[k] then
    local v = fieldValue(rec, IDX[k])
    if v and v &gt; 0x10000 and v &lt; 0x7fffffffffff and test(v) then return IDX[k] end
    return findField(rec, test)
  end
  return nil
end

local function fieldAddr(rec, idx)
  local dsc = readQword(rec + 8) + idx * 16
  if (readSmallInteger(dsc + 0xC) or 0) == 0 then return nil end
  return readQword(rec + 0x10) + readSmallInteger(dsc + 0xE) * 8
end

local function isChanceStruct(v)
  local d = readQword(v + 0x10) or 0
  return d &gt; 0x10000 and d &lt; 0x7fffffffffff and catOf(readQword(d + 8) or 0) == 'Combat/Buffs/ChanceTypes'
end

local function resolve(item)
  local r = {}
  local bi = fieldWhere(item, 'buff', function(v) return catOf(v) == 'Combat/Buffs' end)
  local buff = bi and fieldValue(item, bi)
  if buff and buff ~= 0 then
    r.buff = buff
    local ci = fieldWhere(buff, 'chance', isChanceStruct)
    local cs = ci and fieldValue(buff, ci)
    if cs and cs ~= 0 then r.chance = readQword(cs + 0x10) end
    local pi = fieldWhere(buff, 'proc', function(v) return catOf(v) == 'Combat/Buffs/Procs/Definitions' end)
    local proc = pi and fieldValue(buff, pi)
    if proc and proc ~= 0 then
      r.proc = proc
      local gi = fieldWhere(proc, 'gain', function(v) return catOf(v) == 'Combat/ResourceGain' end)
      if gi then
        r.gainRef = fieldAddr(proc, gi)
        local rg = readQword(r.gainRef) or 0
        if rg ~= 0 then
          local vals = readQword(rg + 0x10)
          r.flat, r.pct, r.res = vals, vals + 8, vals + 0x10
        end
      end
    end
  end
  local ui = fieldWhere(item, 'ui', function(v) return catOf(v) == 'Inventory/UIItemData' end)
  local uirec = ui and fieldValue(item, ui)
  if uirec and uirec ~= 0 then r.display = fieldAddr(uirec, 3) end
  return r
end

local function setPtr(name, a) writeQword(name, a or 0) end

if not SoM_evOrig or SoM_evOrig.pid ~= getOpenedProcessID() then
  SoM_evOrig = {pid = getOpenedProcessID(), flat = {}, gain = {}, text = {}, keyText = {}}
end
local O = SoM_evOrig
local strTab

local function findStrings()
  for _, h in ipairs(scan('73 74 72 69 6E 67 2E 73 74 72 64 62 00', vtByteArray, '+W*X*C', fsmNotAligned, '1', 0, 0x7fffffffffff)) do
    for x = h + 8 - h % 8, h + 0x80, 8 do
      local t, c, b = readQword(x) or 0, readQword(x + 8) or 0, readQword(x + 0x20) or 0
      if t ~= 0 and c &gt; 1000 and c &lt; 2000000 and b == t + c * 8 then return x end
    end
  end
  return nil
end

local function textOfId(id)
  local t, c, blob = readQword(strTab), readQword(strTab + 8), readQword(strTab + 0x20)
  local lo, hi = 0, c - 1
  while lo &lt;= hi do
    local m = (lo + hi) // 2
    local v = readInteger(t + m * 8)
    if v == id then return blob + readInteger(t + m * 8 + 4) * 2 end
    if v &lt; id then lo = m + 1 else hi = m - 1 end
  end
  return nil
end

local function textOf(item, peek)
  local ui = fieldWhere(item, 'ui', function(v) return catOf(v) == 'Inventory/UIItemData' end)
  local uirec = ui and fieldValue(item, ui)
  if not uirec or uirec == 0 then return nil end
  local ki = fieldWhere(uirec, 'uis', function(v) return (readString(v, 4) or '') == 'UIS_' end)
  local key = ki and fieldValue(uirec, ki)
  if not key then return nil end
  if O.keyText[key] == nil and not peek then
    O.keyText[key] = false
    strTab = strTab or findStrings()
    if not strTab then return nil end

    local near = O.keyArea and scan(string.format('%X', key), vtQword, '+W*X*C', fsmAligned, '8', O.keyArea - 0x1000000, O.keyArea + 0x1000000) or {}
    if #near == 0 then near = scan(string.format('%X', key), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff) end
    for _, h in ipairs(near) do
      local a = (readInteger(h + 0xC) or 1) == 0 and textOfId(readInteger(h + 8))
      if a then O.keyText[key], O.keyArea = a, h; break end
    end
  end
  return O.keyText[key] or nil
end

local function units(s)
  local n = 0
  for i = 1, #s do local b = s:byte(i); if b &lt; 0x80 or b &gt;= 0xC0 then n = n + 1 end end
  return n
end

local function num(v)
  if math.abs(v - math.floor(v + 0.5)) &lt; 0.001 then return tostring(math.floor(v + 0.5)) end
  return string.format('%.1f', v)
end

local function setText(addr, from, to)
  local o = O.text[addr]
  if not o then
    local s = readString(addr, 2000, true)
    if not s then return false end
    o = {s = s, n = units(s)}
    O.text[addr] = o
  end
  local s, new = o.s, nil
  local a, b = s:find('+' .. from, 1, true)
  if a then
    new = s:sub(1, a - 1) .. '+' .. to .. s:sub(b + 1)
    if units(new) &gt; o.n then new = s:sub(1, a - 1) .. to .. s:sub(b + 1) end
  else
    a, b = s:find('%f[%d]' .. from:gsub('%.', '%%.') .. '%f[%D]')
    if not a then return false end
    new = s:sub(1, a - 1) .. to .. s:sub(b + 1)
  end
  if units(new) &gt; o.n then return false end
  writeString(addr, new, true)
  local pad = {}
  for i = 1, (o.n - units(new) + 1) * 2 do pad[i] = 0 end
  writeBytes(addr + units(new) * 2, pad)
  return true
end

local function updateTexts()
  local done = {}
  for _, e in ipairs(runeEffects()) do
    local r = resolve(e[1])
    if r.proc and r.gainRef then
      local cur = readQword(r.gainRef) or 0
      local og = O.gain[r.proc] or cur
      local from, vals = O.flat[og], cur ~= 0 and readQword(cur + 0x10)
      if from and vals then
        local changed = num(from) ~= num(readFloat(vals))

        local addr = textOf(e[1], not changed)        if addr and not done[addr] and (changed or O.text[addr]) then
          done[addr] = true
          setText(addr, num(from), num(readFloat(vals)))
        end
      end
    end
  end
end

local EDITS = {chance = {}, display = {}, gain = {}, flat = {}, pct = {}}
local ORDER = {'gain', 'flat', 'pct', 'chance', 'display'}

local SHORT = {chance = 'c', display = 'd', gain = 'g', flat = 'f', pct = 'p'}
local LONG = {c = 'chance', d = 'display', g = 'gain', f = 'flat', p = 'pct'}

local function editsPath()
  local dir = os.getenv('APPDATA')
  return dir and (dir .. '\\SoM_RuneEdits.txt') or nil
end

local function saveEdits()
  local path = editsPath()
  local out = {}
  for _, kind in ipairs(ORDER) do
    for key, v in pairs(EDITS[kind]) do
      out[#out + 1] = string.format('%s|%s|%s', SHORT[kind], key, type(v) == 'number' and string.format('%.6g', v) or v)
    end
  end
  local fh = path and io.open(path, 'w')
  if not fh then return end
  fh:write(table.concat(out, '\n'))
  fh:close()
  if #out == 0 then os.remove(path) end
end

local function loadEdits()
  EDITS = {chance = {}, display = {}, gain = {}, flat = {}, pct = {}}
  local path = editsPath()
  local fh = path and io.open(path, 'r')
  if not fh then return 0 end
  local n = 0
  for line in fh:lines() do
    local k, key, v = line:match('^(%a)|([^|]+)|(.+)$')
    local kind = k and LONG[k]
    if kind then
      EDITS[kind][key] = (kind == 'gain') and v or tonumber(v)
      n = n + 1
    end
  end
  fh:close()
  return n
end

local function byName(cat)
  local t = {}
  for _, r in ipairs(records(cat)) do t[r[2]] = r[1] end
  return t
end

local function applyEdits()
  local items, procs, gains = byName('Inventory/Items'), byName('Combat/Buffs/Procs/Definitions'),
                              byName('Combat/ResourceGain')
  local n = 0
  for proc, rg in pairs(EDITS.gain) do
    local pr, g = procs[proc], gains[rg]
    local gi = pr and fieldWhere(pr, 'gain', function(v) return catOf(v) == 'Combat/ResourceGain' end)
    local a = gi and fieldAddr(pr, gi)
    if a and g then writeQword(a, g); n = n + 1 end
  end
  for kind, off in pairs({flat = 0, pct = 8}) do
    for rg, v in pairs(EDITS[kind]) do
      local g = gains[rg]
      if g then writeFloat(readQword(g + 0x10) + off, v); n = n + 1 end
    end
  end
  for name, v in pairs(EDITS.chance) do
    local r = items[name] and resolve(items[name]) or {}
    if r.chance then writeFloat(r.chance, v); n = n + 1 end
  end
  for name, v in pairs(EDITS.display) do
    local r = items[name] and resolve(items[name]) or {}
    if r.display then writeQword(r.display, v); n = n + 1 end
  end
  return n
end

local evSeen = nil
local function noteEdits(it)
  local pc, pd, pg = readQword('ev_pchance') or 0, readQword('ev_pdisplay') or 0, readQword('ev_pgain') or 0
  local pf, pp = readQword('ev_pflat') or 0, readQword('ev_ppct') or 0
  local now = {
    chance = pc ~= 0 and readFloat(pc) or nil,
    display = pd ~= 0 and readInteger(pd) or nil,
    gain = pg ~= 0 and readQword(pg) or nil,
    flat = pf ~= 0 and readFloat(pf) or nil,
    pct = pp ~= 0 and readFloat(pp) or nil,
  }
  local seen = evSeen
  evSeen = now
  if not seen or seen.item ~= it then evSeen.item = it; return end
  evSeen.item = it
  local changed = false
  local itemName = recName(it)
  local rgName = now.gain and recName(now.gain)
  if now.chance and now.chance ~= seen.chance then EDITS.chance[itemName] = now.chance; changed = true end
  if now.display and now.display ~= seen.display then EDITS.display[itemName] = now.display; changed = true end
  local amount = false
  if now.gain and now.gain ~= seen.gain and evProc then
    if O.gain[evProc] == nil then O.gain[evProc] = seen.gain end
    EDITS.gain[recName(evProc)] = rgName; changed, amount = true, true
  end
  if rgName and now.gain == seen.gain then
    if now.flat and now.flat ~= seen.flat then EDITS.flat[rgName] = now.flat; changed, amount = true, true end
    if now.pct and now.pct ~= seen.pct then EDITS.pct[rgName] = now.pct; changed = true end
  end
  if changed then saveEdits() end  if amount then updateTexts() end
end

function SoM_evClearEdits()
  EDITS = {chance = {}, display = {}, gain = {}, flat = {}, pct = {}}
  local path = editsPath()
  if path then os.remove(path) end
  return 'remembered effect edits cleared (the edited values stay until the game is restarted)'
end

function SoM_evTick()
  if not getAddressSafe('ev_effect') then return end

  local pr = readQword('p_rune') or 0
  local it = pr ~= 0 and (readQword(pr + 0x18) or 0) or 0
  if it ~= readQword('ev_effect') then writeQword('ev_effect', it) end
  if it ~= evLast then
    evLast, evChance = it, nil
    local r = it ~= 0 and resolve(it) or {}
    setPtr('ev_pchance', r.chance); setPtr('ev_pdisplay', r.display)
    setPtr('ev_pgain', r.gainRef); setPtr('ev_pflat', r.flat); setPtr('ev_ppct', r.pct); setPtr('ev_pres', r.res)
    evProc = r.proc
  end

  local pc, pd = readQword('ev_pchance') or 0, readQword('ev_pdisplay') or 0
  if pc ~= 0 then
    local c = readFloat(pc)
    if c and c ~= evChance then
      evChance = c
      if pd ~= 0 and (readBytes('ev_sync', 1) or 0) == 1 then writeQword(pd, math.floor(c * 100 + 0.5)) end
    end
  end

  local pg = readQword('ev_pgain') or 0
  if pg ~= 0 then
    local rg = readQword(pg) or 0
    local vals = rg ~= 0 and readQword(rg + 0x10) or 0
    if vals ~= 0 and readQword('ev_pflat') ~= vals then
      setPtr('ev_pflat', vals); setPtr('ev_ppct', vals + 8); setPtr('ev_pres', vals + 0x10)
    end
  end
  noteEdits(it)
end

local function evGroup() return getAddressList().getMemoryRecordByDescription('Rune Editor') end

function SoM_evEnable()
  evLast, IDX = nil, {}
  local fx, lines = runeEffects(), {}
  for _, e in ipairs(fx) do lines[#lines + 1] = string.format('%016X:%s', e[1], fxLabel(e)) end
  local gains = {'0000000000000000:&lt;none&gt;'}
  for _, r in ipairs(records('Combat/ResourceGain')) do gains[#gains + 1] = string.format('%016X:%s', r[1], r[2]) end
  local g = evGroup()
  if g then
    eachChild(g, function(mr)
      if mr.Description == 'Effect being edited' then addDD(mr, table.concat(lines, '\n'))
      elseif mr.Description == 'Amount record (shared by every level of this effect)' then addDD(mr, table.concat(gains, '\n')) end
    end)
  end
  evSeen = nil

  for _, r in ipairs(records('Combat/ResourceGain')) do
    if O.flat[r[1]] == nil then O.flat[r[1]] = readFloat(readQword(r[1] + 0x10)) end
  end
  local n = loadEdits()
  if n &gt; 0 then
    local procs = byName('Combat/Buffs/Procs/Definitions')
    for proc in pairs(EDITS.gain) do
      local pr = procs[proc]
      local gi = pr and fieldWhere(pr, 'gain', function(v) return catOf(v) == 'Combat/ResourceGain' end)
      local a = gi and fieldAddr(pr, gi)
      if a and O.gain[pr] == nil then O.gain[pr] = readQword(a) end
    end
    local applied = applyEdits()
    if next(EDITS.gain) or next(EDITS.flat) then updateTexts() end
    print(string.format('SoM Rune Editor: re-applied %d of %d remembered effect edits from %s',
                        applied, n, editsPath()))
  end
  return true
end

function SoM_evDisable()
  local g = evGroup()
  if g then
    eachChild(g, function(mr)
      if mr.Description == 'Effect being edited' or mr.Description == 'Amount record (shared by every level of this effect)' then remDD(mr) end
    end)
  end
end

if not SoM_runeEnable() then error('SoM Rune Editor: enable failed') end
[DISABLE]
{$lua}
if syntaxcheck then return end
if SoM_runeDisable then SoM_runeDisable() end
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>1151</ID>
          <Description>"Select rune"</Description>
          <Color>FF00FF</Color>
          <VariableType>4 Bytes</VariableType>
          <Address>r_index</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1152</ID>
          <Description>"Hover select (rune menu)"</Description>
          <Color>4080FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
aobscanmodule(aob_runehover,ShadowOfMordor.exe,E8 ?? ?? ?? ?? 4C 8B F8 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 48 18 E8)
alloc(newmem_runehover,$80,aob_runehover)
label(return_runehover)
label(done_runehover)
label(rune_hover)
registersymbol(rune_hover)
registersymbol(aob_runehover)

newmem_runehover:
  mov r15,rax
  test rax,rax
  je done_runehover
  mov [rune_hover],rax
done_runehover:
  jmp return_runehover

rune_hover:
  dq 0

aob_runehover+5:
  jmp newmem_runehover
  nop
return_runehover:

[DISABLE]
aob_runehover+5:
  db 4C 8B F8 48 85 C0
unregistersymbol(rune_hover)
unregistersymbol(aob_runehover)
dealloc(newmem_runehover)
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>1153</ID>
          <Description>"Refresh rune list"</Description>
          <Color>4080FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_runeRefresh then error('Enable Rune Editor first') end
print(string.format('SoM Rune Editor: %d runes listed', SoM_runeRefresh()))
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>1154</ID>
          <Description>"Rune entry (address)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>8 Bytes</VariableType>
          <Address>p_rune</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1155</ID>
          <Description>"Effect (changes the whole rune)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>8 Bytes</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>18</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1156</ID>
          <Description>"Rune ID (weapon slots refer to this - don't change)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>4 Bytes</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>20</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1157</ID>
          <Description>"Source captain faction"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>8 Bytes</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>0</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1158</ID>
          <Description>"Source orc index in that army (-1 = none)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>8</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1159</ID>
          <Description>"Captain level at the drop"</Description>
          <VariableType>Byte</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>11</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1160</ID>
          <Description>"Flags (not decoded yet)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>Byte</VariableType>
          <Address>p_rune</Address>
          <Offsets>
            <Offset>10</Offset>
          </Offsets>
        </CheatEntry>
        <CheatEntry>
          <ID>1161</ID>
          <Description>"Sync saved list with my runes (repairs older gives/edits)"</Description>
          <Color>4080FF</Color>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_runeSyncSave then error('Enable Rune Editor first') end
print('SoM Rune Editor: ' .. SoM_runeSyncSave())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>1171</ID>
          <Description>"Effect values (of the selected rune)"</Description>
          <Options moHideChildren="1"/>
          <Color>C080C0</Color>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>1162</ID>
              <Description>"Effect being edited"</Description>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>8 Bytes</VariableType>
              <Address>ev_effect</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1163</ID>
              <Description>"Forget my remembered effect edits"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_evClearEdits then error('Enable Rune Editor first') end
print('SoM Rune Editor: ' .. SoM_evClearEdits())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
            <CheatEntry>
              <ID>1164</ID>
              <Description>"Chance (real, 0..1, per level)"</Description>
              <VariableType>Float</VariableType>
              <Address>ev_pchance</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1165</ID>
              <Description>"Shown % in the rune text"</Description>
              <VariableType>4 Bytes</VariableType>
              <Address>ev_pdisplay</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1166</ID>
              <Description>"Keep shown % = chance"</Description>
              <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
              <VariableType>Byte</VariableType>
              <Address>ev_sync</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1167</ID>
              <Description>"Amount record (shared by every level of this effect)"</Description>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>8 Bytes</VariableType>
              <Address>ev_pgain</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1168</ID>
              <Description>"Amount: flat (shared record!)"</Description>
              <VariableType>Float</VariableType>
              <Address>ev_pflat</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1169</ID>
              <Description>"Amount: percent 0..1 (shared record!)"</Description>
              <VariableType>Float</VariableType>
              <Address>ev_ppct</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
            <CheatEntry>
              <ID>1170</ID>
              <Description>"Amount: resource (0 health, 1 focus, 2 arrows)"</Description>
              <VariableType>4 Bytes</VariableType>
              <Address>ev_pres</Address>
              <Offsets>
                <Offset>0</Offset>
              </Offsets>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
        <CheatEntry>
          <ID>1174</ID>
          <Description>"Give a new rune"</Description>
          <Options moHideChildren="1"/>
          <GroupHeader>1</GroupHeader>
          <CheatEntries>
            <CheatEntry>
              <ID>1172</ID>
              <Description>"Give: effect"</Description>
              <ShowAsHex>1</ShowAsHex>
              <VariableType>8 Bytes</VariableType>
              <Address>g_effect</Address>
            </CheatEntry>
            <CheatEntry>
              <ID>1173</ID>
              <Description>"Give the rune"</Description>
              <Color>4080FF</Color>
              <VariableType>Auto Assembler Script</VariableType>
              <AssemblerScript>[ENABLE]
{$lua}
if syntaxcheck then return end
if not SoM_giveRune then error('Enable Rune Editor first') end
print('SoM Rune Editor: ' .. SoM_giveRune())
local mr = memrec
synchronize(function()
  local t = createTimer(nil, false)
  t.Interval = 100
  t.OnTimer = function(tm) tm.destroy(); if mr then mr.Active = false end end
  t.Enabled = true
end)
[DISABLE]
{$lua}
if syntaxcheck then return end
</AssemblerScript>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>1180</ID>
      <Description>"Drop Chooser"</Description>
      <Options moHideChildren="1"/>
      <Color>40C0C0</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript Async="1">[ENABLE]
{$lua}
if syntaxcheck then return 'define(DC_RUNEDATA,0)' end

local DB_STR = '64 61 74 61 62 61 73 65 5C 67 61 6D 65 5C 47 61 6D 65 2E 67 61 6D 65 64 62 00'
local CAT_STRIDE, REC_STRIDE = 0x58, 0x30
local runeDB

local function scan(input, vtype, flags, align, alignParam, startA, stopA)
  local ms = createMemScan()
  ms.firstScan(soExactValue, vtype, rtRounded, input, nil, startA, stopA, flags,
               align, alignParam, true, true, false, false)
  ms.waitTillDone()
  local fl = createFoundList(ms)
  fl.initialize()
  local res = {}
  for i = 0, fl.Count - 1 do res[#res + 1] = tonumber(fl.Address[i], 16) end
  fl.destroy()
  ms.destroy()
  return res
end

local function findGameDB()
  local base = getAddress(process)
  local hits = scan(DB_STR, vtByteArray, '*X*W*C', fsmNotAligned, '1', base, base + getModuleSize(process))
  for _, h in ipairs(hits) do
    local db = readQword(h - 8)
    if db and db ~= 0 and readString(readQword(db) or 0, 20) == 'GameDatabase' then return db end
  end
  return nil
end

local function records(name)
  local base, cnt = readQword(runeDB + 0x38), readInteger(runeDB + 0x30)
  local list = {}
  for i = 0, cnt - 1 do
    local c = base + i * CAT_STRIDE
    if readString(readQword(c), 100) == name then
      local first, n = readQword(c + 0x38), readInteger(c + 0x28)
      for k = 0, n - 1 do
        local r = first + k * REC_STRIDE
        local s = readString(readQword(r + 0x20), 100)
        if s then list[#list + 1] = {r, s} end
      end
      break
    end
  end
  return list
end

local function filtered(cat)
  local out = {}
  for _, r in ipairs(records(cat[1])) do
    if r[2]:find(cat[2]) then out[#out + 1] = r end
  end
  return out
end

local function recName(r)
  if not r or r == 0 then return nil end
  return readString(readQword(r + 0x20) or 0, 100)
end

local function catOf(r)
  local c = readQword(r + 0x18) or 0
  return c ~= 0 and readString(readQword(c) or 0, 60) or nil
end

local function addDD(mr, str)
  mr.DropDownList.Text = str
  mr.DropDownReadOnly = false
  mr.DropDownDescriptionOnly = true
  mr.DisplayAsDropDownListItem = true
end

local function remDD(mr)
  mr.DropDownList.Text = ''
  mr.DisplayAsDropDownListItem = false
end

local function eachChild(mr, fn)
  for i = 0, mr.Count - 1 do
    local c = mr.Child[i]
    fn(c)
    eachChild(c, fn)
  end
end

local function fieldValue(rec, idx)
  local dsc = readQword(rec + 8) + idx * 16
  if (readSmallInteger(dsc + 0xC) or 0) == 0 then return nil end
  return readQword(readQword(rec + 0x10) + readSmallInteger(dsc + 0xE) * 8)
end

local function findField(rec, test)
  for idx = 0, 63 do
    local v = fieldValue(rec, idx)
    if v and v &gt; 0x10000 and v &lt; 0x7fffffffffff and test(v) then return idx end
  end
  return nil
end

local uiIdxBySchema, nameIdxBySchema = {}, {}
local function hasName(item)
  local s = readQword(item + 8)
  if uiIdxBySchema[s] == nil then
    uiIdxBySchema[s] = findField(item, function(v) return catOf(v) == 'Inventory/UIItemData' end) or false
  end
  if not uiIdxBySchema[s] then return false end
  local ui = fieldValue(item, uiIdxBySchema[s])
  if not ui or ui == 0 then return false end
  local us = readQword(ui + 8)
  if not nameIdxBySchema[us] then
    nameIdxBySchema[us] = findField(ui, function(v) return (readString(v, 8) or ''):sub(1, 7) == 'InvUIN_' end)
    if not nameIdxBySchema[us] then return false end
  end
  local key = fieldValue(ui, nameIdxBySchema[us])
  return key ~= nil and (readString(key, 8) or '') ~= ''
end

local function runeEffects()
  if SoM_runeFx then return SoM_runeFx end
  local fx, byAddr = {}, {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_'})) do
    local v = readQword(r[1] + 0x10) or 0
    local look = ''
    for o = 0x20, 0x38, 8 do
      local q = readQword(v + o) or 0
      if q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/WorldItemData' then
        look = recName(q) or ''; break
      end
    end
    local weapon = look:match('^(Sword)') or look:match('^(Dagger)') or look:match('^(Bow)')
    if weapon then
      local e = {r[1], r[2], weapon, look:find('Gold') ~= nil, tonumber(r[2]:match('_(%d+)$')) or 0, hasName(r[1])}
      fx[#fx + 1] = e
      byAddr[r[1]] = e
    end
  end
  table.sort(fx, function(a, b)
    if a[3] ~= b[3] then return a[3] &gt; b[3] end
    if a[4] ~= b[4] then return a[4] end
    return a[2] &lt; b[2]
  end)
  SoM_runeFx, SoM_runeFxBy = fx, byAddr
  return fx
end

local function fxLabel(e)
  return string.format('[%s%s] %s%s', e[3], e[4] and ' EPIC' or '', e[2]:gsub('^BD_', ''), e[6] and '' or ' ?')
end

local RUNE = 0x28

local function isItem(q) return q and q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/Items' end

local function isRuneEntry(e)
  local f = readQword(e) or -1
  if f ~= 0 and not (f &gt; 0x10000 and f &lt; 0x7fffffffffff and catOf(f) == 'Faction/Definition') then return false end
  local id = readInteger(e + 0x20)
  return isItem(readQword(e + 0x18)) and id and id &gt; 0 and id &lt; 0x100000
end

function SoM_findRunes()
  SoM_runeHdr = nil
  local anchors = {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Dag_Fear$'})) do anchors[#anchors + 1] = r[1] end
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Brutal_Terror$'})) do anchors[#anchors + 1] = r[1] end
  for _, a in ipairs(anchors) do
    for _, h in ipairs(scan(string.format('%X', a), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
      local e = h - 0x18
      if isRuneEntry(e) then
        local b = e
        while isRuneEntry(b - RUNE) do b = b - RUNE end
        for _, hd in ipairs(scan(string.format('%X', b), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
          local en = readQword(hd + 8) or 0
          if en &gt; b and (en - b) % RUNE == 0 and (en - b) // RUNE &lt;= (readInteger(hd + 0x10) or 0) then
            SoM_runeHdr = hd
            return hd
          end
        end
      end
    end
  end
  return nil
end

local ITEM = 0x10
SoM_itemObj = nil

function SoM_findItemList()
  SoM_itemObj = nil
  if not SoM_runeHdr then SoM_findRunes() end
  if not SoM_runeHdr then return nil end
  local inv = SoM_runeHdr - 0x30
  for _, h in ipairs(scan(string.format('%X', inv), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
    local obj = h - 0x230
    local b, e = readQword(obj + 0x298) or 0, readQword(obj + 0x2A0) or 0
    local cap = readInteger(obj + 0x2A8) or 0
    if b ~= 0 and e &gt;= b and (e - b) % ITEM == 0 and (e - b) // ITEM &lt;= cap and cap &lt; 100000 then
      SoM_itemObj = obj
      return obj
    end
  end
  return nil
end

local function itemEntry(item)
  local obj = SoM_itemObj
  local b, e = readQword(obj + 0x298), readQword(obj + 0x2A0)
  for a = b, e - ITEM, ITEM do
    if readQword(a) == item then return a end
  end
  return nil
end

function SoM_itemAdd(item, template)
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a = itemEntry(item)
  if a then
    writeSmallInteger(a + 8, (readSmallInteger(a + 8) or 0) + 1)
    return true
  end
  local obj = SoM_itemObj
  local b, e, cap = readQword(obj + 0x298), readQword(obj + 0x2A0), readInteger(obj + 0x2A8) or 0
  if (e - b) // ITEM &gt;= cap then
    return nil, string.format('the saved item list is full (%d of %d) - pick up any item so the game grows it, then try again', (e - b) // ITEM, cap)
  end
  writeQword(e, item)
  writeSmallInteger(e + 8, 1)
  writeSmallInteger(e + 0xA, 0)
  writeInteger(e + 0xC, template and readInteger(template + 0xC) or 7)
  writeQword(obj + 0x2A0, e + ITEM)
  return true
end

function SoM_itemSwap(old, new)
  if old == new then return true end
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a, bnew = itemEntry(old), itemEntry(new)
  if a and (readSmallInteger(a + 8) or 0) &lt;= 1 and not bnew then
    writeQword(a, new)
    return true
  end
  local ok, why = SoM_itemAdd(new, a)
  if not ok then return nil, why end
  if a then
    local c = readSmallInteger(a + 8) or 0
    if c &gt; 1 then
      writeSmallInteger(a + 8, c - 1)
    else
      local obj = SoM_itemObj
      local e = readQword(obj + 0x2A0)
      local n = (e - (a + ITEM))
      if n &gt; 0 then writeBytes(a, readBytes(a + ITEM, n, true)) end
      writeQword(obj + 0x2A0, e - ITEM)
    end
  end
  return true
end

local WEAPONS = {[1] = 'Sword', [2] = 'Dagger', [3] = 'Bow'}
local dropGroupName = 'Drop Chooser'

local function baseName(n) return (n:gsub('_%d+$', '')) end

local byBase
local function indexByBase()
  if byBase then return byBase end
  byBase = {}
  for _, e in ipairs(runeEffects()) do
    local b = baseName(e[2])
    byBase[b] = byBase[b] or {}
    byBase[b][e[5]] = e
  end
  return byBase
end

local function atLevel(e, lvl)
  local fam = indexByBase()[baseName(e[2])]
  if not fam or e[5] == 0 then return e end
  if fam[lvl] then return fam[lvl] end
  local best, bd
  for l, x in pairs(fam) do
    local dd = math.abs(l - lvl)
    if not bd or dd &lt; bd then best, bd = x, dd end
  end
  return best or e
end

local function candidates(mode, weapon)
  local out = {}
  local w = WEAPONS[weapon]
  for _, e in ipairs(runeEffects()) do
    if e[6] and (not w or e[3] == w) then
      if mode == 4 or (mode == 2 and not e[4]) or (mode == 3 and e[4]) then out[#out + 1] = e end
    end
  end
  return out
end

local function refill()
  if not getAddressSafe('dc_table') then return end
  local mode = readBytes('dc_mode', 1) or 0
  local t = getAddress('dc_table')
  if mode == 0 then return end
  local weapon = readBytes('dc_weapon', 1) or 0
  if mode == 1 then
    local it = readQword('dc_effect') or 0
    runeEffects()
    local e = SoM_runeFxBy[it]
    for lvl = 0, 30 do writeQword(t + lvl * 8, e and atLevel(e, lvl)[1] or 0) end
    return
  end
  local list = candidates(mode, weapon)
  for lvl = 0, 30 do
    if #list == 0 then writeQword(t + lvl * 8, 0)
    else
      local e = list[math.random(#list)]
      writeQword(t + lvl * 8, e[4] and e[1] or atLevel(e, lvl)[1])
    end
  end
end

local function dropGroup() return getAddressList().getMemoryRecordByDescription(dropGroupName) end

function SoM_dropEnable()
  runeDB = findGameDB()
  if not runeDB then
    messageDialog('Game database not found. Load a save first, then enable again.', mtError, mbOK)
    return false
  end
  SoM_runeFx, byBase = nil, nil
  local fx = runeEffects()
  local lines = {}
  for _, e in ipairs(fx) do lines[#lines + 1] = string.format('%016X:%s', e[1], fxLabel(e)) end
  local g = dropGroup()
  if g then
    eachChild(g, function(mr)
      if mr.Description == 'Specific effect (mode 1)' then addDD(mr, table.concat(lines, '\n')) end
    end)
  end
  local lastErr
  synchronize(function()
    if SoM_dropTimer then SoM_dropTimer.destroy() end
    SoM_dropTimer = createTimer(nil, false)
    SoM_dropTimer.Interval = 250
    SoM_dropTimer.OnTimer = function()
      local ok, err = pcall(refill)
      if not ok and err ~= lastErr then
        lastErr = err
        print('SoM Drop Chooser timer error: ' .. tostring(err))
      end
    end
    SoM_dropTimer.Enabled = true
  end)
  print(string.format('SoM Drop Chooser: %d rune effects loaded', #fx))
  return true
end

function SoM_dropDisable()
  synchronize(function()
    if SoM_dropTimer then SoM_dropTimer.destroy(); SoM_dropTimer = nil end
  end)
  local g = dropGroup()
  if g then
    eachChild(g, function(mr)
      if mr.Description == 'Specific effect (mode 1)' then remDD(mr) end
    end)
  end
end

if not SoM_dropEnable() then error('SoM Drop Chooser: enable failed') end
local ar = AOBScanModuleUnique('ShadowOfMordor.exe', '48 89 74 24 ?? 44 89 44 24 ?? 57 41 54 41 55 41 56 41 57 48 83 EC 70')
if not ar then error('Drop Chooser: add rune not found (game version?)') end
local rel = readInteger(ar + 0x27); if rel &gt;= 0x80000000 then rel = rel - 0x100000000 end
return string.format('define(DC_RUNEDATA,%X)', ar + 0x2B + rel)
{$asm}
aobscanmodule(aob_addrune,ShadowOfMordor.exe,4C 89 44 24 18 53 55 56 57 41 57 48 83 EC ?? 48 8B F1 48 8B CA 41 8B E9)
alloc(newmem_addrune,$400,aob_addrune)
label(return_addrune)
label(orig_addrune)
label(dc_mode)
label(dc_weapon)
label(dc_effect)
label(dc_table)
label(dc_count)
registersymbol(dc_mode)
registersymbol(dc_weapon)
registersymbol(dc_effect)
registersymbol(dc_table)
registersymbol(dc_count)
registersymbol(aob_addrune)

newmem_addrune:
  cmp byte ptr [dc_mode],0
  je orig_addrune
  test rdx,rdx
  je orig_addrune
  test r8,r8
  je orig_addrune
  push rcx
  push rdx
  push r8
  push r9
  sub rsp,28
  mov rcx,rdx
  mov rax,DC_RUNEDATA
  call rax
  add rsp,28
  pop r9
  pop r8
  pop rdx
  pop rcx
  test rax,rax
  je orig_addrune
  movzx eax,byte ptr [r8+11]
  cmp eax,1E
  jbe @f
  mov eax,1E
@@:
  lea r10,[dc_table]
  mov r11,[r10+rax*8]
  test r11,r11
  je orig_addrune
  mov rdx,r11
  inc dword ptr [dc_count]
orig_addrune:
  mov [rsp+18],r8
  jmp return_addrune

dc_effect:
  dq 0
dc_count:
  dd 0
dc_mode:
  db 0
dc_weapon:
  db 0 0 0
dc_table:
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0

aob_addrune:
  jmp newmem_addrune
return_addrune:

[DISABLE]
{$lua}
if syntaxcheck then return end
if SoM_dropDisable then SoM_dropDisable() end
{$asm}
aob_addrune:
  db 4C 89 44 24 18
unregistersymbol(dc_mode)
unregistersymbol(dc_weapon)
unregistersymbol(dc_effect)
unregistersymbol(dc_table)
unregistersymbol(dc_count)
unregistersymbol(aob_addrune)
dealloc(newmem_addrune)
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>1176</ID>
          <Description>"Mode"</Description>
          <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Off
1:Specific effect
2:Random regular
3:Random epic
4:Random any
</DropDownList>
          <VariableType>Byte</VariableType>
          <Address>dc_mode</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1177</ID>
          <Description>"Weapon (random modes)"</Description>
          <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:Any
1:Sword
2:Dagger
3:Bow
</DropDownList>
          <VariableType>Byte</VariableType>
          <Address>dc_weapon</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1178</ID>
          <Description>"Specific effect (mode 1)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>8 Bytes</VariableType>
          <Address>dc_effect</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1179</ID>
          <Description>"runes changed (times)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dc_count</Address>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>1183</ID>
      <Description>"All Runes"</Description>
      <Options moHideChildren="1"/>
      <Color>C0A040</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript Async="1">[ENABLE]
{$lua}
if syntaxcheck then return 'define(AR_REMOVE,0)' end

local DB_STR = '64 61 74 61 62 61 73 65 5C 67 61 6D 65 5C 47 61 6D 65 2E 67 61 6D 65 64 62 00'
local CAT_STRIDE, REC_STRIDE = 0x58, 0x30
local runeDB

local function scan(input, vtype, flags, align, alignParam, startA, stopA)
  local ms = createMemScan()
  ms.firstScan(soExactValue, vtype, rtRounded, input, nil, startA, stopA, flags,
               align, alignParam, true, true, false, false)
  ms.waitTillDone()
  local fl = createFoundList(ms)
  fl.initialize()
  local res = {}
  for i = 0, fl.Count - 1 do res[#res + 1] = tonumber(fl.Address[i], 16) end
  fl.destroy()
  ms.destroy()
  return res
end

local function findGameDB()
  local base = getAddress(process)
  local hits = scan(DB_STR, vtByteArray, '*X*W*C', fsmNotAligned, '1', base, base + getModuleSize(process))
  for _, h in ipairs(hits) do
    local db = readQword(h - 8)
    if db and db ~= 0 and readString(readQword(db) or 0, 20) == 'GameDatabase' then return db end
  end
  return nil
end

local function records(name)
  local base, cnt = readQword(runeDB + 0x38), readInteger(runeDB + 0x30)
  local list = {}
  for i = 0, cnt - 1 do
    local c = base + i * CAT_STRIDE
    if readString(readQword(c), 100) == name then
      local first, n = readQword(c + 0x38), readInteger(c + 0x28)
      for k = 0, n - 1 do
        local r = first + k * REC_STRIDE
        local s = readString(readQword(r + 0x20), 100)
        if s then list[#list + 1] = {r, s} end
      end
      break
    end
  end
  return list
end

local function filtered(cat)
  local out = {}
  for _, r in ipairs(records(cat[1])) do
    if r[2]:find(cat[2]) then out[#out + 1] = r end
  end
  return out
end

local function recName(r)
  if not r or r == 0 then return nil end
  return readString(readQword(r + 0x20) or 0, 100)
end

local function catOf(r)
  local c = readQword(r + 0x18) or 0
  return c ~= 0 and readString(readQword(c) or 0, 60) or nil
end

local function addDD(mr, str)
  mr.DropDownList.Text = str
  mr.DropDownReadOnly = false
  mr.DropDownDescriptionOnly = true
  mr.DisplayAsDropDownListItem = true
end

local function remDD(mr)
  mr.DropDownList.Text = ''
  mr.DisplayAsDropDownListItem = false
end

local function eachChild(mr, fn)
  for i = 0, mr.Count - 1 do
    local c = mr.Child[i]
    fn(c)
    eachChild(c, fn)
  end
end

local function fieldValue(rec, idx)
  local dsc = readQword(rec + 8) + idx * 16
  if (readSmallInteger(dsc + 0xC) or 0) == 0 then return nil end
  return readQword(readQword(rec + 0x10) + readSmallInteger(dsc + 0xE) * 8)
end

local function findField(rec, test)
  for idx = 0, 63 do
    local v = fieldValue(rec, idx)
    if v and v &gt; 0x10000 and v &lt; 0x7fffffffffff and test(v) then return idx end
  end
  return nil
end

local uiIdxBySchema, nameIdxBySchema = {}, {}
local function hasName(item)
  local s = readQword(item + 8)
  if uiIdxBySchema[s] == nil then
    uiIdxBySchema[s] = findField(item, function(v) return catOf(v) == 'Inventory/UIItemData' end) or false
  end
  if not uiIdxBySchema[s] then return false end
  local ui = fieldValue(item, uiIdxBySchema[s])
  if not ui or ui == 0 then return false end
  local us = readQword(ui + 8)
  if not nameIdxBySchema[us] then
    nameIdxBySchema[us] = findField(ui, function(v) return (readString(v, 8) or ''):sub(1, 7) == 'InvUIN_' end)
    if not nameIdxBySchema[us] then return false end
  end
  local key = fieldValue(ui, nameIdxBySchema[us])
  return key ~= nil and (readString(key, 8) or '') ~= ''
end

local function runeEffects()
  if SoM_runeFx then return SoM_runeFx end
  local fx, byAddr = {}, {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_'})) do
    local v = readQword(r[1] + 0x10) or 0
    local look = ''
    for o = 0x20, 0x38, 8 do
      local q = readQword(v + o) or 0
      if q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/WorldItemData' then
        look = recName(q) or ''; break
      end
    end
    local weapon = look:match('^(Sword)') or look:match('^(Dagger)') or look:match('^(Bow)')
    if weapon then
      local e = {r[1], r[2], weapon, look:find('Gold') ~= nil, tonumber(r[2]:match('_(%d+)$')) or 0, hasName(r[1])}
      fx[#fx + 1] = e
      byAddr[r[1]] = e
    end
  end
  table.sort(fx, function(a, b)
    if a[3] ~= b[3] then return a[3] &gt; b[3] end
    if a[4] ~= b[4] then return a[4] end
    return a[2] &lt; b[2]
  end)
  SoM_runeFx, SoM_runeFxBy = fx, byAddr
  return fx
end

local function fxLabel(e)
  return string.format('[%s%s] %s%s', e[3], e[4] and ' EPIC' or '', e[2]:gsub('^BD_', ''), e[6] and '' or ' ?')
end

local RUNE = 0x28

local function isItem(q) return q and q &gt; 0x10000 and q &lt; 0x7fffffffffff and catOf(q) == 'Inventory/Items' end

local function isRuneEntry(e)
  local f = readQword(e) or -1
  if f ~= 0 and not (f &gt; 0x10000 and f &lt; 0x7fffffffffff and catOf(f) == 'Faction/Definition') then return false end
  local id = readInteger(e + 0x20)
  return isItem(readQword(e + 0x18)) and id and id &gt; 0 and id &lt; 0x100000
end

function SoM_findRunes()
  SoM_runeHdr = nil
  local anchors = {}
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Dag_Fear$'})) do anchors[#anchors + 1] = r[1] end
  for _, r in ipairs(filtered({'Inventory/Items', '^BD_Brutal_Terror$'})) do anchors[#anchors + 1] = r[1] end
  for _, a in ipairs(anchors) do
    for _, h in ipairs(scan(string.format('%X', a), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
      local e = h - 0x18
      if isRuneEntry(e) then
        local b = e
        while isRuneEntry(b - RUNE) do b = b - RUNE end
        for _, hd in ipairs(scan(string.format('%X', b), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
          local en = readQword(hd + 8) or 0
          if en &gt; b and (en - b) % RUNE == 0 and (en - b) // RUNE &lt;= (readInteger(hd + 0x10) or 0) then
            SoM_runeHdr = hd
            return hd
          end
        end
      end
    end
  end
  return nil
end

local ITEM = 0x10
SoM_itemObj = nil

function SoM_findItemList()
  SoM_itemObj = nil
  if not SoM_runeHdr then SoM_findRunes() end
  if not SoM_runeHdr then return nil end
  local inv = SoM_runeHdr - 0x30
  for _, h in ipairs(scan(string.format('%X', inv), vtQword, '+W*X*C', fsmAligned, '8', 0, 0x7fffffffffff)) do
    local obj = h - 0x230
    local b, e = readQword(obj + 0x298) or 0, readQword(obj + 0x2A0) or 0
    local cap = readInteger(obj + 0x2A8) or 0
    if b ~= 0 and e &gt;= b and (e - b) % ITEM == 0 and (e - b) // ITEM &lt;= cap and cap &lt; 100000 then
      SoM_itemObj = obj
      return obj
    end
  end
  return nil
end

local function itemEntry(item)
  local obj = SoM_itemObj
  local b, e = readQword(obj + 0x298), readQword(obj + 0x2A0)
  for a = b, e - ITEM, ITEM do
    if readQword(a) == item then return a end
  end
  return nil
end

function SoM_itemAdd(item, template)
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a = itemEntry(item)
  if a then
    writeSmallInteger(a + 8, (readSmallInteger(a + 8) or 0) + 1)
    return true
  end
  local obj = SoM_itemObj
  local b, e, cap = readQword(obj + 0x298), readQword(obj + 0x2A0), readInteger(obj + 0x2A8) or 0
  if (e - b) // ITEM &gt;= cap then
    return nil, string.format('the saved item list is full (%d of %d) - pick up any item so the game grows it, then try again', (e - b) // ITEM, cap)
  end
  writeQword(e, item)
  writeSmallInteger(e + 8, 1)
  writeSmallInteger(e + 0xA, 0)
  writeInteger(e + 0xC, template and readInteger(template + 0xC) or 7)
  writeQword(obj + 0x2A0, e + ITEM)
  return true
end

function SoM_itemSwap(old, new)
  if old == new then return true end
  if not SoM_itemObj and not SoM_findItemList() then return nil, 'saved item list not found' end
  local a, bnew = itemEntry(old), itemEntry(new)
  if a and (readSmallInteger(a + 8) or 0) &lt;= 1 and not bnew then
    writeQword(a, new)
    return true
  end
  local ok, why = SoM_itemAdd(new, a)
  if not ok then return nil, why end
  if a then
    local c = readSmallInteger(a + 8) or 0
    if c &gt; 1 then
      writeSmallInteger(a + 8, c - 1)
    else
      local obj = SoM_itemObj
      local e = readQword(obj + 0x2A0)
      local n = (e - (a + ITEM))
      if n &gt; 0 then writeBytes(a, readBytes(a + ITEM, n, true)) end
      writeQword(obj + 0x2A0, e - ITEM)
    end
  end
  return true
end

local AR_MAX = 256
local arBuffs, arAdded = nil, {}

local function buffComp()
  if not SoM_runeHdr or (readInteger(SoM_runeHdr + 0x10) or 0) ~= 0x78 then SoM_findRunes() end
  if not SoM_runeHdr then return nil end
  local inv = SoM_runeHdr - 0x30
  local a = readQword(inv + 0x48) or 0
  local b = a ~= 0 and readQword(a + 0x1F0) or 0
  local c = b ~= 0 and readQword(b + 0x6D0) or 0
  return c ~= 0 and c or nil
end

local function currentBuffs(comp)
  local have = {}
  local head = comp + 0x120
  local n, k = readQword(head + 8), 0
  while n and n ~= 0 and n ~= head and k &lt; 2000 do
    if (readBytes(n + 0x80, 1) or 1) == 0 then have[readQword(n + 0x60) or 0] = true end
    n = readQword(n + 8)
    k = k + 1
  end
  return have
end

local buffIdxBySchema = {}
local function activeBuff(item)
  local s = readQword(item + 8)
  if buffIdxBySchema[s] == nil then
    buffIdxBySchema[s] = findField(item, function(v) return catOf(v) == 'Combat/Buffs' end) or false
  end
  if not buffIdxBySchema[s] then return nil end
  local b = fieldValue(item, buffIdxBySchema[s])
  return b and b ~= 0 and b or nil
end

local function wantedBuffs()
  if arBuffs then return arBuffs end
  local best = {}
  for _, e in ipairs(runeEffects()) do
    if e[6] then
      local key = e[4] and e[2] or (e[2]:gsub('_%d+$', ''))
      if not best[key] or e[5] &gt; best[key][5] then best[key] = e end
    end
  end
  arBuffs = {}
  for _, e in pairs(best) do
    local b = activeBuff(e[1])
    if b then arBuffs[#arBuffs + 1] = b end
  end
  return arBuffs
end

local function request(req, list, comp)
  local t = getAddress('ar_list')
  local n = math.min(#list, AR_MAX)
  for i = 1, n do writeQword(t + (i - 1) * 8, list[i]) end
  writeInteger('ar_n', n)
  writeQword('ar_comp', comp)
  writeInteger('ar_req', req)
end

local function tick()
  if not getAddressSafe('ar_req') or (readInteger('ar_req') or 0) ~= 0 then return end
  local comp = buffComp()
  if not comp then return end
  local have = currentBuffs(comp)
  local missing = {}
  for _, b in ipairs(wantedBuffs()) do
    if not have[b] then missing[#missing + 1] = b end
  end
  if #missing &gt; 0 then
    for _, b in ipairs(missing) do arAdded[b] = true end
    request(1, missing, comp)
  end
  writeInteger('ar_have', #wantedBuffs() - #missing)
end

function SoM_allRunesEnable()
  runeDB = findGameDB()
  if not runeDB then
    messageDialog('Game database not found. Load a save first, then enable again.', mtError, mbOK)
    return false
  end
  SoM_runeFx, arBuffs, arAdded, SoM_runeHdr = nil, nil, {}, nil
  local list = wantedBuffs()
  local lastErr, n = nil, 0
  synchronize(function()
    if SoM_allRunesTimer then SoM_allRunesTimer.destroy() end
    SoM_allRunesTimer = createTimer(nil, false)
    SoM_allRunesTimer.Interval = 250
    SoM_allRunesTimer.OnTimer = function()
      n = n + 1
      if n % 8 ~= 1 then return end
      local ok, err = pcall(tick)
      if not ok and err ~= lastErr then
        lastErr = err
        print('SoM All Runes timer error: ' .. tostring(err))
      end
    end
    SoM_allRunesTimer.Enabled = true
  end)
  print(string.format('SoM All Runes: %d rune buffs to give (one per effect family at its best level + every epic)', #list))
  return true
end

function SoM_allRunesDisable()
  synchronize(function()
    if SoM_allRunesTimer then SoM_allRunesTimer.destroy(); SoM_allRunesTimer = nil end
  end)
  if not getAddressSafe('ar_req') then return end
  local comp = buffComp()
  local list = {}
  for b in pairs(arAdded) do list[#list + 1] = b end
  if comp and #list &gt; 0 then
    local t = 0
    while (readInteger('ar_req') or 0) ~= 0 and t &lt; 3000 do sleep(50); t = t + 50 end
    request(2, list, comp)
    t = 0
    while (readInteger('ar_req') or 0) ~= 0 and t &lt; 3000 do sleep(50); t = t + 50 end
    if (readInteger('ar_req') or 0) ~= 0 then
      writeInteger('ar_req', 0)
      print('SoM All Runes: the game added no buff within 3 s, so the extra rune buffs are still on Talion ' ..
            '(they go away on the next reload)')
    else
      print(string.format('SoM All Runes: removed %d rune buffs', #list))
    end
  end
  arAdded = {}
end

if not SoM_allRunesEnable() then error('SoM All Runes: enable failed') end
local rb = AOBScanModuleUnique('ShadowOfMordor.exe', '48 89 5C 24 ?? 57 48 83 EC ?? F3 0F 10 0D ?? ?? ?? ?? 48 8B FA 48 8B D9 E8')
if not rb then error('All Runes: RemoveBuff not found (game version?)') end
return string.format('define(AR_REMOVE,%X)', rb)
{$asm}
aobscanmodule(aob_addbuff,ShadowOfMordor.exe,45 33 C0 E9 58 C3 FF FF CC)
alloc(newmem_addbuff,$1000,aob_addbuff)
label(pass_addbuff)
label(loop_addbuff)
label(next_addbuff)
label(rem_addbuff)
label(done_addbuff)
label(ar_req)
label(ar_busy)
label(ar_n)
label(ar_comp)
label(ar_done)
label(ar_have)
label(ar_list)
registersymbol(ar_req)
registersymbol(ar_n)
registersymbol(ar_comp)
registersymbol(ar_done)
registersymbol(ar_have)
registersymbol(ar_list)
registersymbol(aob_addbuff)

newmem_addbuff:
  cmp dword ptr [ar_req],0
  je pass_addbuff
  cmp byte ptr [ar_busy],0
  jne pass_addbuff
  mov byte ptr [ar_busy],1
  push rcx
  push rdx
  push rbx
  push rsi
  push rdi
  sub rsp,20
  mov rbx,[ar_comp]
  lea rsi,[ar_list]
  mov edi,[ar_n]
loop_addbuff:
  test edi,edi
  je done_addbuff
  mov rdx,[rsi]
  test rdx,rdx
  je next_addbuff
  mov rcx,rbx
  cmp dword ptr [ar_req],2
  je rem_addbuff
  call aob_addbuff
  jmp next_addbuff
rem_addbuff:
  mov rax,AR_REMOVE
  call rax
next_addbuff:
  add rsi,8
  dec edi
  jmp loop_addbuff
done_addbuff:
  inc dword ptr [ar_done]
  mov dword ptr [ar_req],0
  add rsp,20
  pop rdi
  pop rsi
  pop rbx
  pop rdx
  pop rcx
  mov byte ptr [ar_busy],0
pass_addbuff:
  xor r8d,r8d
  jmp aob_addbuff-3CA0

ar_comp:
  dq 0
ar_req:
  dd 0
ar_n:
  dd 0
ar_done:
  dd 0
ar_have:
  dd 0
ar_busy:
  db 0 0 0 0 0 0 0 0
ar_list:
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0

aob_addbuff:
  jmp newmem_addbuff
  nop 3

[DISABLE]
{$lua}
if syntaxcheck then return end
if SoM_allRunesDisable then SoM_allRunesDisable() end
{$asm}
aob_addbuff:
  db 45 33 C0 E9 58 C3 FF FF
unregistersymbol(ar_req)
unregistersymbol(ar_n)
unregistersymbol(ar_comp)
unregistersymbol(ar_done)
unregistersymbol(ar_have)
unregistersymbol(ar_list)
unregistersymbol(aob_addbuff)
dealloc(newmem_addbuff)
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>1181</ID>
          <Description>"rune buffs on Talion (of the wanted ones)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>ar_have</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1182</ID>
          <Description>"times applied by the hook"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>ar_done</Address>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
    <CheatEntry>
      <ID>1195</ID>
      <Description>"Debug Menu (F1 open/close, arrows move, Enter select, Backspace back)"</Description>
      <Options moHideChildren="1"/>
      <Color>80C040</Color>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript Async="1">[ENABLE]
{$lua}
if syntaxcheck then return 'define(DM_SHOW,0)\ndefine(DM_FLAG,0)\ndefine(DM_GLOBAL,0)\ndefine(DM_FRAMES,0)\ndefine(DM_LOAD,0)\ndefine(DM_CGET,0)\ndefine(DM_CREG,0)\ndefine(DM_CFIND,0)\ndefine(DM_START,0)\ndefine(DM_CFREC,0)\ndefine(DM_CVEC,0)\ndefine(DM_CEMPTY,0)\ndefine(DM_CGROW,0)\ndefine(DM_SERVER,0)\ndefine(DM_PERMOFF,0)' end
local show = AOBScanModuleUnique('ShadowOfMordor.exe', '40 56 57 48 81 EC ?? ?? ?? ?? 80 3D ?? ?? ?? ?? 00 0F B6 F2 48 8B F9')
local hook = AOBScanModuleUnique('ShadowOfMordor.exe', 'FF 05 ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B CB 48 8B D0 E8')
local load = AOBScanModuleUnique('ShadowOfMordor.exe', '40 53 57 41 56 48 83 EC ?? 4C 8B F1 48 8B 49 08 48 83 79 50 00 0F 85')
local cget = AOBScanModuleUnique('ShadowOfMordor.exe', '48 8B 83 68 06 00 00 48 8B 9B 30 0A 00 00 4A 8B 0C D8 48 8B 3B 8B D6 E8')
local creg = AOBScanModuleUnique('ShadowOfMordor.exe', '48 83 EC 28 8B 05 ?? ?? ?? ?? A8 01 0F 85 ?? ?? ?? ?? 83 C8 01 89 05 ?? ?? ?? ?? 48 8D 05')
local cfind = AOBScanModuleUnique('ShadowOfMordor.exe', '48 89 6C 24 18 56 48 83 EC ?? 48 8B EA 48 8B F1 48 85 D2 0F 84 ?? ?? ?? ?? 80 3A 00 0F 84')
local cini = AOBScanModuleUnique('ShadowOfMordor.exe', '33 C9 E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 0F 84 ?? ?? ?? ?? 4C 8B 05 ?? ?? ?? ?? 48 8D 05 ?? ?? ?? ??')
local srv = AOBScanModuleUnique('ShadowOfMordor.exe', '48 8B 0D ?? ?? ?? ?? 48 89 B4 24 ?? ?? ?? ?? 4C 89 A4 24 ?? ?? ?? ?? E8')
local cperm = AOBScanModuleUnique('ShadowOfMordor.exe', '48 8B 8E A0 02 00 00 4C 8B C3 40 0F B6 D7 48 8B 5C 24 ?? 48 8B 74 24 ?? 48 83 C4 ?? 5F E9')
local wload = AOBScanModuleUnique('ShadowOfMordor.exe', '4C 8D 4C 24 ?? 48 89 4C 24 ?? 48 8B 0D ?? ?? ?? ?? 45 33 C0 48 8B D0 C6 44 24 ?? 01 C6 44 24 ?? 01 E8')
if not show or not hook or not load or not cget or not creg or not cfind or not wload or not cini or not srv or not cperm then error('Debug Menu: game code not found (game version?)') end
local function rip(at, dispOff, len) return at + len + readInteger(at + dispOff, true) end
return string.format('define(DM_SHOW,%X)\ndefine(DM_FLAG,%X)\ndefine(DM_GLOBAL,%X)\ndefine(DM_FRAMES,%X)\ndefine(DM_LOAD,%X)\ndefine(DM_CGET,%X)\ndefine(DM_CREG,%X)\ndefine(DM_CFIND,%X)\ndefine(DM_START,%X)',
  show, rip(show + 0xA, 2, 7), rip(show + 0xC4, 3, 7), rip(hook, 2, 6), load, rip(cget + 0x17, 1, 5), creg, cfind,
  rip(wload + 0x21, 1, 5)) ..
  string.format('\ndefine(DM_CFREC,%X)\ndefine(DM_CVEC,%X)\ndefine(DM_CEMPTY,%X)\ndefine(DM_CGROW,%X)\ndefine(DM_SERVER,%X)\ndefine(DM_PERMOFF,%X)',
  rip(cini + 2, 1, 5), rip(cini + 0x13, 3, 7) - 8, rip(cini + 0x1A, 3, 7), rip(cini + 0x7E, 1, 5),
  rip(srv, 3, 7), readInteger(cperm + 3))
{$asm}
aobscanmodule(aob_dmframe,ShadowOfMordor.exe,FF 05 ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B CB 48 8B D0 E8)
aobscanmodule(aob_dmcexec,ShadowOfMordor.exe,8B 93 B4 09 00 00 48 8B 0C C1 E8 ?? ?? ?? ?? B0 01 48 8B 5C 24 30)
aobscanmodule(aob_dmwfilt,ShadowOfMordor.exe,48 8B 7B 08 48 85 FF 0F 84 ?? ?? ?? ?? 48 8B CF E8 ?? ?? ?? ?? 85 C0 0F 88 ?? ?? ?? ?? 48 63 C8 48 03 CF 75 ?? 83 C8 FF EB ?? 48 8D 15 ?? ?? ?? ?? E8)
aobscanmodule(aob_dmwload,ShadowOfMordor.exe,4C 8D 4C 24 ?? 48 89 4C 24 ?? 48 8B 0D ?? ?? ?? ?? 45 33 C0 48 8B D0 C6 44 24 ?? 01 C6 44 24 ?? 01 E8)
alloc(newmem_dm,$2000,aob_dmframe)
label(return_dm)
label(up_dm)
label(work_dm)
label(open_dm)
label(close_dm)
label(drop_dm)
label(done_dm)
label(dm_key)
label(dm_down)
label(dm_req)
label(dm_pid)
label(dm_count)
label(dm_loads)
label(dm_wait)
label(dm_orig)
label(req_dm)
label(dm_tick)
label(dm_ticks)
label(dm_obj)
label(dm_after)
label(tick_dm)
label(dm_was)
label(closed_dm)
label(open_now_dm)
label(nav_dm)
label(navnext_dm)
label(navdone_dm)
label(dm_nav)
label(dm_evt)
label(dm_keys)
label(newlog_dm)
label(return_log_dm)
label(dm_logi)
label(dm_log)
label(dm_origshow)
label(dm_showat)
label(dm_exec)
label(dm_cbuf)
label(dm_argv)
label(dm_cmds)
label(dm_cbad)
label(dm_corig)
label(cx_copy)
label(cx_copied)
label(cx_next)
label(cx_skip)
label(cx_start)
label(cx_name)
label(cx_args)
label(cx_tok)
label(cx_tokend)
label(cx_semi)
label(cx_strend)
label(cx_run)
label(cx_bad)
label(cx_ran)
label(cx_out)
label(cx_argsp)
label(dm_wfilt)
label(dm_wlist)
label(wf_next)
label(wf_cmp)
label(wf_low)
label(wf_skip)
label(wf_yes)
label(wf_no)
label(dm_wload)
label(wl_scan)
label(wl_n)
label(wl_found)
label(wl_go)
label(dm_wloads)
label(dm_wforig)
label(dm_wlorig)
label(dm_wpath)
label(wl_copy)
label(wl_copied)
label(dm_cheats)
label(ch_loop)
label(ch_has)
label(ch_new)
label(ch_push)
label(ch_next)
label(dm_allcheats)
label(open2_dm)
label(dm_cheatn)
label(ch_perm)
label(ch_pnext)
label(ch_done)
label(dm_permn)
registersymbol(dm_keys)
registersymbol(dm_key)
registersymbol(dm_count)
registersymbol(dm_loads)
registersymbol(dm_orig)
registersymbol(dm_tick)
registersymbol(dm_ticks)
registersymbol(aob_dmframe)

newmem_dm:
  mov rax,DM_FRAMES
  inc dword ptr [rax]
  sub rsp,20
  movzx ecx,byte ptr [dm_key]
  call GetAsyncKeyState
  test ax,8000
  je up_dm
  cmp byte ptr [dm_down],0
  jne work_dm
  mov byte ptr [dm_down],1
  call GetForegroundWindow
  mov rcx,rax
  lea rdx,[dm_pid]
  call GetWindowThreadProcessId
  call GetCurrentProcessId
  cmp eax,[dm_pid]
  jne work_dm
  mov byte ptr [dm_req],1
  mov dword ptr [dm_wait],0
  jmp work_dm
up_dm:
  mov byte ptr [dm_down],0
work_dm:
  cmp byte ptr [dm_tick],0
  je req_dm
  mov rax,DM_GLOBAL
  mov rax,[rax]
  test rax,rax
  je req_dm
  mov rcx,[rax+81000]
  test rcx,rcx
  je req_dm
  add rcx,20
  mov [dm_obj],rcx
  cmp byte ptr [rcx+2652],0
  jne open_now_dm
  cmp byte ptr [dm_was],0
  je closed_dm
  mov byte ptr [dm_was],0
  mov dword ptr [dm_after],#10
closed_dm:
  cmp dword ptr [dm_after],0
  je req_dm
  dec dword ptr [dm_after]
  jmp tick_dm
open_now_dm:
  mov byte ptr [dm_was],1
  push rbx
  push rsi
  sub rsp,20
  lea rsi,[dm_nav]
nav_dm:
  movzx ecx,byte ptr [rsi]
  test ecx,ecx
  je navdone_dm
  call GetAsyncKeyState
  xor ebx,ebx
  test ax,8000
  setne bl
  cmp bl,[rsi+1]
  je navnext_dm
  mov [rsi+1],bl
  test bl,bl
  je navnext_dm
  movzx eax,word ptr [rsi+2]
  mov [dm_evt],ax
  inc dword ptr [dm_keys]
  mov rcx,[dm_obj]
  add rcx,10
  mov rax,[rcx]
  lea rdx,[dm_evt]
  call qword ptr [rax+38]
navnext_dm:
  add rsi,4
  jmp nav_dm
navdone_dm:
  add rsp,20
  pop rsi
  pop rbx
tick_dm:
  mov rcx,[dm_obj]
  mov rax,[rcx]
  inc dword ptr [dm_ticks]
  call qword ptr [rax+10]
  mov rcx,[dm_obj]
  mov rax,[rcx]
  call qword ptr [rax+20]
req_dm:
  cmp byte ptr [dm_req],0
  je done_dm
  mov rax,DM_GLOBAL
  mov rax,[rax]
  test rax,rax
  je done_dm
  mov rcx,[rax+81000]
  test rcx,rcx
  je done_dm
  add rcx,20
  mov rax,DM_FLAG
  mov byte ptr [rax],1
  cmp byte ptr [rcx+2652],0
  jne close_dm
  mov rax,[rcx+8]
  test rax,rax
  je drop_dm
  cmp qword ptr [rax+50],0
  jne open_dm
  inc dword ptr [dm_loads]
  inc dword ptr [dm_wait]
  cmp dword ptr [dm_wait],#1200
  ja drop_dm
  mov rax,DM_LOAD
  call rax
  jmp done_dm
open_dm:
  cmp byte ptr [dm_allcheats],0
  je open2_dm
  call dm_cheats
  mov rcx,[dm_obj]
open2_dm:
  mov edx,1
  inc dword ptr [dm_count]
  mov rax,DM_SHOW
  call rax
  jmp drop_dm
close_dm:
  xor edx,edx
  inc dword ptr [dm_count]
  mov rax,DM_SHOW
  call rax
  mov dword ptr [dm_after],#10
drop_dm:
  mov byte ptr [dm_req],0
done_dm:
  add rsp,20
  jmp return_dm

dm_key:
  db 70
dm_down:
  db 0
dm_req:
  db 0
dm_pid:
  dd 0
dm_count:
  dd 0
dm_loads:
  dd 0
dm_wait:
  dd 0
dm_obj:
  dq 0
dm_after:
  dd 0
dm_was:
  db 0
dm_keys:
  dd 0
dm_nav:
  db 26 0
  dw 2C
  db 28 0
  dw 2D
  db 25 0
  dw 2E
  db 27 0
  dw 2F
  db 0D 0
  dw 36
  db 08 0
  dw 37
  db 0 0 0 0
dm_evt:
  dq 0 0 0 0 0 0 0 0
dm_tick:
  db 1
dm_ticks:
  dd 0
dm_orig:
  readmem(aob_dmframe,6)

aob_dmframe:
  jmp newmem_dm
  nop
return_dm:

registersymbol(dm_logi)
registersymbol(dm_log)
registersymbol(dm_origshow)
newmem_dm+800:
newlog_dm:
  mov eax,[dm_logi]
  and eax,F
  shl eax,4
  lea r11,[dm_log]
  add r11,rax
  mov rax,[rsp]
  mov [r11],rax
  mov [r11+8],rdx
  inc dword ptr [dm_logi]
  push rsi
  push rdi
  sub rsp,A8
  jmp return_log_dm
dm_logi:
  dd 0 0
dm_origshow:
  readmem(DM_SHOW,10)
  db 0 0 0 0 0 0
dm_log:
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0
  dq 0 0

registersymbol(dm_showat)
DM_SHOW:
dm_showat:
  jmp newlog_dm
  nop 5
return_log_dm:

registersymbol(dm_cmds)
registersymbol(dm_cbad)
registersymbol(dm_corig)
registersymbol(aob_dmcexec)
newmem_dm+1000:
dm_exec:
  push rbx
  push rsi
  push rdi
  push r12
  push r13
  sub rsp,30
  mov rax,DM_CGET
  call rax
  test rax,rax
  je cx_out
  lea rdi,[dm_cbuf]
  mov rsi,rax
  mov ecx,7FF
cx_copy:
  mov al,[rsi]
  mov [rdi],al
  test al,al
  je cx_copied
  inc rsi
  inc rdi
  dec ecx
  jne cx_copy
  mov byte ptr [rdi],0
cx_copied:
  lea rsi,[dm_cbuf]
cx_next:
  mov al,[rsi]
  cmp al,20
  je cx_skip
  cmp al,9
  je cx_skip
  cmp al,3B
  je cx_skip
  jmp cx_start
cx_skip:
  inc rsi
  jmp cx_next
cx_start:
  test al,al
  je cx_out
  mov r12,rsi
  xor ebx,ebx
  xor edi,edi
cx_name:
  mov al,[rsi]
  test al,al
  je cx_strend
  cmp al,3B
  je cx_semi
  cmp al,20
  je cx_tokend
  cmp al,9
  je cx_tokend
  inc rsi
  jmp cx_name
cx_args:
  mov al,[rsi]
  test al,al
  je cx_strend
  cmp al,3B
  je cx_semi
  cmp al,20
  je cx_argsp
  cmp al,9
  je cx_argsp
  cmp ebx,#15
  jae cx_tok
  lea rax,[dm_argv]
  mov [rax+rbx*8],rsi
  inc ebx
cx_tok:
  mov al,[rsi]
  test al,al
  je cx_strend
  cmp al,3B
  je cx_semi
  cmp al,20
  je cx_tokend
  cmp al,9
  je cx_tokend
  inc rsi
  jmp cx_tok
cx_argsp:
  inc rsi
  jmp cx_args
cx_tokend:
  mov byte ptr [rsi],0
  inc rsi
  jmp cx_args
cx_semi:
  mov byte ptr [rsi],0
  inc rsi
  mov edi,1
  jmp cx_run
cx_strend:
  mov edi,2
cx_run:
  mov rax,DM_CREG
  call rax
  mov rcx,rax
  mov rdx,r12
  mov rax,DM_CFIND
  call rax
  test rax,rax
  je cx_bad
  inc dword ptr [dm_cmds]
  mov rcx,rax
  mov edx,ebx
  lea r8,[dm_argv]
  mov rax,[rcx]
  call qword ptr [rax+8]
  jmp cx_ran
cx_bad:
  inc dword ptr [dm_cbad]
cx_ran:
  cmp edi,2
  jne cx_next
cx_out:
  add rsp,30
  pop r13
  pop r12
  pop rdi
  pop rsi
  pop rbx
  ret
dm_cmds:
  dd 0
dm_cbad:
  dd 0
dm_corig:
  readmem(aob_dmcexec+A,5)
  db 0 0 0
dm_argv:
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0
  dq 0

dm_cbuf:
  db 0

aob_dmcexec+A:
  call dm_exec

registersymbol(dm_wloads)
registersymbol(dm_wforig)
registersymbol(dm_wlorig)
registersymbol(dm_wpath)
registersymbol(aob_dmwfilt)
registersymbol(aob_dmwload)
newmem_dm+A00:
dm_wfilt:
  push rsi
  lea rsi,[dm_wlist]
wf_next:
  cmp byte ptr [rsi],0
  je wf_no
  mov r8,rdi
  mov r9,rsi
wf_cmp:
  mov al,[r8]
  cmp al,41
  jb wf_low
  cmp al,5A
  ja wf_low
  or al,20
wf_low:
  cmp al,[r9]
  jne wf_skip
  test al,al
  je wf_yes
  inc r8
  inc r9
  jmp wf_cmp
wf_skip:
  mov al,[rsi]
  inc rsi
  test al,al
  jne wf_skip
  jmp wf_next
wf_yes:
  xor eax,eax
  pop rsi
  ret
wf_no:
  mov eax,1
  pop rsi
  ret
dm_wload:
  mov r10,r9
  xor r11,r11
wl_scan:
  mov al,[r10]
  test al,al
  je wl_found
  cmp al,2E
  jne wl_n
  mov r11,r10
wl_n:
  inc r10
  jmp wl_scan
wl_found:
  test r11,r11
  je wl_go
  mov dword ptr [r11+1],6C726F77
  mov dword ptr [r11+5],63353064
  mov byte ptr [r11+9],0
  inc dword ptr [dm_wloads]
wl_go:
  push rsi
  push rdi
  mov rsi,r9
  lea rdi,[dm_wpath]
  mov r10d,FF
wl_copy:
  mov al,[rsi]
  mov [rdi],al
  inc rsi
  inc rdi
  test al,al
  je wl_copied
  dec r10d
  jne wl_copy
wl_copied:
  pop rdi
  pop rsi
  mov rax,DM_START
  jmp rax
dm_wloads:
  dd 0
dm_wforig:
  readmem(aob_dmwfilt+31,5)
  db 0 0 0
dm_wlorig:
  readmem(aob_dmwload+21,5)
  db 0 0 0
dm_wlist:
  db 'seaofnurnen.embb',0
  db 'blackgates.embb',0
  db 'blackgates_dlc2.embb',0
  db 'intro_blackgates.embb',0
  db 'narchostend.embb',0
  db 'outro1.embb',0
  db 'benchmark.embb',0
  db 'gp_torvin_3_part2.embb',0
  db 0
dm_wpath:
  db 0

aob_dmwfilt+31:
  call dm_wfilt

registersymbol(dm_allcheats)
registersymbol(dm_cheatn)
registersymbol(dm_permn)
newmem_dm+D00:
dm_cheats:
  push rbx
  push rsi
  push rdi
  sub rsp,40
  mov esi,1
ch_loop:
  mov r11,DM_CVEC
  mov rax,[r11]
  mov rcx,[r11+8]
ch_has:
  cmp rax,rcx
  je ch_new
  cmp [rax+10],esi
  je ch_next
  add rax,28
  jmp ch_has
ch_new:
  mov ecx,esi
  mov rax,DM_CFREC
  call rax
  test rax,rax
  je ch_next
  mov rbx,rax
  mov r11,DM_CVEC
  mov rax,[r11+8]
  sub rax,[r11]
  xor edx,edx
  mov ecx,28
  div rcx
  cmp [r11+10],eax
  ja ch_push
  lea edx,[rax+1]
  mov rax,DM_CEMPTY
  mov [rsp+20],rax
  mov qword ptr [rsp+28],0
  mov [rsp+30],rax
  mov dword ptr [rsp+38],0
  mov dword ptr [rsp+3C],FFFFFFFF
  lea r8,[rsp+20]
  mov rcx,DM_CVEC
  mov rax,DM_CGROW
  call rax
  test al,al
  je ch_next
ch_push:
  mov r11,DM_CVEC
  mov rax,[r11+8]
  lea rcx,[rax+28]
  mov [r11+8],rcx
  xor ecx,ecx
  mov [rax],rcx
  mov [rax+8],rcx
  mov [rax+10],esi
  mov [rax+18],rbx
  mov word ptr [rax+20],1
  inc dword ptr [dm_cheatn]
ch_next:
  inc esi
  cmp esi,#10
  jb ch_loop
  mov rax,DM_SERVER
  mov rax,[rax]
  test rax,rax
  je ch_done
  mov rax,[rax+DM_PERMOFF]
  test rax,rax
  je ch_done
  mov rcx,[rax+8]
  mov rax,[rax]
ch_perm:
  cmp rax,rcx
  jae ch_done
  mov edx,[rax]
  test dl,1
  je ch_pnext
  test dl,8
  jne ch_pnext
  or dword ptr [rax],8
  inc dword ptr [dm_permn]
ch_pnext:
  add rax,4
  jmp ch_perm
ch_done:
  add rsp,40
  pop rdi
  pop rsi
  pop rbx
  ret
dm_allcheats:
  db 1
dm_cheatn:
  dd 0
dm_permn:
  dd 0

aob_dmwload+21:
  call dm_wload

[DISABLE]
aob_dmframe:
  readmem(dm_orig,6)

aob_dmcexec+A:
  readmem(dm_corig,5)

aob_dmwfilt+31:
  readmem(dm_wforig,5)

aob_dmwload+21:
  readmem(dm_wlorig,5)

unregistersymbol(dm_wloads)
unregistersymbol(dm_wforig)
unregistersymbol(dm_wlorig)
unregistersymbol(dm_wpath)
unregistersymbol(dm_allcheats)
unregistersymbol(dm_cheatn)
unregistersymbol(dm_permn)
unregistersymbol(aob_dmwfilt)
unregistersymbol(aob_dmwload)
unregistersymbol(dm_cmds)
unregistersymbol(dm_cbad)
unregistersymbol(dm_corig)
unregistersymbol(aob_dmcexec)
dm_showat:
  readmem(dm_origshow,10)

unregistersymbol(dm_logi)
unregistersymbol(dm_log)
unregistersymbol(dm_origshow)
unregistersymbol(dm_showat)
unregistersymbol(dm_key)
unregistersymbol(dm_count)
unregistersymbol(dm_loads)
unregistersymbol(dm_orig)
unregistersymbol(dm_tick)
unregistersymbol(dm_ticks)
unregistersymbol(dm_keys)
unregistersymbol(aob_dmframe)
dealloc(newmem_dm)
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>1184</ID>
          <Description>"Key (virtual-key code, 70 = F1)"</Description>
          <ShowAsHex>1</ShowAsHex>
          <VariableType>Byte</VariableType>
          <Address>dm_key</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1185</ID>
          <Description>"times opened/closed"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_count</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1186</ID>
          <Description>"frames spent loading the menu (diagnostic)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_loads</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1187</ID>
          <Description>"Run the menu's update while open (untick if it crashes)"</Description>
          <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
          <VariableType>Byte</VariableType>
          <Address>dm_tick</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1188</ID>
          <Description>"menu updates run (diagnostic)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_ticks</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1189</ID>
          <Description>"menu keys sent (diagnostic)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_keys</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1190</ID>
          <Description>"console commands run"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_cmds</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1191</ID>
          <Description>"console commands not in this build (skipped)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_cbad</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1192</ID>
          <Description>"Add the cheats retail leaves out (on menu open)"</Description>
          <DropDownList DescriptionOnly="1" DisplayValueAsItem="1">0:No
1:Yes
</DropDownList>
          <VariableType>Byte</VariableType>
          <Address>dm_allcheats</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1193</ID>
          <Description>"cheats added (diagnostic)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_cheatn</Address>
        </CheatEntry>
        <CheatEntry>
          <ID>1194</ID>
          <Description>"cheats unlocked on the server (diagnostic)"</Description>
          <VariableType>4 Bytes</VariableType>
          <Address>dm_permn</Address>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
  </CheatEntries>
  <UserdefinedSymbols/>
</CheatTable>
