local formStringClass = '<?xml version="1.0" encoding="utf-8"?><FormData><formMonoClass Class="TCEForm" Encoding="Ascii85">/yXy*/3td4;=F41+]S%7-9[S)8wjBhaO9R]HoK7}lMsjH2I_C;q!qi^+qxopm3Kfq!%Dma2hhF)$K_poB)7+L@pD3sNsL0}z:4a=UG;#ZcYR(mdDGOi$+A_KIq5#[)I_K?:br(3wCkfdu3o^M89!9((jBx%%y=iJ6B-(nYPGMi$Ck#Es?X+$!/5u))%[]*V/0Sn_D]*;euu6OfUHd].NJnlM6-C+D;Mba;5H#*ag73a,$OUnja:-3$l#)(35%$2*]m9)qDPsQXS1c0DeH_SWBsv6SZ@fxzSYh.H1Z(FLo}x:aiPGcMWM7v7xqZ)MoXslMXSwNgr^o.Cp^kZqPs,q3)P!eU%UNpyU+KW[vyruswDm(ww#GY*tx=;Q4(*hG1QRa5oWWcY0kqQz6DSD^Bn)a%r.#5-DQO4Od)yi?xiIj._{#b%@9JHZ*:zOG[o3MB4Cy#w^)OE[.x71Vw.cJ(8;:?6.a}Xv^4+(C{T8ul4-_c+.@sbx7Azb=]L]=C^HobZ#gIy-FU6bs),YN-Kd)%LG0Afifig/B?9folId7zrQ96Oj*z5:bfY*Ui(Et?N2RPJ#dGqycutI{H9@y%aHVVJ3moIA50;We%DN%v27{1ShM%cJ]g=jve@slmsMYH:O/F#S:sN7sTYE7b09#ToIoxkc=2HfeTHD79Vp[.o+]?0,6AxLMm0{QUG3^aSlNfqgh(PoaL;;9EAa=:k$}yC{Yns-S%w9mk4J,j1_0TVDQM;qYO7BK^#[(rJn]2rGO_L0}[]l]6C_#d.aI5Qime;/MahCwh:6R_rZ($r_HhwCejdzA5,7+u33k*_6pAVE-84a;PB}-](rFG6A$Jcsq9^+Htt*6aytoPle][4GDv=jkG*(Yr_m28iP@LY%,8epQ?!$)P+xX=0Cb,QRz2V)rJ/uT(e;_$D#97eqf5;21=2wU;}lHY%{H3sAjiNCS!F{-oYhZ)bTea^x/mA9U0y@O}Y)unaUkP*cC[cQ53JB^hzP^/J!O6/xwl#5kDq8y?;MLEpzG{?oaK_K1#DDbFRy$6rB%Kv%d@S3-j#KVN6[#GARBEK(s$_psl+drRQL-{w#3Dr/tKKCV1*,y68/g]ehS3BepPvrpw*DHMZT9nZyjZi_GR9^M(6mdLZ[9bx.U0xmAb]E[T_9Stnaqj7xE4^[p.4(2P#n@m!1!TdQr*jeM.;jS]l;H0gGCu4x2/!k4B@7clB?yIMX90FOnK}QE,8(PL=B)*MXj,_)]oqwYN-bx5u58ss)AlVNY]52pc9$=*,1cL=:{6()KZVS?dXKwW)wjb6LWCK3]?Ed.ZS}h4v^F;wgQGlr74wI}KI=_iwxn9i,tma7j53-v12HRRCM29L5Hn7g;.asL@6pL+^1W0B5DyWS88_tiC_[fec7sAQFo#4c%,tJ7in!I@</formMonoClass></FormData>'
local formStringImage = '<?xml version="1.0" encoding="utf-8"?><FormData><formMonoImage Class="TCEForm" Encoding="Ascii85">#goBu):ZT+Wtl^dlQI).N^(5n(hQF{X^MQJn@1eo5fn$ZHIXe;RtGr4190F.cqtCN1hVAXjWcD*0l3*K^?z^hx4+qE0Ksbh3I+QToQw1+QkP4f?kNSb@5lX/)jLR@IiYbP=eCTy?ZA0ZI(bMlCiZ8DJe+6*v,mF+jD,(E%sth}1CLRX$vU-f(i9$XRV@:QA!q!f%hX+b6)]dyOt_prhLp]G9=zuU40V/tG9sQgs]],Y[?WW7%bqQH*RxO9*We0CaVdE@Zgdl/@M}s?b/WI$4H*R(bICOq:vG*bM5b2J?8GRcx[DbAAiUL]9lSLiz[S=[8zTH1hct@Prs^Avs4E{u#(j9zj(]G[D0JP0B(qo^2%G=}m+ra!0MJv,XL@_ZnSM_HxTV)cJhf9I9+4KLxJqx*H(z;36.k,8JMD^Qkiz#h)TZim?DT^LGY6G3@@j/pDI11cbh0/UjyJR^0G:vZE5TzyieiSvQC;a(0j_I1#Y1q]28AO^@P9DA)8H^GkPRD[5Z%y%t^HKF?{v6g?tUv!E3cP_:l]R+yV4f4v4-{wf!3Qc8fU38JaQy9#^U-{8!JCVHkhj,CSHPtPb3.lul(V3=_gdgp9Os[Rb%_?;C,WXNw,f/6f.*]R#b*wa-.kPtDstrDfs3NGb%qJ6y3fSIFkJp7HU$DGcFw09Lg7,2RjA700</formMonoImage></FormData>'
local formStringSearch = '<?xml version="1.0" encoding="utf-8"?><FormData><formMonoSearch Class="TCEForm" Encoding="Ascii85">%}#KF,cp%Yq+#;fF0-;[=QS^sGN4#^E)+k6vj(Lgz(5foZ80]sniM43Np}sshx8vPu*}UB$D$ZEpujG;?1GedO3?FSj[d9*9_ZEf$H%Addmm}2)+$66arzA$cAlT_Xuf_XA6=G*0HbI.-8.uu!g/qhUtlts3g:JT8S;Ei8Pt9tftWhe8tkA?5YF5@wd!7n63j#z^fphu@DKC.i]fEtbNTEd!oa?V@#V/Bhcgjz;V/Nw9ABQW?svjc5?1!pqtp+.y:#V{queWtsW_WF(b{)5.y$dp*d;11j?n!ZK7Ww:wZXi1;aQe75VPwUV6PLv=0SmVuvR{A3$n!Ot*VZ[NaGB3,PG0eGa2uqnMwoEEGg1MGo2h9w0RWZXb[==VUtICuU6TZ=Ds_4mZY*khh=iyYS,CX8lABK-]GG6dn)2Tz#q!VV2ZIQ6q=cP;Uo+*pUQ-v+MRD*:K(G;SU(WUA-*kNg7b_GB/BHTpzZr5od4b*DX,(mQ@X]4hvgczwE(1Wz.qx]By8Zz.W:g#F__QU1-}FnoXQ$Iobm_5F;O.co_Rk!?seWbUd3%r10a!Bz0WXhgzBmH-5Hk)z6a%}N^/;fl5exSPS%#XNh:]J%B9xI+R]yBwpGPCTI,4R#Pp0w%_dUq;=0E6KpT4tA(qdfR15HJM.Cc0xAkSUt?Ea@KtuBQUBnk+o8NpsQ3IVqm1=51xRrs;Vu,EiV$kl48LHXD8O%8AYtHPHvp:lb2[,K-T_@VEG6umd%(/dI9OJkQ?v{0-?2%oNq#!.gvA87ox/%zc+P13Wfm_iS0CY9X2zE7acfdDPwv_wxY0$Gv[]S8QP?p]X7IPNwTeo!76BLiXM_13T)T(b^=08,mlCd46yF9SujgZ]d-y)s0ua8mCWAvr)[xZIv4*Iv^1:4(*mOk;lW!%RhkA(M{YArlk.ZT_D)mSHE3(u0{*oMiQ0:gXrAtge{xEZT}juan)rM/p@=flG1K=H?9x;+oPf}?^}@p_B(!z)9MgNLZ?=lpQ=Nm;=0zbuSbPL8o%I)u4$rs_R*pm/9t,@qTwUA;i7_NGrqc25bWK{E[;%N4(fO1YVntn-,Zyz$kFT3!t[s)CeZcRlY6^jH+)mKFW)QxsiGz{zh6UjZmANF/heJ][E!^JSH[[(+c:VU:-Z-Js}=Ww+rATHnTN6/yR#vp]/5-)7vMT_LeRLqX1OmibihP8Na;$dB01?5=S0Q,_iY-dfdTVa/J,CNimP1qg%4R@p5r]*Fv+65CYVE)({5DGGBnlK*GI9j*-4YE1q7q58U3AL02oxU_XPnZR;6Cuei?U3y#F!)=6vIY;ivP37p%9%{2cUL:3?.RO#Dp.},_RHO7Rhdq]!V5=_NOohGtZ16ZQNKKC*gLP3*Dp5}V1@*s</formMonoSearch></FormData>'

function saveForm(name, text)
    local path = getCheatEngineDir()..[[\autorun\forms\]]..name
    local f, err = io.open(path, "w")
    if f == nil then return nil, err end
    f:write(text)
    f:close()
    return true
end

saveForm('formMonoClass.frm', formStringClass)
saveForm('formMonoImage.frm', formStringImage)
saveForm('formMonoSearch.frm', formStringSearch)

formMonoClass = createFormFromFile(getCheatEngineDir()..[[\autorun\forms\formMonoClass.frm]])
formMonoImage = createFormFromFile(getCheatEngineDir()..[[\autorun\forms\formMonoImage.frm]])
formMonoSearch = createFormFromFile(getCheatEngineDir()..[[\autorun\forms\formMonoSearch.frm]])

--[[================================================================================
--  Table Script
--================================================================================]]

function loadTextFile(name, useTableFile)
    if useTableFile then
        local tableFile = findTableFile(name)
        if not tableFile then return nil, 'Unable to open table file "'..tostring(name)..'"' end
        local ss = createStringStream()
        ss.Position = 0 -- recommended on wiki: https://wiki.cheatengine.org/index.php?title=Lua:Class:TableFile
        ss.CopyFrom(tableFile.Stream, 0)
        local text = ss.DataString
        ss.destroy()
        return text
    else
        local path = getMainForm().openDialog1.InitialDir..name
        local f, err = io.open(path, "r")
        -- fall back to table file if disk file error (doesn't exist)
        if f == nil then return loadTextFile(name, true) end
        local text = f:read("*all")
        f:close()
        return text
    end
end

popupMethodsCount = popupMethodsCount or 0

function popupMethodsPopup(sender)
  local popup = formMonoClass.popupMethods

  -- clear menu items
  for i=popup.Items.Count,1,-1 do popup.Items[i-1].destroy() end

  -- item to JIT method

  local item1 = createMenuItem(popup)
  popupMethodsCount = popupMethodsCount + 1
  item1.Caption = 'Item '..tostring(popupMethodsCount)
  popup.Items.Add(item1)
  return true
end

--[[================================================================================
--  bootstrap.lua
--================================================================================]]

function loadTextFile(name, useTableFile)
    if useTableFile then
      local tableFile = findTableFile(name)
      if not tableFile then return nil, 'Unable to open table file "'..tostring(name)..'"' end
      local ss = createStringStream()
      ss.Position = 0 -- recommended on wiki: https://wiki.cheatengine.org/index.php?title=Lua:Class:TableFile
      ss.CopyFrom(tableFile.Stream, 0)
      local text = ss.DataString
      ss.destroy()
      return text
    else
      local path = getMainForm().openDialog1.InitialDir..name
      local f, err = io.open(path, "r")
      -- fall back to table file if disk file error (doesn't exist)
      if f == nil then return loadTextFile(name, true) end
      local text = f:read("*all")
      f:close()
      return text
    end
  end
  
  --[[
    Save a string to a text file.  If useTableFile is true it will be saved as
    a TableFile.  The directory should be where the cheat file is, it is the
    initial directory for the dialog when you are saving your cheat table.
  --]]
  function saveTextFile(name, text, useTableFile)
    if useTableFile then
      local tf = findTableFile(name)
      if tf ~= nil then
        tf.delete()
        tf = nil
      end
      tf = createTableFile(name)
      local ss = createStringStream(text)
      tf.Stream.CopyFrom(ss, 0)
      ss.destroy()
      return true
    else
      local path = getMainForm().saveDialog1.InitialDir..name
      local f, err = io.open(path, "w")
      if f == nil then return nil, err end
      f:write(text)
      f:close()
      return true
    end
  end
  
--[[================================================================================
--  util.lua
--================================================================================]]

util = util or {}

--[[
  For mono symbols, you have to surround them in quotes for use in AA
]]
function util.safeAddress(s)
  local tbl = {}
  for a in string.gmatch(s, "([^+]+)[+]?") do table.insert(tbl, a) end
  local result = '"'..tbl[1]..'"'
  if #tbl > 1 then result = result..'+'..tbl[2] end
  return result
end

function util.split(s, separator)
  local tbl = {}
  for str in string.gmatch(s, "([^"..separator.."]+)") do table.insert(tbl, str) end
  return tbl
end

function util.trim(s)
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

--[[
    Pretty-print serialize the passed object and return the results
    as a string.  This does nice indenting and follows metatables.  It
    is not guaranteed to provide the same object back if the string is
    loaded because it just replaces circular references with a string
    among other things.  It does try to keep tables/arrays on one line
    if there is room (120 characters).
--]]
function util.pretty(value, funcs, indent, done, stack)
  indent = indent or 0
  funcs = funcs or {}
  done = done or {}
  stack = stack or { {value=value} } -- initialize with passed value, no key

  -- if a 'userdata', use metatable
  local l = value
  if type(value) == "userdata" then
    l = getmetatable(value)
  end

  if type(l) == "table" then
    -- if we've already serialized the table, return string representation
    -- to avoid self-referencing in recursion.  Only the first serialization
    -- will have the full serialized string
    if done[l] then return tostring(l) end
    done[l] = true

    local list = {}

    -- here we return "{", the values, and "}"
    if (#l > 0) then
      -- if we have a count ('#'), then it's just an array of values
      for i,v in ipairs(l) do
        table.insert(list, util.pretty(v, funcs, indent + 2, done, stack))
      end
    else
      -- no count, so either empty or object with key/value
      -- first we sort the keys
      local keys = {}
      for k,v in pairs(l) do table.insert(keys, k) end
      table.sort(keys)

      local last = {key = "UNKNOWN", value = "NONE"}
      table.insert(stack, last)
      for i,k in ipairs(keys) do
        local propValue = value[k]
        last.key = k
        last.value = value[k]
        for j,f in ipairs(funcs) do
          local returnValue = f(value, k, value[k], stack, indent)
          if returnValue ~= nil then
            propValue = returnValue
            break
          end
        end
        table.insert(list, string.format("%s = %s", tostring(k), util.pretty(propValue, funcs, indent+2, done, stack)))
      end
      table.remove(stack, #stack)
    end

    -- now we find the total size, add 200 if there are line breaks
    local size = 0
    for i,v in ipairs(list) do
      size = size + string.len(v) + 2
      if string.find(v, "\r\n") then
        size = 200
        break;
      end
    end
    -- if it's small enough, join with commas
    if (size < 120) then
      local result = {"{ "}
      for i,v in ipairs(list) do
        if i > 1 then table.insert(result, ", ") end
        table.insert(result, v)
      end
      table.insert(result, " }")
      return table.concat(result);
    else
      local indentString1 = string.rep(" ", indent)
      local indentString2 = string.rep(" ", indent + 2)
      local result = {"{\r\n"}
      for i,v in ipairs(list) do
        table.insert(result, indentString2)
        table.insert(result, v)
        if (i < #list) then table.insert(result, ",") end
        table.insert(result, "\r\n")
      end
      -- table.insert(result, indentString)
      table.insert(result, indentString1)
      table.insert(result, "}")
      return table.concat(result);
    end
  end

  if type(value) == "string" then
    return string.format("\"%s\"", value)
  end
  return tostring(value)
end



--[[
    Values and functions used by util.serialize(t)
---]]

local oddvals = {[tostring(1/0)] = '1/0', [tostring(-1/0)] = '-1/0', [tostring(-(0/0))] = '-(0/0)', [tostring(0/0)] = '0/0'}

local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true,
	['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true,
	['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true,
	['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true,
	['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true,
	['until'] = true, ['while'] = true}

local getchr = function(c)
  return "\\" .. c:byte()
end

local make_safe = function(text)
  return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
end

local write = function(t, memo, rev_memo)
	local ty = type(t)
	if ty == 'number' then
		t = format("%.17g", t)
		return oddvals[t] or t
	elseif ty == 'boolean' or ty == 'nil' then
		return tostring(t)
	elseif ty == 'string' then
		return make_safe(t)
	elseif ty == 'table' or ty == 'function' then
		if not memo[t] then
			local index = #rev_memo + 1
			memo[t] = index
			rev_memo[index] = t
		end
		return '_[' .. memo[t] .. ']'
	else
		error("Trying to serialize unsupported type " .. ty)
	end
end

local write_key_value_pair = function(k, v, memo, rev_memo, name)
	if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then
		return (name and name .. '.' or '') .. k ..'=' .. write(v, memo, rev_memo)
	else
		return (name or '') .. '[' .. write(k, memo, rev_memo) .. ']=' .. write(v, memo, rev_memo)
	end
end

local is_cyclic = function(memo, sub, super)
	local m = memo[sub]
	local p = memo[super]
	return m and p and m < p
end

local write_table_ex = function(t, memo, rev_memo, srefs, name)
	if type(t) == 'function' then
		return '_[' .. name .. ']=loadstring' .. make_safe(string.dump(t))
	end
	local m = {}
	local mi = 1
	for i = 1, #t do -- don't use ipairs here, we need the gaps
		local v = t[i]
		if v == t or is_cyclic(memo, v, t) then
			srefs[#srefs + 1] = {name, i, v}
			m[mi] = 'nil'
			mi = mi + 1
		else
			m[mi] = write(v, memo, rev_memo)
			mi = mi + 1
		end
	end
	for k,v in pairs(t) do
		if type(k) ~= 'number' or math.floor(k) ~= k or k < 1 or k > #t then
			if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then
				srefs[#srefs + 1] = {name, k, v}
			else
				m[mi] = write_key_value_pair(k, v, memo, rev_memo)
				mi = mi + 1
			end
		end
	end
	return '_[' .. name .. ']={' .. table.concat(m, ',') .. '}'
end


--[[
    A function more appropriate for serializing objects in a reproduceable way.
--]]
function util.serialize(t)
  local memo = {[t] = 0}
	local rev_memo = {[0] = t}
	local srefs = {}
	local result = {}

	-- phase 1: recursively descend the table structure
	local n = 0
	while rev_memo[n] do
		result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n)
		n = n + 1
	end

	-- phase 2: reverse order
	for i = 1, n*.5 do
		local j = n - i + 1
		result[i], result[j] = result[j], result[i]
	end

	-- phase 3: add all the tricky cyclic stuff
	for i, v in ipairs(srefs) do
		n = n + 1
		result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_[' .. v[1] .. ']')
	end

	-- phase 4: add something about returning the main table
	if result[n]:sub(1, 5) == '_[0]=' then
		result[n] = 'return ' .. result[n]:sub(6)
	else
		result[n + 1] = 'return _[0]'
	end

	-- phase 5: just concatenate everything
	result = table.concat(result, '\n')
	return n > 1 and 'local _={}\n' .. result or result
end

function util.map(t, f)
  local results = {}
  local func = f or tostring
  for i,v in ipairs(t) do
    table.insert(results, func(v))
  end
  return results
end

--[[================================================================================
-- notes.lua
--================================================================================]]

notes = notes or {}
notesmt = notesmt or {}

note = note or {}
notemt = notemt or {}

notemt.__index = note
notemt.__lt =  function(a,b) return a.key < b.key end

function notes.new(filename, useTableFile)
  local obj = {}
  setmetatable(obj, {__index = notes})
  obj:load(filename, useTableFile)
  return obj
end

function notes:load(filename, useTableFile)
  self.filename = filename
  self.useTableFile = useTableFile
  local s = loadTextFile(self.filename, self.useTableFile)
  self.dict = {}
  if s then self.dict = loadstring(s)() end
  self.lastSave = os.clock()
  self.lastChange = 0
end

function notes:save()
  local text = util.serialize(self.dict)
  saveTextFile(self.filename, text, self.useTableFile)
  self.lastSave = os.clock()
end

function notes:saveAs(filename, useTableFile)
  self.filename = filename
  self.useTableFile = useTableFile
  self:save()
end


function notes:update(key, text)
  if text == nil or string.len(text) == 0 then
    if self.dict[key] then self.dict[key] = nil end
  else
    self.dict[key] = {key = key, text = text}
  end
  self.lastUpdate = os.clock()
end

function notes:getList()
  local keys = {}
  for k,n in pairs(self.dict) do table.insert(keys, n) end
  return keys
end

--[[================================================================================
--  mono.lua
--================================================================================]]

mono = mono or {}

-- classes
mono.MonoClass = mono.MonoClass or {}
mono.MonoField = mono.MonoField or {}
mono.MonoMethod = mono.MonoMethod or {}
mono.MonoImage = mono.MonoImage or {}

mono.menu = mono.menu or {}

mono.popups = mono.popups or {}

mono.formSelectImage = mono.formSelectImage or {}
mono.formSearch = mono.formSearch or {}
mono.formClass = mono.formClass or {}

-- defines
mono.TYPE_NAMES = {
  [0] = 'END',
  [1] = 'void',
  [2] = 'bool',
  [3] = 'char',
  [4] = 'sbyte',
  [5] = 'byte',
  [6] = 'short',
  [7] = 'ushort',
  [8]= 'int',
  [9]= 'uint',
  [10] = 'long',
  [11] = 'ulong',
  [12] = 'float',
  [13] = 'double',
  [14] = 'string',
  [15] = 'ptr',
  [16] = 'byref',
  [17] = 'valuetype',
  [18] = 'class',
  [19] = 'var',
  [20] = 'array',
  [21] = 'genericinst',
  [22] = 'typedbyref',
  [24] = 'I',
  [25] = 'U',
  [0x1b] = 'FNPTR',
  [0x1c] = 'object',
  [0x1d] = 'szarray', -- 0-based one-dim-array
  [0x1e] = 'mvar',
  [0x1f] = 'CMOD_REQD', -- typedef or typeref token
  [0x20] = 'CMOD_OPT',  -- optional arg: typedef or typeref token
  [0x21] = 'INTERNAL',
  [0x40] = 'MODIFIER', -- Or with the following types
  [0x41] = 'SENTINEL', -- sentinel for varargs method signature
  [0x45] = 'PINNED', -- local var that points to pinned object
  [0x55] = 'ENUM', -- an enumeration
}


--[[================================================================================
--  monofield.lua
--================================================================================]]

local MonoField = mono.MonoField
MonoField.mt = {
  __index = MonoField,
  __tostring = function(t)
    return 'MonoField '..tostring(t.id)..' "'..tostring(t.name)..'"'
  end,
  __lt = function(a, b)
    return a.lowerName < b.lowerName
  end
}

-- class is a monoclass table, f is a table with results from
-- mono_class_enumFields
function MonoField.new(class, f)
  local obj = {
    class = class,
    id = f.field,
    name = f.name,
    isStatic = f.isStatic,
    isConst = f.isConst,
    offset = f.offset,
    monoType = f.monotype,
    lowerName = string.lower(f.name),
    foundTypeName = f.typeName,
    typeName = mono.TYPE_NAMES[f.monotype] or 'UnknownType'..tostring(f.monotype)
  }

  -- should be types where we might have a 'Class'
  --if f.monotype == 15 or f.monotype == 16 or f.monotype == 18 or f.monotype then
  --end

  obj.typeClassId = mono_type_getClass(obj.id)
  if obj.typeClassId ~= nil then
    -- this is the 'type' of the field
    obj.typeClass = class.image.classesById[obj.typeClassId]
    if obj.typeClass ~= nil then
      obj.typeName = obj.typeClass.name
    else
      if class.image.missingNames[obj.typeClassId] then
        obj.typeName = class.image.missingNames[obj.typeClassId]
      else
        obj.typeName = mono_class_getFullName(obj.typeClassId)
        class.image.missingNames[obj.typeClassId] = obj.typeName
      end
    end
  end

  setmetatable(obj, MonoField.mt)
  return obj
end

function MonoField:getFullName()
  local s = ""
  if self.class.namespace ~= nil and self.class.namespace:len() > 0 then s = self.class.namespace..':' end
  s = s..self.class.name..'.'
  s = s..self.name
  return s
end


--[[================================================================================
--  monomethod.lua
--================================================================================]]

local MonoMethod = mono.MonoMethod
MonoMethod.mt = {
  __index = MonoMethod,
  __tostring = function(t)
    return 'MonoMethod '..tostring(t.id)..' "'..tostring(t.name)..'"'
  end,
  __lt = function(a, b)
    return a.lowerName < b.lowerName
  end
}

function MonoMethod.new(class, m)
  local obj = {
    class = class,
    id = m.method,
    name = m.name,
    lowerName = string.lower(m.name),
    parameters = {}
  }
  setmetatable(obj, MonoMethod.mt)

  lastmethod = obj
  local types, parameternames, returntype = mono_method_getSignature(obj.id)
  local typenames={}
  local tn
  if types ~= nil then
    for tn in string.gmatch(types, '([^,]+)') do
      table.insert(typenames, tn)
    end
  end
  
  for i=1,#typenames do
    table.insert(obj.parameters, { name = parameternames[i], type = typenames[i] })
  end
  obj.returnType = returntype

  return obj
end

--[[
    Get parameters for method
--]]
function MonoMethod:fetchParms()
  if self.parms ~= nil then return nil end
  if self.class.image.ignoredClassesByName[self.class.name] ~= nil then return nil end
  local status, parms = pcall(function()
         local result = mono_method_get_parameters(self.id)
         return result
       end)

  if status then
    -- success!
    self.parms = parms
  else
    self.class.image.ignoredClassesByName[self.class.name] = true
    print('Error with class '..tostring(self.class.name)..' method '..tostring(self.name))
    error('Error fetching parameters for '..tostring(self.class.name)..'.'..tostring(self.name))
  end
end

--[[================================================================================
--  monoclass.lua
--================================================================================]]

local MonoClass = mono.MonoClass
MonoClass.mt = {
  __index = MonoClass,
  __tostring = function(t)
    return 'MonoClass '..tostring(t.id)..' "'..tostring(t.name)..'"'
  end,
  __lt = function(a, b)
    return a.lowerName < b.lowerName
  end
  
}

local MonoField = mono.MonoField
local MonoMethod = mono.MonoMethod

--[[
    Called from MonoImage:_init, 'c' is what is returned from enumerating classes.
--]]
function MonoClass.new(image, c)
  local obj = {
    name = c.classname,
    namespace = c.namespace,
    id = c.class,
    lowerName = string.lower(c.classname),
    image = image
  }

  setmetatable(obj, MonoClass.mt)
  return obj
end

function MonoClass:initFields()
  self.fields = {}
  self.fieldsByName = {}
  self.fieldsByLowerName = {}
  local constFieldCount = 0
  
  local temp = mono_class_enumFields(self.id)
  for i,f in ipairs(temp) do
    local field = MonoField.new(self, f)
    table.insert(self.fields, field)
    self.fieldsByLowerName[field.lowerName] = field;
    self.fieldsByName[field.name] = field

    if field.isStatic and field.isConst then
      field.constValue = constFieldCount
      constFieldCount = constFieldCount + 1
    end
  end

  table.sort(self.fields)
end

function MonoClass:initMethods()
  self.methods = {}
  self.methodsByName = {}
  self.methodsByLowerName = {}
  
  local temp = mono_class_enumMethods(self.id)
  for i,m in ipairs(temp) do
    local method = MonoMethod.new(self, m)
    table.insert(self.methods, method)
    self.methodsByName[method.name] = method
    self.methodsByLowerName[method.lowerName] = method
  end

  table.sort(self.methods)
end


--[[================================================================================
--  monoimage.lua
--================================================================================]]

local MonoImage = mono.MonoImage
MonoImage.mt = {
  __index = MonoImage,
  __tostring = function(t)
    return 'MonoImage '..tostring(t.name)
  end
}

local MonoClass = mono.MonoClass

--[[
    List names of images, a name can be passed to MonoImage.new()
    or MonoImage(name) to create a MonoImage instance.
--]]
function MonoImage.enumerate()
  local names = {}
  mono_enumImages(function(img)
    local name = mono_image_get_name(img)
    table.insert(names, name)
  end)
  table.sort(names)
  return names
end

--[[
    Constructor doesn't do much, call :init(name, progress)
--]]
function MonoImage.new()
  local obj = { }
  setmetatable(obj, MonoImage.mt)
  return obj
end

--[[
    init() takes the image name and an optional callback for reporting progress
    and when it is complete.  The signature for this function is:
        function progress(complete, image, message, processed, total)
    This callback should be executed on the main CE thread.
--]]
function MonoImage:init(name, progress)
  if monopipe == nil then
    print('Launching mono data collector...')
    LaunchMonoDataCollector()
  end
  
  self.domains = mono_enumDomains()
  self.domain = self.domains[1]

  self.classes = {}                -- straight list of all classes
  self.classesByName = {}          -- dictionary for access by name
  self.classesByLowerName = {}     -- for access by lower case name
  self.classesById = {}            -- for access by id
  self.ignoredClassesByName = {}   -- for ignoring classes in searches, etc.
  self.progress = progress
  self.name = name or 'Assembly-CSharp' -- intelligent default for unity
  
  -- get id
  self.id = nil
  mono_enumImages(function(img)
    local foundName = mono_image_get_name(img)
    if foundName == self.name then self.id = img end
  end)
  
  if not self.id then
    print('NO ID!')
    print('name is', name, self.name)
    self:report(false, 'Error finding image named '..tostring(name))
    self.progress = nil
    self.total = nil
    self.count = nil
    return false
  end
  
  createThread(function(thread)
      self.thread = thread
      self:_init(thread)
    end)
end

--[[
    This is the function called by init() on another thread
--]]
function MonoImage:_init(thread)
  --print('MonoImage:_init thread is', thread, 'self is', tostring(self))
  self.thread = thread
  local temp = mono_image_enumClasses(self.id)
--  thread.synchronize(function(th)
--      print('Image '..tostring(self.id)..' has classes: '..tostring(temp))
--    end)
    
  self.classes = {}
  self.classesByName = {}
  self.classesByLowerName = {}
  self.classesById = {}
  self.missingNames = {}
  
  self.total = #temp
  self.count = 0
  self.message = "Getting classes"
  self:report(false, "Getting classes")
  
  -- populate classes without much information first so we can
  -- access our definitions when filling in parents, properties, etc.
  for i,c in ipairs(temp) do
    local class = MonoClass.new(self, c)
    if class.lowerName ~= nil then
      table.insert(self.classes, class)
      self.classesByName[class.name] = class
      self.classesByLowerName[class.lowerName] = class
      self.classesById[class.id] = class
    else
      --print("Nil lowername for class", c.class)
    end
    self.count = i
    if (i % 100 == 0 or i == #temp) then
      self:report(false, 'Fetching classes')
    end
  end
  table.sort(self.classes)
  
  for i,class in ipairs(self.classes) do
    class.parentId = mono_class_getParent(class.id)
    class.parent = self.classesById[class.parentId]
    self.count = i
    if (i % 100 == 0 or i == #temp) then
      self:report(false, 'Fetching parents')
    end
  end
  
  self.count = 0
  for i,class in ipairs(self.classes) do
    class:initFields()
    class:initMethods()
    self.count = i
    if (i % 100 == 0 or i == #temp) then
      self:report(false, 'Initializing fields and methods')
    end
  end
  
  self:report(true, 'Done')
end

--[[
    Report progress on the main CE thread to do gui updates
--]]
function MonoImage:report(done, message)
  if self.progress ~= nil and self.thread ~= nil then
    self.thread.synchronize(function(thread)
        self.progress(done, self, message, self.count, self.total)
      end)
  end
  
  if done then
    -- do cleanup here
    self.count = nil
    self.total = nil
    self.progress = nil
    self.thread = nil
  end
end


--[[================================================================================
--  monomenu.lua
--================================================================================]]

function mono.menu:init(parent)
    self.menuSearch = createMenuItem(parent)
    self.menuSearch.Caption = 'Search'
    self.menuSearch.Name = 'miMonoSearch'
    self.menuSearch.OnClick = function(sender) self:OnSearchClick() end
    parent.add(self.menuSearch)
end
  
function mono.menu:OnSearchClick()
    if mono.selectedImage then
        mono.formSearch:show()
    else
        mono.formSelectImage:show()
    end
end
  
--[[================================================================================
--  formSelectImage.lua
--================================================================================]]

function mono.formSelectImage:show()
    LaunchMonoDataCollector()
    formMonoImage.listImages.Visible = true
    formMonoImage.buttonSelectImage.Visible = true
    formMonoImage.progressImage.Visible = false
    formMonoImage.labelMessage.Visible = false
    
    self.imageNames = mono.MonoImage.enumerate()
    table.sort(self.imageNames)
    local items = formMonoImage.listImages.Items
    items.Clear()
    for i,name in ipairs(self.imageNames) do items.add(name) end
    
    local handler = function(sender)
      mono.formSelectImage.OnSelectImage(self)
    end
    
    formMonoImage.buttonSelectImage.OnClick = handler
    formMonoImage.listImages.OnDblClick = handler
    
    formMonoImage.show()
  end
  
  function mono.formSelectImage:OnSelectImage()
    local index = formMonoImage.listImages.ItemIndex + 1
    if self.imageNames == nil or index < 1 or index > #self.imageNames then return end
    local imageName = self.imageNames[index]
   
    formMonoImage.listImages.Visible = false
    formMonoImage.buttonSelectImage.Visible = false
    formMonoImage.progressImage.Visible = true
    formMonoImage.labelMessage.Visible = true
    formMonoImage.labelMessage.Caption = 'Finding classes...'
    
    local image = mono.MonoImage.new()
    
    formMonoImage.progressImage.Min = 0
    local progressHandler = function(done, image, message, count, total)
      if total ~= nil and total > 0 and count ~= nil and count >= 0 then
        message = string.format('%s  %d/%d', message, count, total)
        formMonoImage.progressImage.Max = total
        formMonoImage.progressImage.Position = count
      end
      
      formMonoImage.labelMessage.Caption = message
      if done then self:OnImageComplete(image) end
    end
    
    image:init(imageName, progressHandler)
  end
  
  function mono.formSelectImage:OnImageComplete(image)
    self.image = image
    mono.selectedImage = image
    formMonoImage.Close()
    mono.formSearch:show()
  end
  

--[[================================================================================
--  formSearch.lua
--================================================================================]]

--[[
    Form for searching an image.
    
    Controls:
      pageMain.tabSearch - main tab
      editSearchText: TCEEdit
      listSearchClasses: TCEListView
      listSearchFields: TCEListView
      listSearchMethods: TCEListView
      miImage: TMenuItem - main menu Image entry
      miImageSelectImage - entry to re-open select image form
--]]

mono.formSearch.found = mono.formSearch.found or {}
mono.formSearch.found.classes = mono.formSearch.found.classes or {}
mono.formSearch.found.fields = mono.formSearch.found.fields or {}
mono.formSearch.found.methods = mono.formSearch.found.methods or {}

--[[
    Show the search form, mono.selectedImage should be set to the
    image to search.
--]]
function mono.formSearch:show()
  if mono.selectedImage == nil then return end
  self.image = mono.selectedImage
  
  -- setup event handlers
  formMonoSearch.miImageSelectImage.OnClick = function(sender) self:SelectImageClick() end
  
  formMonoSearch.listSearchClasses.OnData = function(sender, listitem)
    self:listSearchClasses_OnData(sender, listitem)
  end
  
  formMonoSearch.listSearchClasses.OnDblClick = function(sender)
    local class = self.found.classes[sender.ItemIndex + 1]
    mono.formClass:show(class, nil, nil)
  end
  
  formMonoSearch.listSearchFields.OnData = function(sender, listitem)
    self:listSearchFields_OnData(sender, listitem)
  end
  
  formMonoSearch.listSearchFields.OnDblClick = function(sender)
    local field = self.found.fields[sender.ItemIndex + 1]
    if field then
      mono.formClass:show(field.class, field, nil)
    end
  end
  
  formMonoSearch.listSearchMethods.OnData = function(sender, listitem)
    self:listSearchMethods_OnData(sender, listitem)
  end
  
  formMonoSearch.listSearchMethods.OnDblClick = function(sender)
    local method = self.found.methods[sender.ItemIndex + 1]
    if method then
      mono.formClass:show(method.class, nil, method)
    end
  end
  
  formMonoSearch.editSearchText.OnChange = function(sender)
    self:search(sender.text)
  end
  
  -- popups
  formMonoSearch.listSearchClasses.PopupMenu = formMonoSearch.popupClasses
  local count = 0
  formMonoSearch.popupClasses.OnPopup = function(sender)
    print('formMonoSearch.popupClasses.OnPopup "'..tostring(sender.name)..'" count '..tostring(count))
    count = count + 1
  end
  
  -- perform initial search to set 'found' results, probably empty text
  self:search(formMonoSearch.editSearchText.Text)
  

  -- show form
  formMonoSearch.show()
end

--[[
    When menu item to select an image is clicked, hide this form and
    show the image select form
--]]
function mono.formSearch:SelectImageClick()
  formMonoSearch.close()
  mono.formSelectImage:show()
end

--[[
    When typing in the edit box to filter results, filter the lists of
    classes, fields, and methods with the lower case of the text
--]]
function mono.formSearch:search(text)
  local lower = nil
  if text ~= nil then lower = text:lower() end

  local classes = {}
  local methods = {}
  local fields = {}
  for i,class in ipairs(self.image.classes) do
    if lower == nil or class.lowerName:find(lower, 1, true) ~= nil then table.insert(classes, class) end
    for j,method in ipairs(class.methods) do
      if lower == nil or method.lowerName:find(lower, 1, true) ~= nil then table.insert(methods, method) end
    end
    for j,field in ipairs(class.fields) do
      if lower == nil or field.lowerName:find(lower, 1, true) ~= nil then table.insert(fields, field) end
    end
  end
  self.found.classes = classes
  self.found.fields = fields
  self.found.methods = methods
  formMonoSearch.listSearchClasses.Items.Count = #classes
  formMonoSearch.listSearchFields.Items.Count = #fields
  formMonoSearch.listSearchMethods.Items.Count = #methods
end

-- handler to display classes in list view
function mono.formSearch:listSearchClasses_OnData(sender, listitem)
  local index = listitem.Index + 1
  local class = self.found.classes[index]
  listitem.Caption = class.name
end

-- handler to display fields in list view
function mono.formSearch:listSearchFields_OnData(sender, listitem)
  local field = self.found.fields[listitem.Index + 1]
  if field == nil then
    listitem.Caption = 'nil index '..tostring(index)
  else
    listitem.Caption = field.name
    listitem.SubItems.text = field.class.name
  end
end

-- handler to display methods in list view
function mono.formSearch:listSearchMethods_OnData(sender, listitem)
  local method = self.found.methods[listitem.Index + 1]
  if method == nil then
    listitem.Caption = 'nil index '..tostring(index)
  else
    listitem.Caption = method.name
    listitem.SubItems.text = method.class.name
  end
end

--[[================================================================================
--  formClass.lua
--================================================================================]]

local sortByClassThenName = function(a, b)
    if a.isConst and not b.isConst then return true end
    if b.isConst and not a.isConst then return false end
    if a.isConst and b.isConst then return a.constValue < b.constValue end
    if a.class.lowerName < b.class.lowerName then return true end
    if b.class.lowerName < a.class.lowerName then return false end
    return a.name < b.name
  end
  
  local sortByClassThenOffset = function(a, b)
    if a.isConst and not b.isConst then return true end
    if b.isConst and not a.isConst then return false end
    if a.isConst and b.isConst then return a.constValue < b.constValue end
    if a.class.lowerName < b.class.lowerName then return true end
    if b.class.lowerName < a.class.lowerName then return false end
    return a.offset < b.offset
  end
  
  local sortByName = function(a, b)
    if a.isConst and not b.isConst then return true end
    if b.isConst and not a.isConst then return false end
    if a.isConst and b.isConst then return a.constValue < b.constValue end
    return a.lowerName < b.lowerName
  end
  
  local sortByOffset = function(a, b)
    if a.isConst and not b.isConst then return true end
    if b.isConst and not a.isConst then return false end
    if a.isConst and b.isConst then return a.constValue < b.constValue end
    return a.offset < b.offset
  end
  
  
  --[[
      Show the search form, mono.selectedImage should be set to the
      image to search.
  --]]
  function mono.formClass:show(class, field, method)
    if mono.selectedImage == nil or class == nil then return end
    self.image = mono.selectedImage
    self.class = class
    
    -- function to update lists, for setting on menu items that
    -- make other changes
    local funcUpdate = function(sender) self:setFieldsAndMethods() end
    
    formMonoClass.listFields.OnData = function(sender, listitem)
      self:listFields_OnData(sender, listitem)
    end
    
    formMonoClass.listFields.OnDblClick = function(sender)
      self:listFields_OnDblClick(sender)
    end
  
    formMonoClass.listMethods.OnData = function(sender, listitem)
      self:listMethods_OnData(sender, listitem)
    end
  
    formMonoClass.listMethods.OnDblClick = function(sender)
      self:listMethods_OnDblClick(sender)
    end
  
    formMonoClass.miSortFieldsByOffset.OnClick = funcUpdate
    formMonoClass.miShowInherited.OnClick = funcUpdate
    formMonoClass.miShowUsage.OnClick = funcUpdate
    
    -- create our own lists of fields and methods that we can sort
    -- and filter
    self:setFieldsAndMethods()
    
    -- show form
    formMonoClass.show()
  end
  
  --[[
      Set fields and methods arrays, sorted appropriately and
      with extra fields, including possibly parent fields and methods
  --]]
  function mono.formClass:setFieldsAndMethods()
    local other = ''
    if formMonoClass.miShowUsage.Checked then other = ' (usage by other classes)' end
  
    formMonoClass.labelClassName.Caption = string.format('Mono Class: %s:%s%s', self.class.namespace, self.class.name, other)
    local fields = {}
    local methods = {}
    if formMonoClass.miShowUsage.Checked then
      ---------- show where class is used in other classes
      for i,class in ipairs(self.image.classes) do
        for j,method in ipairs(class.methods) do
          local found = false
          if method.returnType == self.class.name then
            found = true
          else
            for k,p in ipairs(method.parameters) do
              if p.typeName == self.class.name then found = true end
            end
          end
          if found then table.insert(methods, method) end
        end
        
        for j,field in ipairs(class.fields) do
          if field.typeName == self.class.name then table.insert(fields, field) end
        end
      end
    else
      ---------- basic class fields and methods
      local c = self.class
      
      while c ~= nil do
        for i,field in ipairs(c.fields) do
          table.insert(fields, field)
        end
        
        for i,method in ipairs(c.methods) do
          table.insert(methods, method)
        end
        
        if formMonoClass.miShowInherited.Checked then
          c = c.parent
        else
          c = nil
        end
      end
    end
  
  
    if formMonoClass.miSortByClassFirst.Checked then
      table.sort(fields, formMonoClass.miSortFieldsByOffset.Checked and sortByClassThenOffset or sortByClassThenName)
      table.sort(methods, sortByClassThenName)
    else
      table.sort(fields, formMonoClass.miSortFieldsByOffset.Checked and sortByOffset or sortByName)
      table.sort(methods, sortByName)
    end
  
    self.fields = fields
    self.methods = methods
    formMonoClass.listFields.Items.Count = 0
    formMonoClass.listFields.Items.Count = #fields
    formMonoClass.listMethods.Items.Count = 0
    formMonoClass.listMethods.Items.Count = #methods
  end
  
  -- handler to display fields in list view
  function mono.formClass:listFields_OnData(sender, listitem)
    -- columns are offset (or 'STATIC'), Type, Name
    local index = listitem.Index + 1
    local field = self.fields[index]
    
    -- columns are Offset, Type, Name
    if field.isStatic then
      if field.isConst then
        listitem.Caption = 'Const:'..string.format('%2X', field.constValue or 0)
      else
        listitem.Caption = 'Static:'..string.format('%2X', field.offset or 0)
      end
    else
      listitem.Caption = string.format('%02X', field.offset or 0)
    end
    local className = ''
    if field.class.name ~= self.class.name then className = field.class.name end
    listitem.SubItems.text = table.concat({field.typeName or '??', field.name, className}, '\n')
  end
  
  -- handler to display methods in list view
  function mono.formClass:listMethods_OnData(sender, listitem)
    local method = self.methods[listitem.Index + 1]
    if method == nil then
      listitem.Caption = 'nil index '..tostring(listitem.Index + 1)
    else
      listitem.Caption = method.name
      
      local className = ''
      if method.class.name ~= self.class.name then className = method.class.name end
      
      local ps = {}
      for i,p in ipairs(method.parameters) do
        table.insert(ps, string.format('%s %s', p.type, p.name))
      end
      local parms = method.returnType..' ('..table.concat(ps, ', ')..')'
     
      --print('className, method.class.name', className, method.class.name)
      listitem.SubItems.text = table.concat({ parms, className }, '\n')
    end
  end
  
  local parameters = { 'RCX', 'RDX', 'R8', 'R9', '[RBP+30]', '[RBP+38]', '[RBP+40]', '[RBP+48]', '[RBP+50]' }
  local floatParameters = { 'XMM0', 'XMM1', 'XMM2', 'XMM3', 'XMM4', 'XMM5', 'XMM6'}
  
  function mono.formClass:popupMethod_OnPopup(sender)
    
  end
  
  function mono.formClass:listMethods_OnDblClick(sender)
    local method = self.methods[sender.ItemIndex + 1]
    --print("method: "..tostring(method.id))
    if method then
      local address = mono_compile_method(method.id)
      --print("address: "..tostring(address))
      getMemoryViewForm().DisassemblerView.SelectedAddress = address
      getMemoryViewForm().show()
      local hookInfo = hookAt(address)
      -- have aobString, hookString, returnString, instructions
      --[[ how to get method signature
      local ps = {}
      for i,p in ipairs(method.parameters) do
        table.insert(ps, string.format('%s %s', p.type, p.name))
      end
      local parms = method.returnType..' ('..table.concat(ps, ', ')..')'
      ]]
  
      local lines = {}
      table.insert(lines, "define(hook,"..hookInfo.hookString..")")
      table.insert(lines, "define(bytes,"..hookInfo.aobString..")")
      table.insert(lines, "")
      table.insert(lines, "[enable]")
      table.insert(lines, "")
      table.insert(lines, "assert(hook, bytes)")
      table.insert(lines, "alloc(newmem,$1000, hook)")
      table.insert(lines, "{")
  
  
      -- note: per x64 calling convention, RCX might actually be space for
      -- a pre-allocated structure for the return value and other parameters
      -- might be moved one further down the list
      table.insert(lines, "  RCX: "..method.class.name.." (this)")
      for i,p in ipairs(method.parameters) do
        local param = parameters[i + 1]
        if (p.type == "System.Single" or p.type == "System.Double") then param = floatParameters[i + 1] end
        table.insert(lines, "  "..param..": "..tostring(p.type).." "..tostring(p.name))
      end
      table.insert(lines, "")
  
      table.insert(lines, "  Returns (RAX) "..method.returnType)
      table.insert(lines, "}")
      table.insert(lines, "")
      table.insert(lines, "newmem:")
      table.insert(lines, "  // original code")
      for i,c in ipairs(hookInfo.instructions) do
        table.insert(lines, "  "..c)
      end
      table.insert(lines, "  jmp hook+"..string.format("%X", hookInfo.returnOffset))
      table.insert(lines, "")
      table.insert(lines, "hook:")
      table.insert(lines, "  jmp newmem")
      table.insert(lines, "")
      table.insert(lines, "[disable]")
      table.insert(lines, "")
      table.insert(lines, "hook:")
      table.insert(lines, "  db bytes")
      table.insert(lines, "")
      table.insert(lines, "dealloc(newmem)")
  
      local t = {}
      for i,v in ipairs(lines) do
        table.insert(t, v);
        table.insert(t, "\r\n")
      end
    
      local aa = table.concat(t)
  
      getMemoryViewForm().AutoInject1.DoClick()
      
      for i=0,getFormCount()-1 do --this is sorted from z-level. 0 is top
        local f=getForm(i)
    
        if getForm(i).ClassName == 'TfrmAutoInject' then
          f.assemblescreen.Lines.Text = aa
          break
        end
      end
    end
  end
  
  --[[
        When double-clicking a field, print out the base address for the statics of the
        class and the address of the clicked-on static field.
  --]]
  function mono.formClass:listFields_OnDblClick(sender)
    local field = self.fields[sender.ItemIndex + 1]
    if field then
      local class = field.class
      local image = class.image
      local domainId = image.domain
      print('double-clicked on class '..tostring(field.class.name)..' field '..tostring(field.name)..' domain '..tostring(domainId))
      local address = mono_class_getStaticFieldAddress(domainId, class.id)
      print('statics base address: '..string.format("%x", address))
      print(class.name..'.'..field.name..': '..string.format("%x", address + field.offset))
    end
  end
  
  --[[
        IN PROGRESS - take an address and create an AA script to hook at that address
        expecting MONO code.  Will process instructions until 5 bytes (for jmp) are
        processed.  Basic format is like this for address 'CryingSuns.PlayerStatus:BattleshipState:HasAuxiliarySystemType+28'
  
  [enable]
  assert("CryingSuns.PlayerStatus:BattleshipState:HasAuxiliarySystemType":+28, )
  ]]
  function hookAt(address)
    local pos = string.find(address, "+", 1, true)
    local name = address
    local offset = 0
    if (pos ~= nil) then
      name = string.substring(1, pos - 1)
      offset = tonumber(string.sub(pos + 1), 16)
    end
    local actualAddress = getAddress(name) + offset
  
    local data = {
      hookString = util.safeAddress(getNameFromAddress(actualAddress)), -- used for injection, etc
      instructions = {},
      aobString = ""
    }
  
    local aobs = {}
  
    while (offset < 5) do
      local parsed = disassembleAndParse(actualAddress + offset)
      if #aobs > 0 then table.insert(aobs, " ") end
      table.insert(aobs, parsed.bytesString)
      table.insert(data.instructions, parsed.instructionString)
      offset = offset + parsed.length
    end
  
    data.aobString = table.concat(aobs)
    data.returnString = util.safeAddress(getNameFromAddress(actualAddress + offset))
    data.returnOffset = offset
    return data
  end
  
  function hookMethod(method)
  
  end
  
  --[[
        Expects address to be a number
  ]]
  function disassembleAndParse(address)
    local disassembly = disassemble(address)
    local parts = util.split(disassembly, "-")
    for i = 1,#parts do
      if i == 2 then
        parts[i] = parts[i]:gsub("%s+", "") -- remove ALL whitespace from bytes
      else
        parts[i] = parts[i]:gsub("^%s*(.-)%s*$", "%1") -- remove whitespace from ends
      end
    end
  
    local aob = {}
    local i = 1
    while i < string.len(parts[2]) do
      if (i ~= 1) then table.insert(aob, " ") end
      table.insert(aob, string.sub(parts[2], i, i+1))
      i = i + 2
    end
  
    local instructionString = parts[3]
    for k,v in parts[3]:gmatch("[0-9a-fA-F]+") do
      if k:len() == 8 or k:len() == 16 then
        instructionString = instructionString:gsub(k, getNameFromAddress(k))
      end
    end
    
  
    local result = {
      address = getAddress(address),
      addressString = util.safeAddress(getNameFromAddress(address)),
      aob = aob,
      bytesString = table.concat(aob),
      disassembly = disassembly,
      instructionString = instructionString,
      length = getInstructionSize(address),
      originalInstructionString = parts[3]
    }
  
    return result
  end
  
--[[================================================================================
--  Replacement code to activate top level menu
--================================================================================]]

function monohelper_OpenProcessMT(t)
    if t ~= nil then
        t.destroy()
    end

    local usesmono=false
    local m=enumModules()
    local i
    for i=1, #m do
      if (m[i].Name=='mono.dll') or (string.sub(m[i].Name,1,5)=='mono-') then
        usesmono=true
        break
      end
    end

    if usesmono then
        --create a menu item if needed
        if (miMonoHelperTopMenuItem == nil) then
            local mfm = getMainForm().Menu
            
            if (mfm) then
                local mi
                miMonoHelperTopMenuItem = createMenuItem(mfm)
                miMonoHelperTopMenuItem.Caption = "Mono Search"
                mfm.Items.insert(mfm.Items.Count - 1, miMonoHelperTopMenuItem) --add it before help

                mono.menu:init(miMonoHelperTopMenuItem)
            end
        end
    else
        --destroy the menu item if needed
        if miMonoTopMenuItem~=nil then
            miMonoTopMenuItem.miMonoDissect.destroy() --clean up the onclick handler
            miMonoTopMenuItem.miMonoActivate.destroy()  --clean up the onclick handler

            miMonoTopMenuItem.destroy() --also destroys the subitems as they are owned by this menuitem
            miMonoTopMenuItem=nil
        end
    end
end

function monohelper_OpenProcess(processid)
    --call the original onOpenProcess if there was one
    if monohelper_oldOnOpenProcess~=nil then
        monohelper_oldOnOpenProcess(processid)
    end
  
    synchronize("monohelper_OpenProcessMT")
end

function monohelper_Init()
    monohelper_oldOnOpenProcess = onOpenProcess
    onOpenProcess = monohelper_OpenProcess
end

monohelper_Init()