<?xml version="1.0" encoding="utf-8"?>
<CheatTable UsesMono="1" CheatEngineTableVersion="52">
  <CheatEntries>
    <CheatEntry>
      <ID>1</ID>
      <Description>"Register Custom Type"</Description>
      <Options moHideChildren="1"/>
      <VariableType>Auto Assembler Script</VariableType>
      <AssemblerScript>{$lua}
if syntaxcheck then return end
local TYPE_NAME = "ObfuscatedInt (BB97)"
local SIZE = 0xC
local USE_AA = true   -- set to false to force the pure-Lua converter
-- Layout of the 12 bytes at the start of the ObfuscatedInt fields: _key, _obfuscatedValue, _checksum
local OFF_KEY, OFF_OBF, OFF_CHK = 0, 4, 8
[ENABLE]
bb97 = bb97 or {}
bb97.CustomTypes = bb97.CustomTypes or {}

local function registered() return getCustomType(TYPE_NAME) end

if not registered() and USE_AA then
  local script = [==[
alloc(TypeName,256)
alloc(ByteSize,4)
alloc(UsesFloat,1)
alloc(ConvertRoutine,256)
alloc(ConvertBackRoutine,256)

TypeName:
db 'ObfuscatedInt (BB97)',0

ByteSize:
dd C

UsesFloat:
db 0

// value = obfuscated ^ key
ConvertRoutine:
[64-bit]
mov eax,[rcx+@ROBF@]
xor eax,[rcx+@RKEY@]
ret
[/64-bit]
[32-bit]
mov ecx,[esp+4]
mov eax,[ecx+@ROBF@]
xor eax,[ecx+@RKEY@]
ret 8
[/32-bit]

// key = random, obfuscated = i ^ key, checksum = i*31 + key
ConvertBackRoutine:
[64-bit]
rdtsc
mov [r8+@WKEY@],eax
mov edx,ecx
xor edx,eax
mov [r8+@WOBF@],edx
imul ecx,ecx,1F
add ecx,eax
mov [r8+@CHK@],ecx
ret
[/64-bit]
[32-bit]
push ebx
mov ebx,[esp+10]
mov ecx,[esp+8]
rdtsc
mov [ebx+@WKEY@],eax
mov edx,ecx
xor edx,eax
mov [ebx+@WOBF@],edx
imul ecx,ecx,1F
add ecx,eax
mov [ebx+@CHK@],ecx
pop ebx
ret 0C
[/32-bit]
]==]
  script = script:gsub("@ROBF@", tostring(OFF_OBF)):gsub("@RKEY@", tostring(OFF_KEY))
                 :gsub("@WKEY@", tostring(OFF_KEY)):gsub("@WOBF@", tostring(OFF_OBF))
                 :gsub("@CHK@",  tostring(OFF_CHK))
  -- The signature differs between CE versions, so try each form
  local attempts = {
    function() return registerCustomTypeAutoAssembler(script) end,
    function() return registerCustomTypeAutoAssembler(TYPE_NAME, script) end,
    function() return registerCustomTypeAutoAssembler(TYPE_NAME, TYPE_NAME, script) end,
  }
  for _, f in ipairs(attempts) do
    pcall(f)
    if registered() then bb97.CustomTypeMode = "assembly"; break end
  end
end

if not registered() then
  -- Pure Lua fallback (slower, but works on every CE version)
  local function toSigned(v) v = v % 0x100000000; if v &gt;= 0x80000000 then v = v - 0x100000000 end; return v end
  local function dw(b, i) return b[i] + b[i+1]*0x100 + b[i+2]*0x10000 + b[i+3]*0x1000000 end
  math.randomseed(os.time() + math.floor(os.clock() * 1000000))
  registerCustomTypeLua(TYPE_NAME, SIZE,
    function(...)
      local b = {...}
      return toSigned(bXor(dw(b, OFF_OBF + 1), dw(b, OFF_KEY + 1)))
    end,
    function(v, address)
      v = math.floor(tonumber(v) or 0)
      local key = math.random(0, 0xFFFFFFFF)
      local obf = bXor(v % 0x100000000, key)
      local chk = (v * 31 + key) % 0x100000000
      local out = {}
      local function put(off, val)
        local t = dwordToByteTable(val)
        for j = 1, 4 do out[off + j] = t[j] end
      end
      put(OFF_KEY, key); put(OFF_OBF, obf); put(OFF_CHK, chk)
      return out
    end, false)
  bb97.CustomTypeMode = "lua"
end

if not registered() then error("Could not register the custom type " .. TYPE_NAME) end
bb97.CustomTypes[TYPE_NAME] = registered()

-- make sure the table's wallet record picked up the type even though it loaded before the type existed
pcall(function()
  local mr = getAddressList().getMemoryRecordByID(10)
  if mr then mr.Type = vtCustom; mr.CustomTypeName = TYPE_NAME end
end)
[DISABLE]
local guard = 0
local ct = getCustomType(TYPE_NAME)
while ct and guard &lt; 10 do
  ct.destroy()
  ct = getCustomType(TYPE_NAME)
  guard = guard + 1
end
if bb97 and bb97.CustomTypes then bb97.CustomTypes[TYPE_NAME] = nil end
</AssemblerScript>
      <CheatEntries>
        <CheatEntry>
          <ID>2</ID>
          <Description>"Locate Token Balance (Mono, takes 1-3s)"</Description>
          <Options moHideChildren="1"/>
          <VariableType>Auto Assembler Script</VariableType>
          <AssemblerScript>{$lua}
if syntaxcheck then return end
local NS = ""                                   -- both classes are in the global namespace (no 'namespace' line in dnSpy)
local CLS_TB, FLD_TOKEN = "TokenBalance", "token"
local CLS_TOK, FLD_INT  = "ObfuscatedToken", "intValue"
local CLS_OI            = "ObfuscatedInt"
local SYMBOL = "BB97_TokenSlot"
local ID_WALLET, ID_RAWKEY, ID_RAWOBF, ID_RAWCHK = 10, 21, 22, 23
[ENABLE]
bb97 = bb97 or {}
local function log(fmt, ...) print("[BB97] " .. string.format(fmt, ...)) end
local function toSigned(v) v = v % 0x100000000; if v &gt;= 0x80000000 then v = v - 0x100000000 end; return v end

if getOpenedProcessID() == 0 then error("Attach Cheat Engine to the game first.") end
if monopipe == nil and LaunchMonoDataCollector then pcall(LaunchMonoDataCollector) end
if mono_findClass == nil then error("Mono support isn't loaded. Use the Mono menu &gt; Activate mono features, then enable this again.") end

local HDR = targetIs64Bit() and 0x10 or 0x8

local function findClass(name)
  local c = mono_findClass(NS, name)
  if not c or c == 0 then error("Class '" .. name .. "' not found (is Mono active, and is the game past its loading screen?)") end
  return c
end

local function findField(c, name)          -- walks up the parent chain too
  local guard = 0
  while c and c ~= 0 and guard &lt; 16 do
    for _, f in ipairs(mono_class_enumFields(c) or {}) do
      if f.name == name and not f.isStatic then return f end
    end
    c = mono_class_getParent(c); guard = guard + 1
  end
end

local function minInstanceOffset(c)
  local m
  for _, f in ipairs(mono_class_enumFields(c) or {}) do
    if not f.isStatic and (not m or f.offset &lt; m) then m = f.offset end
  end
  return m or 0
end

local function resolveLayout()
  local cTB, cTok, cOI = findClass(CLS_TB), findClass(CLS_TOK), findClass(CLS_OI)
  local fToken = findField(cTB, FLD_TOKEN) or error(CLS_TB .. "." .. FLD_TOKEN .. " field not found")
  local fInt   = findField(cTok, FLD_INT)  or error(CLS_TOK .. "." .. FLD_INT .. " field not found")
  local L = { class = cTB }
  -- ObfuscatedToken is a struct embedded inside TokenBalance. Mono reports struct field offsets as if the
  -- struct were boxed, so subtract the smallest instance-field offset to get the offset inside the struct.
  L.slotOff = fToken.offset + (fInt.offset - minInstanceOffset(cTok))

  local fk, fo, fc = findField(cOI, "_key"), findField(cOI, "_obfuscatedValue"), findField(cOI, "_checksum")
  local shift = 0
  local mo = minInstanceOffset(cOI)
  if mo &lt; HDR then shift = HDR - mo end       -- ObfuscatedInt is a class: offsets must include the object header
  if fk and fo and fc then
    L.keyOff, L.obfOff, L.chkOff = fk.offset + shift, fo.offset + shift, fc.offset + shift
  else
    L.keyOff, L.obfOff, L.chkOff = HDR, HDR + 4, HDR + 8
  end
  L.startOff = math.min(L.keyOff, L.obfOff, L.chkOff)
  L.safe = (L.obfOff == L.keyOff + 4) and (L.chkOff == L.keyOff + 8)
  if not L.safe then
  end
  return L
end

local function findInstances(c)
  local domain = 0
  local okD, doms = pcall(mono_enumDomains)
  if okD and type(doms) == "table" and doms[1] then domain = doms[1] end
  local ok, res = pcall(mono_class_findInstancesOfClassListOnly, domain, c)
  if not (ok and type(res) == "table" and #res &gt; 0) and mono_class_findInstancesOfClass then
    ok, res = pcall(mono_class_findInstancesOfClass, domain, c)
  end
  local out = {}
  if ok and type(res) == "table" then
    for _, a in ipairs(res) do if type(a) == "number" then out[#out + 1] = a end end
  end
  return out
end

local function inspect(inst)
  local L = bb97.layout
  local p = readPointer(inst + L.slotOff)
  if not p or p == 0 then return "null" end
  local k, o, c = readInteger(p + L.keyOff), readInteger(p + L.obfOff), readInteger(p + L.chkOff)
  if not (k and o and c) then return "bad" end
  k, o, c = k % 0x100000000, o % 0x100000000, c % 0x100000000
  local val = toSigned(bXor(o, k))
  if c == (val * 31 + k) % 0x100000000 then return "ok", val end
  return "bad"
end

function bb97.apply(inst)
  local L = bb97.layout
  pcall(unregisterSymbol, SYMBOL)
  registerSymbol(SYMBOL, inst + L.slotOff, true)
  local al = getAddressList()
  local function setOff(id, off)
    local mr = al.getMemoryRecordByID(id)
    if mr then mr.Offset[0] = off end
  end
  setOff(ID_WALLET, L.startOff)
  setOff(ID_RAWKEY, L.keyOff); setOff(ID_RAWOBF, L.obfOff); setOff(ID_RAWCHK, L.chkOff)
end

-- pick(n): switch to the n-th candidate if more than one live TokenBalance was found
function bb97.pick(n)
  local c = bb97.candidates and bb97.candidates[n]
  if not c then log("no candidate #%s", tostring(n)) return end
  bb97.apply(c.addr)
end

-- use(addr): manual override, e.g. an address from Mono &gt; Dissect &gt; TokenBalance &gt; Find instances
function bb97.use(addr)
  if type(addr) == "string" then addr = getAddress(addr) end
  bb97.layout = bb97.layout or resolveLayout()
  bb97.apply(addr)
end

bb97.layout = resolveLayout()
local cands, empties = {}, {}
for _, a in ipairs(findInstances(bb97.layout.class)) do
  local st, val = inspect(a)
  if st == "ok" then cands[#cands + 1] = { addr = a, val = val }
  elseif st == "null" then empties[#empties + 1] = { addr = a, val = 0 } end
end
bb97.candidates = (#cands &gt; 0) and cands or empties
if #bb97.candidates == 0 then
  error("No usable TokenBalance instance found. Open a screen that shows tokens and enable this again, or use bb97.use(address) with an address from the Mono dissector.")
end
for i, c in ipairs(bb97.candidates) do end
if #cands == 0 then log("only instances with a not-yet-created ObfuscatedInt were found (wallet never read yet?)") end
bb97.apply(bb97.candidates[1].addr)
if #bb97.candidates &gt; 1 then log("%d candidates; if the value looks wrong run: bb97.pick(2)", #bb97.candidates) end
[DISABLE]
pcall(unregisterSymbol, SYMBOL)
</AssemblerScript>
          <CheatEntries>
            <CheatEntry>
              <ID>10</ID>
              <Description>"Tokens"</Description>
              <ShowAsSigned>1</ShowAsSigned>
              <VariableType>Custom</VariableType>
              <CustomType>ObfuscatedInt (BB97)</CustomType>
              <Address>BB97_TokenSlot</Address>
              <Offsets>
                <Offset>10</Offset>
              </Offsets>
            </CheatEntry>
          </CheatEntries>
        </CheatEntry>
      </CheatEntries>
    </CheatEntry>
  </CheatEntries>
  <UserdefinedSymbols/>
  <Comments>Backyard Baseball (2026) - Unity/Mono, ObfuscatedInt tokens
1) Attach CE to the game. Enable '1. ObfuscatedInt custom type' (auto-enabled on load).
2) Get to a screen that shows your tokens, then enable '2. Locate TokenBalance (Mono)'.
   It finds the live TokenBalance object and points BB97_TokenSlot at its token-&gt;intValue reference.
3) 'Token Wallet' follows that reference, so it keeps working after the game replaces the ObfuscatedInt.
   Edit or freeze it. The game UI may only refresh after the next earn/spend or a screen change.
Console helpers after step 2: bb97.pick(n) to switch candidate, bb97.use(0xADDR) to set a TokenBalance manually.
</Comments>
</CheatTable>
