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

--todo: split up into multiple units and use the java table for the methods as well
--especially strip the hotspot stuff to it's own file



JAVACMD_STARTCODECALLBACKS=0
JAVACMD_STOPCODECALLBACKS=1
JAVACMD_GETLOADEDCLASSES=2
JAVACMD_DEREFERENCELOCALOBJECT=3
JAVACMD_GETCLASSMETHODS=4
JAVACMD_GETCLASSFIELDS=5
JAVACMD_GETIMPLEMENTEDINTERFACES=6
JAVACMD_FINDREFERENCESTOOBJECT=7
JAVACMD_FINDJOBJECT=8
JAVACMD_GETCLASSSIGNATURE=9  --=getClassName
JAVACMD_GETSUPERCLASS=10
JAVACMD_GETOBJECTCLASS=11
JAVACMD_GETCLASSDATA=12
JAVACMD_REDEFINECLASS=13
JAVACMD_FINDCLASS=14
JAVACMD_GETCAPABILITIES=15
JAVACMD_GETMETHODNAME=16 --gets the methodname and the signature
JAVACMD_INVOKEMETHOD=17
JAVACMD_FINDCLASSINSTANCES=18 --find objects that belong to the given class
JAVACMD_ADDTOBOOTSTRAPCLASSLOADERPATH=19
JAVACMD_ADDTOSYSTEMCLASSLOADERPATH=20
JAVACMD_PUSHLOCALFRAME=21
JAVACMD_POPLOCALFRAME=22
JAVACMD_GETFIELDDECLARINGCLASS=23
JAVACMD_GETFIELDSIGNATURE=24
JAVACMD_GETFIELD=25
JAVACMD_SETFIELD=26

JAVACMD_STARTSCAN=27
JAVACMD_REFINESCANRESULTS=28
JAVACMD_GETSCANRESULTS=29
JAVACMD_FINDWHATWRITES=30
JAVACMD_STOPFINDWHATWRITES=31
JAVACMD_GETMETHODDECLARINGCLASS=32
JAVACMD_RELEASECLASSLIST=33
JAVACMD_COMPILEMETHOD=34

JAVACMD_DEREFERENCEGLOBALOBJECTS=35
JAVACMD_GETFIELDVALUES=36
JAVACMD_COLLECTGARBAGE=37

JAVACMD_ANDROID_DECODEOBJECT=38
JAVACMD_FINDFIELDS=39
JAVACMD_FINDMETHODS=40
JAVAVMD_GETOBJECTCLASSNAME=41 --gets the name of the class instead of just a local ref
JAVACMD_SETFIELDVALUES=42
JAVACMD_GETOBJECTCLASSNAMES=43


JAVACMD_GETFIELDSIGNATUREBYOBJECT=44

JAVACMD_TERMINATESERVER=255



JAVACODECMD_METHODLOAD=0
JAVACODECMD_METHODUNLOAD=1
JAVACODECMD_DYNAMICCODEGENERATED=2
JAVACODECMD_TERMINATED=255



JAVA_TIMEOUT=5000 --5 seconds

ACC_STATIC=8


local trueboolstr={}
trueboolstr[tostring(true)]=true
trueboolstr['1']=true

java_field_writers={} --function(stream,stringvalue) end : returns true if parsed properly
java_field_writers[0]=function(s) return true end
java_field_writers[1]=function(s,v) s.writeByte(trueboolstr[v] and 1 or 0) return true end --boolean
java_field_writers[2]=function(s,v)  --byte
  local vn=tonumber(v) 
  if vn then 
    s.writeByte(vn) 
    return true 
  end 
end --byte
java_field_writers[3]=function(s,v)  --2 byte (hex formatted)
  local vn
  if v:startsWith('0x') then
    vn=tonumber(v)
  else
    vn=tonumber(v,16)
  end
  if vn then s.writeWord(vn) 
    return true
  end 
end --boolean
java_field_writers[4]=function(s,v)  --word
  local vn=tonumber(v) 
  if vn then 
    s.writeWord(vn) 
    return true 
  end 
end 
java_field_writers[5]=function(s,v)  --dword
  local vn=tonumber(v) 
  if vn then 
    s.writeDword(vn) 
    return true 
  end 
end 
java_field_writers[6]=function(s,v)  --qword
  local vn=tonumber(v) 
  if vn then 
    s.writeQword(vn) 
    return true 
  end 
end 
java_field_writers[7]=function(s,v)  --float
  local vn=tonumber(v) 
  if vn then 
    local bt=floatToByteTable(v)
    s.write(bt)
    return true 
  end 
end 
java_field_writers[8]=function(s,v)  --double
  local vn=tonumber(v) 
  if vn then 
    local bt=doubleToByteTable(v)
    s.write(bt)
    return true
  end 
end 
java_field_writers[9]=java_field_writers[6] --object (jObject)
java_field_writers[10]=java_field_writers[6]  --array (jObject)
java_field_writers[11]=function(s,v) --string  (utf8 formatted)  
  s.writeWord(#v)
  s.writeString(v)
  return true
end



java_field_readers={}
java_field_readers[0]=function(ms) return {Value='<void>'} end --void (wtf)
java_field_readers[1]=function(ms) return {Value=tostring(ms.readByte()~=0)} end --boolean
java_field_readers[2]=function(ms) return {Value=string.format("%d",ms.readByte())} end --byte
java_field_readers[3]=function(ms) return {Value=string.format("char: 0x%x",ms.readWord())} end --utf-16 char
java_field_readers[4]=function(ms) return {Value=string.format("%d", ms.readWord())} end --short
java_field_readers[5]=function(ms) return {Value=string.format("%d", ms.readDword())} end --int
java_field_readers[6]=function(ms) return {Value=string.format("%d", ms.readQword())} end --long    
java_field_readers[7]=function(ms) 
  local r=ms.read(4);
  local f=byteTableToFloat(r);
  
  return {Value=string.format("%.2f", f)} 
end --float    
java_field_readers[8]=function(ms)
  local r=ms.read(8);
  local f=byteTableToDouble(r);
  
  return {Value=string.format("%.2f", f)}
end --double     
java_field_readers[9]=function(ms)  
  local o=ms.readQword()
  if o==0 then
    return {Value='nil'}
  else
    return {Value='<object>', Object=o}
  end   
end --object  

java_field_readers[10]=function(ms)
  local o=ms.readQword()
  if o==0 then --just returns true if it's not nil
    return {Value='nil'}
  else
    return {Value='<array>', Object=o}
  end   
end --array

java_field_readers[11]=function(ms)
  local isnil=ms.readByte()==0
  
  if isnil then
    return {Value='nil'}
  else
    local sl=ms.readWord()
    return {Value='"'..ms.readString(sl)..'"'}
  end
end--string



function getFieldFromType(type, field, infloopprotection)
  if type==nil then return nil end

  if infloopprotection==nil then
  infloopprotection=0
  else
    infloopprotection=infloopprotection+1
    if infloopprotection>20 then
      return nil
    end
  end

  type=type:gsub("<.->(.-)","<%1>") --replace the <xxx> part with <>
  local Struct=JavaStructs[type]

  if (Struct==nil) or (Struct[field]==nil) then
    return getFieldFromType(JavaTypes[type].Alternate, field, infloopprotection) --check the super type if that one has fields
  else
    return Struct[field]
  end

end

function getKlassFromObject(object)
  return readQword(object+getFieldFromType("oopDesc","_metadata._klass").Offset)+JavaTypes["oopDesc"].Size
end

function CollectJavaSymbolsNonInjected(thread)
  if thread~=nil then
    thread.name="CollectJavaSymbolsNonInjected"
  end

  JavaStructs={}
  JavaTypes={}

  local s,s2


  s=readPointer("jvm.gHotSpotVMStructs")
  if (s==nil) or (s==0) then
    return --invalid JVM
  end

  VMStructEntryTypeNameOffset=readInteger("jvm.gHotSpotVMStructEntryTypeNameOffset")
  VMStructEntryFieldNameOffset=readInteger("jvm.gHotSpotVMStructEntryFieldNameOffset")
  VMStructEntryTypestringOffset=readInteger("jvm.gHotSpotVMStructEntryTypestringOffset")
  VMStructEntryIsStaticOffset=readInteger("jvm.gHotSpotVMStructEntryIsStaticOffset")
  VMStructEntryOffsetOffset=readInteger("jvm.gHotSpotVMStructEntryOffsetOffset")
  VMStructEntryAddressOffset=readInteger("jvm.gHotSpotVMStructEntryAddressOffset")
  VMStructEntryArrayStride=readInteger("jvm.gHotSpotVMStructEntryArrayStride")







--[[
  const char* typeName;            // The type name containing the given field (example: "Klass")
  const char* fieldName;           // The field name within the type           (example: "_name")
  const char* Typestring;          // Quoted name of the type of this field (example: "Symbol*";
                                   // parsed in Java to ensure type correctness
  int32_t  isStatic;               // Indicates whether following field is an offset or an address
  uint64_t offset;                 // Offset of field within structure; only used for nonstatic fields
  void* address;                   // Address of field; only used for static fields
                                   // ("offset" can not be reused because of apparent SparcWorks compiler bug
                                   // in generation of initializer data)
--]]




  while readString(readQword(s+VMStructEntryTypeNameOffset))~=nil do
    local a,b,c,d;
    a=readString(readPointer(s+VMStructEntryTypeNameOffset),255)
    b=readString(readPointer(s+VMStructEntryFieldNameOffset),255)
    c=readString(readPointer(s+VMStructEntryTypestringOffset),255)

    d=readPointer(s+VMStructEntryIsStaticOffset)

    if a and b and c then
      if JavaStructs[a]==nil then
        JavaStructs[a]={}
      end

      JavaStructs[a][b]={}
      JavaStructs[a][b].Typestring=c

      if d==0 then
        JavaStructs[a][b].Offset=readPointer(s+VMStructEntryOffsetOffset)
      else
        JavaStructs[a][b].Address=readPointer(s+VMStructEntryAddressOffset)
      end



      --if d~=0 then
      --  print(a.."  -  "..b.."  -  "..c.."  :  "..string.format("%x  ( %x )",readPointer(s+VMStructEntryAddressOffset), readPointer(readPointer(s+VMStructEntryAddressOffset)) ))
      --else
      --  print(a.."  -  "..b.."  -  "..c.."  :  "..string.format("%x",readPointer(s+VMStructEntryOffsetOffset)))
      --end
    end
    s=s+VMStructEntryArrayStride
  end



  --print("--------------------------------------------------------------------------------")


  s2=readPointer("jvm.gHotSpotVMTypes")
  VMTypeEntryTypeNameOffset=readInteger("jvm.gHotSpotVMTypeEntryTypeNameOffset")
  VMTypeEntrySuperclassNameOffset=readInteger("jvm.gHotSpotVMTypeEntrySuperclassNameOffset")
  VMTypeEntryIsOopTypeOffset=readInteger("jvm.gHotSpotVMTypeEntryIsOopTypeOffset")
  VMTypeEntryIsIntegerTypeOffset=readInteger("jvm.gHotSpotVMTypeEntryIsIntegerTypeOffset")
  VMTypeEntryIsUnsignedOffset=readInteger("jvm.gHotSpotVMTypeEntryIsUnsignedOffset")
  VMTypeEntrySizeOffset=readInteger("jvm.gHotSpotVMTypeEntrySizeOffset")
  VMTypeEntryArrayStride=readInteger("jvm.gHotSpotVMTypeEntryArrayStride")





  while readString(readPointer(s2+VMTypeEntryTypeNameOffset))~=nil do
    local a,b,isInteger, isOop, size;
    a=readString(readPointer(s2+VMTypeEntryTypeNameOffset),255)
    b=readString(readPointer(s2+VMTypeEntrySuperclassNameOffset),255)


    isOop=readInteger(s2+VMTypeEntryIsOopTypeOffset)
    isInteger=readInteger(s2+VMTypeEntryIsIntegerTypeOffset)
    size=readInteger(s2+VMTypeEntrySizeOffset)


    if a then
      local _a,_b
      _a=a:gsub("<.->(.-)","<%1>")
      JavaTypes[_a]={}
      JavaTypes[_a].Size=size

      if b then
        _b=b:gsub("<.->(.-)","<%1>")
        JavaTypes[_a].Alternate=_b
      end

    end



    local r=''
    if a then
      r=r..a
    end

    if b then
      r=r.."  -  "..b
    end


    --print(r.."  (size="..size..")")
    s2=s2+VMTypeEntryArrayStride
  end


 -- print("-------------------------------------------------")
--[[
  s=readPointer("jvm.gHotSpotVMIntConstants")
  VMIntConstantEntryNameOffset=readInteger("jvm.gHotSpotVMIntConstantEntryNameOffset")
  VMIntConstantEntryValueOffset=readInteger("jvm.gHotSpotVMIntConstantEntryValueOffset")
  VMIntConstantEntryArrayStride=readInteger("jvm.gHotSpotVMIntConstantEntryArrayStride")


  while readString(readPointer(s+VMIntConstantEntryNameOffset))~=nil do
    local name,value
    name=readString(readPointer(s+VMIntConstantEntryNameOffset))
    value=readInteger(s+VMIntConstantEntryValueOffset)

    --print(name.."="..string.format("%x",value))

    s=s+VMIntConstantEntryArrayStride
  end

  --print("-------------------------------------------------")
  s=readPointer("jvm.gHotSpotVMLongConstants")
  VMLongConstantEntryNameOffset=readInteger("jvm.gHotSpotVMLongConstantEntryNameOffset")
  VMLongConstantEntryValueOffset=readInteger("jvm.gHotSpotVMLongConstantEntryValueOffset")
  VMLongConstantEntryArrayStride=readInteger("jvm.gHotSpotVMLongConstantEntryArrayStride")

  while readString(readPointer(s+VMLongConstantEntryNameOffset))~=nil do
    local name,value
    name=readString(readPointer(s+VMLongConstantEntryNameOffset))
    value=readQword(s+VMLongConstantEntryValueOffset)

    --print(name.."="..string.format("%x",value))

    s=s+VMLongConstantEntryArrayStride
  end


  --Fetch the interpreter functions
  local InterpreterFunctionList=getFieldFromType('AbstractInterpreter', '_code').Address
  local BufferOffset=getFieldFromType('StubQueue', '_stub_buffer').Offset
  local QueueEndOffset=getFieldFromType('StubQueue', '_queue_end').Offset

  local InterpreterCodeletSizeOffset=getFieldFromType('InterpreterCodelet', '_size').Offset
  local InterpreterCodeletDescriptionOffset=getFieldFromType('InterpreterCodelet', '_description').Offset
  local InterpreterCodeletHeaderSize=JavaTypes['InterpreterCodelet'].Size
  local InterpreterCodeletHeaderSizeAligned

  InterpreterCodeletHeaderSizeAligned=InterpreterCodeletHeaderSize

  if targetIs64Bit() then
    --increase InterpreterCodeletHeaderSizeAligned so it's dividable by 32
    if (InterpreterCodeletHeaderSizeAligned % 32)~=0 then
      InterpreterCodeletHeaderSizeAligned=(InterpreterCodeletHeaderSizeAligned+32) - (InterpreterCodeletHeaderSizeAligned % 32)
    end
  else
    --increase InterpreterCodeletHeaderSizeAligned so it's dividable by 16
    if (InterpreterCodeletHeaderSizeAligned % 16)~=0 then
      InterpreterCodeletHeaderSizeAligned=(InterpreterCodeletHeaderSizeAligned+16) - (InterpreterCodeletHeaderSizeAligned % 16)
    end
  end

  StubQueueAddress=readPointer(InterpreterFunctionList)
  BufferStart=readPointer(StubQueueAddress+BufferOffset)
  BufferEnd=BufferStart+readInteger(StubQueueAddress+QueueEndOffset)

  CurrentPos=BufferStart
  while (CurrentPos<BufferEnd) do
    local CodeletSize=readInteger(CurrentPos+InterpreterCodeletSizeOffset)
    local Description=readString(readInteger(CurrentPos+InterpreterCodeletDescriptionOffset))
    local Codestart=CurrentPos+InterpreterCodeletHeaderSizeAligned

    --print(string.format("%x = %s", Codestart, Description))

    JavaSymbols.addSymbol("","jInterpreter_"..Description,Codestart, CodeletSize-InterpreterCodeletHeaderSizeAligned)



    CurrentPos=CurrentPos+CodeletSize
  end

--]]
  JavaHotSpotFieldsLoaded=true
end

function javaOpenAgent()
  if javapipe and (java.attachedProcess==getOpenedProcessID()) then
    return true
  end
  
  if (JavaSymbols==nil) then
    JavaSymbols=createSymbolList()
  else
    JavaSymbols.clear()
  end    
  
  javapipe=connectToPipe('cejavadc_pid'.. getOpenedProcessID(),10000)
  
  if javapipe then
    java.capabilities=java_getCapabilities()
    java.attachedProcess=getOpenedProcessID()
  
    if javaInjectedProcesses==nil then
      javaInjectedProcesses={}
    end
    
    javaInjectedProcesses[getOpenedProcessID()]=true
    
    javapipe.OnTimeout=function()
      print("javapipe timeout")
    end
    
    javapipe.OnError=function()
      print("javapipe error")
    end
    
    
    
    return true
  end    
end

function javaInjectAgent()
  if (java~=nil) and (java.attachedProcess==getOpenedProcessID()) and (javapipe) then
    return true
  end
  
  if (JavaSymbols==nil) then
    JavaSymbols=createSymbolList()
  else
    JavaSymbols.clear()
  end  

  if (javapipe ~= nil) then
    javapipe.destroy()  --this will cause the pipe listener to destroy the java event server, which will stop the javaeventthread (so no need to wait for that)
    javapipe=nil
  end


  if true then --java.android then
    local errorstr
    if not inMainThread() then error('Only call javaInjectAgent from the main thread') end
    
    local waitform=createForm(false)
    waitform.Position=poScreenCenter
    waitform.OnCloseQuery=function() 
      getLuaEngine().show()
      _G.waitform=waitform
      print("It will take a while. But if you're sure, issue the command:")
      print("waitform.OnCloseQuery=nil")
      print("waitform.close()")
      print("-----")
      local status=readInteger("ceagentloadstatus")
      if status then
        printf("readInteger(\"ceagentloadstatus\")=%d (<0 = error, 0=loading, 1=active, 2=finished)",status)
      end
      return false
    end
    waitform.caption='Please wait'
    local lbl=createLabel(waitform)
    lbl.caption='Please wait while the process is being injected into'
    
    waitform.BorderWidth=8
    waitform.autosize=true
    
    waitform.OnShow=function()
      createThread(function(t)
        function injectAndroidAgent()
          local status=readInteger("ceagentloadstatus")
          local extra=createStringList()
          if targetIsAndroid() then
            extra.add('#define ANDROID')          
          end 
         
          
          if (status==nil) or (status<0) or (status==2) then
            if not fileExists(getAutorunPath()..'java/jvmti.h') and (findTableFile('include/jvmti.h')==nil) then
              local l=getInternet()
              local r=l.getURL('https://github.com/openjdk-mirror/jdk7u-jdk/raw/master/src/share/javavm/export/jvmti.h')

              local ss=createStringStream(r)
              local tf=createTableFile('include/jvmti.h')
              tf.DoNotSave=true
              tf.Stream.copyFrom(ss,0)
              ss.destroy()
              ss=nil
            end
          
            local spath=getAutorunPath()..'java/androidloadagent.CEA'
            local sl=createStringList()
            
            
            if sl.loadFromFile(spath)==false then
              errorstr='Failure reading '..spath
              return
            end
                        
            local injectagentscript=extra.text..'\n'..sl.Text
            sl.destroy()
          
            local r,di=autoAssemble(injectagentscript);
            if not r then
              errorstr='Failure assembling inject script: '..di 
              return            
            end      
          end
          
          local start=getTickCount()
          
          while (readInteger("ceagentloadstatus")==0) or (getTickCount()-start>5000) do
            sleep(50);      
          end
          
          --printf("took: %d ms", getTickCount()-start)
          
          if readInteger("ceagentloadstatus")<1 then --1 when active, 2 when closed (e.g another server running)
            errorstr='Failure executing inject script: '..readInteger("ceagentloadstatus")            
            return
          end 
        end

        injectAndroidAgent()

        t.synchronize(function()
          if waitform then
            waitform.OnCloseQuery=nil
            waitform.close()
          end
        end)
      end)        
    end
    waitform.showModal()
    
    if errorstr then     
      return nil, errorstr
    end

       
    return javaOpenAgent()
  end
  
  printf("old code reached")
  if true then return end
  

  createNativeThread(CollectJavaSymbolsNonInjected)


  local alreadyinjected=false

  
  if javaInjectedProcesses==nil then
    javaInjectedProcesses={}

    if not java.android then
      local address=getAddressSafe('CEJVMTI.dll')
      if (address~=nil) and (address~=0) then
        javaInjectedProcesses[getOpenedProcessID()]=true
        alreadyinjected=true
        --opened a process with the JVMTI agent already running
      end
    end;

  else
    --check if already injected
    alreadyinjected=javaInjectedProcesses[getOpenedProcessID()]==true
  end
  



  local dllpath

  if targetIs64Bit() then
    dllpath=getCheatEngineDir()..[[autorun\dlls\64\CEJVMTI]]
  else
    dllpath=getCheatEngineDir()..[[autorun\dlls\32\CEJVMTI]]
  end



  if (alreadyinjected==false) then
    local script=''

    if targetIs64Bit() then  
      script=[[
        globalalloc(bla,1024)

        globalalloc(cmd,16)
        globalalloc(arg0,256)
        globalalloc(arg1,256)
        globalalloc(arg2,256)
        globalalloc(result,4)

        globalalloc(pipename,256)

        cmd:
        db 'load',0

        arg0:

        db ']]..dllpath..[[',0

        arg1:
        db 0

        arg2:
        db 0

        pipename:
        db '\\.\pipe\cejavapipe',0


        bla:
        sub rsp,8
        sub rsp,30

        mov rcx,cmd
        mov rdx,arg0
        mov r8,arg1
        mov r9,arg2
        mov rax,pipename
                       
        mov [rsp],rcx
        mov [rsp+8],rdx
        mov [rsp+10],r8
        mov [rsp+18],r9
        mov [rsp+20],rax
        
        call jvm.JVM_EnqueueOperation
        mov [result],eax

        add rsp,38
        ret

        createthread(bla)
      ]]
    else
      script=[[
        globalalloc(bla,1024)

        globalalloc(cmd,16)
        globalalloc(arg0,256)
        globalalloc(arg1,256)
        globalalloc(arg2,256)
        globalalloc(result,4)

        globalalloc(pipename,256)

        cmd:
        db 'load',0

        arg0:

        db ']]..dllpath..[[',0

        arg1:
        db 0

        arg2:
        db 0

        pipename:
        db '\\.\pipe\cejavapipe',0


        bla:
        push pipename
        push arg2
        push arg1
        push arg0
        push cmd


        call jvm.JVM_EnqueueOperation
        mov [result],eax

        ret

        createthread(bla)
      ]]
    end
  if autoAssemble(script)==false then
    error(translate('Auto assembler failed:')..script)
  end


  javaInjectedProcesses[getOpenedProcessID()]=true
  end




  --wait till attached

  local timeout=getTickCount()+JAVA_TIMEOUT
  while (javapipe==nil) and (getTickCount()<timeout) do
    javapipe=connectToPipe('cejavadc_pid'..getOpenedProcessID())
  end

  if (javapipe==nil) then
    return 0 --failure
  end

  java_StartListeneningForEvents()

  JavaSymbols.register() --make these symbols available to all of cheat engine


  java.capabilities=java_getCapabilities()
  java.attachedProcess=getOpenedProcessID();

  return true;
end

function JavaEventListener(thread)
  if thread~=nil then
    thread.name="JavaEventListener"
  end

  --this code runs in another thread
  local EVENTCMD_METHODLOAD=0
  local EVENTCMD_METHODUNLOAD=1
  local EVENTCMD_DYNAMICCODEGENERATED=2
  local EVENTCMD_FIELDMODIFICATION=3

  local JavaEventPipe


  local timeout=getTickCount()+JAVA_TIMEOUT --5 seconds
  while (JavaEventPipe==nil) and (getTickCount()<timeout) do
    JavaEventPipe=connectToPipe('cejavaevents_pid'..getOpenedProcessID())
  end

  if (JavaEventPipe==nil) then
    return  --failure
  end


  while true do
    local command=JavaEventPipe.readByte()
  if command==EVENTCMD_METHODLOAD then  --methodload

    local size1, size2, size3,ssize,classname, methodname, methodsig

    local method=JavaEventPipe.readQword()
    local code_size=JavaEventPipe.readDword()
    local code_addr=JavaEventPipe.readQword()
    size1=JavaEventPipe.readWord()
    if (size1>0) then
      classname=JavaEventPipe.readString(size1)
    else
      classname=''
    end

    size2=JavaEventPipe.readWord()
    if (size2>0) then
      methodname=JavaEventPipe.readString(size2)
    else
      methodname=''
    end

    size3=JavaEventPipe.readWord()
    if (size3>0) then
        methodsig=JavaEventPipe.readString(size3)
    else
      methodsig=''
    end

    local endpos=classname:match'^.*();'
    if endpos~=nil then
        classname=string.sub(classname,1,endpos-1)
    end
    local name=classname.."::"..methodname..methodsig


    JavaSymbols.addSymbol("",classname.."::"..methodname,code_addr,code_size)

    --print(string.format("s1=%d s2=%d s3=%d  (cn=%s  mn=%s  ms=%s)", size1,size2,size3, classname, methodname, methodsig))

    --print(string.format("Methodload: %s -  (%x) %x-%x", name, method, code_addr, code_addr+code_size))


    --
  elseif command==EVENTCMD_METHODUNLOAD then --methodunload
    local method=JavaEventPipe.readQword()
    local code_addr=JavaEventPipe.readQword()

    print("EVENTCMD_METHODUNLOAD")
    JavaSymbols.deleteSymbol(code_addr)
    --
  elseif command==EVENTCMD_DYNAMICCODEGENERATED then --DynamicCodeGenerated
    local ssize
    local address=JavaEventPipe.readQword()
    local length=JavaEventPipe.readDword()
    ssize=JavaEventPipe.readWord()
    local name=JavaEventPipe.readString(ssize)

    --print(string.format("DynamicCode: %s  -  %x-%x", name, address, address+length))
    JavaSymbols.addSymbol("",name,address,length)


    --
  elseif command==EVENTCMD_FIELDMODIFICATION then

    --print("EVENTCMD_FIELDMODIFICATION")
    local id=JavaEventPipe.readDword()
    local entry={}

    --print("id=="..id)

    entry.methodid=JavaEventPipe.readQword()
    entry.location=JavaEventPipe.readQword()

    local stackcount=JavaEventPipe.readByte()
    local i
    local stack={}

    --print("stackcount=="..stackcount)

    for i=1, stackcount do
      stack[i]={}
      stack[i].methodid=JavaEventPipe.readQword()
      stack[i].location=JavaEventPipe.readQword()
    end

    entry.stack=stack



    if java.findwhatwriteslist~=nil then
    local fcd=java.findwhatwriteslist[id]

    if fcd~=nil then
      --check if this entry is already in the list
      local found=false

      for i=1, #fcd.entries  do
        if (fcd.entries[i].methodid==entry.methodid) and (fcd.entries[i].location==entry.location) then
          found=true
          break
        end
      end

      if not found then
        local mname=java_getMethodName(entry.methodid)

        local class=java_getMethodDeclaringClass(entry.methodid)
        local classname=java_getClassSignature(class)

        java_dereferenceLocalObject(class)

        entry.classname=classname
        entry.methodname=mname

        --execute this in the main thread (gui access)
        synchronize(function(classname, id, mname)
        local fcd=java.findwhatwriteslist[id]


        if fcd~=nil then --check that the found code dialog hasn't been freed while waiting for sync
          tventry=fcd.lv.items.add()
          tventry.Caption=classname

          tventry.SubItems.add(string.format('%x: %s', entry.methodid, mname))
          tventry.SubItems.add(entry.location)

          table.insert(fcd.entries, entry)
        end
      end, classname, id, mname)


      else
       -- print("Already in the list")
      end
    else
     -- print("fcd==nil")

    end
    else
     -- print("java.findwhatwriteslist==nil")
    end

    --print("done")
  elseif command==JAVACODECMD_TERMINATED then
    print(translate("Java:eventserver terminated"))
    break
  elseif command==nil then
    print(translate("Java:Disconnected"))
    break
    else
    print(translate("Java:Unexpected event received"))  --synchronize isn't necesary for print as that function is designed to synchronize internally
      break --unknown command
  end
  end

  print(translate("Java:Event handler terminating"))
  JavaEventPipe.destroy();
end

function java_getCapabilities()
  local result={}
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETCAPABILITIES)  
  local r=javapipe.readBytes(16)
  javapipe.unlock()
  
  local v=byteTableToQword(r) --only the first 40 bits are implemented atm
  
  local capabilityBitPositions={
     can_tag_objects = 0,
     can_generate_field_modification_events = 1,
     can_generate_field_access_events = 2,
     can_get_bytecodes = 3,
     can_get_synthetic_attribute = 4,
     can_get_owned_monitor_info = 5,
     can_get_current_contended_monitor = 6,
     can_get_monitor_info = 7,
     can_pop_frame = 8,
     can_redefine_classes = 9,
     can_signal_thread = 10,
     can_get_source_file_name = 11,
     can_get_line_numbers = 12,
     can_get_source_debug_extension = 13,
     can_access_local_variables = 14,
     can_maintain_original_method_order = 15,
     can_generate_single_step_events = 16,
     can_generate_exception_events = 17,
     can_generate_frame_pop_events = 18,
     can_generate_breakpoint_events = 19,
     can_suspend = 20,
     can_redefine_any_class = 21,
     can_get_current_thread_cpu_time = 22,
     can_get_thread_cpu_time = 23,
     can_generate_method_entry_events = 24,
     can_generate_method_exit_events = 25,
     can_generate_all_class_hook_events = 26,
     can_generate_compiled_method_load_events = 27,
     can_generate_monitor_events = 28,
     can_generate_vm_object_alloc_events = 29,
     can_generate_native_method_bind_events = 30,
     can_generate_garbage_collection_events = 31,
     can_generate_object_free_events = 32,
     can_force_early_return = 33,
     can_get_owned_monitor_stack_depth_info = 34,
     can_get_constant_pool = 35,
     can_set_native_method_prefix = 36,
     can_retransform_classes = 37,
     can_retransform_any_class = 38,
     can_generate_resource_exhaustion_heap_events = 39,
     can_generate_resource_exhaustion_threads_events = 40    
  }
  
  result.can_access_local_variables=v & (1<<capabilityBitPositions.can_access_local_variables) ~= 0
  result.can_generate_all_class_hook_events=v & (1<<capabilityBitPositions.can_generate_all_class_hook_events) ~= 0
  result.can_generate_breakpoint_events=v & (1<<capabilityBitPositions.can_generate_breakpoint_events) ~= 0
  result.can_generate_method_entry_events=v & (1<<capabilityBitPositions.can_generate_method_entry_events)~=0;
  result.can_generate_compiled_method_load_events=v & (1<<capabilityBitPositions.can_generate_compiled_method_load_events) ~= 0
  result.can_generate_field_access_events=v & (1<<capabilityBitPositions.can_generate_field_access_events) ~= 0
  result.can_generate_field_modification_events=v & (1<<capabilityBitPositions.can_generate_field_modification_events) ~= 0
  result.can_generate_single_step_events=v & (1<<capabilityBitPositions.can_generate_single_step_events) ~= 0
  result.can_get_bytecodes=v & (1<<capabilityBitPositions.can_get_bytecodes) ~= 0
  result.can_get_constant_pool=v & (1<<capabilityBitPositions.can_get_constant_pool) ~= 0
  result.can_maintain_original_method_order=v & (1<<capabilityBitPositions.can_maintain_original_method_order) ~= 0
  result.can_redefine_any_class=v & (1<<capabilityBitPositions.can_redefine_any_class) ~= 0
  result.can_redefine_classes=v & (1<<capabilityBitPositions.can_redefine_classes) ~= 0
  result.can_retransform_any_class=v & (1<<capabilityBitPositions.can_retransform_any_class) ~= 0
  result.can_retransform_classes=v & (1<<capabilityBitPositions.can_retransform_classes) ~= 0
  result.can_tag_objects=v & (1<<capabilityBitPositions.can_tag_objects) ~= 0



  return result,r;
end


function java_StartListeneningForEvents()
  javapipe.lock();
  javapipe.writeByte(JAVACMD_STARTCODECALLBACKS)


  --the javapipe will now be frozen until a javaeventpipe makes an connection
  createNativeThread(JavaEventListener);

  javapipe.unlock();
end

function java_getLoadedClasses()
  if javapipe==nil then return {} end
  local ms=createMemoryStream()
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETLOADEDCLASSES)

  local streamsize=javapipe.readDword()

  javapipe.readIntoStream(ms,streamsize);
  javapipe.unlock()


  ms.position=0;

  if ms.Size==0 then
    ms.destroy()
    return nil, 'java_getLoadedClasses failed'
  end

  local classcount=ms.readDword()
  local classes={}

  if classcount>0 then
    local i=0
    local length
    for i=1,classcount do
      classes[i]={}
      classes[i].jclass=ms.readQword()
      length=ms.readWord()
      classes[i].signature=ms.readString(length)

      length=ms.readWord()
      classes[i].generic=ms.readString(length)
      
      length=ms.readWord()
      if length>0 then
        classes[i].superclass_signature=ms.readString(length)
      end
      
      length=ms.readWord()
      if length>0 then
        classes[i].superclass_generic=ms.readString(length)
      end
    end

  end
  
  ms.destroy()
  
  table.sort(classes,function(a,b) return a.signature<b.signature end)  
  
  classes.jClassLookup={}
  classes.signatureLookup={}
  for i=1,#classes do
    classes.jClassLookup[classes[i].jclass]=classes[i] 
    classes.signatureLookup[classes[i].signature]=classes[i]
  end

  return classes
end

function java_pushLocalFrame(count)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_PUSHLOCALFRAME)
  javapipe.writeWord(count)
  javapipe.unlock()
end

function java_popLocalFrame(result) --result can be nil
  local result=nil
  javapipe.lock()
  javapipe.writeByte(JAVACMD_POPLOCALFRAME)
  javapipe.writeQword(result)
  result=javapipe.readQword()
  javapipe.unlock()

  return result
end

function java_dereferenceLocalObject(object)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_DEREFERENCELOCALOBJECT)
  javapipe.writeQword(object)
  javapipe.unlock()
end

function java_dereferenceGlobalObjects(objects)
  if objects==nil then return end
  
  if type(objects=='number') then
    --could be someone just passed one object mistakenly
    objects={objects}
  end
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_DEREFERENCEGLOBALOBJECTS)  
  ms.writeDword(#objects)
  for i=1,#objects do
    ms.writeQword(objects[i])    
  end

  ms.position=0  
  
  javapipe.lock()
  javapipe.writeFromStream(ms)
  javapipe.unlock()   

  ms.destroy()  
end

function java_cleanClasslist(classlist)
  local i
  for i=1, #classlist do
    java_dereferenceLocalObject(classlist[i].jclass)
  end
end

function java_compileMethod(method)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_COMPILEMETHOD) 
  javapipe.writeQword(method)
  javapipe.unlock()
end

function java_getClassMethods(class)
  local ms=createMemoryStream()
  
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETCLASSMETHODS)
  javapipe.writeQword(class)
  local streamsize=javapipe.readDword()
  javapipe.readIntoStream(ms, streamsize);
  javapipe.unlock()
  ms.position=0;
  
  if ms.Size==0 then
    ms.destroy()
    return nil, 'java_getClassMethods failed'
  end   
  
  local count=ms.readDword()
  local i
  local result={}
  local length
  for i=1,count do
    result[i]={}
    result[i].jmethodid=ms.readQword()

    length=ms.readWord()
    result[i].name=ms.readString(length)

    length=ms.readWord()
    result[i].signature=ms.readString(length)

    length=ms.readWord()
    result[i].generic=ms.readString(length)
    
    result[i].modifiers=ms.readDword()
    result[i].static=(result[i].modifiers & ACC_STATIC)==ACC_STATIC
  end

  ms.destroy()
  
  table.sort(result,function(a,b) return a.name<b.name end)
  
  result.jmethodidLookup={}
  for i=1,#result do
    result.jmethodidLookup[result[i].jmethodid]=result[i]    
  end
  
  
  return result
end

function java_getClassFields(class)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETCLASSFIELDS)
  javapipe.writeQword(class)
  local streamsize=javapipe.readDword()
  local ms=createMemoryStream()
  javapipe.readIntoStream(ms, streamsize);
  javapipe.unlock()
  ms.position=0;
  
  if ms.Size==0 then
    ms.destroy()
    return nil, 'java_getClassFields failed'
  end  

  local hasoffsets=false
  
  local count=ms.readDword()
  local i
  local result={}
  local staticresult={}
  local length
    
  for i=1,count do
    local e={}
    e.jfieldid=ms.readQword()

    length=ms.readWord()
    e.name=ms.readString(length)

    length=ms.readWord()
    e.signature=ms.readString(length)

    length=ms.readWord()
    e.generic=ms.readString(length)
    
    e.modifiers=ms.readDword()    
    e.offset=ms.readDword()
    
    if e.offset==0xffffffff then
      e.offset=nil
    else
      hasoffsets=true
    end
    
    e.static=(e.modifiers & ACC_STATIC)==ACC_STATIC; 

    if e.static then
      table.insert(staticresult,e)
    else
      table.insert(result,e)
    end
  end

  ms.destroy();
  
  if hasoffsets then
    table.sort(result,function(a,b) return (a.offset or -1)<(b.offset or -1) end)  
  else
    table.sort(result,function(a,b) return a.name<b.name end)
  end
  
  result.jfieldidLookup={}
  for i=1,#result do
    result.jfieldidLookup[result[i].jfieldid]=result[i]    
  end
  
  for i=1,#staticresult do
    result.jfieldidLookup[staticresult[i].jfieldid]=staticresult[i]    
  end
    
  
  return result,staticresult
end

--[[
function java_getAllClassFields(class)
  --get all the fields of the given class, including inherited ones

  --java_pushLocalFrame(16)

  local result={}
  while (class~=nil) and (class~=0) do
    local r=java_getClassFields(class)
    local i
    for i=1,#r do
      result[#result+1]=r[i]
    end

    class=java_getSuperClass(class) --this pushes an object on the local frame
  end

  --java_popLocalFrame(nil)

  return result

end--]]

function java_getImplementedInterfaces(class)
  result={}
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETIMPLEMENTEDINTERFACES)
  javapipe.writeQword(class)
  local count=javapipe.readDword()
  for i=1,count do
    result[i]=javapipe.readQword()
  end

  javapipe.unlock()
  return result
end



function java_findReferencesToObject(jObject)
  result={}
  local count=0
  javapipe.lock()
  javapipe.writeByte(JAVACMD_FINDREFERENCESTOOBJECT)
  javapipe.writeQword(jObject)

  count=javapipe.readDword()
  local i
  for i=1, count do
    result[i]=javapipe.readQword();
  end


  javapipe.unlock()

  return result
end


function java_redefineClassWithCustomData(class, memory)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_REDEFINECLASS)
  javapipe.writeQword(class)
  javapipe.writeDword(#memory)
  javapipe.writeString(memory)
  javapipe.unlock()
end

function java_collectGarbage(class, memory)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_COLLECTGARBAGE)
  javapipe.unlock()
end

function java_redefineClassWithCustomClassFile(class, filename)
  local f=assert(io.open(filename,"rb"))
  local data = f:read("*all")
  f:close()
  java_redefineClassWithCustomData(class, data)
end

function java_getClassData(class)
  --gets the .class binary data (tip: Write a .class parser/editor so you can modify attributes and method bodies)
  local result={}
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETCLASSDATA)
  javapipe.writeQword(class)

  result.size=javapipe.readDword()
  if (result.size > 0) then
    result.data=javapipe.readString(result.size)
  end
  javapipe.unlock()

  return result.data
end

function java_writeClassToDisk(class, filename)
  local data=java_getClassData(class)
  local f=assert(io.open(filename,"wb"))
  f:write(data)
  f:close()
end

function java_getMethodName(methodid)
  local name=nil
  local sig=nil
  local gen=nil

  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETMETHODNAME)
  javapipe.writeQword(methodid)

  local length
  length=javapipe.readWord()
  name=javapipe.readString(length)

  length=javapipe.readWord()
  sig=javapipe.readString(length)

  length=javapipe.readWord()
  gen=javapipe.readString(length)

  javapipe.unlock()

  return name, sig, gen
end

function java_parseSignature_type(sig, i)
  local result=''
  local char=string.sub(sig,i,i)

  if (char=='V') or (char=='Z') or (char=='B') or (char=='C') or (char=='S') or (char=='I') or (char=='J') or (char=='F') or (char=='D') then
  result=char
  elseif char=='L' then
  local classtype
  local newi

  newi=string.find(sig,';', i+1)
  if newi==nil then
    return #sig --error
  end

  result=string.sub(sig, i, newi)

  i=newi
  elseif char=='[' then
  result,i=java_parseSignature_type(sig,i+1)
  result='['..result
  end

  return result,i

end


function java_parseSignature_method(sig, i, result)
  result.parameters={}

  while i<=#sig do
    local parem
    local char=string.sub(sig,i,i)

  --parse every type

  if char==')' then
    return i+1
  end

  param,i=java_parseSignature_type(sig, i)
  result.parameters[#result.parameters+1]=param
  i=i+1
  end
end

function java_parseSignature(sig)
  if sig==nil then
    error(translate('Invalid java signature'))
  end

  --parse the given signature
  local result={}
  local i=1
  while i<=#sig do
    local char=string.sub(sig,i,i)

  if char=='(' then
    i=java_parseSignature_method(sig, i+1, result)
  else
    if char~=' ' then
        result.returntype, i=java_parseSignature_type(sig, i)
    end

    i=i+1
  end
  end

  return result
end


Java_TypeSigToIDConversion={}
Java_TypeSigToIDConversion['V']=0 --void
Java_TypeSigToIDConversion['Z']=1 --boolean
Java_TypeSigToIDConversion['B']=2 --byte
Java_TypeSigToIDConversion['C']=3 --char
Java_TypeSigToIDConversion['S']=4 --short
Java_TypeSigToIDConversion['I']=5 --int
Java_TypeSigToIDConversion['J']=6 --long
Java_TypeSigToIDConversion['F']=7 --float
Java_TypeSigToIDConversion['D']=8 --double
Java_TypeSigToIDConversion['L']=9 --object
Java_TypeSigToIDConversion['[']=10 --array
 --11=string
 
Java_TypeSigToHumanStringConversion={}
Java_TypeSigToHumanStringConversion['V']='void'
Java_TypeSigToHumanStringConversion['Z']='boolean'
Java_TypeSigToHumanStringConversion['B']='byte'
Java_TypeSigToHumanStringConversion['C']='char'
Java_TypeSigToHumanStringConversion['S']='short integer'
Java_TypeSigToHumanStringConversion['I']='integer'
Java_TypeSigToHumanStringConversion['J']='long integer'
Java_TypeSigToHumanStringConversion['F']='float'
Java_TypeSigToHumanStringConversion['D']='double'
Java_TypeSigToHumanStringConversion['[']='array'
 

function java_convertTypeStrToReadableString(typestr)
  local c=string.sub(typestr,1,1)
  
  if c=='L' then
    if typestr=='Ljava/lang/String' then
      return 'string'
    else
      return 'object of type '..typestr:sub(2,-2)   
    end;
    
  else
    return Java_TypeSigToHumanStringConversion[c]
  end
end

function java_invokeMethodEx(object, methodid, returntypestring, parameters)
  --format of parameters array element:  --
  --  type: the java type string
  --  value: the value of the parameter in string format
  
  local result=nil
  local returntype=Java_TypeSigToIDConversion[string.sub(returntypestring,1,1)]
  
  if (returntype==9) and (returntypestring=='Ljava/lang/String;') then
    returntype=11 --string
  end

  if returntype>=100 then
    return nil, translate('Array return types are not supported')
  end  
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_INVOKEMETHOD)
  ms.writeQword(object)
  ms.writeQword(methodid)

  ms.writeByte(returntype)
  ms.writeByte(argumentcount)
 
  for i=1,#parameters do
    local t=Java_TypeSigToIDConversion[parameters[i].type]
    if (t==9) and (parameters[i].type=='Ljava/lang/String;') then
      t=11 --string
    end
    
    ms.writeByte(t)

  
    local writer=java_field_writers[t]
    if writer==nil then error('Invalid internal field type at index '..i) end
    
    if writer(ms,parameters[i].value)~=true then return nil,'Failed interpreting the string :'..parameters[i].value end
  end 
 
  --send the data
  ms.Position=0
  
  javapipe.lock()
  javapipe.writeFromStream(ms, ms.Size)
  
  ms.clear()  
  if returntype~=11 then
    javapipe.readIntoStream(ms,8)
  else
    local sz=javapipe.readDword()
    javapipe.readIntoStream(ms, sz)    
  end
  javapipe.unlock()
  
  --return the result
  
  local r=java_field_readers[returntype]
  ms.Position=0
  local result
  if r then
    result=r(ms)  
  end 
  
  ms.destroy()  
  
  return result
end

function java_invokeMethod(object, methodid, ...)
--todo: get the info, parse it into a struct and hand it to java_invokeMethodEx
  local parameters={}
  
  local arg=...
  local argumentcount=#arg
  local name, sig, gen=java_getMethodName(methodid)

  --parse sig to find out what to give as parameters and what to expect as result (I am assuming the caller KNOWS what he's doing...)

  --format of sig: (ABC)D  () part are the parameters, D is the return type
  local parsedsignature=java_parseSignature(sig)
  
  for i=1,#parsedsignature.parameters do
    parameters[i]={}
    parameters[i].type=parsedsignature.parameters[i]
  end
  
  return java_invokeMethodEx(object, methodid, parsedsignature.returntype, parameters)
  
  --[[
  local i
  for i=1, argumentcount do
    local typeid
    typeid=Java_TypeSigToIDConversion[string.sub(parsedsignature.parameters[i],1,1)]
    if typeid==10 then
      typeid=10+Java_TypeSigToIDConversion[string.sub(parsedsignature.parameters[i],2,2)]
    end

    java_invokeMethod_sendParameter(typeid, arg[i])


  end

  result=javapipe.readQword()
  javapipe.unlock()

  if returntype==1 then
    result=result~=0
  elseif returntype==7 then --float
    result=byteTableToFloat(dwordToByteTable(result))
  elseif returntype==8 then --double
    result=byteTableToDouble(qwordToByteTable(result))
  end

  return result--]]
end

function java_findMethod(class, name, sig)
  local cm=java_getClassMethods(class)
  local i
  for i=1,#cm do
    if cm[i].name==name then
    if (sig==nil) or (sig==cm[i].signature) then
      return cm[i].jmethodid
    end

  end
  end

  return nil --still here
end



function java_findAllInstancesFromClass(jClass, lookupmethod)
  local result={}
  local ms=createMemoryStream()  
  
  javapipe.lock()
  javapipe.writeByte(JAVACMD_FINDCLASSINSTANCES)
  javapipe.writeByte(lookupmethod)  
  javapipe.writeQword(jClass)
  
  local streamsize=javapipe.readDword()
  if streamsize and streamsize>0 then
    javapipe.readIntoStream(ms,streamsize);
  end
  javapipe.unlock()
  
  if ms.size>=4 then  
    ms.position=0
    local count=ms.readDword()
    for i=1,count do
      local e={}
      e.jobject=ms.readQword()
      e.address=ms.readQword()
      table.insert(result,e);
    end
  else
    print("JAVACMD_FINDCLASSINSTANCES returned an empty stream")
  end
  
  ms.destroy() 

  return result
end

function java_addToBootstrapClassLoaderPath(segment)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_ADDTOBOOTSTRAPCLASSLOADERPATH)
  javapipe.writeWord(#segment)
  javapipe.writeString(segment)
  javapipe.unlock()
end


function java_addToSystemClassLoaderPath()
  javapipe.lock()
  javapipe.writeByte(JAVACMD_ADDTOSYSTEMCLASSLOADERPATH)
  javapipe.writeWord(#segment)
  javapipe.writeString(segment)
  javapipe.unlock()

end

function java_getFieldDeclaringClass(klass, fieldid)
  local result=nil
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETFIELDDECLARINGCLASS)
  javapipe.writeQword(klass)
  javapipe.writeQword(fieldid)

  result=javapipe.readQword()

  javapipe.unlock()
  return result
end

function java_getFieldSignatureByObject(object, fieldid)
--slightly faster as 2 pipecalls can be skipped if you don't have the class (get class, release class)
  local result={}
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_GETFIELDSIGNATUREBYOBJECT)
  ms.writeQword(object) 
  ms.writeQword(fieldid)
  ms.Position=0
  
  javapipe.lock()
  javapipe.writeFromStream(ms)
  ms.clear()
  local sz=javapipe.readDword()
  javapipe.readIntoStream(ms, sz)
  javapipe.unlock()
  
  ms.position=0
  
  
  local length
  length=ms.readWord()
  result.name=ms.readString(length)

  length=ms.readWord()
  result.signature=ms.readString(length)

  length=ms.readWord()
  result.generic=ms.readString(length)
  
  ms.destroy()
  return result
end

function java_getFieldSignature(class, fieldid)
  local result={}
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_GETFIELDSIGNATURE)
  ms.writeQword(class) 
  ms.writeQword(fieldid)
  ms.Position=0
  
  javapipe.lock()
  javapipe.writeFromStream(ms)
  ms.clear()
  local sz=javapipe.readDword()
  javapipe.unlock()
  
  
  local length
  length=ms.readWord()
  result.name=ms.readString(length)

  length=ms.readWord()
  result.signature=ms.readString(length)

  length=ms.readWord()
  result.generic=ms.readString(length)
  
  ms.destroy()
  return result
end
 

function java_setFieldValues(newValues)   
  --newValues is a table where each entry contains
  --  ObjectOrClass (the object or class to change)
  --  Field
  --  Value (Value is a string)
  
  if #newValues>65535 then
    error('Do not call java_setFieldValues with more than 65535 entries...')
  end
  

  
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_SETFIELDVALUES)
  ms.writeWord(#newValues)
  
  for i=1,#newValues do
    local t
    if newValues[i].Field.InternalType==nil then    
      t=Java_TypeSigToIDConversion[string.sub(newValues[i].Fields.signature,1,1)]
      
      if (t==9) and (Fields[i].signature=='Ljava/lang/String;') then 
        t=11 --handle string special 
      end
      
      newValues[i].Field.InternalType=t
    else
      t=newValues[i].Field.InternalType
    end
    
    ms.writeQword(newValues[i].ObjectOrClass)     
    ms.writeQword(newValues[i].Field.jfieldid)
    ms.writeByte(t)
    ms.writeByte(newValues[i].Field.static and 1 or 0)
    
    local writer=java_field_writers[t]
    if writer==nil then error('Invalid internal field type at index '..i) end
    
    if writer(ms,newValues[i].Value)~=true then return nil,'Failed interpreting the string :'..newValues[i].Value end
  end
  
  ms.Position=0
  
  javapipe.lock()
  javapipe.writeFromStream(ms, ms.Size)
  javapipe.unlock()
  ms.clear()  
  
  
end

function java_getFieldValuesFromObject(jobject, Fields)
--gets the value of a group of fields all at once
  
  --print("java_getFieldValuesFromObject for "..jobject)
  local ms=createMemoryStream()
  local results={}  
  
  ms.writeByte(JAVACMD_GETFIELDVALUES)
  ms.writeQword(jobject)
  ms.writeQword(Fields.Class.jclass)
  ms.writeDword(#Fields)
  for i=1,#Fields do   
    local t
    if Fields[i].InternalType==nil then    
      t=Java_TypeSigToIDConversion[string.sub(Fields[i].signature,1,1)]
      
      if (t==9) and (Fields[i].signature=='Ljava/lang/String;') then 
        t=11 --handle string special (not just return <null> or <object>)
      end
      
      Fields[i].InternalType=t
    else
      t=Fields[i].InternalType
    end
    
    ms.writeQword(Fields[i].jfieldid)
   
   
    ms.writeByte(t)
    ms.writeByte(Fields[i].static and 1 or 0)      
  end
  
  ms.position=0
  javapipe.lock()
  javapipe.writeFromStream(ms, ms.Size)  
  ms.clear()    
  local streamsize=javapipe.readDword()
  javapipe.readIntoStream(ms, streamsize)
  javapipe.unlock()
  ms.position=0
  
  local readers=java_field_readers
  
  

  
  for i=1,#Fields do
    --convert the data to strings
    local t=Fields[i].InternalType
    local r=readers[t]
    if r then
      results[i]=r(ms)
    else
      results[i].Value='<WTF>' --unknown internal type
      results[i].Object=nil
    end
  end
  
  ms.destroy()
  return results
end

function java_getFieldValuesFromAddress(address, Fields)
  --read the memory (get the max offset+bytelength first)
  local mem
  local i
  local maxoffset=0
  for i=1,#Fields do
    if Fields[i].offset>maxoffset then
      maxoffset=Fields[i].offset      
    end
  end
  mem=readBytes(address, maxoffset+8)
  
  --parse the memory (may require a few pointer follow reads)

  
end



function java_getField(jObject, fieldid, signature)

  if signature==nil then
    signature=java_getFieldSignatureByObject(jObject, fieldid).signature
  end

  --parse the signature
  local vartype=Java_TypeSigToIDConversion[string.sub(signature,1,1)]
  if vartype>9 then  --not sure what to do about arrays. For now, force them to 'objects'
    vartype=9
  end

  local result=nil

  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETFIELD)
  javapipe.writeQword(jObject)
  javapipe.writeQword(fieldid)
  javapipe.writeByte(vartype)

  result=javapipe.readQword()

  javapipe.unlock()

  if vartype==1 then
    result=result~=0
  elseif vartype==7 then --float
    result=byteTableToFloat(dwordToByteTable(result))
  elseif vartype==8 then --double
    result=byteTableToDouble(qwordToByteTable(result))
  end

  return result

end

function java_setField(jObject, fieldid, signature, value)
  if signature==nil then
    --I need to figure it out myself I guess...
    signature=java_getFieldSignatureByObject(jObject, fieldid).signature
  end

  local vartype=Java_TypeSigToIDConversion[string.sub(signature,1,1)]
  if vartype>9 then  --not sure what to do about arrays. For now, force them to 'objects'
    vartype=9
  end

  if vartype==1 then --boolean
    if value then value=1 else value=0 end
  elseif vartype==7 then
    value=byteTableToDword(floatToByteTable(value))
  elseif vartype==8 then
    value=byteTableToQword(doubleToByteTable(value))
  end

  javapipe.lock()
  javapipe.writeByte(JAVACMD_SETFIELD)
  javapipe.writeQword(jObject)
  javapipe.writeQword(fieldid)
  javapipe.writeByte(vartype)
  javapipe.writeQword(value)
  javapipe.unlock()

end

local ST_UNKNOWN=0xffffffff
local ST_EXACT=0
local ST_INCREASED=1
local ST_DECREASED=2
local ST_CHANGED=3
local ST_UNCHANGED=4

function java_search_start(value, boolean)
  --tag all known objects and set a variable to let some functions know they can not function until the scan has finished (they can't set tags)
  
  printf("java_search_start")
  local result=nil
  
  if value=='' then value=nil end
  if value=='*' then value=nil end

  
  ms=createMemoryStream()
  ms.writeByte(JAVACMD_STARTSCAN)
  
  if ms.Size~=1 then error('invalid structure size') end
  
  --setup a scandata block (look at jvarscan.c for the offsets) 
  ms.writeDword(value and ST_EXACT or ST_UNKNOWN) --0-3: value==0 > 0xffffffff else 0 (SO_EXACT)
  ms.writeDword(boolean and 1 or 0)    --4-7
  
  if ms.Size~=1+8 then error('invalid structure size 2') end
  
  ms.writeByte(value and value~=0 and 1 or 0) --zValue:8
  ms.writeByte(value and value or 0) --bValue:9
  ms.writeWord(value and value or 0) --cValue:10-11
  ms.writeWord(value and value or 0) --sValue:12-13
  ms.writeWord(0) --filler: 14-15
  ms.writeDword(value and value or 0) --iValue:16-19
  ms.writeDword(0) --filler: 20-23
  ms.writeQword(value) --jValue: 24-31
  
  if ms.Size~=1+32 then error('invalid structure size 4') end
  
  
  
  local minvalue,maxvalue
  
  if value then
    --parse the user input and figure out the accuracy requested
    local vs=value:trim()
    local v=tonumber(vs)
    if v==nil then          
      messageDialog(value..' can not be parsed by lua', mtError)
      return
    end 
    local accuracy
    local seperator=vs:find('%.')
    if seperator==nil then
      accuracy=0
    else
      accuracy=#s-seperator
    end
    
    if accuracy==0 then
      rounding='0.9'
    else
      rounding='0.'
      for i=1,accuracy do
        rounding=rounding..'0'
      end
      rounding=rounding..'9'
    end

    minvalue=vs-rounding
    maxvalue=vs+rounding        
  end
  ms.writeFloat(minvalue)
  if ms.Size~=1+32+4 then error('invalid structure size 5.1') end
    
  ms.writeFloat(maxvalue)
  if ms.Size~=1+32+4+4 then error('invalid structure size 5.2') end
  
  ms.writeDouble(minvalue)
  if ms.Size~=1+32+4+4+8 then error('invalid structure size 5.3') end
   
  ms.writeDouble(maxvalue) 
  if ms.Size~=1+32+4+4+8+8 then error('invalid structure size 5.4') end  

  if ms.Size~=1+56 then error('invalid structure size 5') end
  
  
  
  ms.position=0
  outputDebugString('Sending firstscan command and scandata. Size of packet:'..ms.Size)
  
  
  javapipe.lock()
  javapipe.writeFromStream(ms)  
  javapipe.Timeout=0
  
  outputDebugString('Waiting for result')
  

  result=javapipe.readQword() --Wait till done, get nr of results)
  
  javapipe.Timeout=JAVA_TIMEOUT
  
  java_scanning=true
  javapipe.unlock()
  ms.destroy()

  return result
end

function java_search_refine(scantype, scanvalue)
  --refines the result of the current scan
  --scantype:
  --0 = exact value
  --1 = increased value
  --2 = decreased value
  --3 = changed value
  --4 = unchanged value
  
  printf("java_search_refine(%d,%s)", scantype, scanvalue)
  



  local result=nil

  if scantype==nil then
    error(translate("Scantype was not set"))
  end
  
  if scanvalue==nil then
    error(translate("scanvalue was not set"))
  end
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_REFINESCANRESULTS)
  ms.writeDword(scantype)
  ms.writeDword(1) --boolean, ignored at this point
  
  ms.writeByte(scanvalue and scanvalue~=0 and 1 or 0) --zValue:8
  ms.writeByte(scanvalue and scanvalue or 0) --bValue:9
  ms.writeWord(scanvalue and scanvalue or 0) --cValue:10-11
  ms.writeWord(scanvalue and scanvalue or 0) --sValue:12-13
  ms.writeWord(0) --filler: 14-15
  ms.writeDword(scanvalue and scanvalue or 0) --iValue:16-19
  ms.writeDword(0) --filler: 20-23
  ms.writeQword(scanvalue) --jValue: 24-31
  
  local minvalue,maxvalue
  
  if scanvalue then
    --parse the user input and figure out the accuracy requested
    local vs=scanvalue:trim()
    local v=tonumber(vs)
    if v==nil then          
      messageDialog(scanvalue..' can not be parsed by lua', mtError)
      return
    end 
    local accuracy
    local seperator=vs:find('%.')
    if seperator==nil then
      accuracy=0
    else
      accuracy=#s-seperator
    end
    
    if accuracy==0 then
      rounding='0.9'
    else
      rounding='0.'
      for i=1,accuracy do
        rounding=rounding..'0'
      end
      rounding=rounding..'9'
    end

    minvalue=vs-rounding
    maxvalue=vs+rounding        
  end
  ms.writeFloat(minvalue)
  ms.writeFloat(maxvalue)
  
  ms.writeDouble(minvalue)

  ms.writeDouble(maxvalue) 


  ms.position=0
  
  javapipe.lock()
  javapipe.writeFromStream(ms)  
  ms.destroy()  
  javapipe.Timeout=0    
  result=javapipe.readQword() --Wait till done, get nr of results)
  javapipe.Timeout=JAVA_TIMEOUT
  
  javapipe.unlock()

  return result

end

function java_search_getResults(maxresults)
  --get the results
  --note, the results are references to the object, so CLEAN UP when done with it (and don't get too many)
  local result={}
  
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETSCANRESULTS)
  if (maxresults==nil) or (maxresults==0) then
    maxresults=10
  end

  javapipe.writeDword(maxresults)


  --local i=1
  local ms=createMemoryStream()
  local sz=javapipe.readDword()
  
  printf("streamsize=%d", sz);
  
  javapipe.readIntoStream(ms,sz)  
  
  javapipe.unlock()
  
  
  printf("successfully read stream");
  
  
  ms.Position=0
  local count=ms.readDword()
  printf("count=%d (maxresults=%d)", count, maxresults)
  for i=1,count do
    local r={}
    r.object=ms.readQword()
    r.fieldid=ms.readQword()
    r.fieldindex=ms.readDword()
    table.insert(result, r)
  end

  return result
end


function java_search_finish()
  java_scanning=false
end

function java_foundCodeDialogClose(sender, closeAction)
  --print("closing")
  local id=sender.Tag
  local fcd=java.findwhatwriteslist[id]
  java.findwhatwriteslist[id]=nil


  java_stopFindWhatWrites(id)
  return caFree
end

function java_MoreInfoDblClick(sender)
  --get the class and method and start the editor
  local index=sender.ItemIndex+1
  if index>0 then
    local methodid=getRef(sender.Tag).stack[index].methodid --the listview also has this tag (tag to entry)
    local class=java_getMethodDeclaringClass(methodid)

    --get the class that defines this method

    local classdata=java_getClassData(class)
    local parsedclass=java_parseClass(classdata)

    local mname=java_getMethodName(methodid)
    local parsedmethod=javaclass_findMethod(parsedclass, mname)

    javaclasseditor_editMethod(parsedclass, parsedmethod, editMethod_applyClick, class)

  end
end


function java_foundCodeDialog_MoreInfo_OnDestroy(sender)
  --cleanup the reference to this entry
  local entry=getRef(sender.Tag)
  if entry~=nil then
    entry.form=nil
    destroyRef(sender.Tag)
  end

end


function java_createEntryListView(owner)
  local lv=createListView(owner)
  lv.ViewStyle=vsReport
  lv.ReadOnly=true
  lv.RowSelect=true

  local c=lv.Columns.add()
  c.caption=translate('Class')
  c.width=150

  c=lv.Columns.add()
  c.caption=translate('Method')
  c.width=150

  c=lv.Columns.add()
  c.caption=translate('Position')
  c.autosize=true

  return lv
end

function java_foundCodeDialogLVDblClick(sender)
  local id=sender.tag
  local fcd=java.findwhatwriteslist[id]

  local index=sender.ItemIndex+1
  if index>0 then
    local entry=fcd.entries[index]

    if (entry.stack~=nil) and (entry.form==nil) then
      --show a form with the stack info
      local ref=createRef(entry)
      entry.form=createForm()
      entry.form.Caption=string.format(translate("More info %s.%s(%d)"), entry.classname, entry.methodname, entry.location)
      entry.form.Tag=ref
      entry.form.Width=400
      entry.form.Height=150
      entry.form.Position=poScreenCenter
      entry.form.BorderStyle=bsSizeable


      local lv=java_createEntryListView(entry.form)
      lv.Tag=ref
      lv.Align=alClient
      entry.form.OnDestroy=java_foundCodeDialog_MoreInfo_OnDestroy

      --fill the listview with the data
      local i
      for i=1, #entry.stack do
        local se=entry.stack[i]
        local mname=java_getMethodName(se.methodid)
        local class=java_getMethodDeclaringClass(se.methodid)
        local classname=java_getClassSignature(class)

        java_dereferenceLocalObject(class)
        class=nil

        local tventry=lv.items.add()
        tventry.Caption=classname

        tventry.SubItems.add(string.format('%x: %s', se.methodid, mname))
        tventry.SubItems.add(se.location)
      end

      lv.OnDblClick=java_MoreInfoDblClick

    end

    if (entry.form~=nil) then
      entry.form.show() --bring to front
    end


  end

end

function java_findWhatWrites(object, fieldid)
  local id=nil
  if java.capabilities.can_generate_field_modification_events then
    --spawn a window to receive the data

    javapipe.lock()
    javapipe.writeByte(JAVACMD_FINDWHATWRITES)
    javapipe.writeQword(object)
    javapipe.writeQword(fieldid)

    id=javapipe.readDword()

    --print("id="..id)

    javapipe.unlock()


    local fcd={} --found code dialog
    fcd.form=createForm()
    fcd.form.width=400
    fcd.form.height=300
    fcd.form.Position=poScreenCenter
    fcd.form.BorderStyle=bsSizeable
    fcd.form.caption=translate('The following methods accessed the given variable')
    fcd.form.OnClose=java_foundCodeDialogClose
    fcd.form.Tag=id

    fcd.lv=java_createEntryListView(fcd.form)
    fcd.lv.Align=alClient
    fcd.lv.OnDblClick=java_foundCodeDialogLVDblClick
    fcd.lv.Tag=id
    fcd.lv.Name=translate('results');


    fcd.entries={}


    if java.findwhatwriteslist==nil then
      java.findwhatwriteslist={}
    end

    java.findwhatwriteslist[id]=fcd
  else
    error(translate('java_find_what_writes only works when the jvmti agent is launched at start'))
  end

  return id
end

function java_stopFindWhatWrites(id)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_STOPFINDWHATWRITES)
  javapipe.writeDword(id)
  javapipe.unlock()
end

function java_getMethodDeclaringClass(methodid)
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETMETHODDECLARINGCLASS)
  javapipe.writeQword(methodid)
  local result=javapipe.readQword()
  javapipe.unlock()

  return result
end


function java_android_decodeObject(jobject)
  local result=0
  javapipe.lock()
  javapipe.writeByte(JAVACMD_ANDROID_DECODEOBJECT)
  javapipe.writeQword(jobject)
  result=javapipe.readQword()
  javapipe.unlock()
  return result 
end



function java_getObjectHandleToAddress(address)
  local result=0
  javapipe.lock()
  javapipe.writeByte(JAVACMD_FINDJOBJECT)
  javapipe.writeQword(address)

  result=javapipe.readQword()
  javapipe.unlock()


  return result
end


function java_getObjectClass(jObject)
  local result
  javapipe.lock()

  javapipe.writeByte(JAVACMD_GETOBJECTCLASS);
  javapipe.writeQword(jObject)
  result=javapipe.readQword()


  javapipe.unlock()
  return result
end

function java_getObjectClassName(jObject)
  local result
  javapipe.lock()

 -- printf("java_getObjectClassName. JAVAVMD_GETOBJECTCLASSNAME=%d",JAVAVMD_GETOBJECTCLASSNAME) 
  javapipe.writeByte(JAVAVMD_GETOBJECTCLASSNAME);
  javapipe.writeQword(jObject)
  local length=javapipe.readWord()
  result=javapipe.readString(length)

  javapipe.unlock()
  return result
end

function java_getObjectClassNames(jObjectList)
  if jObjectList==nil then return nil, 'list may not be nil' end
  if type(jObjectList)~='table' then return nil,'the list must be a table' end
 
  local results={}
  
  local ms=createMemoryStream()
  ms.writeByte(JAVACMD_GETOBJECTCLASSNAMES)
  ms.writeDword(#jObjectList)
  for i=1,#jObjectList do
    ms.writeQword(jObjectList[i])
  end  
  ms.Position=0
  javapipe.lock()
  javapipe.writeFromStream(ms)
  ms.clear()

  local sz=javapipe.readDword()
  javapipe.readIntoStream(ms, sz)
  javapipe.unlock()
  
  ms.Position=0
  local count=ms.readDword()
  if count~=#jObjectList then return nil,'Invalid list returned' end
  
  for i=1,count do
    local strlen=ms.readWord()
    results[i]=ms.readString(strlen)    
  end
  
  ms.destroy()
  
  
  return result
end

function java_getClassSignature(jClass)
  local length
  local result=''
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETCLASSSIGNATURE)
  javapipe.writeQword(jClass)

  length=javapipe.readWord()
  result=javapipe.readString(length)

  length=javapipe.readWord()
  if (length>0) then
    result=result..translate('  Generic=')..javapipe.readString(length);
  end

  javapipe.unlock()

  return result
end

function java_getSuperClass(jClass)
  local result=nil
  javapipe.lock()
  javapipe.writeByte(JAVACMD_GETSUPERCLASS)
  javapipe.writeQword(jClass)

  result=javapipe.readQword()
  javapipe.unlock()

  return result
end


function java_findFields(name, signature, caseSensitive)
  if (name==nil or name=='') and (signature==nil or signature=='') then return {} end
 
  javapipe.lock()
  javapipe.writeByte(JAVACMD_FINDFIELDS)
  javapipe.writeByte((caseSensitive and 1) or 0)
  
  if name then
    javapipe.writeWord(#name)  
    javapipe.writeString(name)  
  else 
    javapipe.writeWord(0)
  end
  
  
  if signature then  
    javapipe.writeWord(#signature)  
    javapipe.writeString(signature)
  else
   javapipe.writeWord(0)
  end
  
  local streamsize=javapipe.readDword()
  local ms=createMemoryStream()
  javapipe.readIntoStream(ms,streamsize)
  javapipe.unlock()    
  
  ms.Position=0
  
  local result={}
  
  while ms.Position<ms.Size do
    local e={}
    e.jclass=ms.readQword()
    e.fieldid=ms.readQword()
    table.insert(result,e)
  end
  
  ms.destroy()
  
  return result
end

function java_findMethods(name, signature, caseSensitive)
  if (name==nil or name=='') and (signature==nil or signature=='') then return {} end
 
  javapipe.lock()
  javapipe.writeByte(JAVACMD_FINDMETHODS)
  javapipe.writeByte((caseSensitive and 1) or 0)
  
  if name then
    javapipe.writeWord(#name)  
    javapipe.writeString(name)  
  else 
    javapipe.writeWord(0)
  end
  
  
  if signature then  
    javapipe.writeWord(#signature)  
    javapipe.writeString(signature)
  else
   javapipe.writeWord(0)
  end
  
  local streamsize=javapipe.readDword()
  local ms=createMemoryStream()
  javapipe.readIntoStream(ms,streamsize)
  javapipe.unlock()    
  
  ms.Position=0
  
  local result={}
  
  while ms.Position<ms.Size do
    local e={}
    e.jclass=ms.readQword()
    e.methodid=ms.readQword()
    table.insert(result,e)
  end
  
  ms.destroy()
  
  return result
end




function miJavaActivateClick(sender)
  javaInjectAgent()
end



function varscan_showResults(count)
  print("showing results for "..count.." results");
  local r=java_search_getResults(math.min(count, 50))
  
  local newcount=#r


  printf("setting count") 

  if newcount~=count then
    java.varscan.Count.Caption=string.format('%d of %d', newcount, count)
  else
    java.varscan.Count.Caption=count
  end
  
  
  
  java.varscan.currentresults=r
  

  local i
  for i=1,count do
    local object=java.varscan.currentresults[i].object
    local fieldid=java.varscan.currentresults[i].fieldid

    outputDebugString(string.format("Getting classname from object %x", object));
    local classname=java_getObjectClassName(object)
    local fieldname='fieldindex '..java.varscan.currentresults[i].fieldindex
    
    if fieldid then          
      local fieldsig=java_getFieldSignatureByObject(object, fieldid)
      if fieldsig.name then
        fieldname=fieldsig.name.. '  ('..fieldname..')'
      end
    end   
    
    java.varscan.Results.Items.Add('Obj('..classname..'::'..fieldname..')')
  end

end

function varscan_cleanupResults()
  local i

  java.varscan.Results.clear()

  java.varscan.currentresults=nil
end

function varscan_firstScan(sender)
  print("first scan")
  if (sender.Tag==0) then
    --first scan

    local count=java_search_start(java.varscan.ValueBox.Text)
    
    printf("found %d results", count)
    
    varscan_showResults(count)

    sender.Caption=translate("New Scan")
    sender.Tag=1
    
    if java.varscan.currentresults then
      java.varscan.NextScan.Enabled=count>0
    end
  else
    --new scan
    varscan_cleanupResults()
    java_search_finish()
    java.varscan.NextScan.Enabled=false

    sender.Caption=translate("First Scan")
    sender.Tag=0
  end
end

function varscan_nextScan(sender)
  --print("next scan")
  varscan_cleanupResults()

  local count=java_search_refine(0, java.varscan.ValueBox.Text)
  varscan_showResults(count)

  java.varscan.NextScan.Enabled=#java.varscan.currentresults>0

end

function miFindWhatAccessClick(sender)
  local i=java.varscan.Results.ItemIndex
  if i~=-1 then
    i=i+1
    local object=java.varscan.currentresults[i].object
    local fieldid=java.varscan.currentresults[i].fieldid

    java_findWhatWrites(object, fieldid)
  end
end

function miJavaVariableScanClick(sender)
  local status,err=javaInjectAgent()
  if not status then 
    messageDialog(err, mtError, mbOK)
    return
  end

  local varscan=java.varscan

  local dpim=getScreenDPI()/96

  if varscan==nil then
    --build a gui
    varscan={}
    varscan.form=createForm()
    varscan.form.Width=400*dpim
    varscan.form.Height=400*dpim
    varscan.form.Position=poScreenCenter
    varscan.form.Caption=translate("Java Variable Scanner")
    varscan.form.BorderStyle=bsSizeable


    varscan.controls=createPanel(varscan.form)
    varscan.controls.Align=alRight
    varscan.controls.Caption=''
    varscan.controls.BevelOuter="bvNone"


    varscan.ValueText=createLabel(varscan.controls)
    varscan.ValueText.Caption=translate("Value")
    
    varscan.ScanType=createComboBox(varscan.controls)
    varscan.ScanType.Items.add('Exact Value')
    varscan.ScanType.Items.add('Increased Value')
    varscan.ScanType.Items.add('Decreased Value')
    varscan.ScanType.Items.add('Changed Value')
    varscan.ScanType.Items.add('Unchanged Value')
    varscan.ScanType.Style='csDropDownList'
    varscan.ScanType.ItemIndex=0
    varscan.ScanType.visible=false

    varscan.FirstScan=createButton(varscan.controls)
    varscan.FirstScan.Caption=translate("First Scan")
    varscan.FirstScan.AutoSize=true

    varscan.NextScan=createButton(varscan.controls)
    varscan.NextScan.Caption=translate("Next Scan")
    varscan.NextScan.AutoSize=true

    local width=6*dpim+math.max(varscan.form.Canvas.getTextWidth(varscan.FirstScan.Caption), varscan.form.Canvas.getTextWidth(varscan.NextScan.Caption)) --guess which one will be bigger... (just in case someone translates this)



    varscan.FirstScan.ClientWidth=width
    varscan.NextScan.ClientWidth=width
    varscan.FirstScan.Height=varscan.form.Canvas.getTextHeight('XXX')+3*dpim
    varscan.NextScan.Height=varscan.FirstScan.Height

    varscan.ValueBox=createEdit(varscan.controls)

    varscan.ValueBox.Top=20*dpim
    varscan.ValueBox.Left=20*dpim;

    varscan.ValueText.AnchorSideLeft.Control=varscan.ValueBox
    varscan.ValueText.AnchorSideLeft.Side=asrLeft
    varscan.ValueText.AnchorSideBottom.Control=varscan.ValueBox
    varscan.ValueText.AnchorSideBottom.Side=asrTop
    varscan.ValueText.Anchors="[akLeft, akBottom]"
    
    varscan.ScanType.AnchorSideLeft.Control=varscan.ValueBox   
    varscan.ScanType.AnchorSideLeft.Side=asrLeft
    varscan.ScanType.AnchorSideRight.Control=varscan.ValueBox   
    varscan.ScanType.AnchorSideRight.Side=asrRight    
    varscan.ScanType.AnchorSideTop.Control=varscan.ValueBox
    varscan.ScanType.AnchorSideTop.Side=asrBottom  
    varscan.ScanType.Anchors="[akTop, akLeft, akRight]"

    varscan.FirstScan.AnchorSideLeft.Control=varscan.ScanType
    varscan.FirstScan.AnchorSideLeft.Side=asrLeft

    varscan.FirstScan.AnchorSideTop.Control=varscan.ScanType
    varscan.FirstScan.AnchorSideTop.Side=asrBottom
    varscan.FirstScan.BorderSpacing.Top=5*dpim
    varscan.FirstScan.Anchors="[akTop, akLeft]"
    varscan.FirstScan.OnClick=varscan_firstScan

    varscan.NextScan.AnchorSideLeft.Control=varscan.FirstScan
    varscan.NextScan.AnchorSideLeft.Side=asrRight
    varscan.NextScan.BorderSpacing.Left=5*dpim
    varscan.NextScan.OnClick=varscan_nextScan

    varscan.NextScan.AnchorSideTop.Control=varscan.ScanType
    varscan.NextScan.AnchorSideTop.Side=asrBottom
    varscan.NextScan.BorderSpacing.Top=5*dpim
    varscan.NextScan.Anchors="[akTop, akLeft]"
    varscan.NextScan.Enabled=false

    varscan.ValueBox.Width=varscan.NextScan.Left+varscan.NextScan.Width-varscan.ValueBox.Left
    varscan.controls.Width=varscan.ValueBox.Width+40*dpim

    varscan.ResultPanel=createPanel(varscan.form)
    varscan.ResultPanel.Align=alClient
    varscan.ResultPanel.Caption=""
    varscan.ResultPanel.BevelOuter="bvNone"


    varscan.Count=createLabel(varscan.ResultPanel)
    varscan.Count.Caption=translate("Found:")
    varscan.Count.Align=alTop

    varscan.Results=createListBox(varscan.ResultPanel)
    varscan.Results.Align=alClient
    varscan.Results.Width=200*dpim

    varscan.Results.PopupMenu=createPopupMenu(varscan.form)
    
    varscan.form.OnClose=function()
      java.varscan=nil
      return caFree
    end

    local mi
    mi=createMenuItem(varscan.Results.PopupMenu)
    mi.Caption=translate("Find what accesses this value")
    mi.OnClick=miFindWhatAccessClick;
    mi.Enabled=java.capabilities.can_generate_field_access_events
    varscan.Results.PopupMenu.Items.add(mi)

  end

  varscan.form.show()

  java.varscan=varscan
end

function editMethod_applyClick(parsedclass, class)
  local newdata=java_writeClass(parsedclass)
  java_redefineClassWithCustomData(class, newdata)


end

function miEditMethodClick(sender)
  --javaclasseditor_editMethod(class, methodid)
  local sel=javaForm.treeview.Selected
  if (sel~=nil) and (sel.Level==1) and (sel.Data~=0) then
    --find the class and the methodid
    --class can be found in the parent
    --methodid is in the data

    local class=sel.Parent.Data
    local methodid=sel.data

    --javaclasseditor_editMethod(class, methodid)

    local classdata=java_getClassData(class)
    local parsedclass=java_parseClass(classdata)

    local mname=java_getMethodName(methodid)
    local parsedmethod=javaclass_findMethod(parsedclass, mname)

    javaclasseditor_editMethod(parsedclass, parsedmethod, editMethod_applyClick, class)
  end
end


function javaDissectPopupOnPopup(sender)
  --check if the current line contains a method, and if so, show miEditMethod else hide it

  local sel=javaForm.treeview.Selected
  javaForm.miEditMethod.Visible=(sel~=nil) and (sel.Level==1) and (sel.Data~=0)
end









function miJavaSetEnvironmentClick(sender)
  if targetIs64Bit() then
  autoAssemble([[
alloc(newenv, 32768)

alloc(sev, 2048)
alloc(path, 512)

alloc(pathstr,5)
alloc(JTOstr, 18)
alloc(JTO, 19)
label(end)
label(hasnosemicolon)
label(copyoption)


path:
{$lua}
return "db ';"..getCheatEngineDir().."autorun\\dlls\\32;"..getCheatEngineDir().."autorun\\dlls\\64',0"
{$asm}

pathstr:
db 'PATH',0

JTOstr:
db 'JAVA_TOOL_OPTIONS',0

JTO:
db ' -agentlib:cejvmti',0

sev:

//sub rsp,8 //align the stack
//sub rsp,20 //allocate scratchspace for function calls
sub rsp,28 //using magic to compine those two

//set the path
mov rcx,pathstr
mov rdx,newenv
mov r8,8000

call GetEnvironmentVariableA


mov rdx,path

cmp byte [newenv+rax],';'
jne hasnosemicolon

add rdx,1 //it already has a semicolon so skip it

hasnosemicolon:

mov rcx,newenv
//rdx=path(+1)
call ntdll.strcat

mov rcx,pathstr
mov rdx,newenv
call SetEnvironmentVariableA


//set the java tool options
mov byte [newenv],0


mov rcx,JTOstr
mov rdx,newenv
mov r8,8000
call GetEnvironmentVariableA

mov rdx, JTO

cmp rax,0 //not yet defined
jne copyoption

//it hasn't been defined yet
add rdx,1 //no space

copyoption:

mov rcx,newenv
//rdx=rdx
call ntdll.strcat

mov rcx,JTOstr
mov rdx,newenv
call SetEnvironmentVariableA


end:

add rsp,28
ret

createthread(sev)
]])

  else
  autoAssemble([[
alloc(newenv, 32768)

alloc(sev, 2048)
alloc(path, 512)

alloc(pathstr,5)
alloc(JTOstr, 18)
alloc(JTO, 19)
label(end)
label(hasnosemicolon)
label(copyoption)


path:
{$lua}
return "db ';"..getCheatEngineDir().."autorun\\dlls\\32;"..getCheatEngineDir().."autorun\\dlls\\64',0"
{$asm}

pathstr:
db 'PATH',0

JTOstr:
db 'JAVA_TOOL_OPTIONS',0

JTO:
db ' -agentlib:cejvmti',0

sev:

//set the path
push 8000
push newenv
push pathstr
call GetEnvironmentVariableA


mov esi,path


cmp byte [newenv+eax],';'
jne hasnosemicolon

add esi,1 //it already has a semicolon so skip it

hasnosemicolon:

push esi
push newenv
call ntdll.strcat
add esp,8


push newenv
push pathstr
call SetEnvironmentVariableA


//set the java tool options
mov byte [newenv],0

push 8000
push newenv
push JTOstr
call GetEnvironmentVariableA

mov esi, JTO

cmp eax,0 //not yet defined
jne copyoption

//it hasn't been defined yet
add esi,1 //no space

copyoption:

push esi
push newenv
call ntdll.strcat
add esp,8

push newenv
push JTOstr
call SetEnvironmentVariableA


end:
ret

createthread(sev)

  ]]
  )
  end
end

function java_OpenProcessAfterwards()
  local usesjava=false
  local m=enumModules()
  local i

  java_classlist=nil

  for i=1, #m do
    if m[i].Name=='jvm.dll' then
      java.android=false
      usesjava=true
      break
    end
    
    if m[i].Name=='libart.so' then
      java.android=true
      usesjava=true
      break    
    end
  end
  
  synchronize(function()
    if usesjava or java.settings.cbAlwaysShowMenu.Checked then
      if (miJavaTopMenuItem==nil) then
        local mfm=getMainForm().Menu
        local mi

        miJavaTopMenuItem=createMenuItem(mfm)
        local s="Java"
        
        if java.android then
          s=s.." (Android)"
        end
        miJavaTopMenuItem.Caption=s
        mfm.Items.insert(mfm.Items.Count-1, miJavaTopMenuItem) --add it before help


        mi=createMenuItem(miJavaTopMenuItem)
        mi.Caption=translate("Activate java features")
        mi.OnClick=miJavaActivateClick
        mi.Enabled=usesjava
        mi.Name="miActivate"
        mi.Visible=false
        miJavaTopMenuItem.Add(mi)

        mi=createMenuItem(miJavaTopMenuItem)
        mi.Caption=translate("Java Info")
        mi.Shortcut="Ctrl+Alt+J"
        mi.OnClick=function(sender)
		  if miJavaInfoClick then		
		    miJavaInfoClick(sender)
		  else
		    messageDialog('JavaInfo extension is not present', mtError, mbOK)
		  end
		end
        mi.Enabled=usesjava
        mi.Name="miDissectJavaClasses"
        miJavaTopMenuItem.Add(mi)

        mi=createMenuItem(miJavaTopMenuItem)
        mi.Caption=translate("Java variable scan")
        mi.Shortcut="Ctrl+Alt+S"
        mi.OnClick=miJavaVariableScanClick
        mi.Enabled=usesjava
        mi.Name="miJavaVariableScan"
        miJavaTopMenuItem.Add(mi)

        if (not java.android) then
          --debug child processes sets the environment option so a spawned child will instantly load the jvmti
          mi=createMenuItem(miJavaTopMenuItem)
          mi.Caption="-"
          miJavaTopMenuItem.Add(mi)

          mi=createMenuItem(miJavaTopMenuItem)
          mi.Caption=translate("Debug child processes")
          mi.OnClick=miJavaSetEnvironmentClick
          mi.Enabled=getOpenedProcessID()~=0
          mi.Name="miDebugChildren"
          miJavaTopMenuItem.Add(mi)
        end
      else
        miJavaTopMenuItem.miActivate.enabled=usesjava
        miJavaTopMenuItem.miDissectJavaClasses.enabled=usesjava
        miJavaTopMenuItem.miJavaVariableScan.enabled=usesjava
        miJavaTopMenuItem.miDebugChildren=getOpenedProcessID()~=0
      end
    end
  end)
end

function java_OpenProcess(processid)
  if java.oldOnOpenProcess~=nil then
    java.oldOnOpenProcess(processid)
  end

  if java_OpenProcessAfterwards_Thread==nil then
    java_OpenProcessAfterwards_Thread=createThread(function(t)
      t.Name='java_OpenProcessAfterwards'
      --print("java_OpenProcessAfterwards")
      java_OpenProcessAfterwards()
      java_OpenProcessAfterwards_Thread=nil    
      --print("java_OpenProcessAfterwards done")
    end )
  end
    --call this function when the whole OpenProcess routine is done (next sync check)
end

function javaAA_USEJAVA(parameters, syntaxcheckonly)
  --called whenever an auto assembler script encounters the USEJAVA() line
  --the value you return will be placed instead of the given line
  --In this case, returning a empty string is fine
  --Special behaviour: Returning nil, with a secondary parameter being a string, will raise an exception on the auto assembler with that string


  if (syntaxcheckonly==false) and (not javaInjectAgent()) then
    return nil,translate("The java handler failed to initialize")
  end

  return "" --return an empty string (removes it from the internal aa assemble list)
end


function java_settingsClose(sender, closeAction)
  local result=closeAction
  if java.settingsOnClose~=nil then
    result=java.settingsOnClose(sender, closeAction)
  end

  if (result==caHide) and (sender.ModalResult==mrOK) then
    --Apply changes

    --if there is an error return caNone (and show a message preferably)
    if java.settings.cbAlwaysShowMenu.Checked then
      java.settings.registry.Value["Always Show Menu"]=1
    else
      java.settings.registry.Value["Always Show Menu"]=0
    end

    --[[
    if java.settings.cbGlobalHook.Checked then
      if (java.settings.registry.Value["Global Hook"]=='') or (java.settings.registry.Value["Global Hook"]==0) then
        --it got selected
      end

      java.settings.registry.Value["Global Hook"]=1
    else
      if java.settings.registry.Value["Global Hook"]==1 then
        --it got deselected
      end
        java.settings.registry.Value["Global Hook"]=0
    end
    --]]

  end
  return result
end

function java_settingsShow(sender)
  if java.settingsOnShow~=nil then
    result=java.settingsOnShow(sender)
  end

  --update the controls based on the registry
  java.settings.cbAlwaysShowMenu.Checked=java.settings.registry.Value["Always Show Menu"]=='1'
  --java.settings.cbGlobalHook.Checked=java.settings.registry.Value["Global Hook"]=='1'

end

function java_initialize()
  --register a function to be called when a process is opened
  if (java==nil) then
    addCIncludePath(getAutorunPath()..'java')
  
    java={}
    java.oldOnOpenProcess=onOpenProcess
    onOpenProcess=java_OpenProcess

    registerAutoAssemblerCommand("USEJAVA", javaAA_USEJAVA)


    local sf=getSettingsForm()
    java.settingsTab=sf.SettingsPageControl.addTab()


    local insertNode=sf.SettingsTreeView.Items[3]  --insert it near the unrandomizer since it'd be used as often as that setting
    local node=sf.SettingsTreeView.Items.insert(insertNode, "Java")
    node.data=userDataToInteger(java.settingsTab)

    java.settingsOnClose=sf.onClose
    sf.onClose=java_settingsClose

    java.settingsOnShow=sf.onShow
    sf.onShow=java_settingsShow


    java.settings={}

    local cbAlwaysShowMenu=createCheckBox(java.settingsTab)
    cbAlwaysShowMenu.Caption=translate("Show java menu item even if the target process hasn't loaded jvm.dll (Used for the local setEnvironment option)")
    cbAlwaysShowMenu.AnchorSideLeft.Control=java.settingsTab
    cbAlwaysShowMenu.AnchorSideLeft.Side=asrLeft

    cbAlwaysShowMenu.AnchorSideTop.Control=java.settingsTab
    cbAlwaysShowMenu.AnchorSideTop.Side=asrTop

    cbAlwaysShowMenu.Anchors="[akTop, akLeft]"

    java.settings.cbAlwaysShowMenu=cbAlwaysShowMenu

  --[[
  --warning: If you uninstall CE while this is checked you won't be able to load any java programs

  local cbGlobalHook=createCheckBox(java.settingsTab)
  cbGlobalHook.Caption="Systemwide java agent injection. (Loads the java agent even when CE isn't running. Reboot recommended)"
  cbGlobalHook.AnchorSideLeft.Control=java.settingsTab
  cbGlobalHook.AnchorSideLeft.Side=asrLeft

  cbGlobalHook.AnchorSideTop.Control=cbAlwaysShowMenu
  cbGlobalHook.AnchorSideTop.Side="asrBottom"
  cbGlobalHook.Anchors="[akTop, akLeft]"

  java.settings.cbGlobalHook=cbGlobalHook
  --]]

    java.settings.registry=getSettings("Java")


    --initialize the settings based on the registry
    java.settings.cbAlwaysShowMenu.Checked=java.settings.registry.Value["Always Show Menu"]=='1'
    --java.settings.cbGlobalHook.Checked=java.settings.registry.Value["Global Hook"]=='1'



  end
end


java_initialize()
