--dotnetinfo is a passive .net query tool, but it can go to a active state if needed
if dotNetInfo==nil then
  dotNetInfo={}
end


if getTranslationFolder()~='' then
  loadPOFile(getTranslationFolder()..'dotnetinfo.po')
end

if getOperatingSystem()==0 then
  pathsep=[[\]]
else
  pathsep='/'
end

debugInstanceLookup=false

local DPIMultiplier=(getScreenDPI()/96)
local CONTROL_MONO=0
local CONTROL_DOTNET=1

DataSource={} --All collected data about the current process. From domains, to images, to classes, to fields and methods. Saves on queries and multiple windows can use it

local ELEMENT_TYPE_END        = 0x00       -- End of List
local ELEMENT_TYPE_VOID       = 0x01
local ELEMENT_TYPE_BOOLEAN    = 0x02
local ELEMENT_TYPE_CHAR       = 0x03
local ELEMENT_TYPE_I1         = 0x04
local ELEMENT_TYPE_U1         = 0x05
local ELEMENT_TYPE_I2         = 0x06
local ELEMENT_TYPE_U2         = 0x07
local ELEMENT_TYPE_I4         = 0x08
local ELEMENT_TYPE_U4         = 0x09
local ELEMENT_TYPE_I8         = 0x0a
local ELEMENT_TYPE_U8         = 0x0b
local ELEMENT_TYPE_R4         = 0x0c
local ELEMENT_TYPE_R8         = 0x0d
local ELEMENT_TYPE_STRING     = 0x0e
local ELEMENT_TYPE_PTR        = 0x0f 
local ELEMENT_TYPE_CLASS      = 0x12 
local ELEMENT_TYPE_GENERICINST = 0x15
local ELEMENT_TYPE_I          = 0x18
local ELEMENT_TYPE_U          = 0x19



local frmDotNetInfos={} --keep track of the windows

function getDotNetInfoList()
  return frmDotNetInfos  
end

local settings={}
settings.s=getSettings('dotnetinfo',true)
settings.ColorPublic=tonumber(settings.s['ColorPublic']) or clGreen
settings.ColorPrivate=tonumber(settings.s['ColorPrivate']) or clRed
settings.ColorMixed=tonumber(settings.s['ColorMixed']) or clOrange
settings.ColorStaticBackground=tonumber(settings.s['ColorStaticBackground']) or clAqua
settings.StartParsingOnFirstShow=settings.s['ParseOnShow']=='1'
settings.ShowOriginalPropertyNames=settings.s['ShowOriginalPropertyNames']=='1'

dotnetinfosettings=settings --in the previous version settings wasn't local, but I can not leave it like this as other tools might use 'settings'

frmDotNetInfoConfig=nil

local function SpawnConfigDialog()
  if frmDotNetInfoConfig==nil then
    frmDotNetInfoConfig=createFormFromFile(getAutorunPath()..'forms'..pathsep..'DotNetInfoConfig.FRM')
    frmDotNetInfoConfig.registerFirstShowCallback(function(f)
      f.beginUpdateBounds()
      f.cbPublic.Width=f.Canvas.getTextWidth("XXXXXXXXXXXX")
      f.cbPrivate.Width=f.cbPublic.Width
      f.cbMixed.Width=f.cbPublic.Width

      f.cbPublic.ItemHeight=f.Canvas.getTextHeight("qjX")
      f.cbPrivate.ItemHeight=f.cbPublic.ItemHeight
      f.cbMixed.ItemHeight=f.cbPublic.ItemHeight

      frmDotNetInfoConfig.cbStaticBackground.ItemHeight=f.cbPublic.ItemHeight
      
      if monopipe==nil then
        frmDotNetInfoConfig.cbSkipSafetyCheck.Visible=false
      end
      f.endUpdateBounds()
    end)
  end
  
  frmDotNetInfoConfig.cbPrivate.selected=settings.ColorPrivate
  frmDotNetInfoConfig.cbPublic.selected=settings.ColorPublic
  frmDotNetInfoConfig.cbMixed.selected=settings.ColorMixed
  frmDotNetInfoConfig.cbStaticBackground.selected=settings.ColorStaticBackground
  frmDotNetInfoConfig.cbParseInBackground.checked=settings.StartParsingOnFirstShow
  frmDotNetInfoConfig.cbShowOriginalPropertyNames.checked=settings.ShowOriginalPropertyNames


  if frmDotNetInfoConfig.showModal()==mrOK then
    settings.ColorPublic=frmDotNetInfoConfig.cbPublic.selected
    settings.ColorMixed=frmDotNetInfoConfig.cbMixed.selected
    settings.ColorPrivate=frmDotNetInfoConfig.cbPrivate.selected
    settings.ColorStaticBackground=frmDotNetInfoConfig.cbStaticBackground.selected
    settings.StartParsingOnFirstShow=frmDotNetInfoConfig.cbParseInBackground.checked
    settings.ShowOriginalPropertyNames=frmDotNetInfoConfig.cbShowOriginalPropertyNames.checked
    
    settings.s['ColorPublic']=settings.ColorPublic
    settings.s['ColorPrivate']=settings.ColorPrivate
    settings.s['ColorMixed']=settings.ColorMixed
    settings.s['ColorStaticBackground']=settings.ColorStaticBackground
    settings.s['StartParsingOnFirstShow']=settings.StartParsingOnFirstShow and '1' or '0'
    settings.s['ShowOriginalPropertyNames']=settings.ShowOriginalPropertyNames and '1' or '0'
  end
end

local function AccessMaskToColor(AccessMask)
  if AccessMask==FIELD_ATTRIBUTE_PUBLIC then       
    return settings.ColorPublic
  elseif (AccessMask==FIELD_ATTRIBUTE_PRIVATE) then
    return settings.ColorPrivate
  else
    return settings.ColorMixed
  end
end

local function CTypeToString(ctype)
  local r=monoTypeToCStringLookup[ctype]
  if r==nil then
    r='Object '
  end
  
  return r
end

local function getClassInstances(Class, ProgressBar) 
  if debugInstanceLookup then print("Looking up instances for "..Class.Name) end
  
  if Class.Image.Domain.Control==CONTROL_MONO then
    --get the vtable and then scan for objects with this
    if debugInstanceLookup then print("Mono method") end
    return mono_class_findInstancesOfClassListOnly(nil, Class.Handle)    
  else
    if debugInstanceLookup then print("MS .NET method") end
    return DataSource.DotNetDataCollector.enumAllObjectsOfType(Class.Image.Handle, Class.Handle)    
  end  
end

local function getClassParent(Class)
  if Class.Image==nil then
    OutputDebugString('Generated class')
    return nil
  end
   
  if Class.Parent==nil then
    --is a class definition with an image entry, but unlinked from the actual class and image list
    Class.Parent={}
    
   

    if Class.ParentHandle and (Class.ParentHandle~=0) then      
      if Class.Image.Domain.Control==CONTROL_MONO then
        --mono  
        Class.Parent.Handle=Class.ParentHandle        
        Class.Parent.Name=mono_class_getName(Class.ParentHandle)
        Class.Parent.NameSpace=mono_class_getNamespace(Class.ParentHandle)
        Class.Parent.ParentHandle=mono_class_getParent(Class.ParentHandle)
                
        local ImageHandle=mono_class_getImage(Class.ParentHandle)
        Class.Parent.Image=Class.Image.Domain.Images.HandleLookup[ImageHandle]       

        if Class.Parent.Name==nil or ImageHandle==nil then
          Class.Parent=nil 
          return nil
        end
          
           
      else
        --dotnet  
        Class.Parent.Handle=Class.ParentHandle.TypedefToken
        local classdata=DataSource.DotNetDataCollector.GetTypeDefData(Class.ParentHandle.ModuleHandle, Class.ParentHandle.TypedefToken) --probably overkill. Perhaps just a getclassname ot something in the future
        if classdata then
          Class.Parent.Name=classdata.ClassName
          Class.Parent.NameSpace=''
          Class.Parent.ParentHandle=DataSource.DotNetDataCollector.GetTypeDefParent(Class.ParentHandle.ModuleHandle, Class.ParentHandle.TypedefToken)
          
          local ImageHandle=Class.ParentHandle.ModuleHandle
          Class.Parent.Image=Class.Image.Domain.Images.HandleLookup[ImageHandle] 
        else
          Class.Parent=nil --.Name=''
          
        end        
      end
      
    else
      Class.Parent=nil
    end
  
  end
  
  return Class.Parent
end

local function getClassMethods(Class)
  
  Class.Methods={}
  
  local i
  if Class.Image.Domain.Control==CONTROL_MONO then
    local methods=mono_class_enumMethods(Class.Handle)
    if methods then
      for i=1,#methods do
        local e={}
        e.Handle=methods[i].method
        e.Name=methods[i].name
        e.Class=Class
        e.Attribs=methods[i].flags
        e.Static=(e.Attribs & METHOD_ATTRIBUTE_STATIC) == METHOD_ATTRIBUTE_STATIC
        
        
        
        --e.Parameters=getParameterFromMethod(e.Handle)    
        local types,paramnames,returntype=mono_method_getSignature(e.Handle)        
       
        if types then        
          local typenames=mono_splitParameters(types)
          --local tn
          --for tn in string.gmatch(types, '([^,]+)') do
          --  table.insert(typenames, tn)
          --end
          
          local r='('
          if #typenames==#paramnames then         
            local i
            
            for i=1,#paramnames do
              if i>1 then r=r..', ' end
                
              r=r..typenames[i]..' '..paramnames[i]              
            end
          end 
          r=r..')'    

          e.Parameters=r
          
          if returntype then
            e.ReturnType=returntype
          end
        end
       
        table.insert(Class.Methods, e)
      end
    else
      Class.Methods=nil      
    end
  else
    local methods=DataSource.DotNetDataCollector.GetTypeDefMethods(Class.Image.Handle, Class.Handle)
         
    if methods then
      --MethodToken, Name, Attributes, ImplementationFlags, ILCode, NativeCode, SecondaryNativeCode[]: Integer
      for i=1,#methods do
        local e={}
        --_G.Methods=methods
        --_G.Class=Class
        e.Handle=methods[i].MethodToken
        e.Name=methods[i].Name
        e.Class=Class
        e.ILCode=methods[i].ILCode
        e.NativeCode=methods[i].NativeCode
        e.Attribs=methods[i].Attributes
        e.Static=(e.Attribs & METHOD_ATTRIBUTE_STATIC) == METHOD_ATTRIBUTE_STATIC
        

        --build parameter list
        if dotnetpipe then
          local moduleid=dotnet_getModuleID(Class.Image.FileName)
          local returntype,parameters=dotnet_getMethodParameters(moduleid, methods[i].MethodToken)
          e.ReturnType=returntype
          e.Parameters=parameters
        else
          local plist,sig=DataSource.DotNetDataCollector.GetMethodParameters(Class.Image.Handle, methods[i].MethodToken)
          
          local parameters={}
          if sig then
            local paramstart=sig:find(" (",nil,true)
            if paramstart then
              e.ReturnType=sig:sub(1,paramstart-1)              
              e.Parameters=sig:sub(paramstart+1)
              
              if plist then --there are names, fill them in
                local types=e.Parameters:sub(2,#e.Parameters-1)
                local typenames=mono_splitParameters(types)
                if #typenames==#plist then
                  e.Parameters="("
                  for i=1,#plist do
                    e.Parameters=e.Parameters..typenames[i].." "..plist[i].Name
                    if i<#plist then
                      e.Parameters=e.Parameters..', '
                    end                  
                  end              
                end
                e.Parameters=e.Parameters..")"
              end                        
            end
            
            
          elseif plist then --no sig, only plist
            --print("Plist is valid.  #plist="..#plist) 
            e.Parameters="("
            for i=1,#plist do
              --print("plist var1 = "..plist[i].CType)
              e.Parameters=e.Parameters..CTypeToString(plist[i].CType).." "..plist[i].Name
              if i<#plist then
                e.Parameters=e.Parameters..', '
              end
            end
            
            e.Parameters=e.Parameters..")"
          else
            --print("plist failed")
            e.Parameters='(...)'        
          end
        end
        
        
       
      
        table.insert(Class.Methods, e)
      end
    end
  end

  if Class.Methods then
    table.sort(Class.Methods,function(m1,m2) return m1.Name:upper()<m2.Name:upper() end)  
  end    
end

local function getClassFields(Class)
 
  local r={}
  
  local i
  if Class.Image.Domain.Control==CONTROL_MONO then  
    local isGeneric=mono_class_isgeneric(Class.Handle)
    
    if isGeneric==nil then return end
    
    local StaticFieldAddress=nil
    
    --if isGeneric==false then      
    --  StaticFieldAddress=mono_class_getStaticFieldAddress(0,Class.Handle)
    --end
         
    local fields=mono_class_enumFields(Class.Handle, true, true)
    if fields==nil then return end
    
    for i=1,#fields do
      local e={}
      e.Handle=fields[i].field
      e.Name=fields[i].name
      e.AltName=fields[i].altname
      e.VarType=fields[i].monotype
      e.VarTypeName=fields[i].typename
      e.Offset=fields[i].offset
      e.Static=fields[i].isStatic
      e.Const=fields[i].isConst
      e.Attribs=fields[i].flags      
      e.Address=fields[i].staticAddress      
      
      e.Class=Class
      
      table.insert(r,e)
    end
   
  else
    --.net
    local classdata=DataSource.DotNetDataCollector.GetTypeDefData(Class.Image.Handle, Class.Handle)
    if classdata and classdata.Fields then
      for i=1,#classdata.Fields do
        --Offset, FieldType, Name
        local e={}
        e.Handle=classdata.Fields[i].Token
        e.Name=classdata.Fields[i].Name
        e.VarType=classdata.Fields[i].FieldType
        e.VarTypeName=classdata.Fields[i].FieldTypeClassName
        e.Offset=classdata.Fields[i].Offset
        e.Static=classdata.Fields[i].IsStatic
        e.Attribs=classdata.Fields[i].Attribs
        e.Class=Class  
        e.Address=classdata.Fields[i].Address --ce 7.7+
        
        table.insert(r,e)
      end
    end    
  end
  
  table.sort(r, function(e1,e2) return e1.Offset < e2.Offset end)
 
  Class.Fields=r
  return r
end

local function getClasses(Image)
  if Image.Classes==nil then
    print("getClasses Error: Image.Classes was not initialized")
    return
  end
  
  if (not Image.Classes.Busy) then
    print("getClasses Error: Image.Classes was not busy")
    return
  end 

  --get the classlist
  local i
  if (Image.Domain.Control==CONTROL_MONO) then
    local classlist=mono_image_enumClassesEx(Image.Handle)
    if classlist then         
      for i=1,#classlist do
        local e={}        
        e.NestingTypeHandle=classlist[i].NestingTypeHandle        
        if e.NestingTypeHandle==0 then e.NestingTypeHandle=nil end   
        
        e.Name=classlist[i].Name
        e.NameSpace=classlist[i].NameSpace
        
        if e.NestingTypeHandle then
          e.FullName=classlist[i].FullName
        else
          if e.NameSpace~='' then
            e.FullName=e.NameSpace..'.'..e.Name
          else
            e.FullName=e.Name
          end
        end
        e.Handle=classlist[i].Handle
        e.ParentHandle=classlist[i].ParentHandle          
        e.Image=Image        
        
        table.insert(Image.Classes,e)
        Image.Domain.ClassHandleLookup[e.Handle]=e         
      end     
    end
  else
    local classlist=DataSource.DotNetDataCollector.EnumTypeDefs(Image.Handle)  
    for i=1,#classlist do
     --TypeDefToken, Name, Flags, Extends
      local e={}
      e.Name=classlist[i].Name
      e.NameSpace='' --nyi
      e.FullName=e.Name
      e.Handle=classlist[i].TypeDefToken
      e.ParentHandle=DataSource.DotNetDataCollector.GetTypeDefParent(Image.Handle, e.Handle) --classlist[i].Extends
      e.Image=Image
      
      table.insert(Image.Classes,e) 
      Image.Domain.ClassHandleLookup[e.Handle]=e        
    end
  end

  table.sort(Image.Classes, function(e1,e2)
    return e1.FullName < e2.FullName
  end)
  
  return Image.Classes  
end

local function getImages(Domain)
  local i
  
  Domain.Images={}
  --create a HandleToImage lookup table  
  Domain.Images.HandleLookup={}
  
  if Domain.Control==CONTROL_MONO then
    local imagesinfo=mono_enumImagesEx(Domain.DomainHandle)
    
    for i=1,#imagesinfo do
      local e={}
      e.Handle=imagesinfo[i].Image
      e.Name=imagesinfo[i].Path
      e.FileName=extractFileName(e.Name)
      e.Domain=Domain
      table.insert(Domain.Images,e)
      
      Domain.Images.HandleLookup[e.Handle]=e
    end
  else
    --dotnet
    local modules=DataSource.DotNetDataCollector.EnumModuleList(Domain.Handle)  
    --ModuleHandle, BaseAddress, Name
    for i=1,#modules do
      local e={}
      e.Handle=modules[i].ModuleHandle
      e.Name=modules[i].Name --full path
      e.FileName=extractFileName(e.Name)
      e.BaseAddress=modules[i].BaseAddress
      e.Domain=Domain
      
      table.insert(Domain.Images,e)
      Domain.Images.HandleLookup[e.Handle]=e
      
    end
  end
  
  --sort the list
  table.sort(Domain.Images, function(e1,e2) return e1.FileName < e2.FileName end)
  

  
end

local function getDomains()  
  DataSource.Domains={}
  DataSource.Domains.ClassHandleLookup={} 
    
  if monopipe then
    local mr=mono_enumDomains() 
    if mr==nil then return DataSource.Domains end    
    
    local i
    for i=1,#mr do
      local e={}
      e.Handle=mr[i]
      e.Control=CONTROL_MONO
      e.ClassHandleLookup={}
      table.insert(DataSource.Domains,e)
    end
  end
  
  if DataSource.DotNetDataCollector then
    local mr=DataSource.DotNetDataCollector.EnumDomains()
    for i=1,#mr do
      local e={}
      e.Handle=mr[i].DomainHandle
      e.Name=mr[i].Name
      e.Control=CONTROL_DOTNET
      e.ClassHandleLookup={}
      table.insert(DataSource.Domains,e)
    end
  end
   
  return DataSource.Domains
end

local function clearClassInformation(frmDotNetInfo)
  frmDotNetInfo.lvStaticFields.Items.clear()
  frmDotNetInfo.lvFields.Items.clear()
  if frmDotNetInfo.lvFields2 then
    frmDotNetInfo.lvFields2.clear()
  end
  frmDotNetInfo.lvMethods.Items.clear()
  frmDotNetInfo.gbClassInformation.Caption='Class Information'
  frmDotNetInfo.CurrentlyDisplayedClass=nil
end

local function ClassFetchWaitTillReadyAndSendData(thread, frmDotNetInfo, Image, Filter, FetchDoneFunction)
  --print("ClassFetchWaitTillReadyAndSendData")
  if Image.Classes==nil then 
    print("ClassFetchWaitTillReadyAndSendData Error: Image.Classes is nil")
    return
  end
  
  while Image.Classes.Busy and (not thread.Terminated) do 
    print("ClassFetchWaitTillReadyAndSendData: Image.Classes = busy") --'should' never happen
    sleep(100)    
  end
  
 -- print("after wait")
  if thread.Terminated or Image.Classes.Busy then return end
  
  local i
  local block={}
  local StartIndex=1
  
  if Filter~='' then       
    for i=1,#Image.Classes do  
      if Image.Classes[i].FullName:upper():find(Filter,1,true) then                
        table.insert(frmDotNetInfo.FilteredClassList, Image.Classes[i]) 
      end      
    end
  end  
  
  synchronize(FetchDoneFunction)  
end

local function StartClassFetch(frmDotNetInfo, Image, Filter, FetchDoneFunction)
  --create a thread that fetches the full classlist if needed and then calls the OnDataFunction untill full 
  --StartClassFetch called while frmDotNetInfo.ClassFetchThread is not nil. Destroy the old one first  
  
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
   
    return 
  end 
  

  
  if frmDotNetInfo.ClassFetchThread then
    --print("killing old thread")
    frmDotNetInfo.ClassFetchThread.terminate()
    if frmDotNetInfo.ClassFetchThread.waitfor(4000) then    
      frmDotNetInfo.ClassFetchThread.destroy()
      frmDotNetInfo.ClassFetchThread=nil
    else
      frmDotNetInfo.ClassFetchThread.FreeOnTerminate(true)
      frmDotNetInfo.ClassFetchThread=nil
    end
    
  end
  
  if Filter=='' then
    frmDotNetInfo.FilteredClassList=nil
  else
    frmDotNetInfo.FilteredClassList={}
  end
  
  if Image.Classes==nil then
    if (Image.Domain.Control==CONTROL_MONO) and mono_skipsafetycheck==false and isPaused() then  
      FetchDoneFunction()
      return
    end
  
    Image.Classes={}
    Image.Classes.Busy=true
    
    --create a thread that fetches the classes
    frmDotNetInfo.ClassFetchThread=createMonoThread(function(t)
      --get the classess
      t.Name='frmDotNetInfo_ClassFetchThread'
      t.FreeOnTerminate(false)
      getClasses(Image)      
      Image.Classes.Busy=nil
      
      if t.Terminated then
        Image.Classes=nil
        return
      end
      
      --all classes are fetched, send to the waiting form
      ClassFetchWaitTillReadyAndSendData(t, frmDotNetInfo, Image, Filter, FetchDoneFunction)      
    end)  
  else
    --create a thread that waits until Busy is false/gone and fill the list normally
    --print("Already has list")
    frmDotNetInfo.ClassFetchThread=createMonoThread(function(t)
      t.FreeOnTerminate(false)
      ClassFetchWaitTillReadyAndSendData(t, frmDotNetInfo, Image, Filter, FetchDoneFunction) 
    end)
  end
  

end

local function CancelClassFetch(frmDotNetInfo)
  --interrupts the current classfetch
  if frmDotNetInfo.ClassFetchThread then
    frmDotNetInfo.ClassFetchThread.terminate()
    if frmDotNetInfo.ClassFetchThread.waitfor(4000) then
      frmDotNetInfo.ClassFetchThread.destroy()
    else
      frmDotNetInfo.ClassFetchThread.FreeOnTerminate(true)
    end
    frmDotNetInfo.ClassFetchThread=nil
  end
end




function GetClassByName(classname)
--checks if the given classname has been queried, and if not return nil
  local r
  if DataSource.ClassNameLookup then
    r=DataSource.ClassNameLookup[classname]
    if r then return r end
  else
    DataSource.ClassNameLookup={}
  end
  
  return r
end

local function FillClassInfoFields(frmDotNetInfo, Class)
  --if tonumber(frmDotNetInfos[frmDotNetInfo.Tag].ProcessID)~=getOpenedProcessID() then 
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
    return 
  end
  
  if frmDotNetInfo==nil then
    print('FillClassInfoFields: Invalid frmDotNetInfo field')
  end
  
  if Class==nil then
    print('FillClassInfoFields: Invalid Class field')
  end
  
  frmDotNetInfo.lvStaticFields.beginUpdate()
  frmDotNetInfo.lvFields.beginUpdate() 
  frmDotNetInfo.lvFields2.beginUpdate()
  frmDotNetInfo.lvMethods.beginUpdate()
  
  clearClassInformation(frmDotNetInfo)
  
  if Class.Fields==nil then
    getClassFields(Class)
  end
  
  if Class.Methods==nil then
    getClassMethods(Class)
  end
  
  if Class.Fields then  
    for i=1,#Class.Fields do
      if Class.Fields[i].Static then
        local li=frmDotNetInfo.lvStaticFields.Items.add()                    
        li.Data=i
        if settings.ShowOriginalPropertyNames then
          li.Caption=Class.Fields[i].Name
        else      
          li.Caption=Class.Fields[i].AltName or Class.Fields[i].Name
        end
        
        if Class.Fields[i].VarTypeName and Class.Fields[i].VarTypeName~='' then
          li.SubItems.add(Class.Fields[i].VarTypeName)              
        else
          local typename='Vartype:'..Class.Fields[i].VarType
          if Class.Image.Domain~=CONTROL_MONO then
            if dotnetpipe and dotnetpipe.isValid() then
              typename=dotnet_getFieldTypeName(dotnet_getModuleID(Class.Image.FileName), Class.Fields[i].Handle)                          
            else
              typename=translate('Launch the .NET interface');
            end
          end        
          li.SubItems.add(typename)
        end
        
        if Class.Fields[i].Address and Class.Fields[i].Address~=0 then
          li.SubItems.add(string.format("%.8x",Class.Fields[i].Address))
        else
          li.SubItems.add('?')
        end    

        li.SubItems.add('')
      else       
        if frmDotNetInfo.lvFields2 then
          local node=frmDotNetInfo.lvFields2.addChild(nil)  
          frmDotNetInfo.lvFields2.setNodeDataAsInteger(node, createRef(Class.Fields[i]))                      
          
         -- printf("Class.Fields[%d].VarType=%x (ELEMENT_TYPE_CLASS=%d) Is it a class: %s", i, Class.Fields[i].VarType, ELEMENT_TYPE_CLASS, ELEMENT_TYPE_CLASS==Class.Fields[i].VarType)
            
          if Class.Fields[i].VarType==ELEMENT_TYPE_CLASS or 
             Class.Fields[i].VarType==ELEMENT_TYPE_GENERICINST then
            frmDotNetInfo.lvFields2.HasChildren[node]=true
          end
          
        end        
      
        local li=frmDotNetInfo.lvFields.Items.add()
      
        li.Caption=string.format("%.3x", Class.Fields[i].Offset)
        li.Data=i
        if settings.ShowOriginalPropertyNames then
          li.SubItems.add(Class.Fields[i].Name)
        else
          li.SubItems.add(Class.Fields[i].AltName or Class.Fields[i].Name)
        end
        if Class.Fields[i].VarTypeName and Class.Fields[i].VarTypeName~='' then
          li.SubItems.add(Class.Fields[i].VarTypeName)              
        else
          li.SubItems.add(Class.Fields[i].VarType)    
        end               
      end
    end
  end
  
  
  if Class.Methods then
    for i=1,#Class.Methods do
      local li=frmDotNetInfo.lvMethods.Items.add()   
      li.Caption=Class.Methods[i].Name
      if Class.Methods[i].ReturnType then
        if Class.Methods[i].Parameters then
          li.SubItems.add(Class.Methods[i].ReturnType.." "..Class.Methods[i].Parameters)
        else
          li.SubItems.add(Class.Methods[i].ReturnType.." (bugged)")
        end
      else
        li.SubItems.add(Class.Methods[i].Parameters)
      end
      
      li.Data=i
    end
  end

  frmDotNetInfo.lvStaticFields.endUpdate()
  frmDotNetInfo.lvFields.endUpdate()
  frmDotNetInfo.lvMethods.endUpdate()
  frmDotNetInfo.lvFields2.endUpdate()
  
  frmDotNetInfo.CurrentlyDisplayedClass=nil
  frmDotNetInfo.CurrentlyDisplayedClass=Class
  --print("Class.Name = "..Class.Name)
  --print("frmDotNetInfo.CurrentlyDisplayedClass.Name = "..frmDotNetInfo.CurrentlyDisplayedClass.Name)
  
  --if frmDotNetInfo.comboFieldBaseAddress.Text~='' then
  frmDotNetInfo.comboFieldBaseAddress.OnChange(frmDotNetInfo.comboFieldBaseAddress)
  --end
  
  --print("Class.Name = "..Class.Name)
  --print("frmDotNetInfo.CurrentlyDisplayedClass.Name = "..frmDotNetInfo.CurrentlyDisplayedClass.Name)
  
end





local function edtClassFilterChange(frmDotNetInfo, sender)
--call image list onSelectionChange.  It will start a new listbuild and cancel any previously one going on
  frmDotNetInfo.lbImages.OnSelectionChange(frmDotNetInfo.lbImages)
end

local function ClassListDataRequest(frmDotNetInfo, sender, listitem)
  if listitem.index==-1 then
    listitem.Caption=''
  end
  
  if frmDotNetInfo.FilteredClassList then    
    local FilteredClassListEntry=frmDotNetInfo.FilteredClassList[listitem.index+1]
    if FilteredClassListEntry then
      listitem.Caption=FilteredClassListEntry.FullName
    else
      listitem.Caption='<FilteredClassListEntry==nil>'
    end
  else 
    if DataSource.Domains[frmDotNetInfo.lbDomains.ItemIndex+1].Images[frmDotNetInfo.lbImages.ItemIndex+1].Classes and
       DataSource.Domains[frmDotNetInfo.lbDomains.ItemIndex+1].Images[frmDotNetInfo.lbImages.ItemIndex+1].Classes[listitem.index+1]
    then   
      listitem.Caption=DataSource.Domains[frmDotNetInfo.lbDomains.ItemIndex+1].Images[frmDotNetInfo.lbImages.ItemIndex+1].Classes[listitem.index+1].FullName
    end
  end
end



local function ClassSelectionChange(frmDotNetInfo, sender)
 
  --printf("ClassSelectionChange.  ItemIndex=%d",sender.ItemIndex)
  --printf("frmDotNetInfo.lvClasses.ItemIndex=%d",frmDotNetInfo.lvClasses.ItemIndex)  
  
  if sender.ItemIndex>=0 then

    local Domain=DataSource.Domains[frmDotNetInfo.lbDomains.ItemIndex+1]
    local Image=Domain.Images[frmDotNetInfo.lbImages.ItemIndex+1] 
    
    if Image.Classes==nil then return end
    
    local lvc=frmDotNetInfo.lvClasses    
    if lvc.ItemIndex==-1 then return end

    local Class
    if frmDotNetInfo.FilteredClassList then
      Class=frmDotNetInfo.FilteredClassList[lvc.ItemIndex+1]
    else
      Class=Image.Classes[lvc.ItemIndex+1]  
    end

    
   -- local ClassIndex=lvc.Items[lvc.ItemIndex].Data
  --  local Class=Image.Classes[ClassIndex]      
    if Class==nil then return end
    
    _G.LastClass=Class 
    
    local ClassParent=getClassParent(Class)   
    
    
    frmDotNetInfo.gbClassInformation.Caption='Class Information ('..Class.Name..')'


    --erase the old inheritance fields  
    while frmDotNetInfo.gbInheritance.ControlCount>0 do
      frmDotNetInfo.gbInheritance.Control[0].destroy()    
    end  
  
    if ClassParent then
      local ClassList={}
      ClassList[1]={}
      ClassList[1].Class=Class
      
      while ClassParent and ClassParent.Handle~=0 and ClassParent.name~='' do
        local e={}
        e.Class=ClassParent
        table.insert(ClassList,e)
        ClassParent=getClassParent(ClassParent)
      end
      
      local l
      for i=1,#ClassList do 
        l=createLabel(frmDotNetInfo.gbInheritance)        
        local fullname=ClassList[i].Class.Name
        if ClassList[i].Class.NameSpace and ClassList[i].Class.NameSpace~='' then
          fullname=ClassList[i].Class.NameSpace..'.'..fullname
        end
        l.Caption=fullname
        ClassList[i].Label=l
        
        
        if i==1 then
          l.Font.Style="[fsBold]"               
        else        
          l.Font.Style="[fsUnderline]"
          l.Font.Color=darkMode() and clRed or clBlue          
        end
        l.Cursor=crHandPoint
        l.OnMouseDown=function(s)
          --show class state
          --print("Showing state "..ClassList[i].Class.Name)            
          FillClassInfoFields(frmDotNetInfo, ClassList[i].Class)
          local j
          for j=1,#ClassList do
            if j==i then --the currently selected index (this is why I like lua)
              ClassList[j].Label.Font.Color=clWindowText
              ClassList[j].Label.Font.Style="[fsBold]" 
            else
              ClassList[j].Label.Font.Style="[fsUnderline]"
              ClassList[j].Label.Font.Color=darkMode() and clRed or clBlue   
            end
          end
          frmDotNetInfo.gbInheritance.OnResize(frmDotNetInfo.gbInheritance, true)
        end
        
        if i~=#ClassList then --not the last item
          l=createLabel(frmDotNetInfo.gbInheritance)          
          l.Caption="->"    
        end
      end    
      
    
      frmDotNetInfo.gbInheritance.Visible=true  
      frmDotNetInfo.gbInheritance.OnResize(frmDotNetInfo.gbInheritance)
      
    else
      frmDotNetInfo.gbInheritance.Visible=false
    end
    
    FillClassInfoFields(frmDotNetInfo, Class)
  
  end  
  
end


local function ImageSelectionChange(frmDotNetInfo, sender)
  frmDotNetInfo.lvClasses.Items.count=0
  clearClassInformation(frmDotNetInfo)

  if sender.ItemIndex>=0 then 
    frmDotNetInfo.lvClasses.beginUpdate()
    frmDotNetInfo.lvClasses.Enabled=false
    frmDotNetInfo.lvClasses.Cursor=crHourGlass
    
    local Domain=DataSource.Domains[frmDotNetInfo.lbDomains.ItemIndex+1]
    local Image=Domain.Images[frmDotNetInfo.lbImages.ItemIndex+1]
    
    
    
    
    StartClassFetch(frmDotNetInfo, Image, frmDotNetInfo.edtClassFilter.Text:upper(),
      function()
        --called when done
        if frmDotNetInfo.FilteredClassList then
          if frmDotNetInfo.FilteredClassList then
            frmDotNetInfo.lvClasses.Items.count=#frmDotNetInfo.FilteredClassList 
          else
            frmDotNetInfo.lvClasses.Items.count=0
          end
        else
          if Image.Classes then
            frmDotNetInfo.lvClasses.Items.count=#Image.Classes
          else
            frmDotNetInfo.lvClasses.Items.count=0
          end
        end
        
        frmDotNetInfo.lvClasses.Enabled=true
        frmDotNetInfo.lvClasses.endUpdate()
        
        frmDotNetInfo.lvClasses.Cursor=crDefault      
      end
    )
 
    
  end
end


local function DomainSelectionChange(frmDotNetInfo, sender)

  frmDotNetInfo.lbImages.Items.clear()
  frmDotNetInfo.lvClasses.Items.count=0
  clearClassInformation(frmDotNetInfo)

  if sender.ItemIndex>=0 then
    local Domain=DataSource.Domains[sender.ItemIndex+1]
    --get the Images list
    if Domain.Images==nil then
      --fill the images list
      getImages(Domain)   
    end
    
    local i
    local ImageList=Domain.Images
    
    
    for i=1,#ImageList do      
      s=extractFileName(ImageList[i].Name)
      frmDotNetInfo.lbImages.Items.Add(s)
    end
  end
end

local function FindInListView(listview)
  _G.lb=lb
  local fd=createFindDialog(frmDotNetInfo)
  fd.Options='[frDown, frHideWholeWord,  frHideEntireScope, frHideUpDown]'
  fd.Title='Search in '..listbox.name

  
    
  fd.OnFind=function()
    local caseSensitive=fd.Options:find('frMatchCase')~=nil
    local start=listview.ItemIndex
    start=start+1
    
    local needle=fd.FindText
    if not caseSensitive then
      needle=needle:upper()
    end
    
    for i=start, listview.Items.Count-1 do      
      local haystack=listview.Items[i].Caption      
      
      if not caseSensitive then
        haystack=haystack:upper()
      end
           
      if haystack:find(needle,0,true)~=nil then
        listview.itemIndex=i
        return
      end
    end
    beep() 
  end
  
  
  fd.execute()  

  local x,y
  x=(listbox.height / 2)
  x=x-(fd.height/2)
  
  y=(listbox.width / 2)
  x=y-(fd.width / 2)
  
  x,y=listview.clientToScreen(x,y)
  fd.top=y
  fd.left=x
end

local function FindInListBox(listbox)
  _G.lb=lb
  local fd=createFindDialog(frmDotNetInfo)
  fd.Options='[frDown, frHideWholeWord,  frHideEntireScope, frHideUpDown]'
  fd.Title='Search in '..listbox.name

  
    
  fd.OnFind=function()
    local caseSensitive=fd.Options:find('frMatchCase')~=nil
    local start=listbox.ItemIndex
    start=start+1
    
    local needle=fd.FindText
    if not caseSensitive then
      needle=needle:upper()
    end
    
    for i=start, listbox.Items.Count-1 do      
      local haystack=listbox.Items[i]      
      
      if not caseSensitive then
        haystack=haystack:upper()
      end
           
      if haystack:find(needle,0,true)~=nil then
        listbox.itemIndex=i
        return
      end
    end
    beep() 
  end
  
  
  fd.execute()  

  local x,y
  x=(listbox.height / 2)
  x=x-(fd.height/2)
  
  y=(listbox.width / 2)
  x=y-(fd.width / 2)
  
  x,y=listbox.clientToScreen(x,y)
  fd.top=y
  fd.left=x
end

local delayedResize

local function InheritanceResize(gbInheritance, now)
  local oldresize=gbInheritance.OnResize
  gbInheritance.OnResize=nil
  
  if delayedResize==nil then
    local f=function()
      local i,x,y
      local width=gbInheritance.ClientWidth
      
      x=0
      y=0
      
      for i=0 , gbInheritance.ControlCount-1 do
        --the labels are in the order they get added to the groupbox
        local c=gbInheritance.Control[i]
        if (x~=0) and (x+c.Width>width) then --next line
          x=0
          y=y+c.height
        end
        
        c.Left=2+x
        c.Top=y
        
        
        x=x+c.Width+1
      end      
      delayedResize=nil      
    end
    
    if now then 
      f()
    else
      delayedResize=createTimer(100,f)
    end
  else
    --reset timer
    delayedResize.Enabled=false
    delayedResize.Enabled=true
  end  
  
  gbInheritance.OnResize=oldresize
end

local function getMethodAddress(Method)
  _G.lastmethod=Method
  if Method.Class.Image.Domain.Control==CONTROL_MONO then
    local address=mono_compile_method(Method.Handle)
    if address and address~=0 then 
      return address 
    else 
      return nil,translate('Failure compiling the method')
    end
  else
    --the method already contains ILCode and NativeCode (requirying won't help until the collector is reloaded)
    if Method.NativeCode and Method.NativeCode~=0 then
      return Method.NativeCode
    elseif dotnetpipe and (tonumber(dotnetpipe.processid)==getOpenedProcessID()) then
      --print("getMethodAddress and dotnetpipe is valid")
      if Method.Class.Image.DotNetPipeModuleID==nil then
        --print("getting module id for "..Method.Class.Image.FileName)
        Method.Class.Image.DotNetPipeModuleID=dotnet_getModuleID(Method.Class.Image.FileName)
   
        --Method.Class.Image.DotNetPipeModuleID=nil --debug
        
        if Method.Class.Image.DotNetPipeModuleID==nil then
          --not found by name, try finding 10 classes in this image
          if Method.Class.Image.Classes==nil then
            Method.Class.Image.Classes={} 
          end
          
          if #Method.Class.Image.Classes==0 then
            getClasses(Method.Class.Image)
          end
          
          if #Method.Class.Image.Classes==0 then
            return nil, "The image that contains this class, doesn't contain any classes..."
          end
          
          local classnamelist={}
          local c=0
          for i=1,#Method.Class.Image.Classes do
            local cn=Method.Class.Image.Classes[i].Name
            if cn:find("%<")==nil and cn:find("%^")==nil then       
              table.insert(classnamelist, Method.Class.Image.Classes[i].Name)
              c=c+1
              if c>=10 then break end
            end
          end
          
          Method.Class.Image.DotNetPipeModuleID=dotnet_getModuleIDFromClassList(Method.Class.Image.FileName, classnamelist)
        end
      end
      
      if Method.Class.Image.DotNetPipeModuleID then
        address=dotnet_getMethodEntryPoint(Method.Class.Image.DotNetPipeModuleID, Method.Handle)        
        if address==nil or address==0 then
          return nil, translate('Failure getting method address')
        end
        
        return address          
      else
        --print("No moduleid found")

        return nil, translate('Failure getting methodid for module named '..Method.Class.Image.FileName)                  
      end
    else
      return nil,'Not jitted and no dotnet pipe present' --not jitted yet and no dotnetpipe present
    end
  end
end


local function GeneratePatchTemplateForMethod(frmDotNetInfo)
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end
  
  local Method=Class.Methods[frmDotNetInfo.lvMethods.ItemIndex+1]
  if Method==nil then return end
  
  if (Class.Image.Domain.Control~=CONTROL_MONO) and 
     (dotnetpipe==nil) and 
     (messageDialog("For better method return and parameter types it's recommended to inject the invasive dotnet data collector.  Do that now?", mtConfirmation,mbYes, mbNo)==mrYes) then
    LaunchDotNetInterface()
    getClassMethods(Class) --reload the methods
    
    --reload the list and reselect the correct entry
    ClassSelectionChange(frmDotNetInfo, frmDotNetInfo.lvClasses)
        
    local i
    for i=1, #Class.Methods do
      if Class.Methods[i].Handle==Method.Handle then
        Method=Class.Methods[i]
        frmDotNetInfo.lvMethods.ItemIndex=i-1
      end
    end
  end
  
  --[[code to create a AA script using a huge {$LUA} block that 
  compiles C# code with the methods header (methodname_new and methodname_old)
  injects it into the target , and then detours the method to methodname_new 
  and detours methodname_old to the original callback stub.  
  
  The methods get jitted before the function gets detoured
  --]]  
  
  local targetmethodname=Method.Name
  local newtargetmethodname='new'..Method.Name
  local oldtargetmethodname='old'..Method.Name
  
  local script=[[
[enable]
{$lua}  
if syntaxcheck then return end


<initcode>

if dotnetdetours==nil then
  dotnetdetours={}
else
  local di=dotnetdetours['<fullnameDotFormat>']
  if di and di.processid==getOpenedProcessID() then
    --already detoured. Undo first
    local r,err=autoAssemble(di.disablescript, di.disableinfo)
    
    if not r then 
      error(err)
    else
      dotnetdetours['<fullnameDotFormat>']=nil
    end    
  end
end
local detourinfo={}

local csharpscript=]].."[["..[[

using System.Runtime.CompilerServices; //for NoInlining
//feel free to add more

<optionalnamespace>
public class patched<classname> : <classname>
{

  public <methodreturntype> new<methodname><methodparamlist>
  {
    //you have access to public fields.  Use reflection if you wish to access private fields
    <calloldmethod>    
  }

  [MethodImpl(MethodImplOptions.NoInlining)]
  public <methodreturntype> old<methodname><methodparamlist>
  {
    //don't bother putting code in here. It will get replaced (just some stuff to make sure there's space)
    return <methodtypedefaultvalue>;
  }
}
<optionalnamespaceend>
]].."]]"..[[

local references, sysfile=dotnetpatch_getAllReferences() --you're free to build your own list
local csfile,msg=compileCS(csharpscript, references, sysfile)

if csfile==nil then
  --sometimes having the sysfile causes an issue. Try without  
  csfile,msg=compileCS(csharpscript, references)
  if csfile==nil then 
    if msg==nil then msg=' (?Unknown error?)' end
    messageDialog('Compilation error:'..msg, mtError, mbOK) --show compile error in a dialog instead of a lua error only
    error(msg)
  end
end

--still here, c# dll created, now inject and hook
local result, disableinfo, disablescript=InjectDotNetDetour(csfile, "<fullname>","<newfullname>","<oldfullname>")

if result then
  detourinfo.disableinfo=di
  detourinfo.disablescript=disablescript
  detourinfo.processid=getOpenedProcessID()
  dotnetdetours['<fullnameDotFormat>']=detourinfo
else   
  if disableinfo==nil then disableinfo='no reason' end  
  error('InjectDotNetDetour failed : '..disableinfo) --prevents checking  
end
{$asm}


[disable]
{$lua}
if syntaxcheck then return end

if dotnetdetours['<fullnameDotFormat>'] then
  autoAssemble(dotnetdetours['<fullnameDotFormat>'].disablescript, dotnetdetours['<fullnameDotFormat>'].disableinfo) 
end
{$asm}
]]

  --replace the tokens with the names  
  local tokens={}  
  local fullname=Class.Name..'::'..Method.Name 

    
  local fullnameDotFormat=Class.Name..'.'..Method.Name  
  
  print("fullname="..fullname);  
  
  local namespace, classname, methodname=SplitDotNetName(fullname)
  
  print("namespace="..namespace)
  print("classname="..classname)
  print("methodname="..methodname)
  
  local newclassname='patched'..classname
  if namespace and namespace~='' then
    newclassname=namespace..'.'..newclassname
  end
  
  
  tokens['fullname']=fullname
  tokens['newfullname']=newclassname..'::new'..methodname
  tokens['oldfullname']=newclassname..'::old'..methodname
  
  
  tokens['fullnameDotFormat']=fullnameDotFormat
  
  if Class.Image.Domain.Control==CONTROL_MONO then 
    tokens['initcode']='LaunchMonoDataCollector()'
  else
    tokens['initcode']='LaunchDotNetInterface()'
  end
  
  if namespace and namespace~='' then
    tokens['optionalnamespace']='namespace '..namespace..'{'
    tokens['optionalnamespaceend']='}'
  else
    tokens['optionalnamespace']=''
    tokens['optionalnamespaceend']=''
  end
  
  
  

  tokens['classname']=classname --namespace.classname
  tokens['methodname']=methodname
  
  tokens['methodparamlist']=Method.Parameters  --(int x, int y, int z)
  
  print("Method.Parameters="..Method.Parameters)
  local parameterstring=string.gsub(Method.Parameters,"%((.-)%)",function(x) return x end)
  
  print("parameterstring="..parameterstring)
  
  local pl=table.pack(string.split(parameterstring,', '))
  local i
  local varstring=''
  for i=1,#pl do
    local vartype,varname=pl[i]:split(' ')
    
    if varname then
      if i>1 then
        varstring=varstring..', '
      end     
      
      varstring=varstring..varname    
    end
    
  end

  if (Method.ReturnType=="System.Void") or (Method.ReturnType=="Void") then --System.Void and Void can not be used as return type
    tokens['methodreturntype']='void'
    
    tokens['calloldmethod']=[[
old]]..methodname..'('..varstring..[[);
    return;]]
  else
    tokens['methodreturntype']=Method.ReturnType --(e.g System.Int32)
    tokens['calloldmethod']='return old'..methodname..'('..varstring..');'
  end
  
  
  --get a generic default return value so the compiler doesn't complains
  methodtypedefaultvalue={}
  methodtypedefaultvalue['System.Void']=''
  methodtypedefaultvalue['void']=''
  methodtypedefaultvalue['System.Boolean']='true'
  methodtypedefaultvalue['boolean']='true'
  methodtypedefaultvalue['System.Int32']='0'
  methodtypedefaultvalue['int']='0'
  methodtypedefaultvalue['System.Single']='0'
  methodtypedefaultvalue['float']='0'
    
  
  tokens['methodtypedefaultvalue']=methodtypedefaultvalue[Method.ReturnType]  
  
  if tokens['methodtypedefaultvalue']==nil then --unknown type, assume class object
    tokens['methodtypedefaultvalue']='null'
  end
  
  
  script=ParseScriptTokens(script,tokens)

  createAutoAssemblerForm(script)
end

function dotNetInfo.SpawnInvokeMethodDialog(Method, instanceAddress, OnResult, OnCreateInstance)
  --spawns an invoke method dialog.
  --It's a modal form, unless OnResult is set
 
  if (Method.Class.Image.Domain.Control~=CONTROL_MONO) then
    if dotnetpipe==nil then    
      if messageDialog("Inject the CE .NET interface into the target process?", mtConfirmation,mbYes,mbNo)~=mrYes then
        return nil,'User declined injection'
      else    
        if LaunchDotNetInterface()~=true then
          return nil,'DotNetInterface did not load'
        end
      end    
    end
    
    local moduleid=dotnet_getModuleID(Method.Class.Image.FileName)
    local methodtoken=Method.Handle
    
    local address
    local wrappedAddress
        
    if (not Method.Static) then    
      address=instanceAddress
      wrappedAddress=dotnet_wrapobject(address)    
    end
    
    local midinfo=dotnet_invoke_method_dialog(Method.Class.Name..'.'..Method.Name, moduleid, methodtoken, wrappedAddress, OnResult)
  else
    --use the already existing mono way
    return mono_invoke_method_dialog(Method.Class.Image.Domain.Handle, Method.Handle, instanceAddress, OnResult, OnCreateInstance) 
  end
end

dotNetInfo_SpawnInvokeMethodDialog=dotNetInfo.SpawnInvokeMethodDialog --compatibility fix for old version (that was never releaseD)


local function SpawnInjectMethodDialog(frmDotNetInfo)
  --called from the invoke method popupmenu item
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end
  
  local Method=Class.Methods[frmDotNetInfo.lvMethods.ItemIndex+1]
  if Method==nil then return end
  
  dotNetInfo.SpawnInvokeMethodDialog(Method, getAddressSafe(frmDotNetInfo.comboFieldBaseAddress.Text), 
    function(result, _secondary, vtype, hrs)       --OnResult
      --if result and vtype and hrs and hrs~='' then
      --  print(hrs)
      --end
    end,

    function(classHandle) --OnCreateInstance
      --print("dotnetinfo.SpawnInjectMethodDialog:  OnCreateInstance")
     -- printf("classHandle=%x",classHandle)
     
      
      --find this class and all the classes that inherit from this
      local newClass=Class.Image.Domain.ClassHandleLookup[classHandle]
      
      --if newClass then
      --  printf("known class. It's name is %s",newClass.Name)
      --else
      --  printf("unknown class")
      --end
      
      if newClass==nil then
        --print("Class not yet found. Searching unopened images")
        local d=Class.Image.Domain
        
        if d.Images==nil then
          DataSource.getImages(d)
        end
        
        if d.Images==nil then return end
        
        for i=1,#d.Images do --assuming that the images list is already populated
          if d.Images[i].Classes==nil then
            --not yet loaded, check it
            d.Images[i].Classes={}
            d.Images[i].Classes.Busy=true            
            DataSource.getClasses(d.Images[i])
            d.Images[i].Classes.Busy=false 
            
            newClass=Class.Image.Domain.ClassHandleLookup[classHandle] --check if found
            if newClass then break end --it has been found           
          end         
        end
      end
      
      if newClass==nil then MessageDialog('Failure finding the class to instantiate', mtError) return end
      
      if newClass.Methods==nil then
        getClassMethods(newClass)    
      end


      local newInstance,msg=dotNetInfo.createInstanceOfClassDialog(newClass)
      
      if newInstance==nil then
        MessageDialog(msg, mtError)
      else
        return string.format('0x%x', newInstance)
      end
    end
  )
  
end


local function OpenAddressOfSelectedMethod(frmDotNetInfo)
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end
  
  local Method=Class.Methods[frmDotNetInfo.lvMethods.ItemIndex+1]
  if Method==nil then return end
  
  --print("Getting the entry point for "..Method.Name)
  local Address,ErrorMessage=getMethodAddress(Method)
 
  if (Address) then
    getMemoryViewForm().DisassemblerView.SelectedAddress=Address
    getMemoryViewForm().show()
  else   
   
    if (Method.Class.Image.Domain.Control~=CONTROL_MONO) then
      --it's not going to be the one you expect
      
      if ((dotnetpipe==nil) or (dotnetpipe.isValid()==false)) and (messageDialog(translate('This method is currently not jitted. Do you wish to inject the .NET Control DLL into the target process to force it to generate an entry point for this method? (Does not work if the target is suspended)'), mtConfirmation, mbYes,mbNo)==mrYes) then
        --inject the control DLL
        LaunchDotNetInterface()    

        if dotnetpipe then
          
          Address,ErrorMessage=getMethodAddress(Method)   
          if Address then
            getMemoryViewForm().DisassemblerView.SelectedAddress=Address
            getMemoryViewForm().show()          
          else
            messageDialog(ErrorMessage, mtError, mbOK)
            return
          end
        end        
      else
        messageDialog(ErrorMessage, mtError, mbOK)
        return   
      end     
      
    else
      messageDialog(ErrorMessage, mtError, mbOK)
      return    
    end
  end
end


local function comboFieldBaseAddressChange(frmDotNetInfo, sender)
  --print("change")
  local address=getAddressSafe(sender.Text)
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  
  if frmDotNetInfo.lvFields2_addresscache then
    frmDotNetInfo.lvFields2_addresscache.string=sender.Text
    frmDotNetInfo.lvFields2_addresscache.address=address
    frmDotNetInfo.lvFields2_addresscache.lastquery=getTickCount()
  end
  
  if address then      
    frmDotNetInfo.lvFields.Columns[0].Caption=translate('Address')
    local i
    for i=0, frmDotNetInfo.lvFields.Items.Count-1 do
      local ci=frmDotNetInfo.lvFields.Items[i].Data
      if ci and ci>0 and ci<=#Class.Fields then
        local a=Class.Fields[ci].Offset+address
        frmDotNetInfo.lvFields.Items[i].Caption=string.format("%.8x ",a)
      end
    end
    frmDotNetInfo.tFieldValueUpdater.Enabled=true
    frmDotNetInfo.lvFields.Columns[3].Visible=true
    
    frmDotNetInfo.lvFields2.Header.Columns[3].Visible=true
    frmDotNetInfo.lvFields2.Header.AutoSizeIndex=3    
    
    frmDotNetInfo.tFieldValueUpdater.OnTimer(frmDotNetInfo.tFieldValueUpdater)
  else
    --invalid
    frmDotNetInfo.lvFields.Columns[0].Caption=translate('Offset')
    
    
    local i
    for i=0, frmDotNetInfo.lvFields.Items.Count-1 do
      local ci=frmDotNetInfo.lvFields.Items[i].Data
      if ci and ci>0 and ci<=#Class.Fields then
        local a=Class.Fields[ci].Offset
        frmDotNetInfo.lvFields.Items[i].Caption=string.format("%.3x",a)
      end
    end  

    frmDotNetInfo.tFieldValueUpdater.Enabled=frmDotNetInfo.lvStaticFields.Items.Count>0
    frmDotNetInfo.lvFields.Columns[3].Visible=false
    
    frmDotNetInfo.lvFields2.Header.Columns[3].Visible=dalse
    frmDotNetInfo.lvFields2.Header.AutoSizeIndex=2    
  end
end



local LocalDotNetValueReaders={} --takes a bytetable of max 8 bytes and convert it to the correct type
LocalDotNetValueReaders[ELEMENT_TYPE_BOOLEAN]=function(bt)
  if bt then
    if bt[1]==0 then
      return translate("False")    
    else
      return translate("True")
    end
  end
end

LocalDotNetValueReaders[ELEMENT_TYPE_CHAR]=function(bt) 
  if bt then
    local c=string.char(bt[1])
    return c..' (#'..bt[1]..')'
  end
end

LocalDotNetValueReaders[ELEMENT_TYPE_I1]=function(bt) 
  if bt then
    local v=bt[1]
    if v and ((v & 0x80)~=0) then
      return v-0x100
    else
      return v
    end
  end
end

LocalDotNetValueReaders[ELEMENT_TYPE_U1]=function(bt) return bt[1] end
LocalDotNetValueReaders[ELEMENT_TYPE_I2]=function(bt) return byteTableToWord(bt,true) end
LocalDotNetValueReaders[ELEMENT_TYPE_U2]=function(bt) return byteTableToWord(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_I4]=function(bt) return byteTableToDword(bt,true) end
LocalDotNetValueReaders[ELEMENT_TYPE_U4]=function(bt) return byteTableToDword(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_I8]=function(bt) return byteTableToQword(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_U8]=function(bt) return byteTableToQword(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_I]=function(bt) if targetIs64Bit() then return LocalDotNetValueReaders[ELEMENT_TYPE_I8](bt) else return LocalDotNetValueReaders[ELEMENT_TYPE_I4](bt) end end
LocalDotNetValueReaders[ELEMENT_TYPE_U]=function(bt) if targetIs64Bit() then return LocalDotNetValueReaders[ELEMENT_TYPE_U8](bt) else return LocalDotNetValueReaders[ELEMENT_TYPE_U8](bt) end end
LocalDotNetValueReaders[ELEMENT_TYPE_R4]=function(bt) return byteTableToFloat(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_R8]=function(bt) return byteTableToDouble(bt) end
LocalDotNetValueReaders[ELEMENT_TYPE_PTR]=function(bt) 
  if targetIs64Bit() then
    return string.format("%.8x",byteTableToQword(bt))
  else
    return string.format("%.8x",byteTableToDword(bt))
  end
end
LocalDotNetValueReaders[ELEMENT_TYPE_CLASS]=LocalDotNetValueReaders[ELEMENT_TYPE_PTR]



LocalDotNetValueWriters={} --value is either integer ot float
LocalDotNetValueWriters[ELEMENT_TYPE_I1]=function(value) return {value} end
LocalDotNetValueWriters[ELEMENT_TYPE_U1]=LocalDotNetValueWriters[ELEMENT_TYPE_I1]
LocalDotNetValueWriters[ELEMENT_TYPE_I2]=function(value) return wordToByteTable(value) end
LocalDotNetValueWriters[ELEMENT_TYPE_U2]=LocalDotNetValueWriters[ELEMENT_TYPE_U2]
LocalDotNetValueWriters[ELEMENT_TYPE_I4]=function(value) return dwordToByteTable(value) end
LocalDotNetValueWriters[ELEMENT_TYPE_U4]=LocalDotNetValueWriters[ELEMENT_TYPE_I4]
LocalDotNetValueWriters[ELEMENT_TYPE_I8]=function(value) return qwordToByteTable(value) end
LocalDotNetValueWriters[ELEMENT_TYPE_U8]=LocalDotNetValueWriters[ELEMENT_TYPE_I8]
LocalDotNetValueWriters[ELEMENT_TYPE_I]=function(value) if targetIs64Bit() then return LocalDotNetValueWriters[ELEMENT_TYPE_I8](value) else return LocalDotNetValueWriters[ELEMENT_TYPE_I4](value) end end
LocalDotNetValueWriters[ELEMENT_TYPE_U]=function(value) if targetIs64Bit() then return LocalDotNetValueWriters[ELEMENT_TYPE_U8](value) else return LocalDotNetValueWriters[ELEMENT_TYPE_U8](value) end end
LocalDotNetValueWriters[ELEMENT_TYPE_R4]=function(value) return floatToByteTable(value) end
LocalDotNetValueWriters[ELEMENT_TYPE_R8]=function(value) return doubleToByteTable(value) end
LocalDotNetValueWriters[ELEMENT_TYPE_BOOLEAN]=LocalDotNetValueWriters[ELEMENT_TYPE_I1]
LocalDotNetValueWriters[ELEMENT_TYPE_CLASS]=LocalDotNetValueWriters[ELEMENT_TYPE_U]

-----------------------------------------------


local DotNetValueReaders={}
DotNetValueReaders[ELEMENT_TYPE_BOOLEAN]=function(address) return LocalDotNetValueReaders[ELEMENT_TYPE_BOOLEAN](readBytes(address,1,true)) end
DotNetValueReaders[ELEMENT_TYPE_CHAR]=function(address) return LocalDotNetValueReaders[ELEMENT_TYPE_CHAR](readBytes(address,1,true)) end
DotNetValueReaders[ELEMENT_TYPE_I1]=function(address) return LocalDotNetValueReaders[ELEMENT_TYPE_I1](readBytes(address,1,true)) end
DotNetValueReaders[ELEMENT_TYPE_U1]=function(address) return readBytes(address,1) end
DotNetValueReaders[ELEMENT_TYPE_I2]=function(address) return readSmallInteger(address,true) end 
DotNetValueReaders[ELEMENT_TYPE_U2]=function(address) return readSmallInteger(address) end 
DotNetValueReaders[ELEMENT_TYPE_I4]=function(address) return readInteger(address,true) end 
DotNetValueReaders[ELEMENT_TYPE_U4]=function(address) return readInteger(address) end 
DotNetValueReaders[ELEMENT_TYPE_I8]=function(address) return readQword(address) end 
DotNetValueReaders[ELEMENT_TYPE_U8]=DotNetValueReaders[ELEMENT_TYPE_I8]
DotNetValueReaders[ELEMENT_TYPE_I]=function(address) if targetIs64Bit() then return DotNetValueReaders[ELEMENT_TYPE_I8](address) else return DotNetValueReaders[ELEMENT_TYPE_I4](address) end end
DotNetValueReaders[ELEMENT_TYPE_U]=function(address) if targetIs64Bit() then return DotNetValueReaders[ELEMENT_TYPE_U8](address) else return DotNetValueReaders[ELEMENT_TYPE_U8](address) end end
DotNetValueReaders[ELEMENT_TYPE_R4]=function(address) return readFloat(address) end 
DotNetValueReaders[ELEMENT_TYPE_R8]=function(address) return readDouble(address) end 
DotNetValueReaders[ELEMENT_TYPE_PTR]=function(address) 
  local v=readPointer(address)
  if v then
    return string.format("%.8x",v) 
  else
    return
  end 
end
DotNetValueReaders[ELEMENT_TYPE_CLASS]=DotNetValueReaders[ELEMENT_TYPE_PTR]

--global
function getDotNetValueReader(dotnettype)
  return DotNetValueReaders[dotnettype]
end



local function readDotNetString(address, Field)
  if address==0 or address==nil then return nil,'address is invalid' end
  if Field==nil then return nil,'Field is nil' end
  
--assumption: address points to the start of the string object, so not the pointer to the object
  if Field.Class.Image.Domain.Control==CONTROL_MONO then
    return mono_string_readString(address)
  else
    local lengthAddress
    local stringAddress
    
    if targetIs64Bit() then
      lengthAddress=address+8
    else
      lengthAddress=address+4
    end
    
    stringAddress=lengthAddress+4
    local length=readInteger(lengthAddress)
    if length and (length<4000) then
      return readString(stringAddress,length*2,true)
    else
      return translate("<Error: Unreadable string>")
    end   
  end
end

local function setFieldValue(Field,Address,Value)
  --for .net Value is a string, for mono it's an integer
  --printf("setFieldValue() Field: %s Address:  %x Value %s", Field.Name, Address, Value)
  
  
  if Field.Class.Image.Domain.Control==CONTROL_MONO then  
    --print("mono path")

    if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
      return nil,translate('Strings can not be written')
    end
    
    --print("mono path 2")

    local v=tonumber(Value)
    if v==nil then
      if Value:lower()=='true' then
        v=1
      elseif Value:lower()=='false' then
        v=0
      end     
    end
    if v then      
      Value=v
    end --else could be a normal string
    
    
    
    
    local vtable=mono_class_getVTable(Field.Class)
    
    --convert Value to bytes   
    local writer=LocalDotNetValueWriters[Field.VarType]
    if writer==nil then
      printf("writer==nil.  Field.VarType=%d", Field.VarType)
      --dfield=Field
      return nil,translate('Unsupported field type')    
    end
    --print("mono path 3")
    
    local bt=writer(Value)
    if bt then
      if Field.Static and ((Address==nil) or (Address==0)) then 
        local qvalue=byteTableToQword(bt)      
        mono_setStaticFieldValue(vtable, Field.Handle, qvalue)
      else
        local addr=Field.Address
        if addr==nil then
          addr=Address+Field.Offset
        end
        
        local bs=''
        for x=1,#bt do
          bs=bs..bt[x]..' '
        end        

        writeBytes(addr,bt)  
     
      end
      return true
    else
      return nil,translate('value convertor failure')    
    end
  else
    if dotnetpipe and dotnetpipe.isValid() then      
      dotnet_setFieldValue(dotnet_getModuleID(Field.Class.Image.FileName), Field.Handle, Address, Value)
      return true
    else      
      local writer=LocalDotNetValueWriters[Field.VarType]
      if writer==nil then
        return nil,translate('Unsupported field type')    
      end
      local bt=writer(tonumber(Value))
      
      local addr
      if Address==nil or Address==0 then
        --static?
        if Field.Address then
          addr=Field.Address
        else
          return nil,translate('No valid address provided for non-static field')
        end
      else 
        addr=Address+Field.Offset
      end
      writeBytes(addr,bt)
      
      return true
    end    
  end
end

local function getStaticFieldValue(Field)
  if Field.Class.Image.Domain.Control==CONTROL_MONO then
    if Field.Address and (Field.Address~=0) then
     
      if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then     
        local a=readPointer(Field.Address)
        if a then
          return readDotNetString(a, Field)
        else
          return '?'
        end
      else
        local reader=DotNetValueReaders[Field.VarType]
        if reader==nil then
          reader=DotNetValueReaders[ELEMENT_TYPE_PTR]
        end
      
        value=reader(Field.Address)
        if not value then
          value='?'          
        end
        
        return value
      end    
    end --no address
  
    local vtable=mono_class_getVTable(Field.Class.Handle) --il2cpp returns 0, but is also doesn't need it
    
    --print("getting qvalue")
    local qvalue=mono_getStaticFieldValue(vtable, Field.Handle)
    
    if qvalue then
      if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
       -- outputDebugString("Getting string")
        return readDotNetString(qvalue, Field)
      else
        --outputDebugString("using reader")
        local reader=LocalDotNetValueReaders[Field.VarType]
        if reader==nil then
          reader=LocalDotNetValueReaders[ELEMENT_TYPE_PTR]
        end
        
        --outputDebugString("getting bytetable")
        
        local bt=qwordToByteTable(qvalue)
        
      
        if bt==nil then
          --print("bt = nil")
        else      
          local vs=reader(bt)
          if vs==nil then
            return 'nil'        
          else        
            return vs
          end
        end
      end
    else
      return '?'
    end
    
  else
    if Field.Address==nil or Field.Address==0 then
      if dotnetpipe and dotnetpipe.isValid() then
        return dotnet_getFieldValue(dotnet_getModuleID(Field.Class.Image.FileName), Field.Handle, 0)
      else
        return translate('Launch the .NET interface')
      end
    else
      --7.7       
      if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
        local a=readPointer(Field.Address)
        if a then 
          local str=readDotNetString(a, Field)
          if str then
            return '"'..readDotNetString(a, Field)..'"' 
          else
            return '<nil>'
          end
        else
          return '?'
        end                
      end
      
      local reader=DotNetValueReaders[Field.VarType]
      if reader==nil then
        reader=DotNetValueReaders[ELEMENT_TYPE_PTR]
      end    

      local r=reader(Field.Address)
      if r then
        return r
      else
        return '?'      
      end
    end
  end
end


local function getFieldValue(Field, Address)
  if Address==nil or Address==0 then     
    return getStaticFieldValue(Field)
  end
  
  local a=Address+Field.Offset
  

  
  if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
    local a=readPointer(a)
    if a then 
      local str=readDotNetString(a, Field)
      if str then
        return '"'..readDotNetString(a, Field)..'"' 
      else
        return '<nil>'
      end
    else
      return '?'
    end
  else
    local reader=DotNetValueReaders[Field.VarType]
    if reader==nil then
      reader=DotNetValueReaders[ELEMENT_TYPE_PTR]
    end
    
     
    local value=reader(a)
    if value==nil then
      return '?'          
    else
      return value
    end       
              
  end
  
end


local function FieldValueUpdaterTimer(frmDotNetInfo, sender)
  
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
    return 
  end


  local i
  local address=getAddressSafe(frmDotNetInfo.comboFieldBaseAddress.Text)
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  local value
  
  if Class and frmDotNetInfo.lvStaticFields.TopItem~=nil then
    for i=frmDotNetInfo.lvStaticFields.TopItem.Index, math.min(frmDotNetInfo.lvStaticFields.Items.Count-1, frmDotNetInfo.lvStaticFields.TopItem.Index+frmDotNetInfo.lvStaticFields.VisibleRowCount) do
      local ci=frmDotNetInfo.lvStaticFields.Items[i].Data
      if Class.Fields and (ci>0) and (ci<=#Class.Fields) then
        
        if Class.Fields[ci].Address and (Class.Fields[ci].Address~=0) and (not (isKeyPressed(VK_CONTROL))) then
          if (Class.Fields[ci].VarType==ELEMENT_TYPE_STRING) or (Class.Fields[ci].VarTypeName == "System.String") then     
            local a=readPointer(Class.Fields[ci].Address)
            if a then
              value=readDotNetString(a, Class.Fields[ci])
            else
              value='?'
            end
          else
            local reader=DotNetValueReaders[Class.Fields[ci].VarType]
            if reader==nil then
              reader=DotNetValueReaders[ELEMENT_TYPE_PTR]
            end
          
            --printf("Calling reader(%8x)", Class.Fields[ci].Address);
            value=reader(Class.Fields[ci].Address)
            if not value then
              value='?'          
            end
            
            --printf("value=%s", value);
          end
        else
          --querry from the target if possible      
          value=getStaticFieldValue(Class.Fields[ci])
        end
        frmDotNetInfo.lvStaticFields.Items[i].SubItems[2]=value 
      end
    end  
  end
  

  
  if address and Class and address>0x10000 then
    if frmDotNetInfo.lvFields2 then
      frmDotNetInfo.lvFields2_valuecache={} --reset the value cache, and repaint
      frmDotNetInfo.lvFields2.refresh()      
    else
      if frmDotNetInfo.lvFields.TopItem then 
        for i=frmDotNetInfo.lvFields.TopItem.Index, math.min(frmDotNetInfo.lvFields.Items.Count-1, frmDotNetInfo.lvFields.TopItem.Index+frmDotNetInfo.lvFields.VisibleRowCount) do
        --for i=0, frmDotNetInfo.lvFields.Items.Count-1 do
          local ci=frmDotNetInfo.lvFields.Items[i].Data
          if ci>0 and ci<=#Class.Fields then
            local a=address+Class.Fields[ci].Offset
           
            if (Class.Fields[ci].VarType==ELEMENT_TYPE_STRING) or (Class.Fields[ci].VarTypeName == "System.String") then        
              local address=readPointer(a)
              if address then
                value=readDotNetString(address, Class.Fields[ci])        
              else
                value='?'
              end 
            else
              local reader=DotNetValueReaders[Class.Fields[ci].VarType]
              if reader==nil then
                reader=DotNetValueReaders[ELEMENT_TYPE_PTR]
              end
              
              value=reader(a)
              if not value then
                value='?'          
              end        
            end
            
            if frmDotNetInfo.lvFields.Items[i].SubItems.Count<3 then
              frmDotNetInfo.lvFields.Items[i].SubItems.add(value)
            else
              frmDotNetInfo.lvFields.Items[i].SubItems[2]=value 
            end
            
                  
            
            --printf("getting value for address %x which has type %d", a, Class.Fields[ci].VarType)
          end
        end
      end
    end
  end
end

local function btnLookupInstancesClick(frmDotNetInfo, sender)
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
    return 
  end


  if debugInstanceLookup then print("btnLookupInstancesClick") end
  
  if frmDotNetInfo.InstanceScanner then
    frmDotNetInfo.InstanceScanner.terminate()
    frmDotNetInfo.InstanceScanner.destroy()
    frmDotNetInfo.InstanceScanner=nil
  end

  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end
  

  
  
  
  --create the thread that will do the scan
  --it's possible the thread finishes before showmodal is called, but thanks to synchronize that won't happen
  frmDotNetInfo.scannerThread=createMonoThread(function(t)
    local results={}
    if debugInstanceLookup then print("scannerThread start") end
    t.Name='Instance Scanner'
    
    local r=getClassInstances(Class,pb)
    if r then
      local i
      for i=1,#r do
        results[i]=r[i]
      end
    end
    
    if debugInstanceLookup then print("scan finished") end

    if t.Terminated then return end  


    synchronize(function() 
      if t.Terminated then return end       
    
      if debugInstanceLookup then print("scanFinishedProperly") end
      local i
      frmDotNetInfo.comboFieldBaseAddress.clear()
      for i=1,#results do
        frmDotNetInfo.comboFieldBaseAddress.Items.add(string.format("%.8x",results[i]))
      end 
      
      if #results then
        frmDotNetInfo.comboFieldBaseAddress.ItemIndex=0      
        frmDotNetInfo.comboFieldBaseAddress.OnChange(frmDotNetInfo.comboFieldBaseAddress)       
        frmDotNetInfo.comboFieldBaseAddress.DroppedDown=true
      end  
    end)
  end)
end

local function lvStaticFieldsDblClick(frmDotNetInfo,sender)
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
    return 
  end


  if sender.Selected==nil then return end
  local ci=sender.Selected.Data
  
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end 
  
  local Field=Class.Fields[ci]
  if Field==nil then return end
      
  if Field.Address==0 then    
    if (dotnetpipe==nil) or (dotnetpipe.isValid()==false) then
      --this needs the .NET interface.
      if messageDialog(translate('Inject the .NET Control DLL into the target process?'), mtConfirmation, mbYes,mbNo)==mrYes then
        LaunchDotNetInterface()   
      end
      return
    end
  end  
    
  local value=getStaticFieldValue(Field)
    
  local fname
  if settings.ShowOriginalPropertyNames then
    fname=Field.Name
  else   
    fname=Field.AltName or Field.Name
    if Field.AltName then
      fname=fname..' ('..Field.Name..')'
    end
  end
  
  value=inputQuery(translate('Change value of ')..fname, 'New value:', value)
  if value then
    setFieldValue(Field, Field.Address or 0,value)    
    
    frmDotNetInfo.tFieldValueUpdater.OnTimer(frmDotNetInfo.tFieldValueUpdater)
  end  
  
  
end




local function getAddressFromNode(frmDotNetInfo, node)
  if tonumber(frmDotNetInfo.ProcessID)~=getOpenedProcessID() then 
    return 
  end


  if node==nil then return nil, 'invalid node(nil)' end

  if type(node)~='userdata' then return nil, 'invalid node(not userdata)' end
  if userDataToInteger(node)==0 then return nil, 'invalid node(0)' end
  
  local address
  local parentnode=frmDotNetInfo.lvFields2.getNodeParent(node)
  if parentnode==nil then
    --get the address from the editfield (todo: add support for usage in the static fields list)
    if frmDotNetInfo.lvFields2_addresscache.address==nil then
      
      if (frmDotNetInfo.lvFields2_addresscache.string==nil) or --not yet tried
         (getTickCount()>frmDotNetInfo.lvFields2_addresscache.lastquery+2000) then --it's been 2 seconds since last lookup
         frmDotNetInfo.lvFields2_addresscache.string=frmDotNetInfo.comboFieldBaseAddress.Text               
         frmDotNetInfo.lvFields2_addresscache.address=getAddressSafe(frmDotNetInfo.comboFieldBaseAddress.Text)
         frmDotNetInfo.lvFields2_addresscache.lastquery=getTickCount()
      end
    else
     -- print("frmDotNetInfo.lvFields2_addresscache.address~=nil")
    end
    address=frmDotNetInfo.lvFields2_addresscache.address        
  else
    --print("parentnode~=nil")
    address=getAddressFromNode(frmDotNetInfo, parentnode)
    
    if address then         
      address=readPointer(address)
      if address==nil then
        return nil,'unreadable pointer'
      end
    else
      return nil,'unreadable pointer'
    end
  end
   
  if address then

    --add the offset of this field    
    
    local ndi=frmDotNetInfo.lvFields2.getNodeDataAsInteger(node)
    if ndi then
      local Field=getRef(ndi)  
     -- print("type field="..type(Field))                  
      return address+Field.Offset
    else
     -- print("getNodeDataAsInteger=nil")
      return nil, 'invalid node data'
    end
    return address
  else
    return nil,'unparsable address'
  end
end  

local function getInstanceAdressFromNode(frmDotNetInfo, node) --in case of multilevel fields
  local Field=getRef(frmDotNetInfo.lvFields2.getNodeDataAsInteger(node))
  if Field==nil then return nil, 'No field data' end
  local Address,err=getAddressFromNode(frmDotNetInfo, node)
    
  if Address==nil then return nil,err end
  

  
  return Address-Field.Offset
end
    

local function miBrowseFieldClick(frmDotNetInfo,sender)
  local address
  if frmDotNetInfo.lvFields2==nil then
    local entry=frmDotNetInfo.lvFields.Selected
    if entry then
      address=tonumber(entry.Caption,16)
    end
  else    
    local node=frmDotNetInfo.lvFields2.FocusedNode
    if node then      
      address=getAddressFromNode(frmDotNetInfo, node)
    end
  end

  if address then  
    getMemoryViewForm().HexadecimalView.Address=address
    getMemoryViewForm().show()
  end  
end

local function lvFieldsDblClick(frmDotNetInfo,sender)
--obsolete
  if sender.Selected==nil then return end
  local ci=sender.Selected.Data
  
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end 
  
  local Field=Class.Fields[ci]
  if Field==nil then return end
  
  if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
    if Field.Class.Image.Domain.Control==CONTROL_MONO then return 
    else
      if (dotnetpipe==nil) or (dotnetpipe.isValid()==false) then
        if messageDialog(translate('To change string types the .NET Control DLL needs to be injected into the target. Inject it?'), mtConfirmation, mbYes,mbNo)~=mrYes then return end
        
        LaunchDotNetInterface()        
        
        if (dotnetpipe==nil) or (dotnetpipe.isValid()==false) then return end --failed to load
      end
    end
  end
  
  
  local Address=getAddressSafe(frmDotNetInfo.comboFieldBaseAddress.Text)
  if Address==nil then return end
  
  local value=sender.Selected.SubItems[2]
  local fname
  if settings.ShowOriginalPropertyNames then
    fname=Field.Name
  else
    fname=Field.AltName or Field.Name
    if Field.AltName then
      fname=fname..' ('..Field.Name..')'
    end
  end
  
  value=inputQuery(translate('Change value of ')..fname, 'New value:', value)
  if value then
    setFieldValue(Field, Address, value)
    frmDotNetInfo.tFieldValueUpdater.OnTimer(frmDotNetInfo.tFieldValueUpdater)
  end
end

local function lvFields2DblClick(frmDotNetInfo,sender)  
  local Class=frmDotNetInfo.CurrentlyDisplayedClass
  if Class==nil then return end 
 
  local Address=getInstanceAdressFromNode(frmDotNetInfo, sender.FocusedNode)
  if Address==nil then return end  
  

  if sender.FocusedNode==nil then return end
  local Field=getRef(sender.getNodeDataAsInteger(sender.FocusedNode))
  if Field==nil then return end
  
  if (Field.VarType==ELEMENT_TYPE_STRING) or (Field.VarTypeName == "System.String") then
    if Field.Class.Image.Domain.Control~=CONTROL_MONO then
      if (dotnetpipe==nil) or (dotnetpipe.isValid()==false) then
        if messageDialog(translate('To change string types the .NET Control DLL needs to be injected into the target. Inject it?'), mtConfirmation, mbYes,mbNo)~=mrYes then return end
        
        LaunchDotNetInterface()        
        
        if (dotnetpipe==nil) or (dotnetpipe.isValid()==false) then return end --failed to load
      end
    end
  end
  
  local value=getFieldValue(Field, Address)
  
  if settings.ShowOriginalPropertyNames then
    fname=Field.Name
  else   
    local fname=Field.AltName or Field.Name
    if Field.AltName then
      fname=fname..' ('..Field.Name..')'
    end
  end
  
  value=inputQuery(translate('Change value of ')..fname, translate('New value:'), value)
  if value then
    setFieldValue(Field, Address, value)
    frmDotNetInfo.tFieldValueUpdater.OnTimer(frmDotNetInfo.tFieldValueUpdater)
  end
end

local function InjectInvasiveCollector()
  if dotnetpipe then return end
  
  LaunchDotNetInterface() 
  
  if dotnetpipe then
    --clear all method data
    local i,j,k
    for i=1,#DataSource.Domains do
      if DataSource.Domains[i].Images then
        for j=1,#DataSource.Domains[i].Images  do   
          if DataSource.Domains[i].Images[j] and DataSource.Domains[i].Images[j].Classes  then        
            for k=1,#DataSource.Domains[i].Images[j].Classes do
              DataSource.Domains[i].Images[j].Classes[k].Methods=nil
              DataSource.Domains[i].Images[j].Classes[k].Fields=nil
            end      
          end
        end
      end
    end
    
    
    
    for i=1, #frmDotNetInfos do --refresh
      if (frmDotNetInfos[i].lvClasses.ItemIndex~=-1) then ClassSelectionChange(frmDotNetInfos[i], frmDotNetInfo.lvClasses) end
    end
  end
end

local function launchBackgroundParser()
--parse everything
  if (not DataSource.FullyParsed) and (DataSource.BackgroundParser==nil) then
    DataSource.BackgroundParserHeartbeat=0
    DataSource.BackgroundParser=createThread(function(t)
      local r,err=pcall(function()
        for i=1,#DataSource.Domains do
          if DataSource.Domains[i].Images==nil then
            DataSource.getImages(DataSource.Domains[i])
          end        

          if DataSource.Domains[i].Images==nil then return end
          for j=1,#DataSource.Domains[i].Images do
            local HasToFetchClasses=false
            
            synchronize(function() --got to check this quickly in the main thread
              --print("mainthread. Checking if Classes is nil")
              if t.Terminated then return end 
              if DataSource.Domains[i].Images[j].Classes==nil then
                HasToFetchClasses=true
                DataSource.Domains[i].Images[j].Classes={}
                DataSource.Domains[i].Images[j].Classes.Busy=true                      
              end
            end)
            
            if HasToFetchClasses then                    
              --print('Getting classes')                  
              DataSource.getClasses(DataSource.Domains[i].Images[j])
              DataSource.Domains[i].Images[j].Classes.Busy=false                      
            end
            
            for k=1,#DataSource.Domains[i].Images[j].Classes do  
              if t.Terminated then break end
              
              --todo: queue multiple classes
              
              --get all the fields of each class
              if DataSource.Domains[i].Images[j].Classes[k].Fields==nil then                  
                DataSource.getClassFields(DataSource.Domains[i].Images[j].Classes[k])
              end   

              if DataSource.Domains[i].Images[j].Classes[k].Methods==nil then                  
                DataSource.getClassMethods(DataSource.Domains[i].Images[j].Classes[k])
              end  
              if k % 10 == 0 then 
                sleep(10) 
                DataSource.BackgroundParserHeartbeat=DataSource.BackgroundParserHeartbeat+1                
              end
            end 
          end

        end
      end)                    

      if r then
        DataSource.FullyParsed=true
      else
        print('Background parser error:'..err)
      end
      DataSource.BackgroundParser=nil 
      DataSource.BackgroundParserHeartbeat=nil      
    end)
  end
end


function miDotNetInfoClick(sender)

  --print("miDotNetInfoClick")
  --in case of a double case scenario where there's both .net and mono, go for net first if the user did not activate mono explicitly 
  if (DataSource.DotNetDataCollector==nil) or (CurrentProcess~=getOpenedProcessID()) then
    --print("getting getDotNetDataCollector")
    DataSource={}  
    DataSource.DotNetDataCollector=getDotNetDataCollector()    
  end
 
  
  --print("miDotNetInfoClick 2")
  if miMonoTopMenuItem and MainForm.miMonoActivate.Visible and (monopipe==nil)  then
    --print("checking with getDotNetDataCollector().Attached")
    local dndc=getDotNetDataCollector()
    if dndc==nil or dndc.Attached==false then --no .net and the user hasn't activated mono features. Do it for the user
      --print("Launching mono data collector")
      LaunchMonoDataCollector()  
    end
  end
  
  
  --print("loading DotNetInfo.frm")
  local frmDotNetInfo=createFormFromFile(getAutorunPath()..'forms'..pathsep..'DotNetInfo.frm')
  --print("after loading DotNetInfo.frm")
  
  local i
  
  _G.df=frmDotNetInfo
 
  --find an unused entry
  i=1
  while frmDotNetInfos[i] do i=i+1 end   

  frmDotNetInfos[i]=frmDotNetInfo
  frmDotNetInfo.Name="frmDotNetInfo"..i   
  frmDotNetInfo.Tag=i
  frmDotNetInfo.ProcessID=getOpenedProcessID()
  
  
  frmDotNetInfo.OnDestroy=function(f)
    --print("destroy frmDotNetInfo")
    --print("f.name=")
    --printf(f.name)
    
    local dataToSave= {
        f.gbDomains.Width,
        f.gbImages.Width,
        f.gbClasses.Width,
        f.gbStaticFields.Height,
        f.gbFields.Height,
        
        --save the column widths
        f.lvStaticFields.Columns[0].Width,
        f.lvStaticFields.Columns[1].Width,
        f.lvStaticFields.Columns[2].Width,
        f.lvStaticFields.Columns[3].Width,
        
        f.lvFields.Columns[0].Width,
        f.lvFields.Columns[1].Width,
        f.lvFields.Columns[2].Width,
        f.lvFields.Columns[3].Width,
    
        f.lvMethods.Columns[0].Width,
        f.lvMethods.Columns[1].Width}
        
    --_G.dts=dataToSave
        
   -- print("after creating dataToSave")
    f.SaveFormPosition(dataToSave)
    
   -- print("after f.SaveFormPosition")
    CancelClassFetch(frmDotNetInfos[f.Tag])
    
    --print("after CancelClassFetch")
    frmDotNetInfos[f.Tag]=nil
    
    --print("after frmDotNetInfos[f.Tag]=nil  wtf?")
  end
  
  frmDotNetInfo.miImageFind.OnClick=function(f)
    FindInListBox(frmDotNetInfo.lbImages)
  end
  frmDotNetInfo.miImageFind.ShortCut=textToShortCut('Ctrl+F')
  
  frmDotNetInfo.miClassFind.OnClick=function(f)
    FindInListView(frmDotNetInfo.lvClasses)
  end  
  frmDotNetInfo.miClassFind.ShortCut=textToShortCut('Ctrl+F')
  
  
  local formdata={}
  frmDotNetInfo.loadedFormPosition,formdata=frmDotNetInfo.LoadFormPosition()
  if frmDotNetInfo.loadedFormPosition then
    --print("Loaded form position")
    if frmDotNetInfo.width>getScreenWidth() then
      frmDotNetInfo.Width=getScreenWidth() * 0.9
    end
    
    if #formdata>=5 then
      frmDotNetInfo.gbDomains.Width=formdata[1]
      frmDotNetInfo.gbImages.Width=formdata[2]
      frmDotNetInfo.gbClasses.Width=formdata[3]
      frmDotNetInfo.gbStaticFields.Height=formdata[4]
      frmDotNetInfo.gbFields.Height=formdata[5]
      
      if #formdata>=14 then      
        frmDotNetInfo.lvStaticFields.Columns[0].Width=formdata[6] or frmDotNetInfo.lvStaticFields.Columns[0].Width
        frmDotNetInfo.lvStaticFields.Columns[1].Width=formdata[7] or frmDotNetInfo.lvStaticFields.Columns[1].Width 
        frmDotNetInfo.lvStaticFields.Columns[2].Width=formdata[8] or frmDotNetInfo.lvStaticFields.Columns[2].Width     
        frmDotNetInfo.lvStaticFields.Columns[3].Width=formdata[9] or frmDotNetInfo.lvStaticFields.Columns[3].Width
        
        frmDotNetInfo.lvFields.Columns[0].Width=formdata[10] or frmDotNetInfo.lvFields.Columns[0].Width 
        frmDotNetInfo.lvFields.Columns[1].Width=formdata[11] or frmDotNetInfo.lvFields.Columns[1].Width 
        frmDotNetInfo.lvFields.Columns[2].Width=formdata[12] or frmDotNetInfo.lvFields.Columns[2].Width
        frmDotNetInfo.lvFields.Columns[3].Width=formdata[13] or frmDotNetInfo.lvFields.Columns[3].Width
    
        frmDotNetInfo.lvMethods.Columns[0].Width=formdata[14] or frmDotNetInfo.lvMethods.Columns[0].Width 
        frmDotNetInfo.lvMethods.Columns[1].Width=formdata[15] or frmDotNetInfo.lvMethods.Columns[1].Width
      end
      
    end   
  else
    --first run. 
    frmDotNetInfo.gbDomains.Width=DPIMultiplier*100
    frmDotNetInfo.gbImages.Width=DPIMultiplier*300
    frmDotNetInfo.gbClasses.Width=DPIMultiplier*200
    frmDotNetInfo.gbStaticFields.Height=DPIMultiplier*100
    frmDotNetInfo.gbFields.Height=DPIMultiplier*100
    frmDotNetInfo.Width=DPIMultiplier*1300
    frmDotNetInfo.Height=DPIMultiplier*700
    
    frmDotNetInfo.lvMethods.Columns[0].Width=frmDotNetInfo.Canvas.getTextWidth('somerandomnormalmethod')
    frmDotNetInfo.lvMethods.Columns[1].Width=frmDotNetInfo.Canvas.getTextWidth('(int a, float b, Object something)')    
    
    frmDotNetInfo.lvFields.Columns[0].Width=frmDotNetInfo.Canvas.getTextWidth(' FFFFFFFFFFFFFFFF ')
    frmDotNetInfo.lvFields.Columns[1].Width=frmDotNetInfo.lvMethods.Columns[0].Width
    frmDotNetInfo.lvStaticFields.Columns[0].Width=frmDotNetInfo.lvMethods.Columns[0].Width
   
    frmDotNetInfo.Position='poScreenCenter'
  end
  
  frmDotNetInfo.OnClose=function()
    return caFree 
  end
  
  --Domain box setup
  frmDotNetInfo.lbDomains.OnSelectionChange=function(sender) DomainSelectionChange(frmDotNetInfo, sender) end
  
  --Images box setup
  frmDotNetInfo.lbImages.OnSelectionChange=function(sender) ImageSelectionChange(frmDotNetInfo, sender) end
  
  --Classes box setup
  frmDotNetInfo.lvClasses.OnSelectItem=function(sender, listitem, selected) ClassSelectionChange(frmDotNetInfo, sender) end
  frmDotNetInfo.lvClasses.OnData=function(sender, listitem) ClassListDataRequest(frmDotNetInfo, sender, listitem) end
  frmDotNetInfo.lvClasses.PopupMenu=createPopupMenu(frmDotNetInfo)
  local miExportToStructureDissect=createMenuItem(frmDotNetInfo)
  miExportToStructureDissect.Caption=translate('Export to structure dissect')
  miExportToStructureDissect.OnClick=function(sender)
    local lv=frmDotNetInfo.lvClasses
    if lv.ItemIndex~=-1 then
      local s=createStructureFromName(lv.Items[lv.ItemIndex].Caption)
      if s then        
        s.addToGlobalStructureList()
        f=createStructureForm()
        f.MainStruct=s        
      end
    end
  end
  frmDotNetInfo.lvClasses.PopupMenu.Items.add(miExportToStructureDissect)
  
  
  frmDotNetInfo.edtClassFilter.OnChange=function(sender) edtClassFilterChange(frmDotNetInfo, sender) end
  frmDotNetInfo.edtClassFilter.OnKeyDown=function(sender, key) if key==VK_DOWN then frmDotNetInfo.lvClasses.setFocus() else return key end end
      
  --Class info setup
  
  frmDotNetInfo.gbInheritance.OnResize=InheritanceResize
  
  frmDotNetInfo.miFindClass.OnClick=function() spawnDotNetSearchDialog(DataSource, frmDotNetInfo, 0) end
  frmDotNetInfo.miFindField.OnClick=function() spawnDotNetSearchDialog(DataSource, frmDotNetInfo, 1) end
  frmDotNetInfo.miFindMethod.OnClick=function() spawnDotNetSearchDialog(DataSource, frmDotNetInfo, 2) end
  frmDotNetInfo.miEnumAllStatics.OnClick=function()     
    spawnEnumStaticsWindow(DataSource, frmDotNetInfo)     
  end
  
  
  frmDotNetInfo.miInjectInvasiveCollector.OnClick=function() InjectInvasiveCollector(frmDotNetInfo) end
  
  frmDotNetInfo.miJitMethod.Default=true
  frmDotNetInfo.miJitMethod.OnClick=function() OpenAddressOfSelectedMethod(frmDotNetInfo) end
  frmDotNetInfo.miInvokeMethod.OnClick=function() SpawnInjectMethodDialog(frmDotNetInfo) end
  frmDotNetInfo.miGeneratePatchTemplateForMethod.OnClick=function() GeneratePatchTemplateForMethod(frmDotNetInfo) end
  
  
  frmDotNetInfo.miChangeColors.Caption=translate('Colors and settings')
  frmDotNetInfo.miChangeColors.OnClick=function() SpawnConfigDialog() end
  
  frmDotNetInfo.lvMethods.OnDblClick=frmDotNetInfo.miJitMethod.OnClick
  
  frmDotNetInfo.comboFieldBaseAddress.OnChange=function(sender) 
    --print("frmDotNetInfo.comboFieldBaseAddress.OnChange")
    comboFieldBaseAddressChange(frmDotNetInfo, sender) 
  end
    
  frmDotNetInfo.btnLookupInstances.OnClick=function(sender) btnLookupInstancesClick(frmDotNetInfo, sender) end
  
 
  frmDotNetInfo.comboFieldBaseAddress.PopupMenu=createPopupMenu(frmDotNetInfo)
  local mi=createMenuItem(frmDotNetInfo)
  mi.Caption=translate('Create new instance')
  mi.OnClick=function(sender) 
    --createInstance      
    local r,err=dotNetInfo.createInstanceOfClassDialog(frmDotNetInfo.CurrentlyDisplayedClass) 
    if r==nil and err then
      messageDialog(err, mtError)
    else
      local addressString=string.format('%x',r)
      frmDotNetInfo.comboFieldBaseAddress.Items.insert(0,addressString)
      frmDotNetInfo.comboFieldBaseAddress.Items.ItemIndex=0
      
      frmDotNetInfo.comboFieldBaseAddress.Text=addressString
      frmDotNetInfo.comboFieldBaseAddress.OnChange(frmDotNetInfo.comboFieldBaseAddress)
    end    
  end 
  
  frmDotNetInfo.comboFieldBaseAddress.PopupMenu.Items.add(mi)
  

  
  
  --frmDotNetInfo.comboFieldBaseAddress.Items.add('<Create new instance>')

  frmDotNetInfo.tFieldValueUpdater.OnTimer=function(t) FieldValueUpdaterTimer(frmDotNetInfo,t) end
  frmDotNetInfo.lvStaticFields.OnDblClick=function(sender) lvStaticFieldsDblClick(frmDotNetInfo,sender) end


  local miCopyName=createMenuItem(frmDotNetInfo)
  miCopyName.Name='miCopyStaticFieldName'
  miCopyName.Caption=translate('Copy Field Name')
  miCopyName.OnClick=function()
    if frmDotNetInfo.lvStaticFields.Selected then
      local Field=frmDotNetInfo.CurrentlyDisplayedClass.Fields[frmDotNetInfo.lvStaticFields.Selected.Data]
      local fname
      if settings.ShowOriginalPropertyNames then
        fname=Field.Name
      else
        fname=Field.AltName or Field.Name
        if Field.AltName then
          fname=fname..' ('..Field.Name..')'
        end
      end
  
      writeToClipboard(fname)
    end
  end
  frmDotNetInfo.lvStaticFields.PopupMenu.Items.add(miCopyName)
  
  local miCopyTypeName=createMenuItem(frmDotNetInfo)
  miCopyTypeName.Name='miCopyStaticTypeName'  
  miCopyTypeName.Caption=translate('Copy Type Name')
  miCopyTypeName.OnClick=function()
    if frmDotNetInfo.lvStaticFields.Selected then
      local Field=frmDotNetInfo.CurrentlyDisplayedClass.Fields[frmDotNetInfo.lvStaticFields.Selected.Data]
      writeToClipboard(Field.VarTypeName)
    end
  end
  frmDotNetInfo.lvStaticFields.PopupMenu.Items.add(miCopyTypeName)
  
  local miCopyAddress=createMenuItem(frmDotNetInfo)  
  miCopyAddress.Caption=translate('Copy Address')
  miCopyAddress.Name='miCopyStaticAddress'
  miCopyAddress.OnClick=function()
    if frmDotNetInfo.lvStaticFields.Selected then
      local Field=frmDotNetInfo.CurrentlyDisplayedClass.Fields[frmDotNetInfo.lvStaticFields.Selected.Data]
      if Field.Address then
        writeToClipboard(string.format('%x',Field.Address))
      end
    end
  end
  frmDotNetInfo.lvStaticFields.PopupMenu.Items.add(miCopyAddress)  
  
  local miCopyValue=createMenuItem(frmDotNetInfo)  
  miCopyValue.Caption=translate('Copy Value')
  miCopyValue.Name='miCopyStaticValue'
  miCopyValue.OnClick=function()
    if frmDotNetInfo.lvStaticFields.Selected then
      local Field=frmDotNetInfo.CurrentlyDisplayedClass.Fields[frmDotNetInfo.lvStaticFields.Selected.Data]
      writeToClipboard(getStaticFieldValue(Field) or '')
    end
  end
  frmDotNetInfo.lvStaticFields.PopupMenu.Items.add(miCopyValue)    
  
  
  
  
  frmDotNetInfo.lvFields.OnDblClick=function(sender) lvFieldsDblClick(frmDotNetInfo,sender) end
  frmDotNetInfo.miBrowseField.OnClick=function(sender) miBrowseFieldClick(frmDotNetInfo, sender) end
  
  frmDotNetInfo.pmFields.OnPopup=function(sender)
    if frmDotNetInfo.lvFields2 then
      frmDotNetInfo.miBrowseField.Enabled=frmDotNetInfo.lvFields2.FocusedNode and frmDotNetInfo.comboFieldBaseAddress.Text~=''           
    else
      frmDotNetInfo.miBrowseField.Enabled=frmDotNetInfo.lvFields.Selected and frmDotNetInfo.comboFieldBaseAddress.Text~=''    
    end
  end
  
  frmDotNetInfo.pmMethods.OnPopup=function(sender)    
    local enabled=(frmDotNetInfo.comboFieldBaseAddress.Text~='') and (getAddressSafe(frmDotNetInfo.comboFieldBaseAddress.Text)~=nil)
    if not enabled then 
      local Class=frmDotNetInfo.CurrentlyDisplayedClass
      if Class then      
        --maybe it's a static method
        if frmDotNetInfo.lvMethods.ItemIndex~=-1 then
          local Method=Class.Methods[frmDotNetInfo.lvMethods.ItemIndex+1]
          if Method and Method.Static then
            enabled=true
          end
        end
      end
      
    end

    frmDotNetInfo.miInvokeMethod.Enabled=enabled
  end
  
  
  if createVirtualStringTree then  
    frmDotNetInfo.lvFields2=createVirtualStringTree(frmDotNetInfo.lvFields.Parent)
    frmDotNetInfo.lvFields2_valuecache={} --gets reset by the update timer
    frmDotNetInfo.lvFields2_addresscache={}
    
    local fv=frmDotNetInfo.lvFields2 --shortcut for frmDotNetInfo.lvFields2

    
    _G.f=frmDotNetInfo.lvFields2 --debug only
   -- frmDotNetInfo.lvFields.Visible=false
    frmDotNetInfo.lvFields2.PopupMenu=frmDotNetInfo.lvFields.PopupMenu
    local cOffset=frmDotNetInfo.lvFields2.Header.Columns.add('Offset')    
    local cName=frmDotNetInfo.lvFields2.Header.Columns.add('Name')
    local cType=frmDotNetInfo.lvFields2.Header.Columns.add('Type')
    local cValue=frmDotNetInfo.lvFields2.Header.Columns.add('Value')
    frmDotNetInfo.lvFields2.FullRowSelect=true 


    
      

    
    local columnhandler={
      [1]=function(node) --offset/address            
            local Field=getRef(fv.getNodeDataAsInteger(node))
            --printf("offset/address")
            
            if frmDotNetInfo.lvFields2_addresscache.address==nil then
              --printf("frmDotNetInfo.lvFields2_addresscache.address==nil")
              return string.format("%.3x", Field.Offset) 
            else            
              local address
              local err
              address,err=getAddressFromNode(frmDotNetInfo, node)
              if address then
                return string.format("%.8x", address)
              else
                if err then
                  return string.format("Error %s %.3x", err, Field.Offset) 
                else
                  return string.format("Error undefined %.3x", Field.Offset) 
                end
              end
            end
          end,
        
      [2]=function(node) --fieldname
            local Field=getRef(fv.getNodeDataAsInteger(node))
            if settings.ShowOriginalPropertyNames then
              return Field.Name
            else
              return Field.AltName or Field.Name
            end
          end,
          
      [3]=function(node) --typename 
            local Field=getRef(fv.getNodeDataAsInteger(node))
            local typename=Field.VarTypeName
            if typename and typename~='' then
              return typename
            else
              return Field.VarType
            end                
          end,
          
      [4]=function(node) --value
            local address,err=getInstanceAdressFromNode(frmDotNetInfo, node)
            local Field=getRef(fv.getNodeDataAsInteger(node))
           
            if address==nil then
              if err then
                return err 
              else
                return 'undefined error'
              end
            end
            
            if frmDotNetInfo.lvFields2_valuecache[node]==nil then               
              frmDotNetInfo.lvFields2_valuecache[node]=getFieldValue(Field, address)   
            end            
              
            return frmDotNetInfo.lvFields2_valuecache[node]
          end
    }
      
    
    frmDotNetInfo.lvFields2.OnGetText=function(sender, nodeindex, columnindex, node, texttype) 
      return columnhandler[columnindex+1](node)    
    end

   
    frmDotNetInfo.lvMethods.OnCustomDrawItem=function(Sender, ListItem, State)
      local Class=frmDotNetInfo.CurrentlyDisplayedClass
      if Class==nil then return true end
      
      local Method=Class.Methods[ListItem.Index+1]
      local AccessMask=Method.Attribs & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK

      Sender.Canvas.Font.Color=AccessMaskToColor(AccessMask)
     
      --if Method.Attribs & METHOD_ATTRIBUTE_STATIC == METHOD_ATTRIBUTE_STATIC then
      if Method.Static then
        Sender.Canvas.Brush.Color=settings.ColorStaticBackground
      end
      return true
    end
   
  
    frmDotNetInfo.lvFields2.OnPaintText=function(sender, canvas, node, column, texttype)
      local Field=getRef(fv.getNodeDataAsInteger(node))
      
      local AccessMask=Field.Attribs & FIELD_ATTRIBUTE_FIELD_ACCESS_MASK
      local c=AccessMaskToColor(AccessMask)
      
      if sender.Selected[node] then
        c=invertColor(c)        
      end
      canvas.Font.Color=c
      
      
    end    
    
    frmDotNetInfo.lvFields2.OnExpanding=function(sender, node)
      --find the class this field describes, and get the dynamic fields 
     -- print("Expanding node")  --debug
      
      local Field=getRef(fv.getNodeDataAsInteger(node))
      local Class=nil
      --if not found, check if it's not a namespace notation
      
      if Field ==nil then return end
      
      if (not ((Field.VarType==ELEMENT_TYPE_CLASS) or (Field.VarType==ELEMENT_TYPE_GENERICINST))) then
        return nil
      end
      
      if fv.getFirstChild(node)~=nil then 
        --print("this node already has children")
        return true 
      end
      
      if frmDotNetInfo.ClassSearcher then
        --print("There was already a class searcher going on. Deleting it")
        frmDotNetInfo.ClassSearcher.terminate()
        if frmDotNetInfo.ClassSearcher.waitfor(2000)==false then
          frmDotNetInfo.ClassSearcher.freeOnTerminate(true) 
        else
          frmDotNetInfo.ClassSearcher.destroy()
        end
        frmDotNetInfo.ClassSearcher=nil
      end
      
      local typename=Field.VarTypeName
      
      if Field.VarType==ELEMENT_TYPE_GENERICINST then
        if Field.Class.Image.Domain.Control==CONTROL_MONO then

          if Field.GenericClassName==nil then             
            local fieldclass=mono_field_getClass(Field.Handle)
            if fieldclass then
              --Field.GenericClassName=mono_class_getFullName(fieldclass) 
              Field.GenericClassName=mono_class_getNamespace(fieldclass)..'.'..mono_class_getName(fieldclass)            
            end
          end

          if Field.GenericClassName then
            typename=Field.GenericClassName
          end
        end
      end
      
      
      frmDotNetInfo.ClassSearcher,Class=SearchClassName(typename, 
      function(c) --onFound
        --print("a result has been found. Reopening this node in 1 ms")
        createTimer(1,function() --(try again) 
          --print("opening this node")        
          fv.Expanded[node]=true 
        end)
      end,
        
      function() --onDone
        --print("done scanning")
        fv.Cursor=crDefault
      end        
      )  
     
      if frmDotNetInfo.ClassSearcher then --not found instantly, doing a scan
        --print("Unknown class. Searching...")
        frmDotNetInfo.lvFields2.Cursor=crHourGlass      
        return false
      else
        --fill in using the returned class
        --print("Found the class. Filling in the results")
        if Class.Fields==nil then
          --print("Getting fields");
          DataSource.getClassFields(Class)
        end
        
        if Class.Fields then 
          for i=1,#Class.Fields do
            if not Class.Fields[i].Static then
              local newnode=fv.addChild(node)  
              fv.setNodeDataAsInteger(newnode, createRef(Class.Fields[i]))                      
            
              if Class.Fields[i].VarType==ELEMENT_TYPE_CLASS or 
                 Class.Fields[i].VarType==ELEMENT_TYPE_GENERICINST then
                fv.HasChildren[newnode]=true
              end              
            end           
          end
          return true
        else
          --print("no fields")
          return false
        end
      end
    end      
    
    frmDotNetInfo.lvFields2.OnFreeNode=function(sender,node)
     -- print("bye node")
      if frmDotNetInfo.ClassSearcher then --stop searcher
        frmDotNetInfo.ClassSearcher.terminate()
        frmDotNetInfo.ClassSearcher.destroy()
        frmDotNetInfo.ClassSearcher=nil
      end
      
      local d=sender.getNodeDataAsInteger(node)
      destroyRef(d)
    end
    
    frmDotNetInfo.lvFields2.OnDblClick=function(sender) lvFields2DblClick(frmDotNetInfo, sender) end
   

    frmDotNetInfo.lvFields2.align=alClient
    frmDotNetInfo.lvFields.align=alBottom  
    frmDotNetInfo.lvFields.visible=false
    cValue.Visible=false
    frmDotNetInfo.lvFields2.Header.AutoResize=true
    frmDotNetInfo.lvFields2.Header.AutoSizeIndex=2
    
    cOffset.Width=100*DPIMultiplier
    cName.Width=200*DPIMultiplier
    cValue.Width=180*DPIMultiplier
    
  end
   
  

  --Init
  
  
  if DataSource.Domains==nil then
    getDomains()
  end
  
  for i=1,#DataSource.Domains do
    local s=string.format("%x",DataSource.Domains[i].Handle)
    if DataSource.Domains[i].Name then
      s=s..' ( '..DataSource.Domains[i].Name..' ) '
    end
    
    frmDotNetInfo.lbDomains.Items.add(s)
  end
  
  if #DataSource.Domains==1 then
    frmDotNetInfo.lbDomains.ItemIndex=0
    frmDotNetInfo.gbDomains.Visible=false
    frmDotNetInfo.split1.Visible=false
  end
  
  
  DataSource.getImages=getImages
  DataSource.getDomains=getDomains
  DataSource.getClasses=getClasses
  DataSource.getClassFields=getClassFields
  DataSource.getClassMethods=getClassMethods
  
  
  if #DataSource.Domains==0 then
    DataSource={}
    messageDialog(translate('No .NET info found'),mtError,mbOK)
    frmDotNetInfo.destroy()
  else    
    frmDotNetInfo.miDotNetInterfaceMenu.visible=(DataSource.Domains[1].Control==CONTROL_DOTNET)     
    frmDotNetInfo.show()  
    
    if settings.StartParsingOnFirstShow then
      launchBackgroundParser()
      
    end
  end
  
  _G.xxx=frmDotNetInfo
end


