unit frmautoinjectunit;

{$MODE Delphi}

interface

uses
  windows, LCLIntf, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Menus, CEFuncProc, StrUtils, types, ComCtrls, LResources,
  NewKernelHandler, SynEdit, SynHighlighterCpp, SynHighlighterAA, LuaSyntax, disassembler,
  MainUnit2, Assemblerunit, autoassembler, symbolhandler, SynEditSearch,
  MemoryRecordUnit, tablist, customtypehandler, registry, SynGutterBase, SynEditMarks,
  luahandler, memscan, foundlisthelper;


type TCallbackRoutine=procedure(memrec: TMemoryRecord; script: string; changed: boolean) of object;
type TCustomCallbackRoutine=procedure(ct: TCustomType; script:string; changed: boolean; lua: boolean) of object;

type TScripts=array of record
                script: string;
                filename: string;
                undoscripts: array [0..4] of record
                               oldscript: string;
                               startpos: integer;
                             end;
                currentundo: integer;
              end;

type TBooleanArray = Array of Boolean;

type TDisassemblyLine = Object
  Address: ptrUint;               // actual address value
  OriginalAddressString: String;  // string from disassembly (hex)
  AddressString: String;          // module+offset if specified
  Original: String;               // entire original disassembly
  Comment: String;                // comment part (second parameter of disassembly)
  OriginalHexBytes : String;      // original hex from disassembly (grouped)
  Code: String;                   // code portion of disassembly
  Size: Integer;                  // number of bytes for this instruction

  procedure Init(_address: ptrUint; _mi: TModuleInfo);
  procedure Shorten(_newsize: Integer); // if we overran our injection point, change to 'db'
  function IsStarter : Boolean;
  function IsEnder : Boolean;
  function IsValid : Boolean;
  function GetHexBytes : String; // hex bytes with spaces between each byte
  function GetMaskFlags : TBooleanArray;
end;

type TAOBFind = Object
  Address: ptrUint;               // address where AOB was found
  CodeSize: Integer;              // size of code we will always use
  Size: Integer;
  Bytes: Array of Byte;           // bytes we'll read from memory

  procedure Init(_address: ptrUint; _codesize: Integer);
  function IsMatch(var maskBytes: Array Of Byte; var maskFlags : TBooleanArray; startIndex, endIndex: Integer): Boolean;
end;

type

  { TfrmAutoInject }

  TfrmAutoInject = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    mifindNext: TMenuItem;
    miCallLua: TMenuItem;
    miNewWindow: TMenuItem;
    Panel1: TPanel;
    Button1: TButton;
    Load1: TMenuItem;
    Panel2: TPanel;
    Save1: TMenuItem;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    Exit1: TMenuItem;
    Assigntocurrentcheattable1: TMenuItem;
    emplate1: TMenuItem;
    Codeinjection1: TMenuItem;
    CheatTablecompliantcodee1: TMenuItem;
    APIHook1: TMenuItem;
    SaveAs1: TMenuItem;
    PopupMenu1: TPopupMenu;
    Coderelocation1: TMenuItem;
    New1: TMenuItem;
    N2: TMenuItem;
    Syntaxhighlighting1: TMenuItem;
    closemenu: TPopupMenu;
    Close1: TMenuItem;
    Inject1: TMenuItem;
    Injectincurrentprocess1: TMenuItem;
    Injectintocurrentprocessandexecute1: TMenuItem;
    Find1: TMenuItem;
    Paste1: TMenuItem;
    Copy1: TMenuItem;
    Cut1: TMenuItem;
    Undo1: TMenuItem;
    N6: TMenuItem;
    FindDialog1: TFindDialog;
    undotimer: TTimer;
    View1: TMenuItem;
    AAPref1: TMenuItem;
    procedure Button1Click(Sender: TObject);
    procedure Load1Click(Sender: TObject);
    procedure menuAOBInjectionClick(Sender: TObject);
    procedure menuFullInjectionClick(Sender: TObject);
    procedure mifindNextClick(Sender: TObject);
    procedure miCallLuaClick(Sender: TObject);
    procedure miNewWindowClick(Sender: TObject);
    procedure Save1Click(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Codeinjection1Click(Sender: TObject);
    procedure Panel1Resize(Sender: TObject);
    procedure CheatTablecompliantcodee1Click(Sender: TObject);

    procedure Assigntocurrentcheattable1Click(Sender: TObject);
    procedure APIHook1Click(Sender: TObject);
    procedure SaveAs1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure assemblescreenKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure Coderelocation1Click(Sender: TObject);
    procedure New1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure TabControl1Change(Sender: TObject);
    procedure Syntaxhighlighting1Click(Sender: TObject);
    procedure TabControl1ContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: Boolean);
    procedure Close1Click(Sender: TObject);
    procedure Injectincurrentprocess1Click(Sender: TObject);
    procedure Injectintocurrentprocessandexecute1Click(Sender: TObject);
    procedure Cut1Click(Sender: TObject);
    procedure Copy1Click(Sender: TObject);
    procedure Paste1Click(Sender: TObject);
    procedure Find1Click(Sender: TObject);
    procedure FindDialog1Find(Sender: TObject);
    procedure AAPref1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Undo1Click(Sender: TObject);
  private
    { Private declarations }

    AAHighlighter: TSynAASyn;
    CPPHighlighter: TSynCppSyn;
    LuaHighlighter: TSynLuaSyn;

    assembleSearch: TSynEditSearch;

    oldtabindex: integer;
    scripts: TScripts;
             
    selectedtab: integer;

    fluamode: boolean;
    fCustomTypeScript: boolean;

    procedure setluamode(state: boolean);
    procedure injectscript(createthread: boolean);
    procedure tlistOnTabChange(sender: TObject; oldselection: integer);
    procedure setCustomTypeScript(x: boolean);
    procedure gutterclick(Sender: TObject; X, Y, Line: integer; mark: TSynEditMark);
    procedure assemblescreenchange(sender: TObject);
    function GetUniqueAOB(mi: TModuleInfo; address: ptrUint; codesize: Integer; var resultOffset: Integer) : string;

  public
    { Public declarations }

    assemblescreen: TSynEdit;
    tlist: TTablist;

    editscript: boolean;
    editscript2: boolean;
    memrec: TMemoryRecord;

    customtype: TCustomType;

    callbackroutine: TCallbackroutine;
    CustomTypeCallback: TCustomCallbackroutine;
    injectintomyself: boolean;
    property luamode: boolean read fluamode write setluamode;
    property CustomTypeScript: boolean read fCustomTypeScript write setCustomTypeScript;
  end;


procedure Getjumpandoverwrittenbytes(address,addressto: ptrUINT; jumppart,originalcodepart: tstrings);
procedure generateAPIHookScript(script: tstrings; address: string; addresstogoto: string; addresstostoreneworiginalfunction: string=''; nameextension:string='0');



implementation


uses frmAAEditPrefsUnit,MainUnit,memorybrowserformunit,APIhooktemplatesettingsfrm;

resourcestring
  rsExecuteScript = 'Execute script';
  rsLuaFilter = 'LUA Script (*.LUA)|*.LUA|All Files ( *.* )|*.*';
  rsLUAScript = 'LUA Script';
  rsWriteCode = 'Write code';
  rsCEAFilter = 'Cheat Engine Assembly (*.CEA)|*.CEA|All Files ( *.* )|*.*';
  rsAutoAssembler = 'Auto assembler';
  rsCodeNeedsEnableAndDisable = 'The code needs an [ENABLE] and a [DISABLE] section if you want to use this script as a table entry';
  rsNotAllCodeIsInjectable = 'Not all code is injectable.'#13#10'%s'#13#10'Are you sure you wan''t to edit it to this?';
  rsCodeInjectTemplate = 'Code inject template';
  rsOnWhatAddressDoYouWantTheJump = 'On what address do you want the jump?';
  rsFailedToAddToTableNotAllCodeIsInjectable = 'Failed to add to table. Not all code is injectable';
  rsStartAddress = 'Start address';
  rsCodeRelocationTemplate = 'Code relocation template';
  rsEndAddressLastBytesAreIncludedIfNecesary = 'End address (last bytes are included if necesary)';
  rsAreYouSureYouWantToClose = 'Are you sure you want to close %s ?';
  rsWhatIdentifierDoYouWantToUse = 'What do you want to name the symbol for the injection point?';

procedure TfrmAutoInject.setCustomTypeScript(x: boolean);
begin
  fCustomTypeScript:=x;
  if x then
    editscript:=true;
end;

procedure TfrmAutoInject.setluamode(state: boolean);
begin
  fluamode:=state;
  if state then
  begin
    assemblescreen.Highlighter:=LuaHighlighter;

    //change gui to lua style
    button1.Caption:=rsExecuteScript;
    opendialog1.DefaultExt:='LUA';
    opendialog1.Filter:=rsLuaFilter;
    savedialog1.DefaultExt:='LUA';
    savedialog1.Filter:=rsLuaFilter;
    Assigntocurrentcheattable1.visible:=false;
    emplate1.Visible:=false;
    caption:=rsLUAScript;
   // inject1.Visible:=true;
    helpcontext:=19; //c-script help
  end
  else
  begin
    assemblescreen.Highlighter:=AAHighlighter;


    //change gui to autoassembler style
    button1.caption:=rsWriteCode;
    opendialog1.DefaultExt:='CEA';
    opendialog1.Filter:=rsCEAFilter;
    savedialog1.DefaultExt:='CEA';
    savedialog1.Filter:=rsCEAFilter;
    Assigntocurrentcheattable1.Visible:=true;
    emplate1.Visible:=true;
    caption:=rsAutoAssembler;
    inject1.Visible:=false;
    helpcontext:=18; //auto asm help
  end;
end;


procedure TfrmAutoInject.Button1Click(Sender: TObject);
var
    a,b: integer;

    aa: TCEAllocArray;

    //variables for injectintomyself:
    check: boolean;
    registeredsymbols: TStringlist;
    errmsg: string;
begin
{$ifndef standalonetrainerwithassembler}
  registeredsymbols:=tstringlist.Create;
  registeredsymbols.CaseSensitive:=false;
  registeredsymbols.Duplicates:=dupIgnore;

  if luamode then
  begin
    //execute
    LUA_DoScript(assemblescreen.Text);
    modalresult:=mrok; //not modal anymore, but can still be used to pass info
    if editscript2 or CustomTypeScript then close;
  end
  else
  begin
    if editscript then
    begin
      //check if both scripts are valid before allowing the edit

      setlength(aa,1);
      getenableanddisablepos(assemblescreen.Lines,a,b);
      if not CustomTypeScript then
        if (a=-1) and (b=-1) then raise exception.create(rsCodeNeedsEnableAndDisable);


      try
        check:=autoassemble(assemblescreen.lines,false,true,true,injectintomyself,aa,registeredsymbols) and
               autoassemble(assemblescreen.lines,false,false,true,injectintomyself,aa,registeredsymbols);

        if not check then
          errmsg:=format(rsNotAllCodeIsInjectable,['']);
      except
        on e: exception do
        begin
          check:=false;
          errmsg:=format(rsNotAllCodeIsInjectable,['('+e.Message+')']);
        end;
      end;

      if check then
      begin
        modalresult:=mrok; //not modal anymore, but can still be used to pass info
        if editscript2 or CustomTypeScript then close; //can only be used when not modal
      end
      else
      begin
        if messagedlg(errmsg, mtWarning, [mbyes, mbno], 0)=mryes then
        begin
          modalresult:=mrok; //not modal anymore, but can still be used to pass info
          if editscript2 or CustomTypeScript then close;
        end;
      end;
    end else autoassemble(assemblescreen.lines,true);
  end;
  registeredsymbols.free;
{$endif}
end;

procedure TfrmAutoInject.Load1Click(Sender: TObject);
begin
{$ifndef standalonetrainerwithassembler}

  if opendialog1.Execute then
  begin

    assemblescreen.Lines.Clear;
    assemblescreen.Lines.LoadFromFile(opendialog1.filename);
    savedialog1.FileName:=opendialog1.filename;
    assemblescreen.AfterLoadFromFile;

  end;
{$endif}
end;

procedure TfrmAutoInject.mifindNextClick(Sender: TObject);
begin
  finddialog1.OnFind(finddialog1);
end;



procedure TfrmAutoInject.miNewWindowClick(Sender: TObject);
var f: TfrmAutoInject;
begin
  f:=TfrmAutoInject.Create(application);
  f.luamode:=luamode;

  f.show;
end;

procedure TfrmAutoInject.Save1Click(Sender: TObject);
var f: tfilestream;
    s: string;
begin
  if (savedialog1.filename='') and (not savedialog1.Execute) then exit;   //filename was empty and the user clicked cancel

  f:=tfilestream.Create(savedialog1.filename,fmcreate);
  try
    s:=assemblescreen.text;
    f.Write(s[1],length(assemblescreen.text));

    assemblescreen.MarkTextAsSaved;

  finally
    f.Free;
  end;
end;

procedure TfrmAutoInject.Exit1Click(Sender: TObject);
begin
  close;
end;

procedure TfrmAutoInject.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
{$ifndef standalonetrainerwithassembler}

  if not editscript then
  begin
    if self<>MainForm.frmLuaTableScript then //don't free the lua table script
      action:=cafree;
  end
  else
  begin
    try
      if editscript2 then
      begin
        //call finish routine with script

        if modalresult=mrok then
          callbackroutine(memrec, assemblescreen.text,true)
        else
          callbackroutine(memrec, assemblescreen.text,false);

        action:=cafree;
      end
      else
      if CustomTypeScript then
      begin

        if modalresult=mrok then
          CustomTypeCallback(customtype, assemblescreen.text,true,luamode)
        else
          CustomTypeCallback(customtype, assemblescreen.text,false,luamode);

        action:=cafree;
      end;

    except
      on e: exception do
      begin
        modalresult:=mrNone;
        raise exception.create(e.message);
      end;
    end;
  end;
{$endif}
end;

procedure TfrmAutoInject.Codeinjection1Click(Sender: TObject);
function inttostr(i:int64):string;
begin
  if i=0 then result:='' else result:=sysutils.IntToStr(i);
end;

var address: string;
    originalcode: array of string;
    originalbytes: array of byte;
    codesize: integer;
    a: ptrUint;
    br: dword;
    c: ptrUint;
    x: string;
    i,j,k: integer;
    injectnr: integer;

    enablepos: integer;
    disablepos: integer;
    enablecode: tstringlist;
    disablecode: tstringlist;

    mi: TModuleInfo;
begin
{$ifndef standalonetrainerwithassembler}

  a:=memorybrowser.disassemblerview.SelectedAddress;

  if symhandler.getmodulebyaddress(a,mi) then
  begin
    address:='"'+mi.modulename+'"+'+inttohex(a-mi.baseaddress,1);
  end
  else
    address:=inttohex(a,8);

  if inputquery(rsCodeInjectTemplate, rsOnWhatAddressDoYouWantTheJump, address) then
  begin
    try
      a:=StrToQWordEx('$'+address);
    except

      a:=symhandler.getaddressfromname(address);

    end;

    c:=a;

    injectnr:=0;
    for i:=0 to assemblescreen.Lines.Count-1 do
    begin
      j:=pos('alloc(newmem',lowercase(assemblescreen.lines[i]));
      if j<>0 then
      begin
        x:=copy(assemblescreen.Lines[i],j+12,length(assemblescreen.Lines[i]));
        x:=copy(x,1,pos(',',x)-1);
        try
          k:=strtoint(x);
          if injectnr<=k then
            injectnr:=k+1;
        except
          inc(injectnr);
        end;
      end;
    end;


    //disassemble the old code
    setlength(originalcode,0);
    codesize:=0;

    while codesize<5 do
    begin
      setlength(originalcode,length(originalcode)+1);
      originalcode[length(originalcode)-1]:=disassemble(c,x);
      i:=posex('-',originalcode[length(originalcode)-1]);
      i:=posex('-',originalcode[length(originalcode)-1],i+1);
      originalcode[length(originalcode)-1]:=copy(originalcode[length(originalcode)-1],i+2,length(originalcode[length(originalcode)-1]));
      codesize:=c-a;
    end;

    setlength(originalbytes,codesize);
    ReadProcessMemory(processhandle, pointer(a), @originalbytes[0], codesize, br);

    enablecode:=tstringlist.Create;
    disablecode:=tstringlist.Create;
    try
      with enablecode do
      begin
        if processhandler.is64bit then
          add('alloc(newmem'+inttostr(injectnr)+',2048,'+address+') ')
        else
          add('alloc(newmem'+inttostr(injectnr)+',2048)');
        add('label(returnhere'+inttostr(injectnr)+')');
        add('label(originalcode'+inttostr(injectnr)+')');
        add('label(exit'+inttostr(injectnr)+')');
        add('');
        add('newmem'+inttostr(injectnr)+': //this is allocated memory, you have read,write,execute access');
        add('//place your code here');

        add('');
        add('originalcode'+inttostr(injectnr)+':');
        for i:=0 to length(originalcode)-1 do
          add(originalcode[i]);
        add('');
        add('exit'+inttostr(injectnr)+':');
        add('jmp returnhere'+inttostr(injectnr)+'');

        add('');
        add(address+':');
        add('jmp newmem'+inttostr(injectnr)+'');
        while codesize>5 do
        begin
          add('nop');
          dec(codesize);
        end;

        add('returnhere'+inttostr(injectnr)+':');
        add('');
      end;

      with disablecode do
      begin
        add('dealloc(newmem'+inttostr(injectnr)+')');
        add(address+':');
        for i:=0 to length(originalcode)-1 do
          add(originalcode[i]);
        x:='db';
        for i:=0 to length(originalbytes)-1 do
          x:=x+' '+inttohex(originalbytes[i],2);
        add('//Alt: '+x);
      end;

      getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);
      //skip first comment(s)
      if enablepos>=0 then
      begin
        while enablepos<assemblescreen.lines.Count-1 do
        begin
          if pos('//',trim(assemblescreen.Lines[enablepos+1]))=1 then inc(enablepos) else break;
        end;
      end;

      for i:=enablecode.Count-1 downto 0 do
        assemblescreen.Lines.Insert(enablepos+1,enablecode[i]);

      getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);
      //skip first comment(s)
      if disablepos>=0 then
      begin
        while disablepos<assemblescreen.lines.Count-1 do
        begin
          if pos('//',trim(assemblescreen.Lines[disablepos+1]))=1 then inc(enablepos) else break;
            inc(disablepos);
        end;
        //only if there actually is a disable section place this code
        for i:=disablecode.Count-1 downto 0 do
          assemblescreen.Lines.Insert(disablepos+1,disablecode[i]);
      end;
    finally
      enablecode.free;
      disablecode.Free;
    end;

  end;

{$endif}
end;

procedure TfrmAutoInject.Panel1Resize(Sender: TObject);
begin
  button1.Left:=panel1.Width div 2-button1.Width div 2;
end;


procedure TfrmAutoInject.CheatTablecompliantcodee1Click(Sender: TObject);
var e,d: integer;
begin
{$ifndef standalonetrainerwithassembler}

  getenableanddisablepos(assemblescreen.lines,e,d);

  if e=-1 then //-2 is 2 or more, so bugged, and >=0 is has one
  begin
    assemblescreen.Lines.Insert(0,'[ENABLE]');
    assemblescreen.Lines.Insert(1,'//code from here to ''[DISABLE]'' will be used to enable the cheat');
    assemblescreen.Lines.Insert(2,'');
  end;

  if d=-1 then
  begin
    assemblescreen.Lines.Add(' ');
    assemblescreen.Lines.Add(' ');
    assemblescreen.Lines.Add('[DISABLE]');
    assemblescreen.Lines.Add('//code from here till the end of the code will be used to disable the cheat');
  end;
{$endif}  
end;

procedure TfrmAutoInject.assemblescreenChange(Sender: TObject);
begin
  if self=mainform.frmLuaTableScript then
    mainform.editedsincelastsave:=true;


end;



procedure TfrmAutoInject.Assigntocurrentcheattable1Click(Sender: TObject);
var a,b: integer;
    aa:TCEAllocArray;
    registeredsymbols: TStringlist;
begin
{$ifndef standalonetrainerwithassembler}
  {$ifndef net}

  registeredsymbols:=tstringlist.Create;
  registeredsymbols.CaseSensitive:=false;
  registeredsymbols.Duplicates:=dupIgnore;
  
  try
    setlength(aa,0);
    getenableanddisablepos(assemblescreen.Lines,a,b);
    if (a=-1) and (b=-1) then raise exception.create(rsCodeNeedsEnableAndDisable);

    if autoassemble(assemblescreen.lines,false,true,true,false,aa,registeredsymbols) and
       autoassemble(assemblescreen.lines,false,false,true,false,aa,registeredsymbols) then
    begin
      //add a entry with type 255
      mainform.AddAutoAssembleScript(assemblescreen.text);


    end
    else showmessage(rsFailedToAddToTableNotAllCodeIsInjectable);
  finally
    registeredsymbols.Free;
  end;
  {$endif}
  {$endif}
end;

procedure Getjumpandoverwrittenbytes(address,addressto: ptrUint; jumppart,originalcodepart: tstrings);
//pre: jumppart and originalcodepart are declared objects
var x,y: ptrUint;
    z: string;
    i: integer;
    ab: TAssemblerBytes;
    jumpsize: integer;
begin
{$ifndef standalonetrainerwithassembler}
  Assemble('jmp '+inttohex(addressto,8),address,ab);
  jumpsize:=length(ab);

  x:=address;
  y:=address;

  while x-y<jumpsize do
  begin
    z:=disassemble(x);
    z:=copy(z,pos('-',z)+1,length(z));
    z:=copy(z,pos('-',z)+1,length(z));

    originalcodepart.add(z);
  end;

  jumppart.Add('jmp '+inttohex(addressto,8));

  for i:=jumpsize to x-y-1 do
    jumppart.Add('nop');
{$endif}
end;


procedure generateAPIHookScript(script: tstrings; address: string; addresstogoto: string; addresstostoreneworiginalfunction: string=''; nameextension:string='0');
var originalcode: array of string;
    originaladdress: array of ptrUint;
    i,j: integer;
    codesize: integer;
    a,b: ptrUint;
    br: dword;
    x: string;

    enablepos,disablepos: integer;
    disablescript: tstringlist;
    enablescript: tstringlist;

    originalcodebuffer: Pbytearray;
    ab: TAssemblerBytes;

    jumpsize: integer;
    tempaddress: ptrUint;

    specifier: array of ptrUint;
    specifiernr: integer;
    s,s2: string;

    d: TDisassembler;

    originalcodestart: integer;
begin
{$ifndef standalonetrainerwithassembler}
  //disassemble the old code
  d:=TDisassembler.Create;
  d.showmodules:=false;
  d.showsymbols:=false;

  setlength(specifier,0);
  setlength(originalcode,0);
  setlength(ab,0);
  specifiernr:=0;


  try
    a:=symhandler.getAddressFromName(address);
  except
    on e: exception do
      raise exception.create(address+':'+e.message);
  end;

  try
    b:=symhandler.getAddressFromName(addresstogoto);
  except
    on e: exception do
      raise exception.create(addresstogoto+':'+e.message);
  end;

  if processhandler.is64bit then
  begin
    //check if there is a region I can make use of for a jump trampoline
    if FindFreeBlockForRegion(a,2048)=nil then
    begin
      Assemble('jmp '+inttohex(b,8),a,ab);
      jumpsize:=length(ab);
    end
    else
      jumpsize:=5;
  end
  else
    jumpsize:=5;



  disablescript:=tstringlist.Create;
  enablescript:=tstringlist.Create;

  codesize:=0;
  b:=a;
  while codesize<jumpsize do
  begin
    setlength(originalcode,length(originalcode)+1);
    setlength(originaladdress,length(originalcode));

    originaladdress[length(originaladdress)-1]:=a;
    originalcode[length(originalcode)-1]:=d.disassemble(a,x);
    i:=posex('-',originalcode[length(originalcode)-1]);
    i:=posex('-',originalcode[length(originalcode)-1],i+1);
    originalcode[length(originalcode)-1]:=copy(originalcode[length(originalcode)-1],i+2,length(originalcode[length(originalcode)-1]));

    codesize:=a-b;
  end;

  getmem(originalcodebuffer,codesize);
  if ReadProcessMemory(processhandle,pointer(b), originalcodebuffer, codesize, br) then
  begin
    disablescript.Add(address+':');
    x:='db';

    for i:=0 to br-1 do
      x:=x+' '+inttohex(originalcodebuffer[i],2);

    disablescript.Add(x);      
  end;

  freemem(originalcodebuffer);



  with enablescript do
  begin
    if not processhandler.is64bit then
      add('alloc(originalcall'+nameextension+',2048)')
    else
    begin
      add('alloc(originalcall'+nameextension+',2048,'+address+')');
      add('alloc(jumptrampoline'+nameextension+',64,'+address+') //special jump trampoline in the current region (64-bit)');
      add('label(jumptrampoline'+nameextension+'address)');
    end;

    add('label(returnhere'+nameextension+')');
    add('');
    if addresstostoreneworiginalfunction<>'' then
    begin
      add(addresstostoreneworiginalfunction+':');
      if processhandler.is64Bit then
        add('dq originalcall'+nameextension)
      else
        add('dd originalcall'+nameextension);
    end;
    add('');
    add('originalcall'+nameextension+':');

    originalcodestart:=enablescript.Count;

    for i:=0 to length(originalcode)-1 do
    begin
      if hasAddress(originalcode[i], tempaddress, nil ) then
      begin
        if InRangeX(tempaddress, b,b+codesize) then
        begin
          s2:='specifier'+nameextension+inttostr(specifiernr);
          setlength(specifier,length(specifier)+1);
          specifier[specifiernr]:=tempaddress;

          Insert(0,'label('+s2+')');
          if has4ByteHexString(originalcode[i], s) then //should be yes
          begin
            s:=copy(s,2,length(s)-1);

            originalcode[i]:=StringReplace(originalcode[i],s,s2,[rfIgnoreCase]);
          end;

          inc(specifiernr);
        end;
      end;
      add(originalcode[i]);
    end;

    //now find the originalcode line that belongs to the specifier
    inc(originalcodestart,specifiernr);
    for i:=0 to length(specifier)-1 do
    begin
      for j:=0 to length(originaladdress)-1 do
      begin
        if specifier[i]=originaladdress[j] then
        begin
          enablescript[originalcodestart+j]:='specifier'+nameextension+inttostr(i)+':'+enablescript[originalcodestart+j]
        end;
      end;
    end;

    i:=0;

    while i<enablescript.count do
    begin
      j:=pos(':',enablescript[i]);

      if j>0 then
      begin
        s:=enablescript[i];
        s2:=copy(s,j+1,length(s));
        delete(i);
        Insert(i,copy(s,1,j));
        inc(i);
        Insert(i,s2);
      end;

      inc(i);
    end;


    add('jmp returnhere'+nameextension+'');

    add('');

    if processhandler.is64bit then
    begin
      add('jumptrampoline'+nameextension+':');
      add('jmp [jumptrampoline'+nameextension+'address]');
      add('jumptrampoline'+nameextension+'address:');
      add('dq '+addresstogoto);
      add('');
    end;


    add(address+':');
    if processhandler.is64bit then
      add('jmp jumptrampoline'+nameextension)
    else
      add('jmp '+addresstogoto);

    while codesize>jumpsize do
    begin
      add('nop');
      dec(codesize);
    end;

    add('returnhere'+nameextension+':');

    add('');
  end;


  getenableanddisablepos(script,enablepos,disablepos);

  if disablepos<>-1 then
  begin
    for i:=0 to disablescript.Count-1 do
      script.Insert(disablepos+i+1,disablescript[i]);
  end;

  getenableanddisablepos(script,enablepos,disablepos); //idiots putting disable first 

  if enablepos<>-1 then
  begin
    for i:=0 to enablescript.Count-1 do
      script.Insert(enablepos+i+1,enablescript[i]);
  end
  else
    script.AddStrings(enablescript);

  disablescript.free;
  enablescript.free;

  d.free;
{$endif}
end;



procedure TfrmAutoInject.APIHook1Click(Sender: TObject);
function inttostr(i:int64):string;
begin
  if i=0 then result:='' else result:=sysutils.IntToStr(i);
end;

var address: string;

    a: ptrUint;
    x: string;
    i,j,k: integer;

    injectnr: integer;

begin
{$ifndef standalonetrainerwithassembler}

  a:=memorybrowser.disassemblerview.SelectedAddress;

  address:=inttohex(a,8);

  with tfrmapihooktemplatesettings.create(self) do
//  if inputquery('Give the address of the api you want to hook',address) and inputquery('Give the address of the replacement function',address) then
  begin
    try
      injectnr:=0;
      for i:=0 to assemblescreen.Lines.Count-1 do
      begin
        j:=pos('alloc(newmem',lowercase(assemblescreen.lines[i]));
        if j<>0 then
        begin
          x:=copy(assemblescreen.Lines[i],j+12,length(assemblescreen.Lines[i]));
          x:=copy(x,1,pos(',',x)-1);
          try
            k:=strtoint(x);
            if injectnr<=k then
              injectnr:=k+1;
          except
            inc(injectnr);
          end;
        end;
      end;

      edit1.text:=address;
      if showmodal<>mrok then exit;


      generateAPIHookScript(assemblescreen.Lines,edit1.Text, edit2.Text, edit3.Text, inttostr(injectnr)); 


    finally
      free;
    end;
  end;

{$endif}
end;

procedure TfrmAutoInject.SaveAs1Click(Sender: TObject);
begin
  if savedialog1.Execute then
    save1.Click;    
end;

procedure TfrmAutoInject.FormShow(Sender: TObject);
begin
{$ifndef standalonetrainerwithassembler}

  if editscript then
    button1.Caption:=strOK;

  assemblescreen.SetFocus;
{$endif}
end;

procedure TfrmAutoInject.assemblescreenKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
{   if (ssCtrl in Shift) and (key=ord('A'))  then
   begin
     TMemo(Sender).SelectAll;
     Key := 0;
   end; }
end;

procedure TfrmAutoInject.miCallLuaClick(Sender: TObject);
var
  luaserverinit: tstringlist;
  i: integer;

  needsinit1: boolean;
begin
  needsinit1:=true;

  for i:=0 to assemblescreen.Lines.Count-1 do
    if trim(assemblescreen.lines[i])='luacall(openLuaServer(''CELUASERVER''))' then
      needsinit1:=false;

  if needsinit1 then
  begin
    luaserverinit:=tstringlist.create;
    if processhandler.is64bit then
      luaserverinit.add('loadlibrary(luaclient-x86_64.dll)')
    else
      luaserverinit.add('loadlibrary(luaclient-i386.dll)');

    luaserverinit.add('luacall(openLuaServer(''CELUASERVER''))');
    luaserverinit.add('globalalloc(luainit, 128)');
    luaserverinit.add('globalalloc(LuaFunctionCall, 128)');
    luaserverinit.add('label(luainit_exit)');
    if processhandler.is64bit then
      luaserverinit.add('globalalloc(luaserverinitialized, 8)')
    else
      luaserverinit.add('globalalloc(luaserverinitialized, 4)');

    luaserverinit.add('globalalloc(luaservername, 12)');
    luaserverinit.add('');
    luaserverinit.add('luaservername:');
    luaserverinit.add('db ''CELUASERVER'',0');
    luaserverinit.add('');
    luaserverinit.add('luainit:');

    if processhandler.is64Bit then
      luaserverinit.add('sub rsp,8 //local scratchspace (and alignment)');

    luaserverinit.add('cmp [luaserverinitialized],0');
    luaserverinit.add('jne luainit_exit');


    if processhandler.is64Bit then
    begin
      luaserverinit.add('sub rsp,20 //allocate 32 bytes scratchspace for CELUA_Initialize');
      luaserverinit.add('mov rcx,luaservername');
    end
    else
      luaserverinit.add('push luaservername');

    luaserverinit.add('call CELUA_Initialize //this function is defined in the luaclient dll');
    if processhandler.is64Bit then
      luaserverinit.add('add rsp,20');

    luaserverinit.add('mov [luaserverinitialized],eax');
    luaserverinit.add('luainit_exit:');
    if processhandler.is64Bit then
      luaserverinit.add('add rsp,8  //undo local scratchspace ');

    luaserverinit.add('ret');
    luaserverinit.add('');

    luaserverinit.add('LuaFunctionCall:');
    if processhandler.is64bit then
    begin
      luaserverinit.add('sub rsp,8 //private scratchspace for this function');
      luaserverinit.add('mov [rsp+10],rcx //save address with function into pre-allocated scratchspace');
      luaserverinit.add('mov [rsp+18],rdx //save integer val');
      luaserverinit.add('sub rsp,20 //allocate 32 bytes of "shadow space" for the callee (not needed here, but good practice) ');
    end
    else
    begin
      luaserverinit.add('push ebp');
      luaserverinit.add('mov ebp,esp');
    end;
    luaserverinit.add('call luainit');

    if processhandler.is64bit then
    begin
      luaserverinit.add('add rsp,20');
      luaserverinit.add('mov rcx,[esp+10] //restore address of function');
      luaserverinit.add('mov rdx,[esp+18] //restore value');
    end;
    luaserverinit.add('');

    if processhandler.is64Bit then
    begin
      luaserverinit.add('sub rsp,20');
      luaserverinit.add('call CELUA_ExecuteFunction //this function is defined in the luaclient dll');
      luaserverinit.add('add rsp,20');
      luaserverinit.add('add rsp,8 //undo scratchpace (alignment fix) you can also combine it into add rsp,28');
      luaserverinit.add('ret');
    end
    else
    begin
      luaserverinit.add('push [ebp+c]');
      luaserverinit.add('push [ebp+8]');
      luaserverinit.add('call CELUA_ExecuteFunction');
      luaserverinit.add('pop ebp');
      luaserverinit.add('ret 8');
    end;

    luaserverinit.add('//luacall call example:');
    if processhandler.is64bit then
    begin
      luaserverinit.add('//Make sure rsp is aligned on a 16-byte boundary when calling this function');
      luaserverinit.add('//mov rcx, addresstostringwithfunction //(The lua function will have access to the variable passed by name "parameter")');
      luaserverinit.add('//mov rdx, integervariableyouwishtopasstolua');
      luaserverinit.add('//sub rsp,20');
      luaserverinit.add('//call LuaFunctionCall');
      luaserverinit.add('//add rsp,20');
      luaserverinit.add('//When done RAX will contain the result of the lua function');
    end
    else
    begin
      luaserverinit.add('//push integervariableyouwishtopasstolua');
      luaserverinit.add('//push addresstostringwithfunction  //(The lua function will have access to the variable passed by name "parameter")');
      luaserverinit.add('//call LuaFunctionCall');
      luaserverinit.add('//When done EAX will contain the result of the lua function');
    end;




    for i:=0 to luaserverinit.count-1 do
      assemblescreen.Lines.Insert(0+i, luaserverinit[i]);

    luaserverinit.free;
  end;





end;

procedure TfrmAutoInject.Coderelocation1Click(Sender: TObject);
var starts,stops: string;
    start,stop,current: ptrUint;
    x: ptrUint;
    i,j: integer;

    labels: tstringlist;
    output: tstringlist;
    s: string;

    a,b: string;
    prev: ptrUint;

    ok: boolean;

begin
{$ifndef standalonetrainerwithassembler}

  starts:=inttohex(memorybrowser.disassemblerview.SelectedAddress,8);
  stops:=inttohex(memorybrowser.disassemblerview.SelectedAddress+128,8);

  if inputquery(rsStartAddress+':', rsCodeRelocationTemplate, starts) then
  begin
    start:=StrToQWordEx('$'+starts);
    if inputquery(rsEndAddressLastBytesAreIncludedIfNecesary, rsCodeRelocationTemplate, stops) then
    begin
      stop:=StrToQWordEx('$'+stops);

      output:=tstringlist.Create;
      labels:=tstringlist.create;
      labels.Duplicates:=dupIgnore;
      labels.Sorted:=true;
      
      output.add('alloc(newmem,'+inttostr(abs(integer(stop-start))*2)+')');
      output.add('');
      output.add('newmem:');


      try
        current:=start;

        while current<stop do
        begin
          prev:=current;
          s:=disassemble(current);
          i:=posex('-',s);
          i:=posex('-',s,i+1);
          s:=copy(s,i+2,length(s));

          i:=pos(' ',s);
          a:=copy(s,1,i-1);
          b:=copy(s,i+1,length(s));


          if length(a)>1 then
          begin
            if (lowercase(a)='loop') or (lowercase(a[1])='j') or (lowercase(a)='call') then
            begin
              try
                x:=symhandler.getAddressFromName(b);
                if (x>=start) and (x<=stop) then
                begin
                  labels.Add('orig_'+inttohex(x,8));
                  s:=a+' orig_'+inttohex(x,8);
                end;
              except
                //nolabel
              end;
            end;
          end;

          output.add('orig_'+inttohex(prev,8)+':');
          output.add(s);
        end;

        labels.Sort;
        //now clean up output so that the result is a readable program
        for i:=0 to labels.Count-1 do
          output.Insert(2+i,'label('+labels[i]+')');

        output.Insert(2+labels.Count,'');

        i:=2+labels.Count+1;
        while i<output.Count do
        begin
          if pos('orig_',output[i])>0 then
          begin
            //determine if it's valid or not
            ok:=false;
            for j:=0 to labels.Count-1 do
              if labels[j]+':'=output[i] then
              begin
                ok:=true;
                break;
              end;

            if not ok then
              output.Delete(i)
            else
            begin
              output.Insert(i,'');
              inc(i,2);
            end;
          end
          else inc(i);
        end;

        assemblescreen.Lines.AddStrings(output);

      finally
        output.free;
      end;

    end;

  end;
{$endif}
end;

procedure TfrmAutoInject.New1Click(Sender: TObject);
var i: integer;
begin
{$ifndef standalonetrainerwithassembler}

  scripts[length(scripts)-1].script:=assemblescreen.Text;
  setlength(scripts,length(scripts)+1);

  scripts[length(scripts)-1].script:='';
  scripts[length(scripts)-1].undoscripts[0].oldscript:='';
  scripts[length(scripts)-1].currentundo:=0;

  assemblescreen.Text:='';


  if length(scripts)=2 then //first time new
  begin
    tlist.AddTab('Script 1');
    tlist.Visible:=true;
  end;

  i:=tlist.AddTab('Script '+inttostr(length(scripts)));
  tlist.SelectedTab:=i;
  oldtabindex:=i;
{$endif}
end;

procedure tfrmautoinject.tlistOnTabChange(sender: TObject; oldselection: integer);
begin
{$ifndef standalonetrainerwithassembler}

  scripts[oldselection].script:=assemblescreen.text;
  scripts[oldselection].filename:=opendialog1.FileName;

  assemblescreen.text:=scripts[tlist.SelectedTab].script;
  opendialog1.FileName:=scripts[tlist.SelectedTab].filename;

  oldtabindex:=tlist.SelectedTab;

  assemblescreen.ClearUndo;

{$endif}
end;

procedure tfrmAutoInject.gutterclick(Sender: TObject; X, Y, Line: integer; mark: TSynEditMark);
begin
  if assemblescreen.Lines.Count>line then
  begin
    assemblescreen.CaretY:=line;
    assemblescreen.CaretX:=0;
    assemblescreen.SelectLine(true);
  end;
end;



procedure TfrmAutoInject.FormCreate(Sender: TObject);
var x: array of integer;
    reg: tregistry;
begin


  {$ifndef standalonetrainerwithassembler}

  setlength(scripts,1);
  scripts[0].currentundo:=0;
  oldtabindex:=0;
{  assemblescreen.SelStart:=0;
  assemblescreen.SelLength:=0; }


  AAHighlighter:=TSynAASyn.Create(self);
  CPPHighlighter:=TSynCppSyn.create(self);
  LuaHighlighter:=TSynLuaSyn.Create(self);

  assembleSearch:=TSyneditSearch.Create;

  tlist:=TTablist.Create(self);
  tlist.height:=20;
  tlist.Align:=alTop;
  tlist.Visible:=false;
  tlist.OnTabChange:=tlistOnTabChange;

  tlist.Parent:=panel2;


  assemblescreen:=TSynEdit.Create(self);
  assemblescreen.Highlighter:=AAHighlighter;
  assemblescreen.Options:=SYNEDIT_DEFAULT_OPTIONS - [eoScrollPastEol]+[eoTabIndent];
  assemblescreen.Font.Quality:=fqDefault;
  assemblescreen.WantTabs:=true;
  assemblescreen.TabWidth:=4;


  assemblescreen.Gutter.MarksPart.Visible:=false;
  assemblescreen.Gutter.Visible:=true;
  assemblescreen.Gutter.LineNumberPart.Visible:=true;
  assemblescreen.Gutter.LeftOffset:=1;
  assemblescreen.Gutter.RightOffset:=1;

  assemblescreen.Align:=alClient;
  assemblescreen.PopupMenu:=PopupMenu1;
  assemblescreen.Parent:=panel2;

  assemblescreen.Gutter.OnGutterClick:=gutterclick;

  assemblescreen.name:='Assemblescreen';
  assemblescreen.Text:='';

  assemblescreen.OnChange:=assemblescreenchange;

  setlength(x,0);
  loadformposition(self,x);

  reg:=tregistry.create;
  try
    if reg.OpenKey('\Software\Cheat Engine\Auto Assembler\',false) then
    begin
      if reg.valueexists('Font.name') then
        assemblescreen.Font.Name:=reg.readstring('Font.name');

      if reg.valueexists('Font.size') then
        assemblescreen.Font.size:=reg.ReadInteger('Font.size');

      if reg.valueexists('Font.quality') then
        assemblescreen.Font.quality:=TFontQuality(reg.ReadInteger('Font.quality'));

      if reg.valueexists('Show Line Numbers') then
        assemblescreen.Gutter.linenumberpart.visible:=reg.ReadBool('Show Line Numbers');

      if reg.valueexists('Show Gutter') then
        assemblescreen.Gutter.Visible:=reg.ReadBool('Show Gutter');

      if reg.valueexists('smart tabs') then
        if reg.ReadBool('smart tabs') then assemblescreen.Options:=assemblescreen.options+[eoSmartTabs];

      if reg.valueexists('tabs to spaces') then
        if reg.ReadBool('tabs to spaces') then assemblescreen.Options:=assemblescreen.options+[eoTabsToSpaces];

      if reg.valueexists('tab width') then
        assemblescreen.tabwidth:=reg.ReadInteger('tab width');
    end;

  finally
    reg.free;
  end;

{$endif}
end;

procedure TfrmAutoInject.TabControl1Change(Sender: TObject);
begin

end;

procedure TfrmAutoInject.Syntaxhighlighting1Click(Sender: TObject);
begin
{$ifndef standalonetrainerwithassembler}

  Syntaxhighlighting1.checked:=not Syntaxhighlighting1.checked;
  if Syntaxhighlighting1.checked then //enable
    assemblescreen.Highlighter:=AAHighlighter
  else //disabl
    assemblescreen.Highlighter:=nil;

{$endif}
end;

procedure TfrmAutoInject.TabControl1ContextPopup(Sender: TObject;
  MousePos: TPoint; var Handled: Boolean);
begin
  //selectedtab:=TabControl1.IndexOfTabAt(mousepos.x,mousepos.y);
  //closemenu.Popup(mouse.CursorPos.X,mouse.cursorpos.Y);   
end;

procedure TfrmAutoInject.Close1Click(Sender: TObject);
var i: integer;
begin
{$ifndef standalonetrainerwithassembler}


  if messagedlg(Format(rsAreYouSureYouWantToClose, [tlist.TabText[selectedtab]]), mtConfirmation, [mbyes, mbno], 0)=mryes then
  begin
    scripts[oldtabindex].script:=assemblescreen.text; //save current script
    tlist.RemoveTab(selectedtab);

    for i:=selectedtab to length(scripts)-2 do
      scripts[i]:=scripts[i+1];

    setlength(scripts,length(scripts)-1);

    if oldtabindex=selectedtab then //it was the current one
    begin
      oldtabindex:=length(scripts)-1;
      tlist.SelectedTab:=oldtabindex;
      assemblescreen.text:=scripts[oldtabindex].script;
      assemblescreen.OnChange(assemblescreen);
    end;

    if (length(scripts)=1) then
    begin
      tlist.RemoveTab(0);
      tlist.Visible:=false;
    end;
//    tabcontrol1.tabs[selectedtab]

  end;
{$endif}
end;

procedure TfrmAutoInject.injectscript(createthread: boolean);
var i: integer;
    setenvscript: tstringlist;
    CEAllocArray: TCEAllocArray;
    callscriptscript: tstringlist;

    totalmem: dword;
    totalwritten: dword;
    address: pointer;
    mi: TModuleInfo;
    hasjustloadedundercdll: boolean;

    aawindowwithstub: tfrmautoinject;
   // setenv_done: dword;
//    setenv_done_value: dword;
    s: string;

    ignore: dword;
    th: thandle;
begin
{$ifndef standalonetrainerwithassembler}
 {
 obsolete
  //this will inject the script dll and generate a assembler script the user can use to call the script
  //first set the environment var for uc_home
  s:=assemblescreen.text;
  if not symhandler.getmodulebyname('undercdll.dll',mi) then
  begin
    //dll was not loaded yet

    setenvscript:=tstringlist.Create;

    with setenvscript do
    begin
      add('[enable]');
      Add('alloc(envname,8)');
      add('alloc(envvar,512)');
      add('alloc(myscript,512)');

      add('envname:');
      add('db ''UC_HOME'',0');
      add('envvar:');
      add('db '''+cheatenginedir+''' ,0');
      add('myscript:');
      add('push envvar');
      add('push envname');
      add('call SetEnvironmentVariableA');
      add('ret');

      //cleanup part:
      add('[disable]');
      add('dealloc(myscript)');
      add('dealloc(envvar)');
      add('dealloc(envname)');
    end;

    setlength(CEAllocArray,1);
    if autoassemble(setenvscript,false,true,false,false,CEAllocArray) then //enabled
    begin
      for i:=0 to length(ceallocarray)-1 do
        if ceallocarray[i].varname='myscript' then
        begin
          th:=createremotethread(processhandle,nil,0,pointer(ceallocarray[i].address),nil,0,ignore);
          if th<>0 then
            waitforsingleobject(th,4000); //4 seconds max
            
          break;
        end;



      //wait done
      autoassemble(setenvscript,false,false,false,false,CEAllocArray); //disable for the deallocs
    end;

    setenvscript.free;


    injectdll(cheatenginedir+'undercdll.dll','');
    symhandler.reinitialize;
    hasjustloadedundercdll:=true;
  end else hasjustloadedundercdll:=false;

  //now allocate memory for the script and write it to there
  totalmem:=length(assemblescreen.text);
  address:=VirtualAllocEx(processhandle,nil,totalmem+512,mem_commit,page_execute_readwrite);
  if address=nil then raise exception.create('Failed allocating memory for the script');
  if not WriteProcessMemory(processhandle,address,@s[1],totalmem,totalwritten) then
    raise exception.create('failed writing the script to the process');



  callscriptscript:=tstringlist.create;
  try
    with callscriptscript do
    begin
      add('label(result)');
      add(inttohex((ptrUint(address)+totalmem+$20) - (ptrUint(address) mod $10),8)+':');
      add('pushfd');
      add('pushad');
      add('push '+inttohex(ptrUint(address),8));
      add('call underc_executescript');
      add('mov [result],eax');
      add('popad');
      add('popfd');
      add('mov eax,[result]');
      add('ret');
      add('result:');
      add('dd 0');
    end;

    if hasjustloadedundercdll then
    begin
      //lets wait before injecting the callscript script
      symhandler.waitforsymbolsloaded;
      if not symhandler.getmodulebyname('undercdll.dll',mi) then
        raise exception.Create('Failure loading undercdll');
    end;
    if not autoassemble(callscriptscript,false,true,false,false,CEAllocArray) then raise exception.Create('Failed creating calling stub for script located at address '+inttohex(ptrUint(address),8));
  finally
    callscriptscript.free;
  end;

  aawindowwithstub:=tfrmautoinject.create(memorybrowser);
  with aawindowwithstub.assemblescreen.Lines do
  begin
    if createthread then
    begin
      add('createthread(myscript)');
      add('alloc(myscript,256)');
      add('myscript:');
    end;

    add('//Call this code to execute the script from assembler');
    add('call '+inttohex((ptrUint(address)+totalmem+$20) - (ptrUint(address) mod $10),8));
    add('');
    add('//eax==0 when successfully executed');
    add('//''call underc_geterror'' to get a pointer to the last generated error buffer');

    if createthread then
      add('ret //interesing thing with createthread is that the return param points to exitthread');
  end;
  aawindowwithstub.show;
     }
{$endif}
end;



procedure TfrmAutoInject.Injectincurrentprocess1Click(Sender: TObject);
begin
  injectscript(false);


end;

procedure TfrmAutoInject.Injectintocurrentprocessandexecute1Click(
  Sender: TObject);
begin
  injectscript(true);
end;

procedure TfrmAutoInject.Cut1Click(Sender: TObject);
begin
  assemblescreen.CutToClipboard;
end;

procedure TfrmAutoInject.Copy1Click(Sender: TObject);
begin
  assemblescreen.CopyToClipboard;
end;

procedure TfrmAutoInject.Paste1Click(Sender: TObject);
begin
  assemblescreen.PasteFromClipboard;
end;

procedure TfrmAutoInject.Find1Click(Sender: TObject);
begin
  if finddialog1.Execute then
    mifindNext.visible:=true;

end;

procedure TfrmAutoInject.FindDialog1Find(Sender: TObject);
begin
  //scan the text for the given text
  assemblescreen.SearchReplace(finddialog1.FindText,'',[]);

  FindDialog1.close;
end;

//follow is just a emergency fix since undo is messed up. At least it's better than nothing
procedure TfrmAutoInject.AAPref1Click(Sender: TObject);
var reg: tregistry;
begin
  with TfrmAAEditPrefs.create(self) do
  begin
    try
      if execute(assemblescreen) then
      begin
        //save these settings
        reg:=tregistry.create;
        try
          if reg.OpenKey('\Software\Cheat Engine\Auto Assembler\',true) then
          begin
            reg.WriteString('Font.name', assemblescreen.Font.Name);
            reg.WriteInteger('Font.size', assemblescreen.Font.size);
            reg.WriteInteger('Font.quality', integer(assemblescreen.Font.Quality));



            //assemblescreen.Font.

            reg.WriteBool('Show Line Numbers', assemblescreen.Gutter.linenumberpart.visible);
            reg.WriteBool('Show Gutter', assemblescreen.Gutter.Visible);

            reg.WriteBool('smart tabs', eoSmartTabs in assemblescreen.Options);
            reg.WriteBool('tabs to spaces', eoTabsToSpaces in assemblescreen.Options);
          end;

        finally
          reg.free;
        end;
      end;
    finally
      free;
    end;
  end;
end;

procedure TfrmAutoInject.FormDestroy(Sender: TObject);
begin
  //if editscript or editscript2 then
  begin
    saveformposition(self,[]); 

  end;
end;

procedure TfrmAutoInject.Undo1Click(Sender: TObject);
begin
  assemblescreen.Undo;
end;

procedure TfrmAutoInject.menuFullInjectionClick(Sender: TObject);
  var address: string;
      originalcode: array of string;
      originalbytes: array of byte;
      codesize: integer;
      a: ptrUint;
      br: dword;
      c: ptrUint;
      x: string;
      i,j,k: integer;
      injectnr: integer;
      nr: string; // injectnr as string
      aobString: string;
      p: integer;

      enablepos: integer;
      disablepos: integer;
      initialcode: tstringlist;
      enablecode: tstringlist;
      disablecode: tstringlist;

      mi: TModuleInfo;

      haveModule: boolean;
      originalAddress: ptrUint;
      AddressString: string;
      maxBytesSize: integer;
      addressList: tstringlist;
      bytesList: tstringlist;
      codeList: tstringlist;
      startIndex: integer;

      injectFirstLine: Integer;
      injectLastLine: Integer;
      dline: TDisassemblyLine;
      ddBytes: string;
begin
  {$ifndef standalonetrainerwithassembler}
    // now heavily modified code from "Code injection" menu
    a:=memorybrowser.disassemblerview.SelectedAddress;

    mi.baseaddress := 0;
    haveModule := symhandler.getmodulebyaddress(a,mi);
    if haveModule then
    begin
      address:='"'+mi.modulename+'"+'+inttohex(a-mi.baseaddress,1);
    end
    else
      address:=inttohex(a,8);

    if inputquery(rsCodeInjectTemplate, rsOnWhatAddressDoYouWantTheJump, address) then
    begin
      try
        a:=StrToQWordEx('$'+address);
      except
        a:=symhandler.getaddressfromname(address);
      end;
      c:=a;
      injectnr:=0;
      for i:=0 to assemblescreen.Lines.Count-1 do
      begin
        j:=pos('alloc(newmem',lowercase(assemblescreen.lines[i]));
        if j<>0 then
        begin
          x:=copy(assemblescreen.Lines[i],j+12,length(assemblescreen.Lines[i]));
          x:=copy(x,1,pos(',',x)-1);
          try
            k:=strtoint(x);
            if injectnr<=k then
              injectnr:=k+1;
          except
            inc(injectnr);
          end;
        end;
      end;
      if injectnr = 0 then nr := '' else nr := sysutils.IntToStr(injectnr);


      // disassemble the old code, simply for putting original code in the script
      // and for the bytes we assert must be there and will replace
      setlength(originalcode,0);
      codesize:=0;

      while codesize<5 do
      begin
        setlength(originalcode,length(originalcode)+1);
        originalcode[length(originalcode)-1]:=disassemble(c,x);
        i:=posex('-',originalcode[length(originalcode)-1]);
        i:=posex('-',originalcode[length(originalcode)-1],i+1);
        originalcode[length(originalcode)-1]:=copy(originalcode[length(originalcode)-1],i+2,length(originalcode[length(originalcode)-1]));
        codesize:=c-a;
      end;

      setlength(originalbytes,codesize);
      ReadProcessMemory(processhandle, pointer(a), @originalbytes[0], codesize, br);


      // same as menu option "Cheat Engine framework code", make sure we
      // have enable and disable
      getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);

      if enablepos=-1 then //-2 is 2 or more, so bugged, and >=0 is has one
      begin
        assemblescreen.Lines.Insert(0,'[ENABLE]');
        assemblescreen.Lines.Insert(1,'');
      end;

      if disablepos=-1 then
      begin
        assemblescreen.Lines.Add('[DISABLE]');
        assemblescreen.Lines.Add('');
      end;


      initialcode:=tstringlist.Create;
      enablecode:=tstringlist.Create;
      disablecode:=tstringlist.Create;
      addressList:=tstringlist.Create;
      bytesList:=tstringlist.Create;
      codeList:=tstringlist.Create;

      try
        aobString:='';
        for i:=0 to length(originalbytes)-1 do
        begin
          if i > 0 then
            aobString := aobString + ' ';
          aobString := aobString + inttohex(originalbytes[i], 2);
        end;

        with initialcode do
        begin
          add('define(address' + nr + ',' + address + ')');
          add('define(bytes' + nr + ',' + aobString + ')');
          add('');
        end;

        with enablecode do
        begin
          add('assert(address'+nr+',bytes'+nr+')');
          if processhandler.is64bit then
            add('alloc(newmem' + nr + ',$1000,' + address + ')')
          else
            add('alloc(newmem' + nr + ',$1000)');
          add('');
          add('label(code'+nr+')');
          add('label(return'+nr+')');
          add('');
          add('newmem'+nr+':');

          add('');
          add('code'+nr+':');
          for i:=0 to length(originalcode)-1 do
            add('  '+originalcode[i]);
          add('  jmp return'+nr+'');

          add('');
          add('address'+nr+':');
          add('  jmp code'+nr+'');
          while codesize>5 do
          begin
            add('  nop');
            dec(codesize);
          end;

          add('return'+nr+':');
          add('');
        end;

        with disablecode do
        begin
          add('address'+nr+':');
          add('  db bytes'+nr);
          for i:=0 to length(originalcode)-1 do
            add('  // ' + originalcode[i]);
          add('');
          add('dealloc(newmem'+nr+')');
        end;


        // add initial defines before enable
        getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);
        p:=0;
        if (enablepos>0) then
          p:=enablepos;
        for i:=initialcode.Count-1 downto 0 do
          assemblescreen.Lines.Insert(p,initialcode[i]);

        // add enable lines before disable
        getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);
        p:=assemblescreen.lines.Count-1;
        if(disablepos>0) then
          p:=disablepos;
        for i:=enablecode.Count-1 downto 0 do
          assemblescreen.Lines.Insert(p,enablecode[i]);

        // add disable lines at very end
        for i:=0 to disablecode.Count-1do
          assemblescreen.Lines.Add(disablecode[i]);

        // finally add comment at the beginning
        assemblescreen.Lines.Insert(0,'{ Game   : ' + copy(mainform.ProcessLabel.Caption, pos('-', mainform.ProcessLabel.Caption) + 1, length(mainform.ProcessLabel.Caption)));
        assemblescreen.Lines.Insert(1,'  Version: ');
        assemblescreen.Lines.Insert(2,'  Date   : ' + FormatDateTime('YYYY-MM-DD', Now));
        assemblescreen.Lines.Insert(3,'  Author : ' + UserName);
        assemblescreen.Lines.Insert(4,'');
        assemblescreen.Lines.Insert(5,'  This script does blah blah blah');
        assemblescreen.Lines.Insert(6,'}');
        assemblescreen.Lines.Insert(7,'');

        // now we disassemble quite a bit more code for comments at the
        // bottom so someone can easily find the code again if the game
        // is updated
        assemblescreen.Lines.Add('');
        assemblescreen.Lines.Add('{');
        assemblescreen.Lines.Add('// ORIGINAL CODE - INJECTION POINT: ' + address);
        assemblescreen.Lines.Add('');

        injectFirstLine := 0;
        injectLastLine := 0;
        maxBytesSize := 0;
        dline.Init(a - 128, mi);

        while dline.Address < (a + 128) do
        begin
          if (dline.Address < a) and ((dline.Address + dline.Size) > a) then dline.Shorten((dline.Address + dline.Size) - a);
          addressList.Add(dline.AddressString);
          ddBytes := dline.GetHexBytes;
          maxBytesSize := Max(maxBytesSize, Length(ddBytes));
          bytesList.Add(ddBytes);
          codeList.Add(dline.Code);
          if (dline.Address >= a) and (injectFirstLine <= 0) then injectFirstLine := addressList.Count - 1;
          if (dline.Address < a + codesize) then injectLastLine := addressList.Count - 1;
          dline.Init(dline.Address + dline.Size, mi);
        end;
        for i := injectFirstLine - 10 to injectLastLine + 10 do
        begin
          if i = injectFirstLine then assemblescreen.Lines.Add('// ---------- INJECTING HERE ----------');
          assemblescreen.Lines.Add(addressList[i] + ': ' + PadRight(bytesList[i],maxBytesSize) + ' - ' + codeList[i]);
          if i = injectLastLine then assemblescreen.Lines.Add('// ---------- DONE INJECTING  ----------');
        end;
        assemblescreen.Lines.Add('}');
      finally
        initialcode.free;
        enablecode.free;
        disablecode.Free;
        addressList.Free;
        bytesList.Free;
        codeList.Free;
      end;

    end;
  {$endif}
end;

procedure TfrmAutoInject.menuAOBInjectionClick(Sender: TObject);
  var address: string;
    a: ptrUint;                     // pointer to injection point
    originalcode: array of string;  // disassembled code we're replacing
    originalbytes: array of byte;   // bytes we're replacing
    codesize: integer;              // # of bytes we're replacing
    aobString: string;              // hex bytes we're replacing
    injectnr: integer;              // # of this injection (multiple can be in 1 script)
    nr: string;                     // injectnr as string

    // lines where [ENABLE] and [DISABLE] are
    enablepos: integer;
    disablepos: integer;

    // temp variables
    br: dword;
    c: ptrUint;
    x: string;
    i,j,k: integer;
    p: integer;

    // lines of code to inject in certain places
    initialcode: tstringlist;
    enablecode: tstringlist;
    disablecode: tstringlist;

    // these are for code in comment at bottom
    maxBytesSize: Integer;
    addressList: TStringList;
    bytesList: TStringList;
    codeList: TStringList;
    ddBytes: String;

    haveModule: boolean;        // true if address is in a module
    mi: TModuleInfo;            // info on the module
    dline: TDisassemblyLine;    // for disassembling code in the bottom comment
    injectFirstLine: Integer;
    injectLastLine: Integer;
    resultAOB: String;
    resultOffset: Integer;
    symbolName: String;
    symbolNameWithOffset: String;
begin
{$ifndef standalonetrainerwithassembler}
  // now heavily modified code from "Code injection" menu
  a:=memorybrowser.disassemblerview.SelectedAddress;

  mi.baseaddress := 0;
  haveModule := symhandler.getmodulebyaddress(a,mi);
  if haveModule then
  begin
    address:='"'+mi.modulename+'"+'+inttohex(a-mi.baseaddress,1);
  end
  else
    address:=inttohex(a,8);

  if inputquery(rsCodeInjectTemplate, rsOnWhatAddressDoYouWantTheJump, address) then
  begin
    try
      a:=StrToQWordEx('$'+address);
    except
      a:=symhandler.getaddressfromname(address);
    end;
    c:=a;
    injectnr:=0;
    for i:=0 to assemblescreen.Lines.Count-1 do
    begin
      j:=pos('alloc(newmem',lowercase(assemblescreen.lines[i]));
      if j<>0 then
      begin
        x:=copy(assemblescreen.Lines[i],j+12,length(assemblescreen.Lines[i]));
        x:=copy(x,1,pos(',',x)-1);
        try
          k:=strtoint(x);
          if injectnr<=k then
            injectnr:=k+1;
        except
          inc(injectnr);
        end;
      end;
    end;
    if injectnr = 0 then nr := '' else nr := sysutils.IntToStr(injectnr);


    // disassemble the old code, simply for putting original code in the script
    // and for the bytes we assert must be there and will replace
    setlength(originalcode,0);
    codesize:=0;

    while codesize<5 do
    begin
      setlength(originalcode,length(originalcode)+1);
      originalcode[length(originalcode)-1]:=disassemble(c,x);
      i:=posex('-',originalcode[length(originalcode)-1]);
      i:=posex('-',originalcode[length(originalcode)-1],i+1);
      originalcode[length(originalcode)-1]:=copy(originalcode[length(originalcode)-1],i+2,length(originalcode[length(originalcode)-1]));
      codesize:=c-a;
    end;

    setlength(originalbytes, codesize);
    ReadProcessMemory(processhandle, pointer(a), @originalbytes[0], codesize, br);

    // same as menu option "Cheat Engine framework code", make sure we
    // have enable and disable
    getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);

    if enablepos=-1 then //-2 is 2 or more, so bugged, and >=0 is has one
    begin
      assemblescreen.Lines.Insert(0,'[ENABLE]');
      assemblescreen.Lines.Insert(1,'');
    end;

    if disablepos=-1 then
    begin
      assemblescreen.Lines.Add('[DISABLE]');
      assemblescreen.Lines.Add('');
    end;

    initialcode:=tstringlist.Create;
    enablecode:=tstringlist.Create;
    disablecode:=tstringlist.Create;
    addressList:=tstringlist.Create;
    bytesList:=tstringlist.Create;
    codeList:=tstringlist.Create;

    try
      //************************************************************************
      //* Now do AOBScan and get name for injection symbol
      //************************************************************************
      resultAOB := GetUniqueAOB(mi, a, codesize, resultOffset);
      symbolName := 'INJECT' + nr;
      if not inputquery(rsCodeInjectTemplate, rsWhatIdentifierDoYouWantToUse, symbolName) then symbolName := 'INJECTION_POINT';
      if resultOffset <> 0 then
        symbolNameWithOffset := symbolName + '+' + IntToHex(resultOffset, 2)
      else
        symbolNameWithOffset := symbolName;

      aobString:='';
      for i:=0 to length(originalbytes)-1 do
      begin
        if i > 0 then
          aobString := aobString + ' ';
        aobString := aobString + IntToHex(originalbytes[i], 2);
      end;

      with enablecode do
      begin
        if (mi.baseAddress > 0) then
          add('aobscanmodule(' + symbolName + ',' + mi.modulename + ',' + resultAOB + ') // should be unique')
        else
          add('aobscan(' + symbolName + ',' + resultAOB + ') // should be unique');

        if processhandler.is64bit then
          add('alloc(newmem' + nr + ',$1000,' + address + ')')
        else
          add('alloc(newmem' + nr + ',$1000)');
        add('');
        add('label(code'+nr+')');
        add('label(return'+nr+')');
        add('');
        add('newmem'+nr+':');

        add('');
        add('code' + nr + ':');
        for i:=0 to length(originalcode) - 1 do
          add('  ' + originalcode[i]);
        add('  jmp return'+nr+'');

        add('');
        add(symbolNameWithOffset + ':');
        add('  jmp code' + nr + '');
        for i := 6 to codesize do
          add('  nop');
        add('return' + nr + ':');
        add('registersymbol(' + symbolName + ')');
        add('');
      end;

      with disablecode do
      begin
        add(symbolNameWithOffset+':');
        add('  db ' + aobString);
        add('');
        add('unregistersymbol(' + symbolName + ')');
        add('dealloc(newmem'+nr+')');
      end;


      // add initial defines before enable
      getenableanddisablepos(assemblescreen.lines,enablepos,disablepos);
      p:=0;
      if (enablepos>0) then
        p:=enablepos;
      for i:= initialcode.Count-1 downto 0 do
        assemblescreen.Lines.Insert(p, initialcode[i]);

      // add enable lines before disable
      getenableanddisablepos(assemblescreen.lines, enablepos, disablepos);
      p := assemblescreen.lines.Count - 1;
      if(disablepos > 0) then
        p := disablepos;
      for i:= enablecode.Count - 1 downto 0 do
        assemblescreen.Lines.Insert(p,enablecode[i]);

      // add disable lines at very end
      for i:= 0 to disablecode.Count - 1 do
        assemblescreen.Lines.Add(disablecode[i]);

      // add template comment at the beginning
      assemblescreen.Lines.Insert(0,'{ Game   : ' + copy(mainform.ProcessLabel.Caption, pos('-', mainform.ProcessLabel.Caption) + 1, length(mainform.ProcessLabel.Caption)));
      assemblescreen.Lines.Insert(1,'  Version: ');
      assemblescreen.Lines.Insert(2,'  Date   : ' + FormatDateTime('YYYY-MM-DD', Now));
      assemblescreen.Lines.Insert(3,'  Author : ' + UserName);
      assemblescreen.Lines.Insert(4,'');
      assemblescreen.Lines.Insert(5,'  This script does blah blah blah');
      assemblescreen.Lines.Insert(6,'}');
      assemblescreen.Lines.Insert(7,'');

      // now we disassemble quite a bit more code for comments at the
      // bottom so someone can easily find the code again if the game
      // is updated
      assemblescreen.Lines.Add('');
      assemblescreen.Lines.Add('{');
      assemblescreen.Lines.Add('// ORIGINAL CODE - INJECTION POINT: ' + address);
      assemblescreen.Lines.Add('');

      injectFirstLine := 0;
      injectLastLine := 0;
      maxBytesSize := 0;
      dline.Init(a - 128, mi);

      while dline.Address < (a + 128) do
      begin
        // see if we overshot our injection point
        if (dline.Address < a) and ((dline.Address + dline.Size) > a) then dline.Shorten((dline.Address + dline.Size) - a);
        addressList.Add(dline.AddressString);
        ddBytes := dline.GetHexBytes;
        maxBytesSize := Max(maxBytesSize, Length(ddBytes));
        bytesList.Add(ddBytes);
        codeList.Add(dline.Code);
        if (dline.Address >= a) and (injectFirstLine <= 0) then injectFirstLine := addressList.Count - 1;
        if (dline.Address < a + codesize) then injectLastLine := addressList.Count - 1;
        dline.Init(dline.Address + dline.Size, mi);
      end;
      for i := injectFirstLine - 10 to injectLastLine + 10 do
      begin
        if i = injectFirstLine then assemblescreen.Lines.Add('// ---------- INJECTING HERE ----------');
        assemblescreen.Lines.Add(addressList[i] + ': ' + PadRight(bytesList[i],maxBytesSize) + ' - ' + codeList[i]);
        if i = injectLastLine then assemblescreen.Lines.Add('// ---------- DONE INJECTING  ----------');
      end;
      assemblescreen.Lines.Add('}');
    finally
      initialcode.free;
      enablecode.free;
      disablecode.Free;
      addressList.Free;
      bytesList.Free;
      codeList.Free;
    end;
  end;
{$ENDIF}
end;

function TfrmAutoInject.GetUniqueAOB(mi: TModuleInfo; address: ptrUint; codesize: Integer; var resultOffset: Integer) : string;
  var
    size: integer;
    dline: TDisassemblyLine;

    maskFlags : Array of Boolean; // true if we need to use **
    maskBytes : Array of Byte;    // bytes around code we're replacing
    flags : Array of Boolean;     // temp for single instruction
    br : dword;
    aob : string;
    i, j, k : Integer;

    // variables used for memory scan
    ms : TMemScan;
    minaddress: ptruint;
    maxaddress: ptrUint;
    foundAddress: ptrUint;
    foundCount: Integer;
    fl: TFoundList;

    instructionOffset: Integer; // offset for copying mask flags to main list from instruction list
    shortestAfter: Integer; // # of bytes, including codesize, index is 20 of course because it only counts starting at original code
    shortestBeforeIndex: Integer; // index to start at, will be 0 - 20
    shortestBeforeLength: Integer; // # of bytes, including before, original code, and possibly after

    finds: Array of TAOBFind; // for each found address has bytes to use for comparison

    // count how many found addresses match the criteria
    function CountMatches(offset: Integer; size: Integer) : Integer;
    var
        i: Integer;
        count: Integer;
        flength: Integer;
    begin
      count := 0;
      for i := 0 to Length(finds) - 1 do
      begin
        if finds[i].IsMatch(maskBytes, maskFlags, offset, offset + size - 1) then count := count + 1;
        if count > 1 then break; // short-circuit, we only care if there is more than 1
      end;
      result := count;
    end;
  begin
    size := 40 + codesize; // 20 bytes on each side of replaced code
    SetLength(maskBytes, size); // setup array for bytes around code we're looking for
    SetLength(maskFlags, size); // flags on whether they need masking or not
    ReadProcessMemory(processhandle, pointer(address - 20), @maskBytes[0], size, br);

    // get AOB to search for using the code we're replacing
    aob := '';
    for i := 0 to codesize - 1 do
    begin
      if (i > 0) then aob := aob + ' ';
      aob := aob + inttohex(maskBytes[20 + i], 2);
    end;

    // Do AOBSCAN for replaced code
    ms := tmemscan.create(nil);
    ms.parseProtectionflags('');
    ms.onlyone := false;
    if mi.baseaddress > 0 then
    begin
      minaddress := mi.baseaddress;
      maxaddress := mi.baseaddress + mi.basesize;
    end else
    begin
      minaddress := 0;
      {$ifdef cpu64}
      if processhandler.is64Bit then
        maxaddress := qword($7fffffffffffffff)
      else
      {$endif}
      begin
        if Is64bitOS then
          maxaddress := $ffffffff
        else
          maxaddress := $7fffffff;
      end;
    end;
    ms.OnlyOne := false;
    fl := TFoundlist.create(nil, ms, '');
    ms.FirstScan(soExactValue, vtByteArray, rtTruncated, aob, '', minaddress, maxaddress, true, false, false, true, fsmAligned, '1');
    ms.WaitTillReallyDone; //wait till it's finished scanning
    foundCount := fl.Initialize(vtByteArray, nil);

    // if there's only one result, the code's AOB is fine
    if foundCount = 1 then
    begin
      resultOffset := 0;
      result := aob;
      exit;
    end;

    // now we need to narrow it down.  start by disassembling around the injection
    // point and creating flags on which bytes need to be masked because they are
    // probably pointers to code or data that may frequently change
    dline.Init(address - 128, mi);

    // 0 to 19: address - 20 to address - 1: before
    // 20 to 20 + codesize - 1): original code
    // 20 + codesize to 39 + codesize: after
    while (dline.Address <= (address + 20)) do
    begin
      // if we overran injection address, shorten to 'db X X X' statement
      if (dline.Address < address) and ((dline.Address + dline.Size) > address) then dline.Shorten(address - dline.Address);
      j := (dline.Address + 20) - address;
      k := j + dline.Size - 1;
      if (k >= 0) and (j <= (codesize + 39)) then
      begin
        // we're in range, get mask flags
        flags := dline.GetMaskFlags();
        for i := j to k do
        begin
          instructionOffset := i - j;
          if (i >= 0) and (i <= 39 + codesize) and (instructionOffset >= 0) then
          begin
            if (i < 20) or (i >= (20 + codesize)) then
              maskFlags[i] := flags[instructionOffset]
            else
              maskFlags[i] := false;
          end;
        end;
      end;

      dline.Init(dline.Address + dline.Size, mi); // next instruction
    end;

    // prep 'finds' array to read memory and make searching easier
    SetLength(finds, foundCount);
    for i := 0 to foundCount - 1 do
    begin
      finds[i].Init(fl.GetAddress(i), codesize);
    end;

    // find shortest way to get a single match starting at original code
    shortestAfter := 100;
    shortestBeforeIndex := 19;
    shortestBeforeLength := 100;
    for i := codesize + 1 to codesize + 20 do
    begin
      if CountMatches(20, i) = 1 then
      begin
        shortestAfter := i;
        break;
      end;
    end;

    // now for before, we step back one at a time and loop up to shortestAfter bytes
    for i := 19 downto 0 do
    begin
      // i is index, j is length (checking indices i to i+j-1
      for j := codesize + (20 - i) to Min(shortestBeforeLength - 1, Min(shortestAfter - 6, (40 + codesize) - i)) do // first round, 6 to 26
      begin
        if CountMatches(i, j) = 1 then
        begin
          shortestBeforeIndex := i;
          shortestBeforeLength := j;
          break;
        end;
      end;
    end;

    if shortestAfter < shortestBeforeLength then
    begin
      shortestBeforeLength := shortestAfter;
      shortestBeforeIndex := 20;
    end;

    // if we can't find unique AOB, return earlier aob with error
    if shortestBeforeLength >= 100 then begin
      result := 'ERROR: Could not find unique AOB, tried code "' + aob + '"';
      exit;
    end;

    // create AOB using masking
    aob := '';
    for i := 0 to shortestBeforeLength - 1 do
    begin
      if i <> 0 then aob := aob + ' ';
      if maskFlags[i + shortestBeforeIndex] then
        aob := aob + '*'
      else
        aob := aob + IntToHex(maskBytes[i + shortestBeforeIndex], 2);
    end;

    resultOffset := 20 - shortestBeforeIndex;
    result := aob;
  end;

procedure TDisassemblyLine.Init(_address: ptrUint; _mi: TModuleInfo);
  var x:string;
      pos1:integer;
      pos2:integer;
      i:integer;
  begin
    Address := _address;
    Original := disassemble(_address, Comment);
    Size := _address - Address;
    pos1 := posex('-', Original);
    pos2 := posex('-', Original, pos1 + 1);
    OriginalAddressString := copy(Original, 1, pos1 - 2);
    OriginalHexBytes := copy(Original, pos1 + 2, (pos2 - pos1) - 3);
    Code := copy(Original, pos2 + 2, length(Original) - pos2 - 1);
    if (_mi.basesize = 0) or (_address < _mi.baseaddress) or (_address > (_mi.baseaddress + _mi.basesize)) then
      AddressString := inttohex(Address, 8)
    else
      AddressString := '"' + _mi.modulename + '"+' + inttohex(Address - _mi.baseaddress, 1);
  end;

function TDisassemblyLine.GetHexBytes() : String;
  var i: Integer;
      s: String;
  begin
    // insert spaces to bytes string will be ready for AOBSCAN
    s := OriginalHexBytes;
    while i<length(s) do
    begin
      if (copy(s, i, 1) <> ' ') then
      begin
        s := copy(s, 1, i - 1) + ' ' + copy(s, i, length(s) - i + 1);
      end;
      i := i + 3;
    end;
    result := s;
  end;

// true if it is an instruction that probably starts a procedure so we can
// start our commented code here
function TDisassemblyLine.IsStarter : Boolean;
  begin
    result := code = 'push ebp';
  end;

// true if it is an instruction that probably ends a procedure so we can end
// our commented code here
function TDisassemblyLine.IsEnder : Boolean;
  begin
    result := Leftstr(code, 3) = 'ret';
  end;

// true if it not an instruction (int3) that probably is not meant to be
// executed, so we know if we our outside a group of code
function TDisassemblyLine.IsValid : Boolean;
  begin
    if (OriginalHexBytes = 'CC') then
      result := false
    else
      result := true;
  end;

// array with a boolean for each byte telling if it should be masked or not
function TDisassemblyLine.GetMaskFlags : TBooleanArray;
  var
    masked : TBooleanArray;
    index : Integer;
    i, pos1, pos2 : Integer;
    part : String;
    mask : Boolean;
    count : Integer;
  begin
    SetLength(masked, Size);
    index := 0;
    pos1 := 1;
    while pos1 < Length(OriginalHexBytes) do
    begin
      pos2 := posex(' ', OriginalHexBytes, pos1 + 1);
      if pos2 <= 0 then pos2 := Length(OriginalHexBytes) + 1;
      part := copy(OriginalHexBytes, pos1, pos2 - pos1);
      mask := (Length(part) = 8) and (RightStr(part, 4) <> '0000');
      count := Length(part) div 2;
      for i := 0 to count - 1 do
        masked[i + index] := mask;
      index := index + count;
      pos1 := pos2 + 1;
    end;
    SetLength(masked, index);
    Result := masked;
  end;

procedure TDisassemblyLine.Shorten(_newSize: Integer);
  var
    i, j: Integer;
    hexbytes: String;
  begin
    // GetHexBytes() gives us the bytes split out with spaces between
    // all, this way we can write our 'db' statement and all bytes will
    // be unmasked
    hexbytes := GetHexBytes();
    OriginalHexBytes := LeftStr(hexbytes, (_newSize * 3) - 1);
    Size := (Length(OriginalHexBytes) + 1) div 3;
    Code := 'db ' + OriginalHexBytes + ' // SHORTENED TO HIT INJECTION FROM: ' + Code;
  end;

procedure TAOBFind.Init(_address: ptrUint; _codesize: Integer);
  var
    i: integer;
    br: dword; // bytes actually read
  begin
    Address := _address;
    Size := _codeSize + 40;
    SetLength(Bytes, Size);
    ReadProcessMemory(processhandle, pointer(Address - 20), @Bytes[0], Size, br);
  end;

function TAOBFind.IsMatch(var maskBytes: Array Of Byte; var maskFlags : TBooleanArray; startIndex, endIndex: Integer): Boolean;
  var
    i: Integer;
    mf: Boolean;
    mb: Byte;
    b: Byte;
  begin
    for i := startIndex to endIndex do
    begin
      if (i > 0) and (i < Length(Bytes)) then
      begin
        mf := maskFlags[i];
        mb := maskBytes[i];
        b := Bytes[i];
        if not maskFlags[i] then
        begin
          if maskBytes[i] <> Bytes[i] then
          begin
            result := false;
            exit;
          end;
        end;
      end;
    end;
    result := true;
  end;

initialization
  {$i frmautoinjectunit.lrs}

end.
