<?xml version='1.0' encoding='utf-8'?>
<CheatTable CheatEngineTableVersion="52">
  <CheatEntries>
    <CheatEntry>
      <ID>1</ID>
      <Description>"Auto-Attach and Version Check: waiting for Metal Dogs"</Description>
      <GroupHeader>1</GroupHeader>
    </CheatEntry>
    <CheatEntry>
      <ID>2</ID>
      <Description>"How to use - Katzy"</Description>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
showMessage([=[Table identification: Katzy.
Metal Dogs | Steam build 15634146 | v3.0.

Open this table in 64-bit Cheat Engine and allow its Lua script when you choose to use it. It attaches to DOGS.exe and verifies the executable and game assembly. Tick MAIN to show its options, then select the cheats you want. Options with the same [Set] number are mutually exclusive: only one option in each set can be active. Enabling one automatically disables and unticks the other. Different sets can be used together. Unticking MAIN hides its options; use Clear All to disable active cheats and untick MAIN.

Infinite HP: blocks positive damage to dog characters. It refills living dogs to their current maximum HP on the next normal update and keeps HP topped up while enabled. Healing and enemy damage remain normal. Scripted deaths and HP changes caused by equipment are outside this option.

Invincibility / Status Immunity: includes Infinite HP's damage-blocking hook and enables the dogs' built-in invincibility check, including protection against newly applied burn, freeze, poison and paralysis. Beneficial buffs and enemy status effects remain normal. Existing burn, freeze, poison and paralysis are cured through the game healing function on the next normal update; their effects and visuals are cleaned up. Living dogs are also refilled to their current maximum HP. Direct scripted HP assignments or scripted death can bypass damage blocking. This option and Infinite HP are mutually exclusive; selecting one automatically unticks the other.

Infinite Ammo / No Reload: ammunition is not consumed and empty guns can fire without a reload wait. The ammunition display may retain its previous count. Normal firing intervals remain intact; rush-power requirements are unchanged.

Infinite Ammo Clips: reserve clips are not consumed, including when you have none left. Bullets in the magazine still decrease normally and reloading retains its original duration. Use this option instead of Infinite Ammo / No Reload; selecting one automatically unticks the other.


Infinite Items: holds owned non-equipment stacks at their current-mode maximum, including Dog Tags at 99,999. Switching off or using Clear All restores the original counts captured when each owned item entered the effect. Gains and spending during the effect are replaced by those original counts on disable. Missing items are not granted. Equipment and storage are unchanged. Reopen inventory to refresh displayed counts. Disable before changing saves or saving if you do not want the boosted quantities saved.

No Item Consumption: using an owned consumable from the quick slots or item menu, or automatically using an equipped status-protection item, keeps its quantity unchanged. You must own at least one. Item effects and normal use restrictions remain active. Weapon Laboratory material costs, including Constituent Mutagen used for modification rerolls and rank upgrades, are also preserved. You must still meet the laboratory material requirements. Selling, discarding, storage transfers, ammunition and save-chip costs remain unchanged. Gold spending follows the separate currency option.

No Skill Cooldown: Rush is always fully charged and dodge has no recharge wait. You must still finish the current dodge animation before dodging again. Normal skill effects, duration, equipment requirements and weapon firing intervals remain unchanged. Turning this off restores the original cooldown checks and charge display.

Infinite Currency: shops and currency displays see a 90,000,000 allowance plus your current saved gold, and purchases deduct from the accumulated gold above that allowance. The displayed total never falls below 90,000,000. Normal earnings increase the total; disabling removes the allowance and leaves the remaining unspent saved gold. Turn the option off to see your normal balance again. Purchases you make remain normal game progress.

Off restores the original code. Clear All, table replacement and ordinary Cheat Engine close restore active patches. The health, status and currency hooks retain small executable pages until the game exits to avoid freeing code beneath a running thread. Game/save files are not patched.

Verification: inspected the installed managed and live compiled code; validation details and remaining gameplay checks are recorded in the accompanying notes. Never assume a successful code readback alone proves gameplay behavior.]=])

local record=getAddressList().getMemoryRecordByID(2)
createTimer(100,function()
 local current=getAddressList().getMemoryRecordByID(2)
 if current and current==record then current.Active=false end
end)
[DISABLE]

</AssemblerScript>
    </CheatEntry>
    <CheatEntry>
      <ID>20</ID>
      <Description>"Clear All"</Description>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
if MD then MD.clear() end
local record=getAddressList().getMemoryRecordByID(20)
createTimer(100,function()
 local current=getAddressList().getMemoryRecordByID(20)
 if current and current==record then current.Active=false end
end)
[DISABLE]

</AssemblerScript>
    </CheatEntry>
    <CheatEntry>
      <ID>3</ID>
      <Description>"[MAIN] Metal Dogs build 15634146"</Description>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
local ok,reason=pcall(function()
 assert(MD,'Table Lua is not running. Reopen the table and allow its Lua script.')
 assert(MD.attach(),'Metal Dogs is not running. Start the game before enabling MAIN.')
 if not MD.mono then
  local connection=LaunchMonoDataCollector()
  assert(connection and connection~=0,'Cannot connect to the game Mono runtime.')
  MD.mono=true
 end
end)
if not ok then
 showMessage('Cannot enable MAIN:\n'..tostring(reason))
 error(reason,0)
end
[DISABLE]

</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>14</ID>
          <Description>"Invincibility / Status Immunity [Set 1]"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('invincible',true)
[DISABLE]
if MD then MD.set('invincible',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>10</ID>
          <Description>"Infinite HP [Set 1]"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('hp',true)
[DISABLE]
if MD then MD.set('hp',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>11</ID>
          <Description>"Infinite Ammo / No Reload [Set 2]"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('ammo',true)
[DISABLE]
if MD then MD.set('ammo',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>12</ID>
          <Description>"Infinite Ammo Clips [Set 2]"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('clips',true)
[DISABLE]
if MD then MD.set('clips',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>13</ID>
          <Description>"Infinite Currency"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('currency',true)
[DISABLE]
if MD then MD.set('currency',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>18</ID>
          <Description>"Infinite Items"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.infiniteItems(true)
[DISABLE]
if MD then MD.infiniteItems(false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>17</ID>
          <Description>"No Item Consumption"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('items',true)
[DISABLE]
if MD then MD.set('items',false) end
</AssemblerScript>
        </CheatEntry>
        <CheatEntry>
          <ID>15</ID>
          <Description>"No Skill Cooldown"</Description>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
[ENABLE]
assert(MD,'Allow the table Lua script first'); MD.set('cooldowns',true)
[DISABLE]
if MD then MD.set('cooldowns',false) end
</AssemblerScript>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
  </CheatEntries>
  <UserdefinedSymbols />
  <LuaScript>--[[
Table identification: Katzy
Metal Dogs | Steam build 15634146 | table v2.8
Original targets derived from the installed managed assembly and live Mono code.
]]
if MD and MD.shutdown then MD.shutdown() end
MD={pid=nil,ready=false,active={},patches={},caves={}}
local M=MD
local function hex(s)
 local t={} for b in s:gmatch('%x%x') do t[#t+1]=tonumber(b,16) end return t
end
local function bytes(s) local t={} for i=1,#s do t[i]=s:byte(i) end return t end
local function join(a,b) local t={} for _,v in ipairs(a) do t[#t+1]=v end for _,v in ipairs(b) do t[#t+1]=v end return t end
local function same(a,b)
 if not a or not b or #a~=#b then return false end
 for i=1,#a do if a[i]~=b[i] then return false end end return true
end
local function read(a,n) local b=readBytes(a,n,true) assert(b and #b==n,'Cannot read game code') return b end
local function expect(a,b) assert(same(read(a,#b),b),'Unexpected game code; no patch applied at '..string.format('%X',a)) end
local function put(a,b) writeBytes(a,b) assert(same(read(a,#b),b),'Game code write/readback failed') end
local function jump(a) return join(hex('FF2500000000'),bytes(string.pack('&lt;I8',a))) end
local function status(s)
 M.status=s
 local r=getAddressList().getMemoryRecordByID(1)
 if r and r.Description~='Auto-Attach and Version Check: '..s then r.Description='Auto-Attach and Version Check: '..s end
end
local function identity()
 if not M.pid or getOpenedProcessID()~=M.pid then return false end
 local pid=getProcessIDFromProcessName('DOGS.exe')
 if pid~=M.pid then return false end
 for _,m in ipairs(enumModules(M.pid) or {}) do
  if m.Name:lower()=='dogs.exe' then return m.Address==M.base and m.PathToFile==M.path end
 end
 return false
end
function M.forgetExitedProcess()
 if not M.pid or (getProcesslist() or {})[M.pid] then return false end
 M.pid=nil;M.ready=false;M.active={};M.patches={};M.caves={}
 M.careFlag=nil;M.mono=false;M.itemSnapshot=nil;M.itemError=nil;M.itemResolver=nil
 if M.timer then M.timer.Interval=1500 end
 for _,id in ipairs({3,10,11,12,13,14,15,17,18}) do
  local r=getAddressList().getMemoryRecordByID(id)
  if r then r.Active=false end
 end
 status('waiting for Metal Dogs')
 return true
end
function M.attach()
 local pid=getProcessIDFromProcessName('DOGS.exe')
 if not pid or pid==0 then
  M.forgetExitedProcess()
  M.ready=false; status('waiting for Metal Dogs');return false
 end
 if M.pid and pid~=M.pid then
  M.ready=false;M.active={};M.patches={};M.caves={};M.careFlag=nil;M.mono=false;M.itemSnapshot=nil;M.itemError=nil;M.itemResolver=nil
  for _,id in ipairs({10,11,12,13,14,15,17,18}) do local r=getAddressList().getMemoryRecordByID(id) if r then r.Active=false end end
 end
 if pid==M.pid and M.ready and identity() then return true end
 openProcess(pid)
 local module
 for _,m in ipairs(enumModules(pid) or {}) do if m.Name:lower()=='dogs.exe' then module=m break end end
 assert(module and module.Is64Bit,'Metal Dogs 64-bit executable not found')
 local dir=module.PathToFile:match('^(.*)[/\\]')
 assert(dir,'Cannot resolve game folder')
 assert(md5file(module.PathToFile):lower()==M.exeHash,'Wrong Metal Dogs executable build')
 assert(md5file(dir..'\\DOGS_Data\\Managed\\Assembly-CSharp.dll'):lower()==M.assemblyHash,'Wrong Metal Dogs assembly build (requires 15634146)')
 M.pid=pid;M.base=module.Address;M.path=module.PathToFile;M.ready=true
 status('build 15634146 verified')
 return true
end
local function method(ns,cl,name)
 local id=mono_findMethod(ns,cl,name);assert(id and id~=0,'Mono method missing: '..name)
 local a=mono_compile_method(id);assert(a and a~=0,'Cannot compile Mono method: '..name)
 local j=mono_getJitInfo(a)
 assert(j and j.method==id and j.code_start==a,'Mono method identity mismatch: '..name)
 return a,j.code_size
end
local function field(ns,cl,name,offset)
 local c=mono_findClass(ns,cl);assert(c and c~=0,'Mono class missing')
 for _,f in ipairs(mono_class_enumFields(c,false) or {}) do
  if f.name==name then assert(f.offset==offset and not f.isStatic,'Unexpected field layout: '..name) return end
 end
 error('Mono field missing: '..name)
end
local function spec(a,old,new) return {address=a,old=old,new=new} end
local function prepare(which)
 if not M.mono then assert(LaunchMonoDataCollector()~=0,'Mono connection failed');M.mono=true end
 if which=='care' then
  local a,n=method('Dogs.Character','CharacterBrain','Update')
  local maxhp=method('Dogs.Character','CharacterEntity','GetMaxHp')
  local sethp=method('Dogs.Character','CharacterEntity','set_Hp')
  local heal=method('Dogs.Character','CharacterEntity','HealBadCondition')
  assert(n&gt;=0x11f,'Unexpected character update size')
  field('Dogs.Character','CharacterBrain','_initialized',0x61)
  field('Dogs.Character','CharacterBrain','m_alreadyKilled',0x74)
  field('Dogs.Character','CharacterBrain','m_characterEntity',0x30)
  field('Dogs.Character','CharacterEntity','m_characterClass',0x80)
  field('Dogs.Character','CharacterEntity','m_hp',0x84)
  field('Dogs.Character','CharacterEntity','m_condition',0x40)
  local old=hex('55488BEC4883EC50488975F048897DF8')
  expect(a,old);expect(a+0x10,hex('488BF10FB6466185C00F8400010000'))
  expect(maxhp,hex('55488BEC4883EC60488975D048897DD8'))
  expect(sethp,hex('55488BEC4883EC40488975F048897DF8488BF9488BF2'))
  expect(heal,hex('55488BEC4883EC6048895DC8488975D0'))
  local cave=M.caves[a]
  if not cave then
   cave=allocateMemory(4096);assert(cave and cave~=0,'Cannot allocate healing hook')
   local flag=cave+0xe00
   local body,labels,fixups={},{},{}
   local function emit(s) body=join(body,hex(s)) end
   local function q(v) body=join(body,bytes(string.pack('&lt;I8',v))) end
   local function label(s) labels[s]=#body end
   local function branch(op,s) emit(op);fixups[#fixups+1]={at=#body,label=s};emit('00000000') end
   local function call(addr) emit('49BB');q(addr);emit('41FFD3') end
   -- Windows x64: preserve nonvolatile registers and provide aligned shadow space.
   emit('53564883EC28488BD9') -- push rbx/rsi, sub rsp,28, rbx=brain
   emit('48B8');q(flag);emit('833800');branch('0F84','done')
   emit('807B6100');branch('0F84','done')
   emit('807B7400');branch('0F85','done')
   emit('488B73304885F6');branch('0F84','done')
   emit('83BE8000000000');branch('0F85','done')
   emit('83BE8400000000');branch('0F8E','done') -- do not revive dead dogs
   emit('48B8');q(flag);emit('F70002000000');branch('0F84','hp')
   emit('488B46404885C0');branch('0F84','hp')
   emit('83781800');branch('0F8E','hp')
   for condition=0,3 do
    emit('488BCEBA');body=join(body,bytes(string.pack('&lt;I4',condition)));call(heal)
   end
   label('hp');emit('488BCE');call(maxhp)
   emit('85C0');branch('0F8E','done')
   emit('3B8684000000');branch('0F8E','done')
   emit('8BD0488BCE');call(sethp)
   label('done');emit('488BCB4883C4285E5B')
   body=join(body,old);body=join(body,jump(a+#old))
   for _,f in ipairs(fixups) do
    local b=bytes(string.pack('&lt;i4',labels[f.label]-(f.at+4)))
    for i=1,4 do body[f.at+i]=b[i] end
   end
   assert(#body&lt;0xe00,'Healing hook exceeds allocation')
   put(flag,{0,0,0,0});put(cave,body);M.caves[a]=cave
  end
  M.careFlag=cave+0xe00
  local patch=jump(cave) while #patch&lt;#old do patch[#patch+1]=0x90 end
  return {spec(a,old,patch)}
 elseif which=='hp' then
  field('Dogs.Character','CharacterBrain','m_characterEntity',0x30)
  field('Dogs.Character','CharacterEntity','m_characterClass',0x80)
  local a,n=method('Dogs.Character','CharacterBrain','DecreaseHp');assert(n&gt;=0xc5,'Unexpected health method size')
  local old=hex('55488BEC4883EC3048897DF8488BF9488955F0')
  expect(a,old);expect(a+0x13,hex('488B4F30488BC1833900486391840000002B55F0'))
  local cave=M.caves[a]
  if not cave then
   cave=allocateMemory(4096);assert(cave and cave~=0,'Cannot allocate health hook')
   M.caves[a]=cave
   -- Positive damage is skipped for a dog; enemies and nonpositive deltas run original code.
   local body=join(hex('85D27E13488B41304885C0740A83B880000000007501C3'),old)
   body=join(body,jump(a+#old));put(cave,body)
  end
  local patch=jump(cave) while #patch&lt;#old do patch[#patch+1]=0x90 end
  return {spec(a,old,patch)}
 elseif which=='invincible' then
  local a,n=method('Dogs.Character','DogBrain','IsInvincible')
  assert(n&gt;=0x21,'Unexpected invincibility method size')
  field('Dogs.Character','CharacterBrain','m_invincibleFlag',0x75)
  field('Dogs.Character','DogBrain','m_dodgeIncredible',0x138)
  expect(a+8,hex('488BF10FB646750FB68E380100000BC1488B34244883C408C3'))
  local buff,bn=method('Dogs.Character','CharacterEntity','AddBuff')
  assert(bn&gt;=0x356,'Unexpected status application method size')
  expect(buff+0x46,hex('4963868000000085C00F8579000000837DC0040F846F000000'))
  expect(buff+0x9a,hex('488BC8488B00FF506885C0740733C0E9A8020000'))
  local patches=prepare('hp')
  patches[#patches+1]=spec(a,hex('4883EC0848893424'),hex('B801000000C39090'))
  return patches
 elseif which=='items' then
  local lab,ln=method('Mm24f.UI','CustomizeShopMenu','RemoveItem')
  local request=method('Mm24f.UI','CustomizeShopMenu','RequestItemCount')
  assert(ln&gt;=0x69,'Unexpected Weapon Laboratory material method size')
  expect(lab+8,hex('48894DF8488955F0'))
  expect(lab+0x36,join(hex('49BB'),bytes(string.pack('&lt;I8',request))))
  expect(lab+0x43,hex('4C8BC0488B45E8488BC8488B55F0833800'))
  expect(lab+0x60,hex('41FFD3488D65005DC3'))
  local a,n=method('Mm24f.UI','InventoryBox','GetStackInventoryItem')
  local manager,mn=method('Mm24f.UI','InventoryManager','GetStackInventoryItem')
  assert(n&gt;=0xff and mn&gt;=0xb8,'Unexpected inventory method size')
  expect(a,hex('55488BEC4883EC50488975E848897DF04C897DF8'))
  expect(a+0x63,hex('85C00F8E7F000000')) -- owning at least one item is still required
  expect(manager,hex('55488BEC4883EC40488975F848894DF0488BF2'))
  expect(manager+0x30,hex('41FFD3488945E8'))
  local allowed={}
  for _,v in ipairs({{'Dogs.Character','UserInput','LateUpdate'},
                    {'Mm24f.UI','DogsItemMenu','UseItem'},
                    {'Dogs.Character','CharacterEntity','UseEquipedDisposableItem'}}) do
   local addr,size=method(v[1],v[2],v[3])
   expect(addr,hex('55488BEC'));allowed[#allowed+1]={addr,addr+size}
  end
  local site=a+0x8b
  local old=hex('4C8BC0488B45D841FFC8488BC8488BD6')
  expect(site,old);expect(a+0x9b,hex('83380049BB'))
  local cave=M.caves[site]
  if not cave then
   cave=allocateMemory(4096);assert(cave and cave~=0,'Cannot allocate item-use hook')
   local body,labels,fixups={},{},{}
   local function emit(s) body=join(body,hex(s)) end
   local function q(v) body=join(body,bytes(string.pack('&lt;I8',v))) end
   local function label(s) labels[s]=#body end
   local function branch(op,s) emit(op);fixups[#fixups+1]={at=#body,label=s};emit('00000000') end
   emit('4C8BC0488B45D850') -- replay count/object loads, save RAX
   emit('488B450849BB');q(manager+0x33);emit('4C39D8');branch('0F85','consume')
   emit('488B4500488B4008') -- verified caller frame: caller of inventory manager
   for i,range in ipairs(allowed) do
    emit('49BB');q(range[1]);emit('4C39D8');branch('0F82','next'..i)
    emit('49BB');q(range[2]);emit('4C39D8');branch('0F82','keep')
    label('next'..i)
   end
   label('consume');emit('5841FFC8');branch('E9','resume')
   label('keep');emit('58')
   label('resume');emit('488BC8488BD6');body=join(body,jump(a+0x9b))
   for _,f in ipairs(fixups) do
    local b=bytes(string.pack('&lt;i4',labels[f.label]-(f.at+4)))
    for i=1,4 do body[f.at+i]=b[i] end
   end
   put(cave,body);M.caves[site]=cave
  end
  local patch=jump(cave) while #patch&lt;#old do patch[#patch+1]=0x90 end
  return {
   spec(site,old,patch),
   -- Only the laboratory's material-spending helper is skipped. Requirement
   -- checks, the modification itself, payment and other inventory paths remain.
   spec(lab,hex('55488BEC4883EC40'),hex('C390909090909090'))
  }
 elseif which=='cooldowns' then
  local rush,rn=method('Dogs.Character','DogBrain','GetRushPower')
  local can,cn=method('Dogs.Character','DogBrain','CanDodge')
  local progress,pn=method('Dogs.Character','DogBrain','GetDodgeCoolingProgress')
  local fire=method('Dogs.Character','DogBrain','Fire')
  assert(rn&gt;=0x90 and cn&gt;=0x8f and pn&gt;=0x6d,'Unexpected skill cooldown method size')
  field('Dogs.Character','DogBrain','m_dodgeCooling',0x130)
  field('Dogs.Character','DogBrain','m_dodgeTime',0x114)
  field('Dogs.Character','DogBrain','m_dodgeDuration',0x10c)
  expect(rush+0x10,hex('488BF1488B4630488BC8BA0400000041B801000000'))
  expect(fire+0x46,join(hex('49BB'),bytes(string.pack('&lt;I8',rush))))
  expect(can+0x44,hex('660F2FC80F8A350000000F872F000000F30F108614010000'))
  expect(can+0x60,hex('F30F108E0C010000F30F5AC94833C0660F2FC8'))
  expect(progress+0xf,hex('F30F108630010000F30F5AC0'))
  return {
   spec(rush,hex('55488BEC4883EC40488975F0'),hex('B80000C842660F6EC0C39090')),
   spec(can+0x48,hex('0F8A350000000F872F000000'),hex('909090909090909090909090')),
   spec(progress,hex('55488BEC4883EC30488975F8'),hex('B80000803F660F6EC0C39090'))
  }
 elseif which=='currency' then
  local gold,gn=method('Dogs.Core','GameManager','get_Gold')
  local pay,pn=method('Dogs.Core','GameManager','PayGold')
  assert(gn&gt;=0x3a and pn&gt;=0xa8,'Unexpected currency method size')
  field('Dogs.Core','GameManager','m_saveData',0x18)
  field('Dogs.Core','GameSaveData','_gold',0xc0)
  expect(gold+0x23,hex('488B4018488BC8833900486380C0000000'))
  expect(pay+0x93,hex('2BCE8338008988C0000000'))
  expect(gold,hex('55488BEC4883EC20'))
  expect(pay,hex('55488BEC4883EC60'))
  -- Keep the original static getter's singleton lookup. Add the allowance
  -- only after it has resolved the current save object.
  local function hook(site,old,body,resume)
   local cave=M.caves[site]
   if not cave then
    cave=allocateMemory(4096,site);assert(cave and cave~=0,'Cannot allocate currency hook')
    put(cave,join(body,jump(resume)));M.caves[site]=cave
   end
   local delta=cave-(site+5)
   assert(delta&gt;=-2147483648 and delta&lt;=2147483647,'Currency hook is too far from game code')
   local entry=join(hex('E9'),bytes(string.pack('&lt;i4',delta)))
   while #entry&lt;#old do entry[#entry+1]=0x90 end
   return spec(site,old,entry)
  end
  expect(pay+0x2b,hex('488B4018488BC8833900486380C0000000'))
  expect(pay+0x3c,hex('3BF00F8F64000000'))
  expect(pay+0x9e,hex('488B75F8488D65005DC3'))
  return {
   hook(gold+0x2d,hex('486380C0000000'),hex('486380C000000005804A5D05'),gold+0x34),
   -- EAX=current saved surplus, ESI=cost, RCX=save object.
   -- Deduct down to zero surplus; the displayed allowance remains available.
   hook(pay+0x3c,hex('3BF00F8F64000000'),hex('2BC633D285C00F48C28981C0000000'),pay+0x9e)
  }
 elseif which=='clips' then
  local a,n=method('Dogs.Item','ItemEntity','UseReloadItem')
  assert(n&gt;=0xcc,'Unexpected reload-item method size')
  local old=hex('55488BEC4881EC80000000')
  expect(a,old)
  -- Return success without removing a reserve clip. Action, Reload and
  -- TimeProgress still deplete the magazine and run the original reload timer.
  return {spec(a,old,hex('B801000000C39090909090'))}
 elseif which=='ammo' then
  field('Dogs.Item','ItemEntity','m_bullet',0x50)
  field('Dogs.Item','ItemEntity','m_elapsedLastFire',0x44)
  local action,an=method('Dogs.Item','ItemEntity','Action')
  local fire,fn=method('Dogs.Character','DogBrain','Fire')
  local progress,pn=method('Dogs.Item','ItemEntity','GetReloadingProgress')
  local reloading=method('Dogs.Item','ItemEntity','IsReloading')
  local can=method('Dogs.Item','ItemEntity','CanAction')
  assert(an&gt;=0xf6 and fn&gt;=0x258 and pn&gt;=0x65,'Unexpected weapon method size')
  expect(action+0x76,hex('660F57C0F20F5AE8F30F116F44'))
  expect(action+0xe3,hex('B801000000'))
  expect(fire+0x138,hex('49635650B90100000033C085D2480F4EC1'))
  -- Confirm that reload checks have not been inlined into these callers.
  expect(reloading+0xe,join(hex('49BB'),bytes(string.pack('&lt;I8',progress))))
  expect(can+0x16,join(hex('49BB'),bytes(string.pack('&lt;I8',reloading))))
  expect(fire+0x17e,join(hex('49BB'),bytes(string.pack('&lt;I8',can))))
  expect(action+0x5e,join(hex('49BB'),bytes(string.pack('&lt;I8',can))))
  local p=hex('B80000803F660F6EC0C39090') -- return float 1.0, reload progress complete
  return {
   spec(action+0x83,hex('488BCF'),hex('EB5E90')),
   spec(fire+0x14b,hex('741B'),hex('EB1B')),
   spec(progress,hex('55488BEC4883EC30488975F8'),p)
  }
 end
 error('Unknown feature')
end
local function transaction(fn)
 assert(identity(),'Game process changed; refusing memory writes')
 pause()
 local ok,err=xpcall(fn,debug.traceback)
 unpause()
 if not ok then error(err) end
end
function M.set(which,enabled)
 if enabled then
  if M.active[which] then return end
  assert(M.attach(),'Start Metal Dogs first')
  local counterparts={hp={'invincible',14},invincible={'hp',10},clips={'ammo',11},ammo={'clips',12}}
  local counterpart=counterparts[which]
  if counterpart then
   -- Restore the previous option before preparing overlapping patch sites.
   -- If cleanup fails, keep its checkbox and report the actual failure.
   M.set(counterpart[1],false)
   local record=getAddressList().getMemoryRecordByID(counterpart[2])
   if record and record.Active then record.Active=false end
  end
  local patches=prepare(which)
  transaction(function()
   for _,p in ipairs(patches) do expect(p.address,p.old) end
   M.patches[which]=patches
   local ok,err=pcall(function() for _,p in ipairs(patches) do put(p.address,p.new) end end)
   if not ok then
    local restored=true
    for _,p in ipairs(patches) do
     local current=read(p.address,#p.old)
     if not same(current,p.old) then
      if same(current,p.new) then local good=pcall(put,p.address,p.old);restored=restored and good else restored=false end
     end
    end
    if restored then M.patches[which]=nil end
    error(err..(restored and ' (rolled back)' or ' (cleanup pending; keep this table open)'))
   end
   M.active[which]=true
  end)
  if which=='hp' or which=='invincible' then M.syncCare() end
 else
  local patches=M.patches[which]
  if not patches then M.active[which]=nil return end
  if not identity() then error('Cannot restore yet: reattach to the same Metal Dogs process') end
  transaction(function()
   for _,p in ipairs(patches) do
    local b=read(p.address,#p.old)
    assert(same(b,p.old) or same(b,p.new),'Foreign code change detected; cleanup stopped')
   end
   for _,p in ipairs(patches) do if not same(read(p.address,#p.old),p.old) then put(p.address,p.old) end end
   M.patches[which]=nil;M.active[which]=nil
  end)
  if which=='hp' or which=='invincible' then M.syncCare() end
 end
end
function M.syncCare()
 local mask=(M.active.hp and 1 or 0)+(M.active.invincible and 2 or 0)
 if mask~=0 then
  M.set('care',true)
  transaction(function() put(M.careFlag,{mask,0,0,0}) end)
 else
  if M.careFlag and M.active.care then transaction(function() put(M.careFlag,{0,0,0,0}) end) end
  M.set('care',false)
 end
end
local itemCaps={
["DOID_HEAL_CAPSULE"]={20,3},
["DOID_ENERGY_DRINK"]={20,3},
["DOID_ENERGY_DRINK_SUPER"]={20,3},
["DOID_ENERGY_DRINK_ULTRA"]={15,3},
["DOID_MAX_HEAL_DRINK"]={5,3},
["DOID_FIRE_EXTINGUISHER"]={99,3},
["DOID_CARE_POISON"]={99,3},
["DOID_PARAWAY"]={99,3},
["DOID_DOPING_TAB"]={25,3},
["DOID_TOUGH_TAB"]={25,3},
["DOID_SPEED_TAB"]={25,3},
["DOID_MODTICKET"]={999,999},
["DOID_EXTEND_ITEM_012"]={25,3},
["DOID_EXTEND_ITEM_013"]={99,3},
["DOID_EXTEND_ITEM_020"]={99,99},
["DOID_EXTEND_ITEM_021"]={9999,500},
["DOID_EXTEND_ITEM_022"]={9999,300},
["DOID_EXTEND_ITEM_023"]={9999,300},
["DOID_EXTEND_ITEM_024"]={999,999},
["DOID_EXTEND_ITEM_025"]={1,1},
["DOID_EXTEND_ITEM_027"]={1,1},
["DOID_EXTEND_ITEM_028"]={1,1},
["DOID_EXTEND_ITEM_029"]={1,1},
["DOID_EXTEND_ITEM_030"]={1,1},
["DOID_EXTEND_ITEM_031"]={1,1},
["DOID_EXTEND_ITEM_032"]={1,1},
["DOID_EXTEND_ITEM_033"]={1,1},
["DOID_EXTEND_ITEM_034"]={1,1},
["DOID_EXTEND_ITEM_035"]={1,1},
["DOID_EXTEND_ITEM_036"]={1,1},
["DOID_EXTEND_ITEM_037"]={1,1},
["DOID_EXTEND_ITEM_038"]={1,1},
["DOID_EXTEND_ITEM_039"]={1,1},
["DOID_EXTEND_ITEM_040"]={1,1},
["DOID_EXTEND_ITEM_041"]={1,1},
["DOID_EXTEND_ITEM_042"]={1,1},
["DOID_EXTEND_ITEM_043"]={1,1},
["MOT_0_DEBUG"]={1,1},
["HERB_0_DEBUG"]={1,1},
["HERB_1_DEBUG"]={1,1},
["RICE_BALL_DEBUG"]={1,1},
["MODTICKET_DEBUG"]={1,1},
["DOPING_TAB_DEBUG"]={1,1}
}
function M.updateInfiniteItems(restoring)
 assert(M.attach(),'Start Metal Dogs and load a save first')
 if not M.mono then assert(LaunchMonoDataCollector()~=0,'Mono connection failed');M.mono=true end
 local caps=itemCaps
 local function pointer(a) local p=readPointer(a);assert(p and p~=0,'Inventory is not initialized; load a save and open inventory first');return p end
 local function singleton(context,getter)
  if same(read(getter,2),hex('49BB')) then
   -- An uncalled generic getter still points at Mono's lazy call trampoline.
   expect(getter+10,hex('41FFD3'))
  else
   expect(getter+0x7e,hex('49BB'))
   local helper=pointer(getter+0x80)
   expect(helper,hex('488B41384885C0740A488B40084885C07401C3'))
  end
  return pointer(pointer(pointer(context+0x38)+8))
 end
 local resolver=M.itemResolver
 if not resolver then
  local lab=method('Mm24f.UI','CustomizeShopMenu','RemoveItem')
  expect(lab+0x10,hex('49BA'));expect(lab+0x1e,hex('49BB'))
  local flagMethod=method('Mm24f.Core','FlagManager','GetIntegerFlag')
  expect(flagMethod+0x16,hex('49BA'));expect(flagMethod+0x26,hex('49BB'))
  field('Mm24f.UI','InventoryManager','_itemBox',0x18)
  field('Mm24f.UI','InventoryBox','_stackItemDictionary',0x10)
  field('Mm24f.Core','FlagManager','m_flags',0x18)
  resolver={lab=lab,flagMethod=flagMethod,managerClass=mono_findClass('Mm24f.UI','InventoryManager'),flagClass=mono_findClass('Mm24f.Core','FlagManager'),classes={}}
  M.itemResolver=resolver
 end
 local manager=singleton(pointer(resolver.lab+0x12),pointer(resolver.lab+0x20))
 local flags=singleton(pointer(resolver.flagMethod+0x18),pointer(resolver.flagMethod+0x28))
 if resolver.manager~=manager then
  assert(mono_object_getClass(manager)==resolver.managerClass,'Inventory object type changed')
  resolver.manager=manager
 end
 if resolver.flags~=flags then
  assert(mono_object_getClass(flags)==resolver.flagClass,'Flag manager object type changed')
  resolver.flags=flags
 end
 local box=pointer(manager+0x18);local dict=pointer(box+0x10)
 local flagDict=pointer(flags+0x18)
 local function validateDictionary(obj)
  if resolver.classes[obj] then return end
  local c=mono_object_getClass(obj);assert(c,'Invalid item dictionary')
  local fs={} for _,f in ipairs(mono_class_enumFields(c,false) or {}) do fs[f.name]=f.offset end
  assert(fs.entries==0x18 and fs.count==0x40 and fs.version==0x44,'Unsupported inventory dictionary layout')
  resolver.classes[obj]=true
 end
 validateDictionary(dict);validateDictionary(flagDict)
 local function entries(obj)
  local ar=pointer(obj+0x18);local count=readInteger(obj+0x40)
  local capacity=readInteger(ar+0x18)
  assert(count and capacity and count&gt;=0 and count&lt;=capacity and capacity&lt;=100000,'Invalid inventory size')
  local result={}
  for i=0,count-1 do
   local e=ar+0x20+i*24;local hash=readInteger(e)
   assert(hash,'Unreadable inventory entry')
   if hash&gt;=0 and hash&lt;0x80000000 then
    local key=pointer(e+8);local length=readInteger(key+0x10)
    assert(length and length&gt;0 and length&lt;512,'Invalid inventory item identifier')
    local id=readString(key+0x14,length*2,true)
    assert(id and #id&gt;0,'Unreadable inventory item identifier')
    result[#result+1]={id=id,address=e+16,count=readInteger(e+16)}
   end
  end
  return result
 end
 local snapshot=M.itemSnapshot
 local pending={}
 local changed=0
 transaction(function()
  assert(pointer(manager+0x18)==box and pointer(box+0x10)==dict and pointer(flags+0x18)==flagDict,'Inventory changed; retry')
  local mode=1
  local flagEntries=entries(flagDict)
  for _,e in ipairs(flagEntries) do if e.id=='SF_EXTEND_FLAG_001' and e.count~=0 then mode=2 end end
  if snapshot then
   assert(snapshot.pid==M.pid and snapshot.box==box and snapshot.dict==dict and snapshot.flags==flagDict,
    'Inventory session changed; original item counts retained. Return to the original save before disabling Infinite Items.')
  end
  local writes={}
  local found={}
  local function consider(e,limit,key)
   assert(e.count,'Unreadable inventory count')
   local original=snapshot and snapshot.counts[key]
   local target
   if original~=nil then
    found[key]=true
    target=restoring and original or limit
   elseif not restoring and limit and limit&gt;0 and e.count&gt;0 then
    pending[key]=e.count;target=limit
   end
   if target and target~=e.count then
    writes[#writes+1]={address=e.address,old=bytes(string.pack('&lt;I4',e.count)),new=bytes(string.pack('&lt;I4',target))}
   end
  end
  for _,e in ipairs(entries(dict)) do
   consider(e,caps[e.id] and caps[e.id][mode],'item:'..e.id)
  end
  for _,e in ipairs(flagEntries) do
   if e.id=='SF_EXTEND_FLAG_007' then consider(e,99999,'tag') end
  end
  if snapshot then
   for key in pairs(snapshot.counts) do assert(found[key],'Owned item entry disappeared: '..key..'; original counts retained for cleanup') end
  end
  local done={}
  local ok,err=pcall(function()
   for _,w in ipairs(writes) do expect(w.address,w.old);done[#done+1]=w;put(w.address,w.new) end
  end)
  if not ok then
   for _,w in ipairs(done) do if same(read(w.address,4),w.new) then put(w.address,w.old) end end
   error(err)
  end
  changed=#writes
  if not restoring then
   if not snapshot then snapshot={pid=M.pid,box=box,dict=dict,flags=flagDict,counts={}} end
   for key,count in pairs(pending) do snapshot.counts[key]=count end
   M.itemSnapshot=snapshot
  end
 end)
 return changed
end
function M.infiniteItems(enabled)
 if enabled then
  M.updateInfiniteItems(false)
  M.active.maxitems=true;M.itemError=nil
 else
  if M.itemSnapshot then M.updateInfiniteItems(true) end
  M.itemSnapshot=nil;M.active.maxitems=nil;M.itemError=nil;M.itemResolver=nil
 end
 if M.timer then M.timer.Interval=M.active.maxitems and 250 or 1500 end
end
function M.clear()
 M.forgetExitedProcess()
 M.infiniteItems(false)
 for _,which in ipairs({'items','cooldowns','invincible','currency','clips','ammo','hp','care'}) do M.set(which,false) end
 for _,id in ipairs({10,11,12,13,14,15,17,18,3}) do local r=getAddressList().getMemoryRecordByID(id) if r then r.Active=false end end
end
function M.shutdown()
 if M.timer then M.timer.Enabled=false end
 if next(M.patches) or M.itemSnapshot then M.clear() end
 if M.timer then M.timer.destroy();M.timer=nil end
 -- A paused game thread could still be inside a hook. Keep hook pages valid
 -- until process exit instead of risking an executable use-after-free.
 if onTableLoad==M.tableHandler then onTableLoad=M.oldTableHandler end
 local form=getMainForm()
 if form.OnCloseQuery==M.closeHandler then form.OnCloseQuery=M.oldCloseHandler end
end
function M.start()
 M.oldTableHandler=onTableLoad
 M.tableHandler=function(before)
  if before then M.shutdown() end
  if M.oldTableHandler then M.oldTableHandler(before) end
 end
 onTableLoad=M.tableHandler
 local form=getMainForm();M.oldCloseHandler=form.OnCloseQuery
 M.closeHandler=function(sender)
  local ok,e=pcall(M.shutdown)
  if not ok then showMessage('Metal Dogs cleanup failed: '..tostring(e));return false end
  if M.oldCloseHandler then return M.oldCloseHandler(sender) end
  return true
 end
 form.OnCloseQuery=M.closeHandler
 M.timer=createTimer(nil);M.timer.Interval=1500
 M.timer.OnTimer=function()
  local ok,e=pcall(M.attach)
  if not ok then M.ready=false;status('not ready: '..tostring(e)) end
  if ok and M.ready and M.active.maxitems and not M.itemError then
   local good,reason=pcall(M.updateInfiniteItems,false)
   if not good then
    if not M.forgetExitedProcess() then
     M.itemError=tostring(reason)
     status('Infinite Items paused: '..M.itemError)
     -- Background inventory teardown can precede process exit. Keep the
     -- snapshot for explicit cleanup, but never interrupt with a timer dialog.
    end
   end
  end
 end
 M.timer.OnTimer()
end
-- Build fingerprints are filled by the build script from the inspected installation.
M.exeHash='636241d259bd2f36c86c0ba19bc3706a'
M.assemblyHash='7763f88c119e5407fc1ef47c32b578a2'
if not MD_TESTING then M.start() end
</LuaScript>
</CheatTable>