--[[--------------------------------------------------------------------------------
    -- Forms - Save strings as temp files, then load using createFormFromFile()  
    --------------------------------------------------------------------------------]]

local stringFormMonoClass = [==========[<?xml version="1.0" encoding="utf-8"?>
<FormData>
  <formMonoClass Class="TCEForm" Encoding="Ascii85">/yTK^8${c!?7msfX.9L%MdIQc2%!i#a/iSfwG[w3Ysw8Y[Kxk5cW0K{aT8zrG5QUV]XxAk]V}0BB[_{Vs2AqX#xv[:OOyz4.If^1_L48mym]Ld2VgSleeO*UPkS@:9=bb=Knd-{Fwg6!XwK_6R]WT,izTxk+!F}][G^4i7DsGpKdC!im?-UB)-%rDskPUQF)Rzc]O6B[^z_1jKxFN;3I7ftCcx#cPvI2B]=2@,6K60%kK1Inq+.g}B+HlEQo:GCJB^HvLD=hC/st1bW!#ougl#/+QCofy@d(g#(?4h)n8Fz+i)?5xP$o*@=dcxr)_y%yGnV1$nxVZO[j:r2!YoA0+:wJL^qMWa,vD*+B#@Aa^}w.+i9/DmKABOH)-/:7OY#V[,0t=5X%AnZNZI@*4P)qUochB[2IovkhHMj}pgGSwG;$,pf+DV1m(P:6eYFu^E_4i5L4sm06/=9(J7sgHB8O!u?#{an(pMPbnmp0mu^idQi:$okj^1k1uL#v47W@Fp/*YhfEm]%Ci;ljoM.XY223(DY6Rw1U16MK{7p_0/,oR*QnI[Fx^!y8#j2ckVB]M#Nx8-_c+WC%64uvTTI4@LHr!jItz$,W392+Q{fnI+0ZtCx}tJD9YPG3]{w.DidKFjDj=a(KvF(!Ybq*}ChFDvxykE{+J#tY*OFjmoR(+2mnbeOfW6L(G}VH6iWtST43*e09*7_ViOIcO=vZQlX[;]P:XP:O6M/=H[=ynj_%^B#R%zdd94B?*STJO,#0_F?tQwd/1zU0A=]i8tY!aETBz0@KQl]EvYNz8vptiL[Ikvlkeh]!QcAwJIEFM-@Dm0,of/B6lzLjd8rM.Gnrpfh[pd*BDc8ZD{3.LaY.#?7HvWhx2*km@.8k@?mT2K5-1RP/@sV)KQkKCt%8mdvjf.RWoE7?]4qdK5r791pEnq06lk%U@FE0#]DJXJVY@=G0$jxo,z).[8j,RK1M0c81DiaKt9iB)TJ^frZR-yyca?(fKZ]Vg)1UN1(9oZN*#C}cZbo/q1+}b-]OP]=bq+tqn,{9S:jv5gov0-xD/b,@6QeXDuMD?87SU{rt4Hrj)e$Es?Lbf0CIUI(nQD0J]K73r}V?b/@f8mm,,6(sDjPbLj]EI^7T4dCGHNGn{Yav,s/u:/3H}_92;K+QF?o7A!IP*hw:FG6p.l-9ssiZVrCF%)tkrZjW7EQC0;Gu5)-93DF{wtVS,.rrp]v9wuiG@k1h]dr]6f0e,)[j1cvUB)p3+ZXQR5Y+IqN*U832[*Bii9Atfj5DM^1R2i),;p7,TTWtgGpCMyR#t=?cSt+w-4Ay5^,$?%1PleRCoEy*I@UNgbZD5JU:h,R1^LE((gIpcuQ6Y!Q}nmbV9Cn%pG5/$YWLSPU?]TWVY8/d*XGbEDX9GFx_.#z,L</formMonoClass>
</FormData>
]==========]

local stringFormMonoImage = [==========[<?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 stringFormMonoSearch = [==========[<?xml version="1.0" encoding="utf-8"?>
<FormData>
  <formMonoSearch Class="TCEForm" Encoding="Ascii85">%}#Ei,cprUq+#;fbZc+SoAl.WE)L-l:ODn@%^B_H;xFG;($Pw03Dcw=)cVojK[8=K)V+xt2#OX6(]Y18()T9kQCj]j@g2!t@,BL!hBKq?n}$;oZUQj6IDG.}-Iu34S^z*-OOG:=#3-7}=w#W,.XJY2SC}v@!ui8=Mm%*;0k!W+AZE[22D;N{gfq9HcD41k](}+f6X}X)=S^V3[T1{XwuIh7UVDfza3G6cpegQhCL(I}P[C8Z%M(*4?n{c[tonGI]eChq(2V([du2{+:Y!liP*$[aM@)34K[A6YhYP[M4_BgZm{^s0N]eG9yPo0pQ-YgaPhlI=;L)LHR#-hX@aUDg,?MYyH*=1u/6SgYX(^.;O=qgqlY[$!j^,PGLo@_5]dHU$m5oWgFUqzgja?$}TXj.q=v2p,j0O0^25Q,=F+X4HNZt%+7;h!q8SCyQ!hb9ieqIA:yj{*G9AJ6TTSoXsm/Y{b8A#3p%0Sx;N.v/uJ5pYsb!Ui4I+!UcYHI-yZ.0toph)!jm(/d@rebD.mI^7Fm@hU@b}*t^gcI7KXfiLkhT:8F)7lQ}oCZK5t!AHL,6dKE+WDYJ3ciQssLG5QHIkRvUnd)_-KG[gY2^LD/=C8DJ-e!66Rq[QUSWF1q-LQvUJVe5aiER.rI1EAUa1W?KgS9?l+jtAI?bA6SfC,!^5Imtu*{XYpQ=pUp._T3SuABBv;0OKs}EIDV{?=FaRxKa2AVTF/=MH;c0$RNK@M]4tBgT#3p4Ce6vsF(L.Ru*?q4Ry5GjbyNh}f2zHU+u)7mRKOdwauFaTW+GlE=W,#m2Qo$Vx*7aH^JUHsnKRVYv%Ps.[153E{^;IsVN?6ZB!J#=cpwjHlWShBW+w8{$Ez7djl.5SGh-y2^Q@cj8a]vZP0k3(2Wet_S!k*?A3Zxs*4$RpwQnc54F+Em$;y7Z2zoPx/5ezhKvx[{.sVMvLPGH%9dp)DjdU{/k2U@[o@QV;Dmq.3_3k_3-9CH-j]*bPeh*!-qjr,b4Q*^;TmF)=b.jgw/,m)HG6VW?f{2q?kW3ODQiLU)Va-T!vCmfcf:X.6^!@K!?Uj8bmqB/nmTC2xJSqL$YKjcQ(G-CxgpLw}F2:Ve8_]xHgj5-[*m]4/2R8xo17o*1NjEC22YGWpMhe@g}$S,@*UdDtDXv@J0NMnxBO):3pe*VpgAcI)IaU{)ts*iR$7-pNw%A@8FO]TMem#f.w{vbR=CSlIBsOcR,?wg!YjWNUFojeRt/W^GWUZXboc^S/_^8p.2q#E#eaPBo?{^z!dYtB;(f8)iAt?:V_bT#(Pc]Yyfg=NM?H2suncbWi2z(mL*OTW@xT]sjj)%TR5.AF5UQE^A:VIAE*2O-zMaeztg.H;7UcPr.[BXO+aDs*K)4</formMonoSearch>
</FormData>
]==========]

local function saveForm(text)
  local path = os.tmpname() -- get temp file name
  local f, err = io.open(path, "w")
  if f == nil then return nil, err end
  f:write(text)
  f:close()
  return path
end

local function createFormFromString(text)
  local path = saveForm(text)
  local form = createFormFromFile(path)
  pcall(os.remove, path)
  return form
end


formMonoClassB = createFormFromString(stringFormMonoClass)
formMonoImage = createFormFromString(stringFormMonoImage)
formMonoSearch = createFormFromString(stringFormMonoSearch)


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/bootstrap.lua
    --------------------------------------------------------------------------------]]
-- loadstring(loadTextFile('src/lua/util.lua'))()
-- loadstring(loadTextFile('../temp/notes.lua'))()
-- loadstring(loadTextFile('src/lua/mono.lua'))()


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/util.lua
    --------------------------------------------------------------------------------]]
util = util or {}

util.loadTextFile = function (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.
--]]
util.saveTextFile = function(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

--[[
  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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/temp/notes.lua
    --------------------------------------------------------------------------------]]
--[[ 
    A note has 'key' and 'text' fields, lists are sorted by key.
--]]

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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/mono.lua
    --------------------------------------------------------------------------------]]

-- moduled
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
}


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/mono/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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/mono/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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/mono/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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/mono/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


--[[  Sample code... weird

--return util.pretty(mono.MonoImage.enumerate())
mi = mono.MonoImage.new()
print('mi is "'..tostring(mi)..'"')
mi:init('Assembly-CSharp', function(c, i, m, p, t)
  print(m, p, t)
end)

--LaunchMonoDataCollector()
local thread = createThread(function(th)
  TESTIMAGES = {}
  mono_enumImages(function(img)
    th.synchronize(function() print(img) end)
  end)
end)

--]]



--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/monomenu.lua
    --------------------------------------------------------------------------------]]
function mono.menu:init()
  --if self.menuSearch or self.timer then return end
  self.timer = createTimer()
  self.timer.Interval = 1000
  self.timer.OnTimer = function(timer)
    -- wait for normal mono script to create mono menu, check every 5 seconds
    if not miMonoTopMenuItem then return end
    
    self.timer.destroy()
    self.timer = nil

    self.menuSearch = createMenuItem(miMonoTopMenuItem)
    self.menuSearch.Caption = 'Search'
    self.menuSearch.Name = 'miMonoSearch'
    self.menuSearch.OnClick = function(sender) self:OnSearchClick() end
    miMonoTopMenuItem.add(self.menuSearch)
  end
end

function mono.menu:OnSearchClick()
  if mono.selectedImage then
    mono.formSearch:show()
    formMonoSearch:centerScreen()
  else
    mono.formSelectImage:show()
  end
end

mono.menu:init()



--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/forms/formSelectImage.lua
    --------------------------------------------------------------------------------]]
local DEFAULT_ASSEMBLY_NAME = "Assembly-CSharp"
local imageHasBeenSelected = false

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()

  local foundIndex = 0
  for i,name in ipairs(self.imageNames) do
    items.add(name)
    if name == DEFAULT_ASSEMBLY_NAME then foundIndex = i end
  end
  
  local handler = function(sender)
    mono.formSelectImage.OnSelectImage(self)
  end
  
  formMonoImage.buttonSelectImage.OnClick = handler
  formMonoImage.listImages.OnDblClick = handler
  
  formMonoImage.show()

  if foundIndex ~= 0 and imageHasBeenSelected == false then
    formMonoImage.listImages.ItemIndex = foundIndex - 1
    mono.formSelectImage:OnSelectImage()
  end
end

function mono.formSelectImage:OnSelectImage()
  imageHasBeenSelected = true
  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()
  formMonoSearch:centerScreen()
end


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/forms/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


--[[--------------------------------------------------------------------------------
    -- Included File: src/lua/forms/formClass.lua
    --------------------------------------------------------------------------------]]
--[[
    Form for viewing a class
    
    Controls:
      listFields: TCEListView
      listMethods: TCEListView
      miSortFieldsByOffset - if Checked, sort by offset, otherwise by name
      miShowInherited - if Checked, add in methods and fields of parent(s)
      miShowUsage - if Checked, funcs and methods will be on other classes that use the type
      
      miFindFields - When clicked, find fields in all classes that have this class as a type
      miFindMethodCode - When clicked, find methods in all classes that have this class as a type
      
      
    Standard Mode:
      listFields (can sort by name (default) or offset)
        Offset
        Type
        Name
        Class - class it is defined on, blank if not inherited
      listMethods (sorted by name)
        Name
        Signature (return type, parameter types and names)
        Class - class it is defined on, blank if not inherited
      
    Usage Mode:
      listFields (sorted by Class then Name)
        Offset
        Type
        Class
--]]

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]
      print('parameter '..tostring(i + 1))
      print(util.pretty(p))
      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
