<?xml version='1.0' encoding='utf-8'?>
<CheatTable CheatEngineTableVersion="45"><CheatEntries><CheatEntry><ID>1</ID><Description>"Auto-Attach and Version Check"</Description><GroupHeader>1</GroupHeader></CheatEntry><CheatEntry><ID>2</ID><Description>"☕ Fuel the Cheats — Tip Katzy (Toggle to open my Ko-fi page)"</Description><GroupHeader>1</GroupHeader></CheatEntry><CheatEntry><ID>3</ID><Description>"How to use - Katzy"</Description><GroupHeader>1</GroupHeader></CheatEntry><CheatEntry><ID>4</ID><Description>"Clear All"</Description><GroupHeader>1</GroupHeader></CheatEntry><CheatEntry><ID>5</ID><Description>"[MAIN] Bloodstained: Curse of the Moon 2"</Description><Options moHideChildren="1" /><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript><CheatEntries><CheatEntry><ID>10</ID><Description>"Infinite HP"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>11</ID><Description>"Invincibility"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>12</ID><Description>"Infinite Lives"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>14</ID><Description>"Infinite Jumps"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>15</ID><Description>"Infinite Weapon Points"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>13</ID><Description>"Hold Timer at Max"</Description><VariableType>Auto Assembler Script</VariableType><AssemblerScript>[ENABLE]
// Controlled by guarded table callbacks.
[DISABLE]
</AssemblerScript></CheatEntry><CheatEntry><ID>19</ID><Description>"Subweapon Picker"</Description><Options moHideChildren="1" /><GroupHeader>1</GroupHeader><CheatEntries><CheatEntry><ID>20</ID><Description>"Alfred"</Description><DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Burning Sphere
2:Frostcalibur
3:Soul Vision
4:Voltaic Ray</DropDownList><VariableType>4 Bytes</VariableType><Address>0</Address></CheatEntry><CheatEntry><ID>21</ID><Description>"Dominique"</Description><DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Resurrection Anthem
2:Healing Plant
3:Electro Crawler
4:Rising Wind
5:Impact Lance</DropDownList><VariableType>4 Bytes</VariableType><Address>0</Address></CheatEntry><CheatEntry><ID>22</ID><Description>"Miriam"</Description><DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Dagger
2:Rapier
3:Axe
4:Scythe</DropDownList><VariableType>4 Bytes</VariableType><Address>0</Address></CheatEntry><CheatEntry><ID>23</ID><Description>"Robert"</Description><DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Partisan
2:Hand Grenade
3:Drake Cannon
4:Rapid Scarf</DropDownList><VariableType>4 Bytes</VariableType><Address>0</Address></CheatEntry><CheatEntry><ID>24</ID><Description>"Zangetsu"</Description><DropDownList ReadOnly="1" DescriptionOnly="1" DisplayValueAsItem="1">1:Ball-and-Chain
2:Magic Charm
3:Demon Essence</DropDownList><VariableType>4 Bytes</VariableType><Address>0</Address></CheatEntry></CheatEntries></CheatEntry></CheatEntries></CheatEntry></CheatEntries><UserdefinedSymbols /><LuaScript>--[[
Table identification: Katzy
Bloodstained: Curse of the Moon 2 / Steam build 5300639 / v1.17
Original native-code derivation; no community trainer code.
]]
if KatzyCOTM2 then
  local previous=KatzyCOTM2
  local ok,result=pcall(previous.shutdown)
  if not ok or not result then
    if next(previous.ledger or {}) or previous.timerOwned then
      showMessage('The previous table still has game changes to restore. Keep the game attached and use Clear All.');return
    end
    -- Upgrade from a version whose UI cleanup threw after game restoration.
    previous.stopped=true;previous.locks={}
    for _,name in ipairs({'watch','service'}) do
      local timer=previous[name];previous[name]=nil
      if timer then pcall(function() timer.Enabled=false end);pcall(function() timer.destroy() end) end
    end
    pcall(function()
      local list=getAddressList()
      if list.OnValueChange==previous.valueChange then list.OnValueChange=previous.previousValueChange end
    end)
    if previous.close then pcall(function() getMainForm().unregisterCloseCallback(previous.close) end);previous.close=nil end
  end
end
local T={ledger={},resolved={},busy=false,hooks={},locks={}}
KatzyCOTM2=T
local patches={
  {group='hp',pattern='F3 0F 10 4D C8 0F 57 D2 0F 2F C1 72 3A 80 7B 20 00 0F 28 CA 74 06 F6 43',offset=8,old={0x0f,0x2f,0xc1,0x72,0x3a},new={}},
  {group='invincible',pattern='55 8B EC 56 8B F1 8B 06 8B 80 D4 00 00 00 FF D0 84 C0 0F 85 B7 00 00 00',offset=0,old={0x55,0x8b,0xec,0x56,0x8b,0xf1},new={}},
  {group='lives',pattern='8B 88 AC 02 00 00 83 78 14 02 89 4D A4 74 79 85 C9 8D 51 FF 0F 44 D1 8D 4D B0 89 55 A4',offset=19,old={0xff},new={0x00}},
  {group='timer',pattern='A1 ?? ?? ?? ?? 8B 48 08 8B 41 08 85 C0 74 04 48 89 41 08',offset=8,old={0x8b,0x41,0x08,0x85,0xc0,0x74,0x04,0x48,0x89,0x41,0x08},new={}},
  {group='timer',pattern='A1 ?? ?? ?? ?? 8B 40 08 C7 40 08 24 EA 00 00 8B B1 8C 01 00 00',offset=8,old={0xc7,0x40,0x08,0x24,0xea,0x00,0x00},new={}},
  {group='points',pattern='85 C0 74 0D 8B 40 08 F6 40 0C 01 74 04 B0 01 5E C3',offset=11,old={0x74,0x04},new={0x90,0x90}},
  {group='points',pattern='85 C0 74 09 8B 40 08 F6 40 0C 01 75 5C E8 C8 FE FF FF',offset=11,old={0x75},new={0xeb}},
  {group='points',pattern='85 C0 74 09 8B 40 08 F6 40 0C 01 75 59 A1 ?? ?? ?? ??',offset=11,old={0x75},new={0xeb}},
  {group='jump',pattern='83 E0 FE 89 87 80 05 00 00 C1 E8 02 A8 01 0F 85 FF 00 00 00',offset=14,old={0x0f,0x85,0xff,0x00,0x00,0x00},new={0x90,0x90,0x90,0x90,0x90,0x90}},
  {group='jump',pattern='8B CF E8 22 B1 FF FF 0F 57 C9 84 C0 0F 84 F0 00 00 00 F3 0F 10 87 18 05 00 00 0F 2F C1',offset=12,old={0x0f,0x84,0xf0,0x00,0x00,0x00},new={0x90,0x90,0x90,0x90,0x90,0x90}},
  {group='jump',pattern='68 01 80 00 00 68 43 A0 1F A9 8D 45 E4 C7 87 18 05 00 00 00 00 00 00 50',offset=0,old={0x68,0x01,0x80,0x00,0x00},new={0xe9,0x77,0xf9,0xff,0xff}},
  {group='invincible',pattern='55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 51 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B F9 8B 8F F8 04 00 00 83 B9 64 01 00 00 00 74 75 8D 45 F0 81 C1 5C 01 00',offset=0,old={0x55,0x8b,0xec,0x6a,0xff},new={}},
  {group='lives',pattern='E8 5A 14 11 00 33 F6 8B 40 5C 48 85 C0 0F 4F F0 E8 4A 14 11 00 89 70 5C',offset=10,old={0x48},new={0x90}},
  {group='invincible',pattern='8B 82 F8 04 00 00 8B 40 0C C1 E8 03 A8 01 74 18 F7 82 9C 04 00 00 80 07 00 00 74 0C 6A 01 8B CA E8 64 ED FF FF 8B 55 E8',offset=26,old={0x74},new={0xeb}},
}
local function rec(id) return getAddressList().getMemoryRecordByID(id) end
local function message(s) showMessage(s) end
local function module(pid)
  local ok,mods=pcall(enumModules,pid)
  if ok and mods then for _,m in ipairs(mods) do
    if m.Name:lower()=='game.exe' and m.PathToFile:lower():gsub('/','\\'):match('bloodstained curse of the moon 2\\game%.exe$') then return m end
  end end
end
local function alive()
  local ok,p=pcall(getProcesslist)
  if ok and p and T.owner then return p[T.owner.pid]~=nil end
end
local function same()
  if not T.owner or getOpenedProcessID()~=T.owner.pid then return false end
  -- The executable cannot change while this validated process is suspended.
  -- Keep checking its lifetime and mapped header before every write.
  if not T.transactionValidated then
    local m=module(T.owner.pid)
    if not m or m.Address~=T.owner.base or m.PathToFile~=T.owner.path then return false end
  end
  local age=getProcessAge()
  if not age then return false end
  local birth=getTickCount()-age
  if math.abs(birth-T.owner.birth)&gt;2000 then return false end
  return readInteger(T.owner.stamp)==0x5f11800b and readSmallInteger(T.owner.base)==0x5a4d
end
local function validate()
  if not AOBScanModuleUnique then return false,'Use Cheat Engine 7.6 or newer.' end
  if next(T.ledger) then
    if same() then return true end
    return false,'Reattach to the original game and use Clear All before changing processes.'
  end
  local candidates={}
  for pid,name in pairs(getProcesslist()) do
    if name:lower()=='game.exe' then local m=module(pid); if m then candidates[#candidates+1]={pid=pid,m=m} end end
  end
  if #candidates~=1 then return false,'Start one copy of Bloodstained: Curse of the Moon 2, then enable MAIN.' end
  local c=candidates[1]; local m=c.m
  local ok,hash=pcall(md5file,m.PathToFile)
  if not ok or not hash or hash:lower()~='c7af661b70d8ed38dbb236ce51ce4150' then
    return false,'This table supports Steam build 5300639. This executable needs a matching table.'
  end
  pcall(getFileVersion,m.PathToFile) -- this supported executable has no version resource
  if getOpenedProcessID()~=c.pid then openProcess(c.pid) end
  local pe=readInteger(m.Address+0x3c)
  if not pe or readInteger(m.Address+pe+8)~=0x5f11800b or readInteger(m.Address+pe+0x50)~=0xad8000 then
    return false,'The running game does not match the supported build. Restart it and try again.'
  end
  local birth=getTickCount()-getProcessAge()
  if not T.owner or T.owner.pid~=c.pid or math.abs(T.owner.birth-birth)&gt;2000 then
    T.resolved={}; T.cave=nil;T.hooks={};T.scratch=nil;T.scratchReady=nil;T.locks={}
  end
  T.owner={pid=c.pid,base=m.Address,path=m.PathToFile,stamp=m.Address+pe+8,birth=birth}
  return true
end
local function bytes(a,n) return readBytes(a,n,true) end
local function equal(a,b)
  local actual=bytes(a,#b)
  if not actual or #actual~=#b then return false end
  for i,v in ipairs(b) do if actual[i]~=v then return false end end
  return true
end
local function write(a,b)
  return same() and pcall(writeBytes,a,b) and equal(a,b)
end
local function u32(v)
  v=v%0x100000000; local b={}
  for i=1,4 do b[i]=v%256; v=math.floor(v/256) end
  return b
end
local function append(a,b) for _,v in ipairs(b) do a[#a+1]=v end end
local function jump(a,b,n)
  local r={0xe9}; append(r,u32(b-a-5)); for i=6,n do r[i]=0x90 end; return r
end
local function transaction(fn,rollback)
  if not same() then message('Reattach to the original game before changing these options.'); return false end
  local wasPaused=isPaused()
  if not wasPaused then local ok=pcall(pause); if not ok or not isPaused() then message('The game could not be paused safely. Try again.'); return false end end
  T.transactionValidated=true
  local ok,result=pcall(fn)
  if (not ok or not result) and rollback then pcall(rollback) end
  T.transactionValidated=nil
  if not wasPaused then pcall(unpause) end
  if not ok or not result then message('The option could not be changed safely. Use Clear All before trying again.'); return false end
  return true
end
local function resolve(group)
  for i,p in ipairs(patches) do if p.group==group and not T.resolved[i] then
    local a=AOBScanModuleUnique('game.exe',p.pattern)
    if not a then return false end
    T.resolved[i]=a+p.offset
  end end
  return true
end
local function timerObject()
  local manager=readInteger(T.owner.base+0x764e44)
  if not manager or manager==0 then return nil end
  local obj=readInteger(manager+8)
  if obj and obj~=0 and readInteger(obj)==T.owner.base+0x64a7a0 then return obj end
end
local function saveObject()
  if not same() then return nil end
  local manager=readInteger(T.owner.base+0x702f74)
  if not manager or readInteger(manager)~=T.owner.base+0x646e90 then return nil end
  local slot=readInteger(manager+8)
  if not slot or slot&gt;32 then return nil end
  local save=readInteger(T.owner.base+0x9f6088+slot*8+4)
  if save and readInteger(save)==T.owner.base+0x640640 then return save,slot end
end
-- Keep native death handling; HP and invincibility use distinct damage filters.
local function prepareDefense(group,i)
  i=i or (group=='hp' and 1 or 2)
  local a=T.resolved[i]
  if T.hooks[i] then return equal(T.hooks[i].address,T.hooks[i].code) end
  local cave=allocateMemory(4096,nil,0x40);if not cave then return false end
  local code
  if group=='hp' then
    -- cmp [source],0; je native; cmp [terrain],0; jne native;
    -- cancel ordinary damage, retaining the native subtraction and reactions.
    code={0x83,0x3b,0,0x74,0x0e,0x80,0x7b,0x20,0,0x75,8,0x0f,0x57,0xc0}
    append(code,jump(cave+#code,a+0x3f,5)) -- native SUBSS at 7c0b1d
    append(code,{0x0f,0x2f,0xc1,0x0f,0x82});append(code,u32((a+0x3f)-(cave+#code+4)))
    append(code,jump(cave+#code,a+5,5))
  else
    -- Preserve forced deaths (flag 8) and source-less non-terrain events.
    -- Reject ordinary terrain damage even when it has no attacker.
    code={0x8b,0x44,0x24,4,0xf6,0x40,0x24,8,0x75,0x10,
      0x80,0x78,0x20,0,0x75,5,0x83,0x38,0,0x74,5,0x32,0xc0,0xc2,4,0}
    append(code,patches[i].old);append(code,jump(cave+#code,a+#patches[i].old,5))
  end
  if not write(cave,code) then return false end
  T.hooks[i]={address=cave,code=code};patches[i].new=jump(a,cave,#patches[i].old)
  return true
end
local function refillPlan()
  local save=saveObject();if not save then return nil end
  local changes={}
  -- Refill only active, living bodies. Do not revive a zero-HP character or
  -- refill a body already transitioning into its native death state.
  for player=0,1 do
    local char=readInteger(save+0x2b0+player*4)
    local body=readInteger(T.owner.base+0x9f633c+player*12+8)
    if char and char&lt;=7 and body and body~=0 and readInteger(body)==T.owner.base+0x642d30
      and readInteger(body+0x184)==player then
      local state=readInteger(body+0x560)
      local component=readInteger(body+0x480)
      local hp=readFloat(save+0x2b8+char*4);local max=readFloat(save+0x2f8+char*4)
      local mirror=component and component~=0 and readFloat(component+0x70)
      if state and state~=1 and state~=3 and hp and hp&gt;0 and max and max&gt;0 and max&lt;=1000 and mirror and mirror&gt;0 then
        local value=bytes(save+0x2f8+char*4,4)
        if hp&lt;max then changes[#changes+1]={save+0x2b8+char*4,value} end
        if mirror&lt;max then changes[#changes+1]={component+0x70,value} end
      end
    end
  end
  return changes
end
local function refill()
  local changes=refillPlan();if not changes then return false end
  for _,p in ipairs(changes) do if not write(p[1],p[2]) then return false end end
  return true
end
-- Two small, separately allocated hooks share only the countdown's state words.
-- Blocks remain allocated after detachment so a suspended thread can finish safely.
-- They are reused while this table is loaded; the OS reclaims them on game exit.
local function prepareTimer()
  if T.cave then return T.cave.ready==true end
  local count=allocateMemory(4096,nil,0x40)
  local reset=allocateMemory(4096,nil,0x40)
  if not count or not reset then return false end
  T.cave={count=count,reset=reset,state=count+0x100}
  local state=T.cave.state
  local a=T.resolved[4]; local r=T.resolved[5]
  -- pushfd; cmp [active],0; je original; compare owner; capture if changed;
  -- set max; popfd; jump back. Original path executes relocated countdown.
  local code={0x9c,0x83,0x3d}; append(code,u32(state+8)); append(code,{0,0x74,0x2c,0x39,0x0d}); append(code,u32(state))
  append(code,{0x74,0x0b,0x89,0x0d}); append(code,u32(state)); append(code,{0x8b,0x41,8,0xa3}); append(code,u32(state+4))
  -- The owner-capture block is 14 bytes (correct the short branch).
  code[18]=0x0e
  append(code,{0xc7,0x41,8,0x24,0xea,0,0,0xb8,0x24,0xea,0,0,0x9d})
  append(code,jump(count+#code,a+11,5))
  local original=count+#code
  code[10]=original-(count+10)
  append(code,{0x9d,0x8b,0x41,8,0x85,0xc0,0x74,4,0x48,0x89,0x41,8})
  append(code,jump(count+#code,a+11,5))
  local init={0xc7,0x40,8,0x24,0xea,0,0,0x9c,0x83,0x3d}; append(init,u32(state+8)); append(init,{0,0x74,0x0f,0xa3}); append(init,u32(state))
  append(init,{0xc7,5}); append(init,u32(state+4)); append(init,{0x24,0xea,0,0,0x9d})
  append(init,jump(reset+#init,r+7,5))
  T.cave.code=code; T.cave.init=init
  if not write(count,code) or not write(reset,init) or not write(state,{0,0,0,0,0,0,0,0,0,0,0,0}) then return false end
  patches[4].new=jump(a,count,11); patches[5].new=jump(r,reset,7)
  T.cave.ready=true
  return true
end
local function restoreBody(group)
  local timer=(not group or group=='timer') and T.timerOwned
  if timer and not write(T.cave.state+8,u32(0)) then return false end
  local good=true
  for i=#patches,1,-1 do
    local p=patches[i]; local a=T.ledger[i]
    if a and (not group or p.group==group) then
      if equal(a,p.old) or (equal(a,p.new) and write(a,p.old)) then T.ledger[i]=nil else good=false end
    end
  end
  if timer and good then
    local obj=timerObject(); local owner=readInteger(T.cave.state); local saved=readInteger(T.cave.state+4)
    if obj and obj==owner and readInteger(obj+8)==59940 then
      if not saved or saved&gt;59940 or not write(obj+8,u32(saved)) then return false end
    end
    T.timerOwned=nil
  end
  return good
end
local function restore(group)
  if not next(T.ledger) and not T.timerOwned then return true end
  if alive()==false then T.ledger={};T.timerOwned=nil;T.cave=nil;T.resolved={};return true end
  return transaction(function() return restoreBody(group) end)
end
local function enable(group)
  local ok,why=validate(); if not ok then message(why);return false end
  if not rec(5).Active then message('Enable MAIN first.');return false end
  if not resolve(group) then message('The required code was not found uniquely. Disable other trainers and restart the game.');return false end
  -- These blocks are unreachable until the hooks are installed below.
  if group=='timer' and not prepareTimer() then return false end
  return transaction(function()
    for i,p in ipairs(patches) do if p.group==group and not T.ledger[i] and not equal(T.resolved[i],p.old) then return false end end
    if (group=='hp' or group=='invincible') and not prepareDefense(group) then return false end
    if group=='invincible' and not prepareDefense(group,12) then return false end
    if group=='timer' then
      if not equal(T.cave.count,T.cave.code) or not equal(T.cave.reset,T.cave.init) then return false end
      local obj=timerObject();local value=obj and readInteger(obj+8)
      if not value or value&gt;59940 then return false end
      if not write(T.cave.state,u32(obj)) or not write(T.cave.state+4,u32(value)) or not write(T.cave.state+8,u32(1)) then return false end
      T.timerOwned=true
    end
    for i,p in ipairs(patches) do if p.group==group and not T.ledger[i] then
      local a=T.resolved[i]; T.ledger[i]=a
      if not write(a,p.new) then restoreBody(group);return false end
    end end
    if group=='timer' then
      local obj=timerObject()
      if not obj or not write(obj+8,u32(59940)) then restoreBody(group);return false end
    end
    if group=='hp' and not refill() then return false end
    return true
  end,function() return restoreBody(group) end)
end
local weaponDefs={
  [20]={character=2,max=4},[21]={character=4,max=5},[22]={character=1,max=4},
  [23]={character=5,max=4},[24]={character=0,max=3}
}
local function refreshUnlockedWeapons()
  if not T.scratch or not rec(19).Active then return end
  local save=saveObject()
  if not save then return end

  for id,definition in pairs(weaponDefs) do
    if not T.locks[id] then
      local character=definition.character
      if character==0 then
        local first=readInteger(save+0x2b0)
        local second=readInteger(save+0x2b4)
        if first==7 or second==7 then character=7 end
      end
      local value=readInteger(save+0x340+character*4)
      local display=T.scratch+(id-20)*4
      if value and value&gt;=0 and value&lt;=definition.max and readInteger(display)~=value then
        -- Update only the selector display; an unlocked picker never equips on its own.
        write(display,u32(value))
      end
    end
  end
end
local function initializePicker()
  local ok,why=validate();if not ok then message(why);return false end
  if not rec(5).Active then message('Enable MAIN first.');return false end
  local save=saveObject();if not save then message('Load your run before opening the picker.');return false end
  return transaction(function()
    if not T.scratch then
      T.scratch=allocateMemory(4096,nil,0x04);if not T.scratch then return false end
    end
    if not T.scratchReady then
      for id,d in pairs(weaponDefs) do
        local value=readInteger(save+0x340+d.character*4)
        if not value or value&lt;1 or value&gt;d.max then value=1 end
        if not write(T.scratch+(id-20)*4,u32(value)) then return false end
      end
      T.scratchReady=true
    end
    for id in pairs(weaponDefs) do rec(id).Address=string.format('%X',T.scratch+(id-20)*4) end
    return true
  end)
end
local function equip(id,value)
  local d=weaponDefs[id]
  if not d or not value or value%1~=0 or value&lt;1 or value&gt;d.max or not same() or not rec(5).Active then return false end
  local save=saveObject();if not save then return nil end
  local targets={save+0x340+d.character*4}
  if d.character==0 then targets[#targets+1]=save+0x340+7*4 end
  local pending=false;for _,a in ipairs(targets) do if readInteger(a)~=value then pending=true end end
  if not pending then return true end
  return transaction(function()
    if saveObject()~=save then return false end
    for _,a in ipairs(targets) do if not write(a,u32(value)) then return false end end
    return write(save+8,{1}) -- same dirty mark as native equipment setter
  end)
end
-- Disposed UI objects need no further cleanup.
local function resetRecord(id,address)
  pcall(function()
    local r=rec(id);if not r then return end
    if address then r.Address='0' elseif r.Active then r.Active=false end
  end)
end
local function stopPicker()
  T.locks={};T.busy=true
  for id in pairs(weaponDefs) do
    resetRecord(id);resetRecord(id,true)
  end
  resetRecord(19);T.busy=false
end
local function uncheck()
  stopPicker()
  T.busy=true
  for _,id in ipairs({10,11,12,13,14,15,5}) do resetRecord(id) end
  T.busy=false
end
function T.clear()
  if T.stopped then return true end
  if not restore() then return false end
  if not T.destroying then uncheck() else T.locks={} end
  return true
end
function T.shutdown(destroying)
  if T.stopped then return true end
  if destroying then T.destroying=true end
  if not T.clear() then return false end
  T.stopped=true
  if T.watch then
    local timer=T.watch;T.watch=nil
    pcall(function() timer.Enabled=false end)
    pcall(function() timer.destroy() end)
  end
  if T.service then
    local timer=T.service;T.service=nil
    pcall(function() timer.Enabled=false end)
    pcall(function() timer.destroy() end)
  end
  pcall(function()
    local list=getAddressList()
    if list and list.OnValueChange==T.valueChange then list.OnValueChange=T.previousValueChange end
  end)
  if T.close then
    local callback=T.close;T.close=nil
    pcall(function() getMainForm().unregisterCloseCallback(callback) end)
  end
  return true
end
local help=[=[Table identification: Katzy.

Start the game, allow this table's Lua, then enable MAIN. Choose each option separately.

Infinite HP: refills the living character you control and preserves health against enemy hits. Characters refill as you switch to them. Dead characters are not revived. Pits, terrain hazards and scripted damage retain their normal effects. Switch off to restore normal health loss; healing is retained.
Invincibility: blocks enemy hits and environmental hazard damage for all characters, including Flying Armor hits and collision damage during the shooter phase, without spending weapon points. Pitfalls and scripted deaths retain their normal effects. Switch off to restore normal hit detection.
Infinite Lives: prevents life loss in normal stages and the Flying Armor shooter phase while retaining earned lives. Enable before running out; it does not grant lives when already at zero. Switch off to restore normal life loss.
Infinite Jumps: press Jump again in midair to jump repeatedly using your character's normal jump strength. Normal action restrictions still apply. Switch off to restore normal jump limits.
Infinite Weapon Points: permits subweapon use even with an empty meter and stops weapon-point spending by attacks and sustained abilities. Pickups still add points; the meter shows the stored amount. Switch off to resume spending.
Hold Timer at Max: holds the stage countdown at 999. Switch off to resume the saved countdown. A stage reset starts a fresh countdown; stage-clear changes are retained.

Subweapon Picker: enable the group to show the character rows. Double-click the value beside a name to select and equip a subweapon. Tick the character name to lock its weapon; changing its dropdown updates the lock. Untick the name to allow pickups and follow the equipped weapon. Unlocking leaves the weapon equipped. Zangetsu covers both versions. Hachi and Gebel have no alternate subweapons. Locks follow stage changes; switch them off before starting a different run. Resurrection Anthem retains its native effects.

Clear All and switching MAIN off restore normal behavior. Clear All before manually changing the attached process. After restarting the game, enable MAIN and the desired options again.
Auto-Attach and Version Check checks the running game. Tip Katzy opens the Ko-fi page only when selected.]=]
for id,group in pairs({[10]='hp',[11]='invincible',[12]='lives',[13]='timer',[14]='jump',[15]='points'}) do
  rec(id).OnActivate=function(_,before)
    if not before or T.busy then return true end
    local ok,result=pcall(enable,group)
    if not ok then pcall(restore,group);message('This option could not be enabled. Use Clear All, then try again.');return false end
    return result
  end
  rec(id).OnDeactivate=function(_,before) if not before or T.busy then return true end;return restore(group) end
end
rec(19).OnActivate=function(_,before) if not before or T.busy then return true end;return initializePicker() end
rec(19).OnDeactivate=function(_,before) if before and not T.busy then stopPicker() end;return true end
for id,d in pairs(weaponDefs) do
  rec(id).OnValueChangedByUser=function(_,_,value)
    value=tonumber(value)
    local ok,result=pcall(equip,id,value)
    if ok and result then if T.locks[id] then T.locks[id]=value end
    else message('The subweapon could not be equipped. Enable MAIN, load your run and select the weapon again.') end
  end
  rec(id).OnActivate=function(_,before)
    if not before or T.busy then return true end
    if not rec(19).Active or not T.scratch then return false end
    local value=readInteger(T.scratch+(id-20)*4)
    if not equip(id,value) then return false end;T.locks[id]=value;return true
  end
  rec(id).OnDeactivate=function(_,before)if before then T.locks[id]=nil end;return true end
end
local list=getAddressList();T.previousValueChange=list.OnValueChange
T.valueChange=function(sender,r)
  if weaponDefs[r.ID] then
    if not rec(5).Active or not rec(19).Active or not same() or not T.scratch then message('Enable MAIN and Subweapon Picker first.');return true end
    return false
  end
  if T.previousValueChange then return T.previousValueChange(sender,r) end;return false
end
list.OnValueChange=T.valueChange
rec(5).OnActivate=function(_,before)
  if not before or T.busy then return true end
  local ok,result,why=pcall(validate)
  if not ok then message('The game could not be checked. Restart the game and reopen this table.');return false end
  if not result then message(why) end;return result
end
rec(5).OnDeactivate=function(_,before) if not before or T.busy then return true end;return T.clear() end
local function action(id,fn) rec(id).OnActivate=function(_,before) if before then fn();return false end;return true end end
action(1,function() local ok,why=validate();if not ok then message(why) end end)
action(2,function() shellExecute('https://ko-fi.com/katzy') end)
action(3,function() message(help) end)
action(4,T.clear)
rec(5).OnDestroy=function() T.shutdown(true) end
T.close=getMainForm().registerCloseCallback(function(_,closeAction)
  if T.clear() then return closeAction end
  return caNone
end)
T.watch=createTimer(nil,false);T.watch.Interval=2000
T.watch.OnTimer=function()
  if T.stopped or T.destroying then return end
  pcall(function()
    if T.owner and alive()==false then T.ledger={};T.timerOwned=nil;T.cave=nil;T.hooks={};T.scratch=nil;T.scratchReady=nil;T.resolved={};T.owner=nil;uncheck() end
    if not T.owner then validate() end
  end)
end
T.watch.Enabled=true;T.watch.OnTimer()
T.service=createTimer(nil,false);T.service.Interval=100
T.service.OnTimer=function()
  if T.stopped or T.destroying or T.busy then return end
  local main=rec(5);if not main or not main.Active then return end
  if not rec(10).Active and not next(T.locks) and not rec(19).Active then return end
  pcall(function()
    if not same() then return end
    if rec(10).Active then
      local pending=refillPlan()
      if pending and #pending&gt;0 then transaction(refill) end
    end
    for id,value in pairs(T.locks) do equip(id,value) end
    refreshUnlockedWeapons()
  end)
end
T.service.Enabled=true
</LuaScript></CheatTable>