// Copyright Cheat Engine. All Rights Reserved. unit autoassembler; {$MODE Delphi} interface {$ifdef jni} uses unixporthelper, Assemblerunit, classes, symbolhandler, sysutils, NewKernelHandler, ProcessHandlerUnit, commonTypeDefs; {$else} uses {$ifdef darwin} macport, math, {$endif} {$ifdef windows} jwawindows, windows, {$endif} Assemblerunit, classes, LCLIntf,symbolhandler, symbolhandlerstructs, sysutils,dialogs,controls, CEFuncProc, NewKernelHandler ,plugin, ProcessHandlerUnit, lua, lualib, lauxlib, LuaClass, commonTypeDefs, OpenSave, SymbolListHandler, tcclib, betterControls; {$endif} type TDisableInfo=class private public allocs: TCEAllocArray; exceptions:TCEExceptionListArray; registeredsymbols: tstringlist; ccodesymbols: TSymbolListHandler; sourcecodeinfo: TSourceCodeInfo; donotfreeccodedata: boolean; allsymbols: tstringlist; //filled at the end with all known symbols (allocs, labels, kallocs, aobscan results, defines that are addresses, etc...) constructor create; destructor destroy; override; end; TMemoryrecord=pointer; function getenableanddisablepos(code:tstrings;var enablepos,disablepos: integer): boolean; procedure getEnableOrDisableScript(code: TStrings; newscript: tstrings; enablescript: boolean); function autoassemble(code: tstrings;popupmessages: boolean):boolean; overload; function autoassemble(code: Tstrings; popupmessages,enable,syntaxcheckonly, targetself: boolean; disableinfo: TDisableInfo=nil; memrec: TMemoryrecord=nil): boolean; overload; type TAutoAssemblerPrologue=procedure(code: TStrings; syntaxcheckonly: boolean) of object; type TAutoAssemblerCallback=function(parameters: string; syntaxcheckonly: boolean): string of object; type TRegisteredAutoAssemblerCommand=class command: string; callback: TAutoAssemblerCallback; end; type EAutoAssembler=class(exception); procedure RegisterAutoAssemblerCommand(command: string; callback: TAutoAssemblerCallback); procedure UnregisterAutoAssemblerCommand(command: string); function registerAutoAssemblerPrologue(m: TAutoAssemblerPrologue; postAOBSCAN: boolean=false): integer; procedure unregisterAutoAssemblerPrologue(id: integer); var oldaamessage: boolean; function autoassemble2(code: tstrings;popupmessages: boolean;syntaxcheckonly:boolean; targetself: boolean; disableinfo: TDisableInfo=nil; memrec: TMemoryRecord=nil):boolean; implementation {$ifdef jni} uses strutils, memscan, disassembler, networkInterface, networkInterfaceApi, Parsers, Globals, memoryQuery,types; {$else} uses simpleaobscanner, StrUtils, LuaHandler, memscan, disassembler, networkInterface, networkInterfaceApi, LuaCaller, SynHighlighterAA, Parsers, Globals, memoryQuery, MemoryBrowserFormUnit, MemoryRecordUnit{$ifdef windows}, vmxfunctions{$endif}, autoassemblerexeptionhandler, UnexpectedExceptionsHelper, types, autoassemblercode, System.UITypes, frmautoinjectunit, DebuggerInterfaceAPIWrapper, GDBServerDebuggerInterface; {$endif} resourcestring rsForwardJumpWithNoLabelDefined = 'Forward jump with no label defined'; rsThereIsCodeDefinedWithoutSpecifyingTheAddressItBel = 'There is code defined without specifying the address it belongs to'; rsIsNotAValidBytestring = '%s is not a valid bytestring'; rsTheBytesAtAreNotWhatWasExpected = 'The bytes at %s are not what was expected'; rsTheMemoryAtCanNotBeRead = 'The memory at +%s can not be read'; rsWrongSyntaxASSERTAddress1122335566 = 'Wrong syntax. ASSERT(address,11 22 33 ** 55 66)'; rsIsNotAValidSize = '%s is not a valid size'; rsWrongSyntaxGLOBALALLOCNameSize = 'Wrong syntax. GLOBALALLOC(name,size)'; rsCouldNotBeFound = '%s could not be found'; rsWrongSyntaxIncludeFilenameCea = 'Wrong syntax. Include(filename.cea)'; rsWrongSyntaxCreateThreadAddress = 'Wrong syntax. CreateThread(address)'; rsCouldNotBeInjected = '%s could not be injected'; rsWrongSyntaxLoadLibraryFilename = 'Wrong syntax. LoadLibrary(filename)'; rsWrongSyntaxLuaCall = 'Wrong Syntax. LuaCall(luacommand)'; rsInvalidAddressForReadMem = 'Invalid address for ReadMem'; rsInvalidSizeForReadMem = 'Invalid size for ReadMem'; rsTheMemoryAtCouldNotBeFullyRead = 'The memory at %s could not be fully read'; rsWrongSyntaxReadMemAddressSize = 'Wrong syntax. ReadMem(address,size)'; rsTheFileDoesNotExist = 'The file %s does not exist'; rsWrongSyntaxLoadBinaryAddressFilename = 'Wrong syntax. LoadBinary(address,filename)'; rsWrongSyntaxReAssemble = 'Wrong syntax. Reassemble(address)'; rsSyntaxError = 'Syntax error'; rsTheArrayOfByteCouldNotBeFound = 'The array of byte ''%s'' could not be found'; rsWrongSyntaxAOBSCANName11223355 = 'Wrong syntax. AOBSCAN(name,11 22 33 ** 55)'; rsWrongSyntaxAOBSCANMODULEName11223355 = 'Wrong syntax. AOBSCANMODULE(name, module, 11 22 33 ** 55)'; rsWrongSyntaxAOBSCANREGION = 'Wrong syntax. AOBSCANREGION(name, startaddress, stopaddress, 11 22 33 ** 55)'; rsDefineAlreadyDefined = 'Define %s already defined'; rsWrongSyntaxDEFINENameWhatever = 'Wrong syntax. DEFINE(name,whatever)'; rsSyntaxErrorFullAccessAddressSize = 'Syntax error. FullAccess(address,size)'; rsIsNotAValidIdentifier = '%s is not a valid identifier'; rsIsBeingRedeclared = '%s is being redeclared'; rsLabelIsBeingDefinedMoreThanOnce = 'label %s is being defined more than once'; rsLabelIsNotDefinedInTheScript = 'label %s is not defined in the script'; rsTheIdentifierHasAlreadyBeenDeclared = 'The identifier %s has already been declared'; rsWrongSyntaxALLOCIdentifierSizeinbytes = 'Wrong syntax. ALLOC(identifier,sizeinbytes)'; rsNeedToUseKernelmodeReadWriteprocessmemory = 'You need to use kernelmode read/writeprocessmemory if you want to use KALLOC'; rsSorryButWithoutTheDriverKALLOCWillNotFunction = 'Sorry, but without the driver KALLOC will not function'; rsWrongSyntaxKallocIdentifierSizeinbytes = 'Wrong syntax. kalloc(identifier,sizeinbytes)'; rsThisAddressSpecifierIsNotValid = 'This address specifier is not valid'; rsThisInstructionCanTBeCompiled = 'This instruction can''t be compiled'; rsErrorInLine = 'Error in line %s (%s) :%s'; rsWasSupposedToBeAddedToTheSymbollistButItIsnTDeclar = '%s was supposed to be added to the symbollist, but it isn''t declared'; rsTheAddressInCreatethreadIsNotValid = 'The address in createthread(%s) is not valid'; rsTheAddressInCreatethreadAndWaitIsNotValid = 'The address in createthreadandwait(%s) is not valid'; rsTheAddressInLoadbinaryIsNotValid = 'The address in loadbinary(%s,%s) is not valid'; rsThisCodeCanBeInjectedAreYouSure = 'This code can be injected. Are you sure?'; rsFailureToAllocateMemory = 'Failure to allocate memory'; rsNotAllInstructionsCouldBeInjected = 'Not all instructions could be injected'; rsTheFollowingKernelAddressesWhereAllocated = 'The following kernel addresses where allocated'; rsTheCodeInjectionWasSuccessfull = 'The code injection was successfull'; rsYouCanOnlyHaveOneEnableSection = 'You can only have one enable section'; rsYouCanOnlyHaveOneDisableSection = 'You can only have one disable section'; rsYouHavnTSpecifiedAEnableSection = 'You havn''t specified a enable section'; rsYouHavnTSpecifiedADisableSection = 'You havn''t specified a disable section'; rsWrongSyntaxSHAREDALLOCNameSize = 'Wrong syntax. SHAREDALLOC(name,size)'; rsAAErrorInTheStructureDefinitionOf = 'Error in the structure definition of %s at line %d'; rsAAIsAReservedWord = '%s is a reserved word'; rsAANoIdeaWhatXis = 'No idea what %s is'; rsAANoEndFound = 'No end found'; rsAATheArrayOfByteNamed = 'The array of byte named %s could not be found'; rsXCouldNotBeFound = '%s could not be found'; rsAAErrorWhileSacnningForAobs = 'Error while scanning for AOB''s : '; rsAAError = 'Error: '; rsAAModuleNotFound = 'module not found:'; rsAALuaErrorInTheScriptAtLine = 'Lua error in the script at line '; rsGoTo = 'Go to '; rsMissingExcept = 'The {$TRY} at line %d has no matching {$EXCEPT}'; rsNoPreferedRangeAllocWarning = 'None of the ALLOC statements specify a ' +'prefered address. Did you take into account that the JMP instruction is' +' going to be 14 bytes long?'; rsFailureAlloc = 'Failure allocating memory near %.8x for variable named %s'; rsFailureGettingOriginalInstruction = 'Failure getting the instruction at %x'; rsNearbyAllocationError = 'Nearby allocation error'; rsNearbyAllocationErrorMessageQuestion = 'This script uses nearby allocation' +' but it is impossible to allocate nearby %x. Please rewrite the script ' +'to function without nearby allocation. Try executing the script anyhow ' +'and allocate on a region outside reach of 2GB? (The target will crash if' +' the script was not designed with this failure in mind)'; rsFailureAssembling = 'Failure assembling %s at %.8x'; //type // TregisteredAutoAssemblerCommands = TFPGList; type TAutoAssemblerPrologues=array of TAutoAssemblerPrologue; PAutoAssemblerPrologues=^TAutoAssemblerPrologues; var registeredAutoAssemblerCommands: TList; AutoAssemblerPrologues: TAutoAssemblerPrologues; AutoAssemblerProloguesPostAOBSCAN: TAutoAssemblerPrologues; type TAllocWarn=class public preferedaddress: ptruint; procedure AllocFailedQuestion; procedure warn; end; procedure TAllocWarn.AllocFailedQuestion; var r:TModalResult; begin r:=MessageDlg(rsNearbyAllocationError, Format( rsNearbyAllocationErrorMessageQuestion, [preferedaddress]), mtError, [mbyes, mbno, mbYesToAll, mbNoToAll], 0); NearbyAllocationFailureFatal:=r in [mrNo,mrNoToAll]; if r in [mrYesToAll, mrNoToAll] then WarnOnNearbyAllocationFailure:=false; end; procedure TAllocWarn.warn; begin if MainThreadID=GetCurrentThreadId then AllocFailedQuestion else tthread.Synchronize(nil, AllocFailedQuestion); end; function registerAutoAssemblerPrologue(m: TAutoAssemblerPrologue; postAOBSCAN: boolean=false): integer; var i: integer; prologues: PAutoAssemblerPrologues; begin if postAOBSCAN then prologues:=@AutoAssemblerProloguesPostAOBSCAN else prologues:=@AutoAssemblerPrologues; for i:=0 to length(prologues^)-1 do begin if assigned(prologues^[i])=false then begin prologues^[i]:=m; result:=i+1; if postAOBSCAN then result:=-result; exit; end end; result:=length(prologues^)+1; //first one is id 1 setlength(prologues^, result); prologues^[result-1]:=m; if postAOBSCAN then result:=-result; //first one is id -1 end; procedure unregisterAutoAssemblerPrologue(id: integer); var prologues: PAutoAssemblerPrologues; begin if id=0 then exit; //does not exist if id<0 then begin prologues:=@AutoAssemblerProloguesPostAOBSCAN; id:=-id; end else prologues:=@AutoAssemblerPrologues; if id<=length(prologues^) then begin {$ifndef jni} CleanupLuaCall(TMethod(prologues^[id-1])); {$endif} prologues^[id-1]:=nil; end; end; procedure RegisterAutoAssemblerCommand(command: string; callback: TAutoAssemblerCallback); var i: integer; c:TRegisteredAutoAssemblerCommand; begin {$ifndef jni} if registeredAutoAssemblerCommands=nil then registeredAutoAssemblerCommands:=TList.Create; UnregisterAutoAssemblerCommand(command); command:=uppercase(command); c:=TRegisteredAutoAssemblerCommand.create; c.command:=command; c.callback:=callback; registeredAutoAssemblerCommands.Add(c); aa_AddExtraCommand(pchar(command)); {$endif} end; procedure UnregisterAutoAssemblerCommand(command: string); var i,j: integer; c:TRegisteredAutoAssemblerCommand; begin {$ifndef jni} command:=uppercase(command); i:=0; while inil) and (donotfreeccodedata=false) then freeandnil(ccodesymbols); if (sourcecodeinfo<>nil) and (donotfreeccodedata=false) then freeandnil(sourcecodeinfo); end; constructor TDisableInfo.create; begin setlength(Allocs,0); setlength(exceptions,0); registeredsymbols:=tstringlist.create; registeredsymbols.CaseSensitive:=false; registeredsymbols.Duplicates:=dupIgnore; ccodesymbols:=TSymbolListHandler.create; ccodesymbols.PID:=processid; allsymbols:=TStringList.create; allsymbols.CaseSensitive:=false; allsymbols.Duplicates:=dupIgnore; end; function lastChanceAllocPrefered(prefered: ptruint; size: integer; protection:dword): ptruint; var starttime: ptruint; distance: qword; address: ptruint; count: integer; begin if SystemSupportsWritableExecutableMemory=false then protection:=PAGE_READWRITE; starttime:=gettickcount64; address:=0; distance:=0; count:=0; if prefered mod systeminfo.dwAllocationGranularity>0 then prefered:=prefered-(prefered mod systeminfo.dwAllocationGranularity); while (address=0) and ((count<10) or (gettickcount640) then address:=ptrUint(virtualallocex(processhandle,pointer(prefered-distance),size, MEM_RESERVE or MEM_COMMIT,protection)); if address=0 then inc(distance, systeminfo.dwAllocationGranularity); inc(count); end; result:=address; end; procedure tokenize(input: string; tokens: tstringlist); var i: integer; a: integer; inquote: boolean=false; inquote2: boolean=false; begin tokens.clear; a:=-1; for i:=1 to length(input) do begin if inquote and (input[i]<>'''') then continue; if inquote2 and (input[i]<>'"') then continue; case input[i] of 'a'..'z','A'..'Z','0'..'9','.', '_','#','@', #128..#255: if a=-1 then a:=i; else begin if (input[i]='''') then begin if inquote then begin if a<>-1 then tokens.AddObject(copy(input,a,i-a),tobject(a)); a:=-1; inquote:=false; end else begin inquote:=true; a:=i; end; continue; end; if (input[i]='"') then begin if inquote2 then begin if a<>-1 then tokens.AddObject(copy(input,a,i-a),tobject(a)); a:=-1; inquote2:=false; end else begin inquote2:=true; a:=i; end; continue; end; if a<>-1 then tokens.AddObject(copy(input,a,i-a),tobject(a)); a:=-1; end; end; end; if a<>-1 then tokens.AddObject(copy(input,a,length(input)),tobject(a)); end; function tokencheck(input,token:string):boolean; var tokens: tstringlist; i: integer; begin tokens:=tstringlist.Create; try tokenize(input,tokens); result:=false; for i:=0 to tokens.Count-1 do if tokens[i]=token then begin result:=true; break; end; finally tokens.free; end; end; function replacetoken(input: string;token:string;replacewith:string):string; var tokens: tstringlist; i,j: integer; begin result:=input; tokens:=tstringlist.Create; try tokenize(result,tokens); for i:=tokens.Count-1 downto 0 do if tokens[i]=token then begin j:=integer(tokens.Objects[i]); result:=copy(result,1,j-1)+replacewith+copy(result,j+length(token),length(result)); end; finally tokens.free; end; end; procedure tokenizeStruct(input: string; tokens: tstringlist); //a version of tokenize using strutils to split up the strings. (I don't want to replace it yet since it is slightly different, probably better, but let's be safe for now till it's fully tested) var delims: TSysCharSet; i: integer; begin delims:=[' ']; tokens.clear; for i:=1 to WordCount(input, delims) do tokens.add(ExtractWord(i, input, delims)); end; procedure replaceStructWithDefines(code: Tstrings; linenr: integer); { parses the structure starting at the given line number. removes the structure definition from the code writes a define(xxxx,xxxxx) inplace starting from the linenumber precondition: linenr is valid and code[linenr] is indeed a STRUCT name line Structure format: struct name db ? db ? db ? db ? db ? ? ? dw ? dd ? dq ? resb 4 resb 4 resw 4 resd 2 resq 1 elementname1: db ? resw 1 dw ? elementname2: dw ? endstruct Res is an alternate to the usual "d* ?" but is specifically for no fill (Res stands for reserve) Usage res* # where # is the number of copies. This is better suited for strings which can not be defined with db ? when done the name.elementname1 and just elementname1 will be defined at the spot the structure was placed. Subsequent structure definitions can override these defines. Elements may NOT have reserved words as they will get replaced. (like assembler commands) } var currentOffset: integer; structname: string; elementname: string; i,j,k: integer; tokens: Tstringlist; elements: tstringlist; starttoken: integer; bytesize: integer; endfound: boolean; lastlinenr: integer; procedure structError(reason: string=''); var error: string; begin error:=format(rsAAErrorInTheStructureDefinitionOf, [structname, lastlinenr+1]); if reason<>'' then error:=error+' :'+reason else error:=error+'.'; raise exception.create(error); end; begin lastlinenr:=linenr; endfound:=false; structname:=trim(copy(code[linenr], 7, length(code[linenr]))); currentOffset:=0; tokens:=tstringlist.create; elements:=tstringlist.create; for i:=linenr+1 to code.count-1 do begin lastlinenr:=i; tokenizeStruct(code[i], tokens); j:=0; if tokens.count>0 then begin //first check if it's a label definition if tokens[0][length(tokens[0])]=':' then begin elementname:=copy(tokens[0], 1, Length(tokens[0])-1); if GetOpcodesIndex(elementname)<>-1 then structError(format(rsAAIsAReservedWord, [elementname])); elements.AddObject(elementname, tobject(currentOffset)); j:=1; end; //then check if it's the end of the struct if (uppercase(tokens[0])='ENDSTRUCT') or (uppercase(tokens[0])='ENDS') then begin endfound:=true; break; //all done end; end; //if it's neither a label or structure end then it's a size defining token while j < tokens.count do begin tokens[j]:=uppercase(tokens[j]); case tokens[j][1] of 'R': begin //could be res* if (length(tokens[j])=4) and (copy(tokens[j],1,3)='RES') then begin case tokens[j][4] of 'B': bytesize:=1; 'W': bytesize:=2; 'D': bytesize:=4; 'Q': bytesize:=8; else StructError; end; //now get the count inc(j); if j>=tokens.count then structError; try inc(currentOffset, bytesize*StrToInt(tokens[j])); except structerror; end; end else structerror; end; 'D': begin //could be d* ? if length(tokens[j])=2 then begin case tokens[j][2] of 'B': bytesize:=1; 'W': bytesize:=2; 'D': bytesize:=4; 'Q': bytesize:=8; else StructError; end; inc(j); if j>=tokens.count then structError; inc(currentOffset, bytesize); //check if there are more ?'s after this (in case of dw ? ? ?) while j'') and (currentline[length(currentline)]=':') then begin if (pos('+', currentline)=0) and (pos('.', currentline)=0) then begin currentline:=copy(currentline,1,length(currentline)-1); if trystrtoint64('$'+currentline, a)=false then labels.add(currentline); end; end; end; end; procedure removecomments(code: tstrings); var i,j: integer; currentline: string; instring: boolean; incomment: boolean; bracecomment: boolean; begin //remove comments instring:=false; incomment:=false; bracecomment:=false; for i:=0 to code.count-1 do begin currentline:=code[i]; instring:=false; //ce doesn't do multiline strings for j:=1 to length(currentline) do begin if incomment then begin //inside a comment, remove everything till a } is encountered if (bracecomment and ((currentline[j]='}') and (processhandler.SystemArchitecture<>archArm))) or ((not bracecomment) and (currentline[j]='*') and (jarchArm)) or ((currentline[j]='/') and (j1 then begin if currentline='@@:' then begin currentline:='RandomLabel'+chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ chr(ord('A')+random(26))+ ':'; code[i]:=currentline; code.Insert(0,'label('+copy(currentline,1,length(currentline)-1)+')'); inc(i); setlength(labels,length(labels)+1); labels[length(labels)-1]:=copy(currentline,1,length(currentline)-1); end else if currentline[length(currentline)]=':' then begin setlength(labels,length(labels)+1); labels[length(labels)-1]:=copy(currentline,1,length(currentline)-1); end; end; inc(i); end; //all label definitions have been filled in //now change @F (forward) and @B (back) to the labels in front and behind lastseenlabel:=-1; for i:=0 to code.Count-1 do begin currentline:=code[i]; if length(currentline)>1 then begin if currentline[length(currentline)]=':' then begin //find this in the array currentline:=copy(currentline,1,length(currentline)-1); for j:=(lastseenlabel+1) to length(labels)-1 do //lastseenlabel+1 since it is ordered in definition begin if uppercase(currentline)=uppercase(labels[j]) then begin lastseenlabel:=j; break; end; end; currentline:=currentline+':'; //lastseenlabel is now updated to the current pos end else if pos('@f',lowercase(currentline))>0 then //forward begin //forward label, so labels[lastseenlabel+1] if lastseenlabel+1 >= length(labels) then raise exception.Create(rsForwardJumpWithNoLabelDefined); currentline:=replacetoken(currentline,'@f',labels[lastseenlabel+1]); currentline:=replacetoken(currentline,'@F',labels[lastseenlabel+1]); end else if pos('@b',lowercase(currentline))>0 then //back begin //forward label, so labels[lastseenlabel] if lastseenlabel=-1 then raise exception.Create(rsThereIsCodeDefinedWithoutSpecifyingTheAddressItBel); currentline:=replacetoken(currentline,'@b',labels[lastseenlabel]); currentline:=replacetoken(currentline,'@B',labels[lastseenlabel]); end; end; code[i]:=currentline; end; end; function aobscans(code: tstrings; syntaxcheckonly: boolean): boolean; //Replaces all AOBSCAN lines with DEFINE(NAME,ADDRESS) type TAOBEntry = record name: string; aobstring: string; linenumber: integer; end; PAOBEntry=^TAOBEntry; var i,j,k, m: integer; aobscanmodules: array of record name: string; entries: array of TAOBEntry; minaddress, maxaddress: ptruint; protection: string; memscan: TMemScan; end; a,b,c,d,e: integer; currentline: string; s1,s2, s3,s4: string; testptr: ptruint; cpucount: integer; threads: integer; mi: TModuleInfo; aobstrings: string; error: boolean; errorstring: string; aob1, aob2: dword; startaddress, stopaddress: ptruint; procedure finished(f: integer); //cleanup a memscan and fill in the results var i,j: integer; results: TAddresses; aoblist: string; begin setlength(results,0); if length(aobscanmodules[f].entries)=1 then begin //if only 1 entry a normal aobscan was done, so use GetOnlyOneResult instead if aobscanmodules[f].memscan.GetOnlyOneResult(testptr) then begin setlength(results,1); results[0]:=testptr; if not InRangeX(results[0], aobscanmodules[f].minaddress, aobscanmodules[f].maxaddress) then begin raise EAutoAssembler.Create('Invalid result for aob region scan'); end; end; end else aobscanmodules[f].memscan.GetOnlyOneResults(results); if length(results)=length(aobscanmodules[f].entries) then begin for i:=0 to length(aobscanmodules[f].entries)-1 do begin if results[i]=0 then begin error:=true; errorstring:=format(rsAATheArrayOfByteNamed, [aobscanmodules[f].entries[i].name]); end else code[aobscanmodules[f].entries[i].linenumber]:='DEFINE('+aobscanmodules[f].entries[i].name+', '+inttohex(results[i],8)+')'; end; end else begin error:=true; aoblist:=''; for i:=0 to length(aobscanmodules[f].entries)-1 do aoblist:=aoblist+aobscanmodules[f].entries[i].name+' '; if aobscanmodules[f].memscan.GetErrorString<>'' then errorstring:=rsAAErrorWhileSacnningForAobs+aoblist+#13#10#13#10+rsAAError+aobscanmodules[f].memscan.GetErrorString else errorstring:=rsAAErrorWhileSacnningForAobs+aoblist+#13#10#13#10+rsAAError+'Not all results found'; end; aobscanmodules[f].memscan.Free; aobscanmodules[f].memscan:=nil; dec(threads); end; begin result:=false; error:=false; setlength(aobscanmodules,0); cpucount:=GetCPUCount; threads:=0; for i:=0 to code.Count-1 do begin //AOBSCAN(variable,aobtring) (works like define) currentline:=code[i]; if uppercase(copy(currentline,1,10))='AOBSCANEX(' then begin //convert this line from AOBSCANEX(varname,bytestring) to DEFINE(varname,address) a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); //s1=varname //s2=AOBstring testPtr:=0; if (not syntaxcheckonly) then begin //find the ' ' module (single space) m:=-1; for j:=0 to length(aobscanmodules)-1 do if aobscanmodules[j].name=' ' then begin m:=j; break; end; if m=-1 then begin setlength(aobscanmodules, length(aobscanmodules)+1); m:=length(aobscanmodules)-1; aobscanmodules[m].name:=' '; aobscanmodules[m].minaddress:=0; aobscanmodules[m].protection:='*C*W+X'; //don't care about copy on write or writable, but executable must be set {$ifdef cpu64} if processhandler.is64Bit then aobscanmodules[m].maxaddress:=qword($7fffffffffffffff) else {$endif} begin if Is64bitOS then aobscanmodules[m].maxaddress:=$ffffffff else aobscanmodules[m].maxaddress:=$7fffffff; end; aobscanmodules[m].maxaddress:=qword($ffffffffffffffff); setlength(aobscanmodules[m].entries,0); //shouldn't be needed, but do it anyhow end; j:=length(aobscanmodules[m].entries); setlength(aobscanmodules[m].entries, j+1); aobscanmodules[m].entries[j].name:=s1; aobscanmodules[m].entries[j].aobstring:=s2; aobscanmodules[m].entries[j].linenumber:=i; end else code[i]:='DEFINE('+s1+', 00000000)'; end else raise exception.Create(rsWrongSyntaxAOBSCANName11223355); end; if uppercase(copy(currentline,1,8))='AOBSCAN(' then begin //convert this line from AOBSCAN(varname,bytestring) to DEFINE(varname,address) a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); //s1=varname //s2=AOBstring testPtr:=0; if (not syntaxcheckonly) then begin //find the '' module m:=-1; for j:=0 to length(aobscanmodules)-1 do if aobscanmodules[j].name='' then begin m:=j; break; end; if m=-1 then begin setlength(aobscanmodules, length(aobscanmodules)+1); m:=length(aobscanmodules)-1; aobscanmodules[m].name:=''; aobscanmodules[m].minaddress:=0; {$ifdef cpu64} if processhandler.is64Bit then aobscanmodules[m].maxaddress:=qword($7fffffffffffffff) else {$endif} begin if Is64bitOS then aobscanmodules[m].maxaddress:=$ffffffff else aobscanmodules[m].maxaddress:=$7fffffff; end; aobscanmodules[m].maxaddress:=qword($ffffffffffffffff); setlength(aobscanmodules[m].entries,0); //shouldn't be needed, but do it anyhow end; j:=length(aobscanmodules[m].entries); setlength(aobscanmodules[m].entries, j+1); aobscanmodules[m].entries[j].name:=s1; aobscanmodules[m].entries[j].aobstring:=s2; aobscanmodules[m].entries[j].linenumber:=i; end else code[i]:='DEFINE('+s1+', 00000000)'; end else raise exception.Create(rsWrongSyntaxAOBSCANName11223355); end; if uppercase(copy(currentline,1,14))='AOBSCANMODULE(' then //AOBSCANMODULE(varname, modulename, bytestring) begin a:=pos('(',currentline); b:=pos(',',currentline); c:=PosEx(',',currentline,b+1); d:=pos(')',currentline); if d<=a then raise exception.create(rsWrongSyntaxAOBSCANMODULEName11223355); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); s3:=trim(copy(currentline,c+1,d-c-1)); //s1=varname //s2=MODULE //s3=aob testPtr:=0; if (not syntaxcheckonly) then begin //find the s2 module m:=-1; for j:=0 to length(aobscanmodules)-1 do if aobscanmodules[j].name=uppercase(s2) then begin m:=j; break; end; if m=-1 then begin setlength(aobscanmodules, length(aobscanmodules)+1); m:=length(aobscanmodules)-1; aobscanmodules[m].name:=uppercase(s2); if symhandler.getmodulebyname(s2, mi) then begin aobscanmodules[m].minaddress:=mi.baseaddress; aobscanmodules[m].maxaddress:=mi.baseaddress+mi.basesize; end else begin //modulename not found. Perhaps a symbol was used try testptr:=symhandler.getAddressFromName(s2); if symhandler.getmodulebyaddress(testptr, mi) then begin aobscanmodules[m].minaddress:=mi.baseaddress; aobscanmodules[m].maxaddress:=mi.baseaddress+mi.basesize; end; except raise exception.create(rsAAModuleNotFound+s2); end; end; setlength(aobscanmodules[m].entries,0); end; j:=length(aobscanmodules[m].entries); setlength(aobscanmodules[m].entries, j+1); aobscanmodules[m].entries[j].name:=s1; aobscanmodules[m].entries[j].aobstring:=s3; aobscanmodules[m].entries[j].linenumber:=i; end else code[i]:='DEFINE('+s1+', 00000000)'; end else raise exception.Create(rsWrongSyntaxAOBSCANMODULEName11223355); end; if uppercase(copy(currentline,1,14))='AOBSCANREGION(' then //AOBSCANREGION(varname, startaddress, stopaddress, bytestring) begin a:=pos('(',currentline); b:=pos(',',currentline); c:=PosEx(',',currentline,b+1); d:=PosEx(',',currentline,c+1); e:=pos(')',currentline); if (d<=a) or (b<=a) or (c<=a) or (d<=a) or (e<=a) then raise exception.create(rsWrongSyntaxAOBSCANREGION); s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); s3:=trim(copy(currentline,c+1,d-c-1)); s4:=trim(copy(currentline,d+1,e-d-1)); if (not syntaxcheckonly) then begin startaddress:=symhandler.getAddressFromName(s2); stopaddress:=symhandler.getAddressFromName(s3); //see if this region is already being scanned m:=-1; for j:=0 to length(aobscanmodules)-1 do begin //exact address only. No widening/appending of the groups is possible (Users may want to scan for 00 00 in a 16 byte region) if (startaddress=aobscanmodules[j].minaddress) and (stopaddress=aobscanmodules[j].maxaddress) then begin m:=j; break; end; end; if m=-1 then begin setlength(aobscanmodules, length(aobscanmodules)+1); m:=length(aobscanmodules)-1; aobscanmodules[m].name:=''; aobscanmodules[m].minaddress:=startaddress; aobscanmodules[m].maxaddress:=stopaddress; setlength(aobscanmodules[m].entries,0); end; j:=length(aobscanmodules[m].entries); setlength(aobscanmodules[m].entries, j+1); aobscanmodules[m].entries[j].name:=s1; aobscanmodules[m].entries[j].aobstring:=s4; aobscanmodules[m].entries[j].linenumber:=i; end else code[i]:='DEFINE('+s1+', 00000000)'; end; end; //do simultaneous scans for the selected modules if length(aobscanmodules)>0 then result:=true; //script uses aobscan for i:=0 to length(aobscanmodules)-1 do begin //wait for one to finish j:=0; while threads>=cpucount do begin if (aobscanmodules[j].memscan<>nil) and (aobscanmodules[j].memscan.waittilldone(50)) then begin finished(j); break; end; j:=(j+1) mod i; end; inc(threads); aobscanmodules[i].memscan:=TMemScan.create(nil); aobscanmodules[i].memscan.OnlyOne:=true; aobstrings:=''; for j:=0 to length(aobscanmodules[i].entries)-1 do aobstrings:=aobstrings+'('+aobscanmodules[i].entries[j].aobstring+')'; if length(aobscanmodules[i].entries)=1 then //bytearrays is slightly slower, so only use it if more than one entry is to be scanned aobscanmodules[i].memscan.firstscan(soExactValue, vtByteArray, rtRounded, aobscanmodules[i].entries[0].aobstring, aobscanmodules[i].protection, aobscanmodules[i].minaddress, aobscanmodules[i].maxaddress, true, false, false, false, fsmNotAligned) else aobscanmodules[i].memscan.firstscan(soExactValue, vtByteArrays, rtRounded, aobstrings, aobscanmodules[i].protection, aobscanmodules[i].minaddress, aobscanmodules[i].maxaddress, true, false, false, false, fsmNotAligned); end; //now wait till all are finished for i:=0 to length(aobscanmodules)-1 do if aobscanmodules[i].memscan<>nil then begin aobscanmodules[i].memscan.waittilldone; finished(i); end; if error then raise EAutoAssembler.create(errorstring); end; procedure parseTryExcept(code: tstrings; var exceptionlist: TAAExceptionInfoList); //Find and replace {$TRY} , {$EXCEPT} with labels var i,j: integer; trynr: integer; trylist: array of record linenr: integer; trynr: integer; hasexcept: boolean; trylabel, exceptlabel: string; end; found: boolean; begin trynr:=0; setlength(trylist,0); for i:=0 to code.Count-1 do begin if uppercase(code[i])='{$TRY}' then begin inc(trynr); j:=length(trylist); setlength(trylist,j+1); trylist[j].trynr:=trynr; trylist[j].hasexcept:=false; trylist[j].linenr:=integer(code.Objects[i]); trylist[j].trylabel:='tryoperation_'+inttostr(trynr); code[i]:=trylist[j].trylabel+':'; end; if uppercase(code[i])='{$EXCEPT}' then begin //find the last try that doesn't have an except filled in found:=false; for j:=length(trylist)-1 downto 0 do begin if trylist[j].hasexcept=false then begin trylist[j].hasexcept:=true; trylist[j].exceptlabel:='tryoperation'+inttostr(trylist[j].trynr)+'_except'; code[i]:=trylist[j].exceptlabel+':'; found:=true; break; end; end; if not found then raise exception.create(format('Found an {$EXCEPT} at line %d with no matching {$TRY}',[integer(code.Objects[i])])); end; end; setlength(exceptionlist, length(trylist)); for i:=0 to length(trylist)-1 do begin code.Insert(0,'label('+trylist[i].trylabel+')'); code.Insert(0,'label('+trylist[i].exceptlabel+')'); exceptionlist[i].trylabel:=trylist[i].trylabel; exceptionlist[i].exceptlabel:=trylist[i].exceptlabel; if trylist[i].hasexcept=false then raise exception.create(format(rsMissingExcept, [trylist[i].linenr])); end; end; procedure luacode(code: TStrings; syntaxcheckonly: boolean; memrec: TMemoryRecord=nil); { Find and execute the LUA parts: function (syntaxcheck) end If the functions return a string, substitute the code with the given string } var i,j,k: integer; s: tstringlist; stack: integer; str: string; error: boolean; L: Plua_State; begin i:=0; while icode.count then code[j]:=''; {$ifndef NOLUA} L:=GetLuaState; try stack:=lua_Gettop(L); error:=false; luaL_loadstring(L, pchar(s.text)); if lua_isfunction(L, -1) then begin lua_pushboolean(L, syntaxcheckonly); luaclass_newClass(L, memrec); if lua.lua_pcall(L, 2, 1, 0)=0 then begin if lua_isstring(L,-1) then begin str:=Lua_ToString(L, -1); s:=tstringlist.create; s.text:=str; for k:=0 to s.count-1 do code.Insert(i+k, s[k]); s.free; end; end else error:=true; end else error:=true; if error then begin if lua_isstring(L, -1) then raise exception.create(rsAALuaErrorInTheScriptAtLine+inttostr(integer(code.Objects[i]))+':'+lua_tostring(L, -1)) else raise exception.create(rsAALuaErrorInTheScriptAtLine+inttostr(integer(code.Objects[i]))); end; finally lua_settop(L, stack); end; {$endif} break; end; inc(j); end; end else inc(i); end; end; var nextaaid: longint; function autoassemble2(code: tstrings;popupmessages: boolean;syntaxcheckonly:boolean; targetself: boolean; disableinfo: TDisableInfo=nil; memrec: TMemoryRecord=nil):boolean; { registeredsymbols is a stringlist that is initialized by the caller as case insensitive and no duplicates } type tassembled=record address: ptrUint; bytes: TAssemblerbytes; createthreadandwait: integer; end; type tlabel=record defined: boolean; afterccode: boolean; insideAllocatedMemory: boolean; address:ptrUint; labelname: string; assemblerline: integer; references: array of integer; //index of assembled array references2: array of integer; //index of assemblerlines array end; type tfullaccess=record address: ptrUint; size: dword; end; type tdefine=record name: string; whatever: string; end; var i: integer=0; j: integer=0; k: integer=0; l: integer=0; e: integer=0; currentline: string=''; currentline2: string=''; currentlinenr: integer=0; currentlinep: pchar=nil; currentaddress: ptrUint=0; assembled: array of tassembled=[]; x: ptruint=0; y:dword=0; op: dword=0; op2:dword=0; ok1:boolean=false; ok2:boolean=false; loadbinary: array of record address: string; //string since it might be a label/alloc/define filename: string; end=[]; readmems: array of record bytelength: integer; bytes: PByteArray; end=[]; //allocs: globalallocs: array of tcealloc=[]; allocs: array of tcealloc=[]; kallocs: array of tcealloc=[]; sallocs: array of tcealloc=[]; tempalloc: tcealloc; labels: array of tlabel=[]; defines: array of tdefine=[]; fullaccess: array of tfullaccess=[]; dealloc: array of PtrUInt=[]; addsymbollist: array of string=[]; deletesymbollist: array of string=[]; createthread: array of string=[]; createthreadandwait: array of record name: string; position: integer; //after what position should the call happen (This is so that the exception handlers can be registered before the final hookcode is written) timeout: integer; end; a,b,c,d: integer; s1,s2,s3: string; slist: TStringDynArray=[]; sli: integer=0; //slist iterator diff: ptruint=0; assemblerlines: array of record linenr: integer; line: string; end=[]; exceptionlist: TAAExceptionInfoList=[]; {$ifdef onebytejumps} onebytejumps: TAAExceptionRIPChangeInfoList=[]; {$endif} varsize: integer=0; tokens: tstringlist=nil; baseaddress: ptrUint=0; multilineinjection: tstringlist=nil; include: tstringlist=nil; testdword,bw: dword; testPtr: ptrUint=0; binaryfile: tmemorystream=nil; incomment: boolean=false; bytebuf: PByteArray=nil; processhandle: THandle=0; ProcessID: DWORD=0; bytes: tbytes=[]; oldprefered: ptrUint=0; prefered: ptrUint=0; protection: dword=0; oldprocessid: dword; oldhandle: thandle=0; oldsymhandler: TSymHandler=nil; disassembler: TDisassembler=nil; threadhandle: THandle=0; potentiallabels: TStringlist=nil; connection: TCEConnection=nil; mi: TModuleInfo; aaid: longint=0; strictmode: boolean=false; hastryexcept: boolean=false; //aggressiveAlloc: boolean; createthreadandwaitid: integer=0; vpe: boolean=false; nops: Tassemblerbytes=[]; mustbefar: boolean=false; usesaobscan: boolean=false; dataForAACodePass2: TAutoAssemblerCodePass2Data; debug_getAddressFromScript: boolean=false; function getAddressFromScript(name: string; alternate: boolean=false): ptruint; var found: boolean; j,k: integer; temps: string; oldname: string; begin if name='' then begin OutputDebugString('getAddressFromScript with an empty name'); exit(0); end; result:=0; found:=false; if debug_getAddressFromScript then OutputDebugString('getAddressFromScript'); oldname:=name; name:=uppercase(name); if debug_getAddressFromScript then OutputDebugString('looking for '+name); if debug_getAddressFromScript then OutputDebugString('allocs...'); for j:=0 to length(allocs)-1 do if uppercase(allocs[j].varname)=name then exit(allocs[j].address); if debug_getAddressFromScript then OutputDebugString('kallocs...'); for j:=0 to length(kallocs)-1 do if uppercase(kallocs[j].varname)=name then exit(kallocs[j].address); if debug_getAddressFromScript then OutputDebugString('labels...'); for j:=0 to length(labels)-1 do if uppercase(labels[j].labelname)=name then begin if labels[j].defined then exit(labels[j].address); end; if debug_getAddressFromScript then OutputDebugString('defines...'); for j:=0 to length(defines)-1 do if uppercase(defines[j].name)=name then begin try result:=symhandler.getAddressFromName(defines[j].whatever); exit; except end; end; if debug_getAddressFromScript then OutputDebugString('symbols original case...'); try if targetself then result:=selfsymhandler.getAddressFromName(oldname) else result:=symhandler.getAddressFromName(oldname); if result<>0 then exit; if debug_getAddressFromScript then OutputDebugString('result=0 and no exception....'); except end; if debug_getAddressFromScript then OutputDebugString('symbols uppercase...'); try if targetself then result:=selfsymhandler.getAddressFromName(name) else result:=symhandler.getAddressFromName(name); if result<>0 then exit; if debug_getAddressFromScript then OutputDebugString('result=0 and no exception....'); except end; if debug_getAddressFromScript then OutputDebugString('not a registered symbol'); if (not alternate) and (not processhandler.is64Bit) and (name[1]='_') and (name[length(name)] in ['0'..'9']) and name.Contains('@') then begin //it could be a _symbolname@### j:=name.IndexOf('@'); temps:=name.Substring(j+1); if TryStrToInt(temps,k) then begin //it is a _symbolname@### temps:=name.Substring(1,j-1); exit(getAddressFromScript(temps,true)); end; end; if debug_getAddressFromScript then OutputDebugString('not found'); end; procedure handleCreateThreadAndWait(ctawi: integer); begin //create the thread and wait for it's result testptr:=getAddressFromScript(createthreadandwait[ctawi].name); threadhandle:=createremotethread(processhandle,nil,0,pointer(testptr),nil,0,bw); ok2:=threadhandle>0; if ok2 then begin {$ifdef windows} try k:=createthreadandwait[ctawi].timeout; if k<=0 then y:=INFINITE else y:=k; if WaitForSingleObject(threadhandle, y)<>WAIT_OBJECT_0 then raise EAssemblerException.create('createthreadandwait did not execute properly'); finally closehandle(threadhandle); end; {$else} sleep(5000); //todo: implement proper wait {$endif} end; createthreadandwait[ctawi].position:=-1; //mark it as handled end; begin setlength(readmems,0); setlength(allocs,0); setlength(kallocs,0); setlength(globalallocs,0); setlength(sallocs,0); setlength(createthread,0); setlength(createthreadandwait,0); setlength(defines,0); setlength(labels,0); FillChar(dataForAACodePass2, sizeof(dataForAACodePass2),0); connection:=nil; i:=0; j:=0; k:=0; l:=0; e:=0; currentaddress:=0; //add all symbols as predefined labels if disableinfo<>nil then begin setlength(labels, disableinfo.allsymbols.count); for i:=0 to disableinfo.allsymbols.count-1 do begin FillMemory(@labels[length(labels)-1],sizeof(labels[0]),0); labels[i].defined:=true; labels[i].address:=ptruint(disableinfo.allsymbols.Objects[i]); labels[i].labelname:=disableinfo.allsymbols[i]; labels[i].assemblerline:=-2; //undo symbol label end; end; {$ifndef jni} if targetself then begin //get this function to use the symbolhandler that's pointing to CE itself and the self processid/handle oldhandle:=processhandlerunit.ProcessHandle; oldprocessid:=processid; processid:=getcurrentprocessid; processhandle:=getcurrentprocess; oldsymhandler:=symhandler; symhandler:=selfsymhandler; processhandler.processid:=getcurrentprocessid; processhandler.processhandle:=processhandle; end else {$endif} begin processid:=processhandlerunit.ProcessID; processhandle:=processhandlerunit.ProcessHandle; end; {$ifndef darwin} symhandler.waitforsymbolsloaded(true); {$endif} {$ifndef jni} if pluginhandler=nil then exit; //Error. Cheat Engine is not properly configured aaid:=InterLockedIncrement(nextaaid); pluginhandler.handleAutoAssemblerPlugin(@currentlinep, 0, aaid); //tell the plugins that an autoassembler script is about to get executed {$endif} potentiallabels:=tstringlist.create; potentiallabels.CaseSensitive:=false; //2 pass scanner try setlength(assembled,1); setlength(kallocs,0); setlength(allocs,0); setlength(dealloc,0); setlength(assemblerlines,0); setlength(fullaccess,0); setlength(addsymbollist,0); setlength(deletesymbollist,0); setlength(loadbinary,0); setlength(exceptionlist,0); tokens:=tstringlist.Create; incomment:=false; if not targetself then for i:=0 to length(AutoAssemblerPrologues)-1 do if assigned(AutoAssemblerPrologues[i]) then AutoAssemblerPrologues[i](code, syntaxcheckonly); luacode(code, syntaxcheckonly, memrec); //replaces {$lua}/{$asm} blocks with the output of those functions AutoAssemblerCodePass1(code,dataForAACodePass2, syntaxcheckonly, targetself); //replaces the {$luacode} and {$ccode} blocks with a call to extra routines added to the script //still here //c-symbol addition for i:=0 to length(dataForAACodePass2.cdata.symbols)-1 do begin //define the c-code symbol as an undefined labels j:=length(labels); setlength(labels, j+1); ZeroMemory(@labels[j],sizeof(labels[j])); labels[j].labelname:=dataForAACodePass2.cdata.symbols[i].name; labels[j].defined:=false; labels[j].afterccode:=true; labels[j].assemblerline:=-1; setlength(labels[j].references,0); setlength(labels[j].references2,0); end; //c-symbol addition^ //one more time getting rid of {$ASM} lines that have been added while they shouldn't be required for i:=0 to code.count-1 do if uppercase(TrimRight(code[i]))='{$ASM}' then code[i]:=''; strictmode:=false; hastryexcept:=false; for i:=0 to code.count-1 do begin currentline:=uppercase(TrimRight(code[i])); if currentline='{$STRICT}' then strictmode:=true; if currentline='{$TRY}' then hastryexcept:=true; //if currentline='{$AGGRESSIVEALLOC}' then // aggressiveAlloc:=true; //1: pause game, find mem_private block even close to here with some non-allocated space free, resize resume // //2: allocate in reserved memory end; if hastryexcept then parseTryExcept(code, exceptionlist); removecomments(code); //also trims each line unlabeledlabels(code); if not strictmode then getPotentialLabels(code, potentiallabels); //6.3: do the aobscans first //this will break scripts that use define(state,33) aobscan(name, 11 22 state 44 55), but really, live with it usesaobscan:=aobscans(code, syntaxcheckonly); if not targetself then for i:=0 to length(AutoAssemblerProloguesPostAOBSCAN)-1 do if assigned(AutoAssemblerProloguesPostAOBSCAN[i]) then AutoAssemblerProloguesPostAOBSCAN[i](code, syntaxcheckonly); //first pass i:=0; while i0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); slist:=s1.Split([',',' ']); for sli:=0 to length(slist)-1 do begin s1:=slist[sli]; setlength(addsymbollist,length(addsymbollist)+1); addsymbollist[length(addsymbollist)-1]:=s1; if disableinfo<>nil then disableinfo.registeredsymbols.Add(s1); end; end else raise exception.Create(rsSyntaxError); continue; end; //apply defines (before DEFINE since define(bla, 123) and define(xxx, bla+123) should work //also, do not touch define with any previous define if uppercase(copy(currentline,1,7))='DEFINE(' then begin //syntax: define(x,whatever) x=variable name size=bytes //allocate memory a:=pos('(',currentline); b:=pos(',',currentline); c:=rpos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=copy(currentline,b+1,c-b-1); //apply earlier defines to the second part for j:=0 to length(defines)-1 do s2:=replacetoken(s2,defines[j].name,defines[j].whatever); ok1:=true; for j:=0 to length(defines)-1 do if uppercase(defines[j].name)=uppercase(s1) then begin //redefined from here on ok1:=false; defines[length(defines)-1].whatever:=s2 end; if ok1 then //not duplicate, create it begin setlength(defines,length(defines)+1); defines[length(defines)-1].name:=s1; defines[length(defines)-1].whatever:=s2; end; continue; end else raise exception.Create(rsWrongSyntaxDEFINENameWhatever+' Got '+currentline); end; //normal loop code for j:=0 to length(defines)-1 do currentline:=replacetoken(currentline,defines[j].name,defines[j].whatever); setlength(assemblerlines,length(assemblerlines)+1); assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=currentline; //plugins currentlinep:=@currentline[1]; {$ifndef jni} pluginhandler.handleAutoAssemblerPlugin(@currentlinep, 1,aaid); {$endif} currentline:=currentlinep; //lua extensions if registeredAutoAssemblerCommands<>nil then begin j:=pos('(', currentline); if j>0 then begin s1:=uppercase(copy(currentline, 1, j-1)); for j:=0 to registeredAutoAssemblerCommands.count-1 do begin if TRegisteredAutoAssemblerCommand(registeredAutoAssemblerCommands[j]).command=s1 then begin a:=pos('(',currentline); b:=RPos(')',currentline); s1:=copy(currentline, a+1, b-a-1); currentline:=TRegisteredAutoAssemblerCommand(registeredAutoAssemblerCommands[j]).callback(s1, syntaxcheckonly); //insert the current text as lines into the codelist multilineinjection:=TStringList.create; try multilineinjection.Text:=currentline; for k:=0 to multilineinjection.Count-1 do code.InsertObject(i+1+k, multilineinjection[k], pointer(ptruint(currentlinenr))); finally multilineinjection.Free; end; //showmessage(code.text); currentline:=''; break; end; end; end; end; //if the newline is empty then it has been handled and the plugin doesn't want it to be added for phase2 if length(currentline)=0 then begin setlength(assemblerlines,length(assemblerlines)-1); continue; end; //otherwise it hasn't been handled, or it has been handled and the string is a compatible string that passes the phase1 tests (so variablenames converted to 00000000 and whatever else is needed) //plugins^^^ if uppercase(copy(currentline,1,7))='ASSERT(' then //assert(address,aob) begin if not syntaxcheckonly then begin a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); testPtr:= symhandler.getAddressFromName(s1,false); setlength(bytes,0); try ConvertStringToBytes(s2, true, bytes); except raise exception.create(Format(rsIsNotAValidBytestring, [s2])); end; if length(bytes)>0 then begin getmem(bytebuf,length(bytes)); try if ReadProcessMemory(processhandle, pointer(testPtr), bytebuf, length(bytes),x) then begin for j:=0 to length(bytes)-1 do begin if bytes[j]>=0 then if byte(bytes[j])<>bytebuf[j] then raise exception.Create(Format(rsTheBytesAtAreNotWhatWasExpected, [s1])); end; end else raise exception.Create(Format(rsTheMemoryAtCanNotBeRead, [s1])); finally freememandnil(bytebuf); end; end else raise exception.Create(Format(rsIsNotAValidBytestring, [s2])); end else raise exception.Create(rsWrongSyntaxASSERTAddress1122335566); end; setlength(assemblerlines,length(assemblerlines)-1); continue; end; { if uppercase(copy(currentline,1,12))='SHAREDALLOC(' then begin a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); try x:=strtoint(s2); except raise exception.Create(Format(rsIsNotAValidSize, [s2])); end; setlength(sallocs,length(sallocs)+1); sallocs[length(sallocs)-1].address:=allocateSharedMemoryIntoTargetProcess(s1,x); sallocs[length(sallocs)-1].varname:=s1; sallocs[length(sallocs)-1].size:=x; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxSHAREDALLOCNameSize); end; } if uppercase(copy(currentline,1,12))='GLOBALALLOC(' then begin a:=pos('(',currentline); b:=pos(',',currentline); c:=PosEx(',',currentline,b+1); d:=pos(')',currentline); if (a>0) and (b>0) and (d>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if c>0 then begin s2:=trim(copy(currentline,b+1,c-b-1)); s3:=trim(copy(currentline,c+1,d-c-1)); end else begin s2:=trim(copy(currentline,b+1,d-b-1)); s3:=''; end; try x:=strtoint(s2); except raise exception.Create(Format(rsIsNotAValidSize, [s2])); end; //define it here already if s3<>'' then symhandler.SetUserdefinedSymbolAllocSize(s1,x, symhandler.getAddressFromName(s3)) else symhandler.SetUserdefinedSymbolAllocSize(s1,x); setlength(globalallocs,length(globalallocs)+1); globalallocs[length(globalallocs)-1].address:=symhandler.GetUserdefinedSymbolByName(s1); globalallocs[length(globalallocs)-1].varname:=s1; globalallocs[length(globalallocs)-1].size:=x; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxGLOBALALLOCNameSize); end; if uppercase(copy(currentline,1,8))='INCLUDE(' then begin a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if ExtractFileExt(uppercase(s1))='.' then s1:=s1+'CEA'; if ExtractFileExt(uppercase(s1))='' then s1:=s1+'.CEA'; if not fileexists(s1) then //check if it's inside the current location begin //if not, check the default paths s2:=cheatenginedir+'includes'+pathdelim+'s1'; if fileexists(s2) then s1:=s2 else begin s2:=cheatenginedir+s1; if fileexists(s2) then s1:=s2 else begin s2:=tablesdir+s1; if fileexists(s2) then s1:=s2; end; end; if not fileexists(s1) then raise exception.Create(Format(rsCouldNotBeFound, [s1])); end; include:=tstringlist.Create; try include.LoadFromFile(s1{$if FPC_FULLVERSION >= 030200}, true{$endif}); removecomments(include); unlabeledlabels(include); for j:=i+1 to (i+1)+(include.Count-1) do code.Insert(j,include[j-(i+1)]); finally include.Free; end; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxIncludeFilenameCea); end; if uppercase(copy(currentline,1,12))='CREATETHREAD' then begin if currentline[13]='(' then //CREATETHREAD( begin //create a thread a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); setlength(createthread,length(createthread)+1); createthread[length(createthread)-1]:=s1; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxCreateThreadAddress); end else begin //could be createthreadandwait if uppercase(copy(currentline,13,8))='ANDWAIT(' then //CREATETHREADANDWAIT( begin a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); slist:=s1.Split([',']); if length(slist)=2 then begin try j:=strtoint(trim(slist[1])); except raise exception.Create('Invalid timeout for createthreadandwait'); end; end else begin if (lastLoadedTableVersion>0) and (lastLoadedTableVersion<=30) then //when the current table was made with an older CE build and it uses CREATETHREADANDWAIT j:=5000 else j:=0; end; setlength(createthreadandwait,length(createthreadandwait)+1); createthreadandwait[length(createthreadandwait)-1].name:=slist[0]; createthreadandwait[length(createthreadandwait)-1].position:=length(assemblerlines)-1; createthreadandwait[length(createthreadandwait)-1].timeout:=j; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxCreateThreadAddress); end; end; end; {$ifndef jni} {$ifdef ONEBYTEJUMPS} if uppercase(copy(currentline,1,5))='JMP1 ' then begin s1:=copy(currentline, 6); assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=''; continue; end; {$endif} if uppercase(copy(currentline,1,12))='LOADLIBRARY(' then begin //load a library into memory , this one already executes BEFORE the 2nd pass to get addressnames correct a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if (length(s1)>1) and ((s1[1]='''') or (s1[1]='"')) then s1:=AnsiDequotedStr(s1,s1[1]); if pos(':',s1)=0 then begin s2:=extractfilename(s1); {$ifdef windows} if getConnection=nil then //no connection, so local. Check if the file can be found locally and if so, set the specific path begin {$endif} if fileexists(cheatenginedir+s2) then s1:=cheatenginedir+s2 else if fileexists(getcurrentdir+ PathDelim+s2) then s1:=getcurrentdir+PathDelim+s2 else if fileexists(cheatenginedir+s1) then s1:=cheatenginedir+s1; {$ifdef windows} end; {$endif} //else just hope it's in the dll searchpath end; //else direct file path try if symhandler.getmodulebyname(extractfilename(s1), mi)=false then //check if it's already injected begin InjectDll(s1,''); symhandler.reinitialize; end; symhandler.waitforsymbolsloaded; symhandler.waitForExports; except raise exception.create(Format(rsCouldNotBeInjected, [s1])); end; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxLoadLibraryFilename); end; if uppercase(copy(currentline,1,8))='LUACALL(' then begin //execute a given lua command a:=pos('(',currentline); b:=length(currentline); if currentline[b]<>')' then b:=-1; if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); LUA_DoScript(s1); //raises an exception on error, which is exactly what we want here setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxLuaCall); end; {$endif} if uppercase(copy(currentline,1,8))='READMEM(' then begin //read memory and place it here (readmem(address,size) ) a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); //read memory and replace with lines of DB xx xx xx xx xx xx xx xx try testptr:=symhandler.getAddressFromName(s1); except if not syntaxcheckonly then raise exception.Create(rsInvalidAddressForReadMem) else testptr:=0; end; try a:=strtoint(s2); except raise exception.Create(rsInvalidSizeForReadMem); end; if a=0 then raise exception.Create(rsInvalidSizeForReadMem); getmem(bytebuf,a); try if not syntaxcheckonly then begin if (not ReadProcessMemory(processhandle, pointer(testptr),bytebuf,a,x)) or (xnil then begin freememandnil(bytebuf); end; raise exception.create(e.Message); end; end; //still here so everything ok assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=''; setlength(readmems, length(readmems)+1); readmems[length(readmems)-1].bytelength:=a; readmems[length(readmems)-1].bytes:=bytebuf; bytebuf:=nil; continue; end else raise exception.Create(rsWrongSyntaxReadMemAddressSize); continue; end; if uppercase(copy(currentline,1,11))='REASSEMBLE(' then begin a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); try testptr:=getAddressFromScript(s1); except raise exception.Create(format(rsXCouldNotBeFound, [s1])); end; if testptr=0 then raise exception.Create(format(rsXCouldNotBeFound, [s1])); multilineinjection:=TStringList.create; GetOriginalInstruction(testptr, multilineinjection, processhandler.is64Bit,true); //don't take on the symbol. (module and section are ok, not symbols) if multilineinjection.count=0 then raise exception.create(format(rsFailureGettingOriginalInstruction, [testptr])); if trim(multilineinjection[0]).StartsWith('//') then //skip comments setlength(assemblerlines, length(assemblerlines)-1) else begin assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=multilineinjection[0]; end; for j:=1 to multilineinjection.Count-1 do begin setlength(assemblerlines, length(assemblerlines)+1); assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=multilineinjection[j]; end; freeandnil(multilineinjection); continue; { disassembler:=TDisassembler.create; disassembler.dataOnly:=true; disassembler.disassemble(testptr, s1); if syntaxcheckonly then currentline:='nop' else currentline:=disassembler.LastDisassembleData.prefix+' '+Disassembler.LastDisassembleData.opcode+' '+disassembler.LastDisassembleData.parameters;; assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=currentline; disassembler.free; } end else raise exception.Create(rsWrongSyntaxReAssemble); end; if uppercase(copy(currentline,1,11))='LOADBINARY(' then begin //load a binary file into memory a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); if not fileexists(s2) then raise exception.Create(Format(rsTheFileDoesNotExist, [s2])); setlength(loadbinary,length(loadbinary)+1); loadbinary[length(loadbinary)-1].address:=s1; loadbinary[length(loadbinary)-1].filename:=s2; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsWrongSyntaxLoadBinaryAddressFilename); end; if uppercase(copy(currentline,1,17))='UNREGISTERSYMBOL(' then begin //add this symbol to the register symbollist a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if (disableinfo<>nil) and (s1='*') then begin j:=length(deletesymbollist); setlength(deletesymbollist, j+ disableinfo.registeredsymbols.Count); for k:=0 to disableinfo.registeredsymbols.Count-1 do deletesymbollist[j+k]:=disableinfo.registeredsymbols[k]; end else begin slist:=s1.Split([',',' ']); for sli:=0 to length(slist)-1 do begin s1:=slist[sli]; setlength(deletesymbollist,length(deletesymbollist)+1); deletesymbollist[length(deletesymbollist)-1]:=s1; end; end; end else raise exception.Create(rsSyntaxError); setlength(assemblerlines,length(assemblerlines)-1); continue; end; //AOBSCAN used to live here, but he moved up //define if uppercase(copy(currentline,1,7))='STRUCT ' then begin replaceStructWithDefines(code, i); setlength(assemblerlines,length(assemblerlines)-1); dec(i); //repeat from this line continue; end; if uppercase(copy(currentline,1,11))='FULLACCESS(' then begin a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); setlength(fullaccess,length(fullaccess)+1); fullaccess[length(fullaccess)-1].address:=symhandler.getAddressFromName(s1); fullaccess[length(fullaccess)-1].size:=strtoint(s2); end else raise exception.Create(rsSyntaxErrorFullAccessAddressSize); setlength(assemblerlines,length(assemblerlines)-1); continue; end; if uppercase(copy(currentline,1,6))='LABEL(' then begin //syntax: label(x) x=name of the label //later on in the code there has to be a line with "labelname:" a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); slist:=s1.Split([',',' ']); for sli:=0 to length(slist)-1 do begin s1:=slist[sli]; val('$'+s1,j,a); if a=0 then raise exception.Create(Format(rsIsNotAValidIdentifier, [s1])); varsize:=length(s1); j:=0; while (j=varsize) do begin //if labels[j].labelname=s1 then // raise exception.Create(Format(rsIsBeingRedeclared, [s1])); inc(j); end; j:=length(labels);//quickfix l:=j; //check for the line "labelname:" ok1:=false; for j:=0 to code.Count-1 do if trim(code[j])=s1+':' then begin if ok1 then raise exception.Create(Format(rsLabelIsBeingDefinedMoreThanOnce, [s1])); ok1:=true; end; if not ok1 then raise exception.Create(Format(rsLabelIsNotDefinedInTheScript, [s1])); //still here so ok //insert it setlength(labels,length(labels)+1); for k:=length(labels)-1 downto j+1 do labels[k]:=labels[k-1]; ZeroMemory(@labels[l], sizeof(labels[l])); labels[l].labelname:=s1; labels[l].defined:=false; setlength(labels[l].references,0); setlength(labels[l].references2,0); end; setlength(assemblerlines,length(assemblerlines)-1); continue; end else raise exception.Create(rsSyntaxError); end; if (uppercase(copy(currentline,1,8))='DEALLOC(') then begin //syntax: dealloc(x) x=name of region to deallocate //later on in the code there has to be a line with "labelname:" a:=pos('(',currentline); b:=pos(')',currentline); if (a>0) and (b>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if (s1='*') then begin //everything that the script allocated if (disableinfo<>nil) then begin setlength(dealloc, length(disableinfo.allocs)); for j:=0 to length(disableinfo.allocs)-1 do dealloc[j]:=disableinfo.allocs[j].address; end; end else begin slist:=s1.Split([',',' ']); for sli:=0 to length(slist)-1 do begin s1:=slist[sli]; //find s1 in the ceallocarray for j:=0 to length(disableinfo.allocs)-1 do begin if uppercase(disableinfo.allocs[j].varname)=uppercase(s1) then begin setlength(dealloc,length(dealloc)+1); dealloc[length(dealloc)-1]:=disableinfo.allocs[j].address; end; end; end; end; end; setlength(assemblerlines,length(assemblerlines)-1); continue; end; //memory alloc if (uppercase(copy(currentline,1,5))='ALLOC') and ( (uppercase(copy(currentline,1,6))='ALLOC(') or (uppercase(copy(currentline,1,8))='ALLOCNX(') or (uppercase(copy(currentline,1,8))='ALLOCXO(') ) then begin //syntax: alloc(x,size) x=variable name size=bytes //or //syntax: alloc(x,size,prefered region) x=variable name size=bytes //allocate memory a:=pos('(',currentline); b:=pos(',',currentline); c:=PosEx(',',currentline,b+1); d:=pos(')',currentline); if (a>0) and (b>0) and (d>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); if c>0 then begin s2:=trim(copy(currentline,b+1,c-b-1)); s3:=trim(copy(currentline,c+1,d-c-1)); end else begin s2:=trim(copy(currentline,b+1,d-b-1)); s3:=''; end; val('$'+s1,j,a); if a=0 then raise exception.Create(Format(rsIsNotAValidIdentifier, [s1])); varsize:=length(s1); //check for duplicate identifiers j:=0; while (jvarsize) do begin if allocs[j].varname=s1 then raise exception.Create(Format(rsTheIdentifierHasAlreadyBeenDeclared, [s1])); inc(j); end; j:=length(allocs);//quickfix setlength(allocs,length(allocs)+1); //longest varnames first so the rename of a shorter matching var wont override the longer one //move up the other allocs so I can inser this element (A linked list might have been better) for k:=length(allocs)-1 downto j+1 do allocs[k]:=allocs[k-1]; allocs[j].varname:=s1; allocs[j].size:=StrToInt(s2); if s3<>'' then allocs[j].prefered:=symhandler.getAddressFromName(s3) else allocs[j].prefered:=0; if SystemSupportsWritableExecutableMemory then allocs[j].protection:=PAGE_EXECUTE_READWRITE else allocs[j].protection:=PAGE_EXECUTE_READ; if uppercase(copy(currentline,1,8))='ALLOCNX(' then allocs[j].protection:=PAGE_READWRITE else if uppercase(copy(currentline,1,8))='ALLOCXO(' then allocs[j].protection:=PAGE_EXECUTE_READ; setlength(assemblerlines,length(assemblerlines)-1); //don't bother with this in the 2nd pass continue; end else raise exception.Create(rsWrongSyntaxALLOCIdentifierSizeinbytes); end; //replace ALLOC identifiers with values so the assemble error check doesnt crash on that if processhandler.is64bit then begin for j:=0 to length(allocs)-1 do currentline:=replacetoken(currentline,allocs[j].varname,'ffffffffffffffff'); end else begin for j:=0 to length(allocs)-1 do currentline:=replacetoken(currentline,allocs[j].varname,'00000000'); end; {$ifndef net} //memory kalloc {$ifdef windows} if uppercase(copy(currentline,1,7))='KALLOC(' then begin if not DBKReadWrite then raise exception.Create(rsNeedToUseKernelmodeReadWriteprocessmemory); if DBKLoaded=false then raise exception.Create(rsSorryButWithoutTheDriverKALLOCWillNotFunction); //syntax: kalloc(x,size) x=variable name size=bytes //kallocate memory a:=pos('(',currentline); b:=pos(',',currentline); c:=pos(')',currentline); if (a>0) and (b>0) and (c>0) then begin s1:=trim(copy(currentline,a+1,b-a-1)); s2:=trim(copy(currentline,b+1,c-b-1)); val('$'+s1,j,a); if a=0 then raise exception.Create(Format(rsIsNotAValidIdentifier, [s1])); varsize:=length(s1); //check for duplicate identifiers j:=0; while (jvarsize) do begin if kallocs[j].varname=s1 then raise exception.Create(Format(rsTheIdentifierHasAlreadyBeenDeclared, [s1])); inc(j); end; j:=length(kallocs);//quickfix setlength(kallocs,length(kallocs)+1); //longest varnames first so the rename of a shorter matching var wont override the longer one //move up the other kallocs so I can inser this element (A linked list might have been better) for k:=length(kallocs)-1 downto j+1 do kallocs[k]:=kallocs[k-1]; kallocs[j].varname:=s1; kallocs[j].size:=StrToInt(s2); setlength(assemblerlines,length(assemblerlines)-1); //don't bother with this in the 2nd pass continue; end else raise exception.Create(rsWrongSyntaxKallocIdentifierSizeinbytes); end; {$endif} {$endif} //replace KALLOC identifiers with values so the assemble error check doesnt crash on that {$ifdef windows} if processhandler.is64bit then begin for j:=0 to length(kallocs)-1 do currentline:=replacetoken(currentline,kallocs[j].varname,'ffffffffffffffff'); end else begin for j:=0 to length(kallocs)-1 do currentline:=replacetoken(currentline,kallocs[j].varname,'00000000'); end; {$endif} //check for assembler errors //address if currentline[length(currentline)]=':' then begin try ok1:=false; for j:=0 to length(labels)-1 do if currentline=labels[j].labelname+':' then begin labels[j].assemblerline:=length(assemblerlines)-1; ok1:=true; continue; end; if ok1 then continue; //no check //still here, so more complex if syntaxcheckonly and (disableinfo<>nil) then begin //replace tokens with registered symbols from the enable part for j:=0 to disableinfo.registeredsymbols.count-1 do currentline:=replacetoken(currentline, disableinfo.registeredsymbols[j], '00000000'); end; try s1:=copy(currentline,1,length(currentline)-1); if s1<>'' then testPtr:=symhandler.getAddressFromName(s1); except currentline:=inttohex(symhandler.getaddressfromname(copy(currentline,1,length(currentline)-1)),8)+':'; assemblerlines[length(assemblerlines)-1].linenr:=currentlinenr; assemblerlines[length(assemblerlines)-1].line:=currentline; end; except //add this as a label if a potential label if potentiallabels.IndexOf(copy(currentline,1,length(currentline)-1))=-1 then raise symexception.Create(rsThisAddressSpecifierIsNotValid); j:=length(labels); setlength(labels,j+1); ZeroMemory(@labels[j],sizeof(labels[j])); labels[j].labelname:=copy(currentline,1,length(currentline)-1); labels[j].assemblerline:=length(assemblerlines)-1; labels[j].defined:=false; setlength(labels[j].references,0); setlength(labels[j].references2,0); //setlength(assemblerlines, length(assemblerlines)-1); // assemblerlines[length(assemblerlines)-1]:=''; // continue; //raise exception.Create(rsThisAddressSpecifierIsNotValid); end; continue; //next line end; //replace label references with 00000000 so the assembler check doesn't complain about it if processhandler.is64bit then begin for j:=0 to length(labels)-1 do currentline:=replacetoken(currentline,labels[j].labelname,'ffffffffffffffff'); end else begin for j:=0 to length(labels)-1 do currentline:=replacetoken(currentline,labels[j].labelname,'00000000'); end; try //replace identifiers in the line with their address ok1:=false; try ok1:=assemble(currentline,currentaddress,assembled[0].bytes, apNone, true); except end; if not ok1 then //the instruction could not be assembled as it is right now begin //try potential labels ok1:=false; for j:=0 to potentiallabels.count-1 do begin if processhandler.is64bit then currentline:=replacetoken(currentline,potentiallabels[j],'ffffffffffffffff') else currentline:=replacetoken(currentline,potentiallabels[j],'00000000'); try ok1:=assemble(currentline,currentaddress,assembled[0].bytes, apNone, true); if ok1 then begin //define this potential label as a full label k:=length(labels); setlength(labels, k+1); ZeroMemory(@labels[k],sizeof(labels[k])); labels[k].labelname:=potentiallabels[j]; labels[k].defined:=false; labels[k].afterccode:=false; setlength(labels[k].references,0); setlength(labels[k].references2,0); break; end; except //don't quit yet on e: exception do begin OutputDebugString('Potential labeling error:'+e.message); end end; end; end; if not ok1 then raise EAutoAssembler.Create('bla'); except //ShowMessage(code.text); raise EAutoAssembler.Create(rsThisInstructionCanTBeCompiled); end; finally inc(i); end; except on E:exception do raise EAutoAssembler.Create(Format(rsErrorInLine, [IntToStr(currentlinenr), currentline, e.Message])); end; end; if length(addsymbollist)>0 then begin //now scan the addsymbollist entries for allocs and labels and see if they exist for i:=0 to length(addsymbollist)-1 do begin ok1:=false; for j:=0 to length(allocs)-1 do //scan allocs if uppercase(addsymbollist[i])=uppercase(allocs[j].varname) then begin ok1:=true; break; end; if not ok1 then //scan labels for j:=0 to length(labels)-1 do if uppercase(addsymbollist[i])=uppercase(labels[j].labelname) then begin ok1:=true; break; end; if not ok1 then //scan defines for j:=0 to length(defines)-1 do if uppercase(addsymbollist[i])=uppercase(defines[j].name) then begin ok1:=true; break; end; if not ok1 then raise EAssemblerException.create(Format(rsWasSupposedToBeAddedToTheSymbollistButItIsnTDeclar, [addsymbollist[i]])); end; end; //check to see if the addresses are valid (label, alloc, define) if length(createthread)>0 then for i:=0 to length(createthread)-1 do begin ok1:=true; try testptr:=symhandler.getAddressFromName(createthread[i]); except ok1:=false; end; if not ok1 then for j:=0 to length(labels)-1 do if uppercase(labels[j].labelname)=uppercase(createthread[i]) then begin ok1:=true; break; end; if not ok1 then for j:=0 to length(allocs)-1 do if uppercase(allocs[j].varname)=uppercase(createthread[i]) then begin ok1:=true; break; end; {$ifndef net} if not ok1 then for j:=0 to length(kallocs)-1 do if uppercase(kallocs[j].varname)=uppercase(createthread[i]) then begin ok1:=true; break; end; {$endif} if not ok1 then for j:=0 to length(defines)-1 do if uppercase(defines[j].name)=uppercase(createthread[i]) then begin try testptr:=symhandler.getAddressFromName(defines[j].whatever); ok1:=true; except end; break; end; if not ok1 then raise EAssemblerException.create(Format(rsTheAddressInCreatethreadIsNotValid, [createthread[i]])); end; if length(createthreadandwait)>0 then for i:=0 to length(createthreadandwait)-1 do begin ok1:=true; try testptr:=symhandler.getAddressFromName(createthreadandwait[i].name); except ok1:=false; end; if not ok1 then for j:=0 to length(labels)-1 do if uppercase(labels[j].labelname)=uppercase(createthreadandwait[i].name) then begin ok1:=true; break; end; if not ok1 then for j:=0 to length(allocs)-1 do if uppercase(allocs[j].varname)=uppercase(createthreadandwait[i].name) then begin ok1:=true; break; end; {$ifndef net} if not ok1 then for j:=0 to length(kallocs)-1 do if uppercase(kallocs[j].varname)=uppercase(createthreadandwait[i].name) then begin ok1:=true; break; end; {$endif} if not ok1 then for j:=0 to length(defines)-1 do if uppercase(defines[j].name)=uppercase(createthreadandwait[i].name) then begin try testptr:=symhandler.getAddressFromName(defines[j].whatever); ok1:=true; except end; break; end; if not ok1 then begin raise EAssemblerException.create(Format(rsTheAddressInCreatethreadAndWaitIsNotValid, [createthreadandwait[i].name])); end; end; if length(loadbinary)>0 then for i:=0 to length(loadbinary)-1 do begin ok1:=true; try testptr:=symhandler.getAddressFromName(loadbinary[i].address); except ok1:=false; end; if not ok1 then for j:=0 to length(labels)-1 do if uppercase(labels[j].labelname)=uppercase(loadbinary[i].address) then begin ok1:=true; break; end; if not ok1 then for j:=0 to length(allocs)-1 do if uppercase(allocs[j].varname)=uppercase(loadbinary[i].address) then begin ok1:=true; break; end; {$ifndef net} if not ok1 then for j:=0 to length(kallocs)-1 do if uppercase(kallocs[j].varname)=uppercase(loadbinary[i].address) then begin ok1:=true; break; end; {$endif} if not ok1 then for j:=0 to length(defines)-1 do if uppercase(defines[j].name)=uppercase(loadbinary[i].address) then begin try testptr:=symhandler.getAddressFromName(defines[j].whatever); ok1:=true; except end; break; end; if not ok1 then raise EAssemblerException.create(Format(rsTheAddressInLoadbinaryIsNotValid, [loadbinary[i].address, loadbinary[i].filename])); end; { //check for the 3th alloc parameter when testing the validity of the script, and ask if the user understands what will happen if popupmessages and processhandler.is64Bit and usesaobscan and (length(allocs)>0) then begin //check if a prefered address is used prefered:=0; for i:=0 to length(allocs)-1 do if allocs[i].prefered<>0 then begin prefered:=allocs[i].prefered; break; end; if (prefered=0) and (MessageDlg(rsNoPreferedRangeAllocWarning, mtWarning, [mbyes, mbno], 0)<>mryes) then exit(false); end;} if syntaxcheckonly then exit(true); {$ifndef jni} if popupmessages and (messagedlg(rsThisCodeCanBeInjectedAreYouSure, mtConfirmation, [mbyes, mbno], 0)<>mryes) then exit; {$endif} //allocate the memory if length(allocs)>0 then begin //move ceinternal_autofree allocs to the end k:=length(allocs); i:=0; while iprotection then begin //increment x to the next pagebase {$ifdef windows} if (x and $fff>0) then begin y:=$1000- (x and $fff); inc(x,y); inc(allocs[i-1].size,y); //adjust the previous entry's size end; {$else} if (x and (getPageSize-1)>0) then begin y:=getPageSize- (x and (getPageSize-1)); inc(x,y); inc(allocs[i-1].size,y); //adjust the previous entry's size end; {$endif} protection:=allocs[i].protection; end; if (allocs[i].prefered<>0) then begin //if yes, is it the same as the previous entry? (or was the previous one that doesn't care?) if prefered=0 then prefered:=allocs[i].prefered; if (prefered<>allocs[i].prefered) then begin //different prefered address if x>0 then //it has some previous entries with compatible locations begin k:=10; allocs[j].address:=0; while (k>0) and (allocs[j].address=0) do begin //try allocating until a memory region has been found (e.g due to quick allocating by the game) if (prefered=0) and (j>0) then //if not a prefered address but there is a previous alloc, allocate near there prefered:=allocs[j-1].address; oldprefered:=prefered; prefered:=ptrUint(FindFreeBlockForRegion(prefered,x)); if (prefered=0) and (oldprefered<>0) then prefered:=oldprefered; if SystemSupportsWritableExecutableMemory then allocs[j].address:=ptrUint(virtualallocex(processhandle,pointer(prefered),x, MEM_RESERVE or MEM_COMMIT,PAGE_EXECUTE_READWRITE)) else allocs[j].address:=ptrUint(virtualallocex(processhandle,pointer(prefered),x, MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE)); if allocs[j].address=0 then begin OutputDebugString(rsFailureToAllocateMemory+' 1'); inc(prefered,65536); end; dec(k); end; if allocs[j].address=0 then allocs[j].address:=lastChanceAllocPrefered(prefered,x, protection); if allocs[j].address=0 then begin if WarnOnNearbyAllocationFailure then begin with TAllocWarn.create do begin if allocs[j].prefered<>0 then preferedaddress:=allocs[j].prefered else preferedaddress:=prefered; warn; free; end; end; if NearbyAllocationFailureFatal then raise EAssemblerException.create(format(rsFailureAlloc, [prefered,allocs[j].varname])) else begin if SystemSupportsWritableExecutableMemory then allocs[j].address:=ptrUint(virtualallocex(processhandle,nil,x, MEM_RESERVE or MEM_COMMIT,PAGE_EXECUTE_READWRITE)) else allocs[j].address:=ptrUint(virtualallocex(processhandle,nil,x, MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE)); end; end; if allocs[j].address=0 then raise EAssemblerException.create(rsFailureToAllocateMemory); //adjust the addresses of entries that are part of this block for k:=j+1 to i-1 do allocs[k].address:=allocs[k-1].address+allocs[k-1].size; x:=0; end; //new prefered address j:=i; prefered:=allocs[i].prefered; protection:=allocs[i].protection; end; end; //no prefered location specified, OR same prefered location inc(x,allocs[i].size); end; //after the loop if x>0 then begin //adjust the address of entries that are part of this final block k:=10; allocs[j].address:=0; while (k>0) and (allocs[j].address=0) do begin i:=0; if (prefered=0) and (j>0) then //if not a prefered address but there is a previous alloc, allocate near there prefered:=allocs[j-1].address; oldprefered:=prefered; prefered:=ptrUint(FindFreeBlockForRegion(prefered,x)); if (prefered=0) and (oldprefered<>0) then prefered:=oldprefered; if SystemSupportsWritableExecutableMemory then allocs[j].address:=ptrUint(virtualallocex(processhandle,pointer(prefered),x, MEM_RESERVE or MEM_COMMIT,PAGE_EXECUTE_READWRITE)) else allocs[j].address:=ptrUint(virtualallocex(processhandle,pointer(prefered),x, MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE)); if allocs[j].address=0 then begin OutputDebugString(rsFailureToAllocateMemory+' 3 (prefered='+inttohex(prefered,8)+')'); inc(prefered, 65536); end; dec(k); end; if allocs[j].address=0 then allocs[j].address:=lastChanceAllocPrefered(prefered,x, protection); if allocs[j].address=0 then begin if WarnOnNearbyAllocationFailure then begin with TAllocWarn.create do begin if allocs[j].prefered<>0 then preferedaddress:=allocs[j].prefered else preferedaddress:=prefered; warn; free; end; end; if NearbyAllocationFailureFatal then raise EAssemblerException.create(format(rsFailureAlloc, [prefered,allocs[j].varname])) else begin if SystemSupportsWritableExecutableMemory then allocs[j].address:=ptrUint(virtualallocex(processhandle,nil,x, MEM_RESERVE or MEM_COMMIT,PAGE_EXECUTE_READWRITE)) else allocs[j].address:=ptrUint(virtualallocex(processhandle,nil,x, MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE)); end; end; // allocs[j].address:=ptrUint(virtualallocex(processhandle,nil,x, MEM_RESERVE or MEM_COMMIT,protection)); if allocs[j].address=0 then raise EAssemblerException.create(rsFailureToAllocateMemory); for i:=j+1 to length(allocs)-1 do allocs[i].address:=allocs[i-1].address+allocs[i-1].size; end; //apply protections: for i:=0 to length(allocs)-1 do begin if (not SystemSupportsWritableExecutableMemory) or (allocs[i].protection<>PAGE_EXECUTE_READWRITE) then VirtualProtectEx(processhandle, pointer(allocs[i].address), allocs[i].size, allocs[i].protection,protection); end; end; {$ifdef windows} {$ifndef net} //kernel alloc if length(kallocs)>0 then begin x:=0; for i:=0 to length(kallocs)-1 do inc(x,kallocs[i].size); kallocs[0].address:=ptrUint(KernelAlloc(x)); for i:=1 to length(kallocs)-1 do kallocs[i].address:=kallocs[i-1].address+kallocs[i-1].size; end; {$endif} {$endif} //-----------------------2nd pass------------------------ //assemblerlines only contains label specifiers and assembler instructions setlength(assembled,0); currentlinenr:=0; try for i:=0 to length(assemblerlines)-1 do begin currentline:=assemblerlines[i].line; currentlinenr:=assemblerlines[i].linenr; createthreadandwaitid:=-1; for j:=0 to length(createthreadandwait)-1 do //there can be multiple at the time of assembly. All entries up to the higest value will be picked at a blockwrite (and made 0 so next blockwrite won't do them) begin if (i>createthreadandwait[j].position) or (i=length(Assemblerlines)-1) then //if it's the last line, then do all remaining createthreadandwaitid:=j; end; //plugin {$ifndef jni} if length(currentline)>0 then begin currentlinep:=@currentline[1]; pluginhandler.handleAutoAssemblerPlugin(@currentlinep, 2,aaid); currentline:=currentlinep; //if handled currentline will have it's identifiers regarding the plugin's previously registered stuff replaced //note that this can be called in a multithreaded situation, so the plugin must hld storage containers on a threadid base and handle the locking itself end; {$endif} //plugin tokenize(currentline,tokens); //if alloc then replace with the address for j:=0 to length(allocs)-1 do currentline:=replacetoken(currentline,allocs[j].varname,IntToHex(allocs[j].address,8)); //if kalloc then replace with the address for j:=0 to length(kallocs)-1 do currentline:=replacetoken(currentline,kallocs[j].varname,IntToHex(kallocs[j].address,8)); for j:=0 to length(defines)-1 do currentline:=replacetoken(currentline,defines[j].name,defines[j].whatever); ok1:=false; if currentline[length(currentline)]<>':' then //if it's not a definition then begin for j:=0 to length(labels)-1 do begin if (tokencheck(currentline,labels[j].labelname)) then begin //this instruction references a label if not labels[j].defined then begin //the address hasn't been found yet //this is the part that causes those nops after a short jump below the current instruction //problem: The size of these instructions determine where this label will be defined //close s1:=replacetoken(currentline,labels[j].labelname,IntToHex(currentaddress,8)); //far and big if processhandler.SystemArchitecture=archarm then begin currentline:=replacetoken(currentline,labels[j].labelname,IntToHex(currentaddress+$4FFFFF8,8)); end else begin if (processhandler.is64Bit) then //and not in region begin //check if between here and the definition of labels[j].labelname is an write pointer change specifier to a region too far away from currentaddress, if not, LONG will suffice //tip: you 'could' disassemble everything inbetween and see if a small jmp is possible as well (just a lot slower) mustbefar:=false; for l:=i+1 to length(assemblerlines)-1 do begin currentline2:=assemblerlines[l].line; if currentline2=labels[j].labelname+':' then break; //reached the label if currentline2[length(currentline2)]=':' then begin //check if it's just a label or alloc in the same group for k:=0 to length(defines)-1 do currentline2:=replacetoken(currentline2,defines[k].name,defines[k].whatever); s2:=copy(currentline2,1,length(currentline2)-1); for k:=0 to length(allocs)-1 do begin if allocs[k].varname=s2 then begin if currentaddress>allocs[k].address then diff:=currentaddress-allocs[k].address else diff:=allocs[k].address-currentaddress; if diff>=$80000000 then begin mustbefar:=true; break; end; end; end; if mustbefar then break; for k:=0 to length(kallocs)-1 do begin if kallocs[k].varname=s2 then begin if currentaddress>kallocs[k].address then diff:=currentaddress-kallocs[k].address else diff:=kallocs[k].address-currentaddress; if diff>=$80000000 then begin mustbefar:=true; break; end; end; end; if mustbefar then break; //if it's a label it's ok ok1:=false; for k:=0 to length(labels)-1 do begin if labels[k].labelname=s2 then begin ok1:=true; break; end; end; if ok1 then continue; //it's a label, no need to do a heavy symbol lookup //not an alloc or kalloc try testptr:=getAddressFromScript(s2); if testptr=0 then testptr:=symhandler.getAddressFromName(s2); if currentaddress>testptr then diff:=currentaddress-testptr else diff:=testptr-currentaddress; if diff>=$80000000 then begin mustbefar:=true; break; end; except mustbefar:=true; end; if mustbefar then break; end; end; if mustbefar=false then begin if dataForAACodePass2.cdata.cscript<>nil then begin for k:=0 to length(dataForAACodePass2.cdata.symbols)-1 do begin if lowercase(labels[j].labelname)=lowercase(dataForAACodePass2.cdata.symbols[k].name) then begin //the c code could be outside reach from the current point testptr:=getAddressFromScript('ceinternal_autofree_ccode'); if currentaddress>testptr then diff:=currentaddress-testptr else diff:=testptr-currentaddress; if diff>=$80000000 then mustbefar:=true; break; end; end; end; end; if mustbefar then currentline:=replacetoken(currentline,labels[j].labelname,IntToHex(currentaddress+$2000FFFFF,8)) else currentline:=replacetoken(currentline,labels[j].labelname,IntToHex(currentaddress+$FFFFF,8)); end else currentline:=replacetoken(currentline,labels[j].labelname,IntToHex(currentaddress+$FFFFF,8)); end; setlength(assembled,length(assembled)+1); assembled[length(assembled)-1].createthreadandwait:=createthreadandwaitid; assembled[length(assembled)-1].address:=currentaddress; ok1:=assemble(currentline,currentaddress,assembled[length(assembled)-1].bytes, apnone, true); //far a:=length(assembled[length(assembled)-1].bytes); ok2:=assemble(s1,currentaddress,assembled[length(assembled)-1].bytes, apnone, true); //close b:=length(assembled[length(assembled)-1].bytes); if not (ok1 or ok2) then raise exception.create(assemblerlines[l].line+' can not be assembled'); if a>b then //pick the biggest one assemble(currentline,currentaddress,assembled[length(assembled)-1].bytes); //add this instruction to the list of lines that reference this label setlength(labels[j].references,length(labels[j].references)+1); labels[j].references[length(labels[j].references)-1]:=length(assembled)-1; setlength(labels[j].references2,length(labels[j].references2)+1); labels[j].references2[length(labels[j].references2)-1]:=i; inc(currentaddress,length(assembled[length(assembled)-1].bytes)); ok1:=true; end else currentline:=replacetoken(currentline,labels[j].labelname,IntToHex(labels[j].address,8)); //break; end; end; end; if ok1 then continue; if currentline[length(currentline)]=':' then //address setter/assigner begin ok1:=false; for j:=0 to length(labels)-1 do begin if i=labels[j].assemblerline then begin if labels[j].defined=true then begin currentaddress:=labels[j].address end else begin labels[j].address:=currentaddress; labels[j].defined:=true; end; ok1:=true; //reassemble the instructions that had no target for k:=0 to length(labels[j].references)-1 do begin a:=length(assembled[labels[j].references[k]].bytes); //original size of the assembled code s1:=replacetoken(assemblerlines[labels[j].references2[k]].line,labels[j].labelname,IntToHex(labels[j].address,8)); {$ifdef cpu64} if processhandler.is64Bit then ok1:=assemble(s1,assembled[labels[j].references[k]].address,assembled[labels[j].references[k]].bytes) else {$endif} ok1:=assemble(s1,assembled[labels[j].references[k]].address,assembled[labels[j].references[k]].bytes, apLong); if not ok1 then raise exception.create(format(rsFailureAssembling,[s1, assembled[labels[j].references[k]].address])); b:=length(assembled[labels[j].references[k]].bytes); //new size setlength(assembled[labels[j].references[k]].bytes,a); //original size (original size is always bigger or equal than newsize) if b>a then begin raise exception.create('Assembler error. The generated instruction referencing a label ended up bigger than expected. Try using the far indicator'); end; if (b8 then replace with the far version assemble('nop '+inttohex(a-b,1),0,nops); for l:=b to a-1 do assembled[labels[j].references[k]].bytes[l]:=nops[l-b]; // for l:=b to a-1 do // assembled[labels[j].references[k]].bytes[l]:=$90; //nop end; end; break; end; end; if ok1 then continue; try currentaddress:=symhandler.getAddressFromName(copy(currentline,1,length(currentline)-1)); continue; //next line except raise EAssemblerException.create(rsThisAddressSpecifierIsNotValid); end; end; setlength(assembled,length(assembled)+1); assembled[length(assembled)-1].address:=currentaddress; assembled[length(assembled)-1].createthreadandwait:=createthreadandwaitid; if (currentline<>'') and (currentline[1]='<') then //special assembler instruction begin if copy(currentline,1,8)='', [@l]); setlength(assembled[length(assembled)-1].bytes, readmems[l].bytelength); CopyMemory(@assembled[length(assembled)-1].bytes[0], readmems[l].bytes, readmems[l].bytelength); end {$ifdef onebytejumps} else if copy(currentline,1,6)='$80000000) and (DBKLoaded) then MakeWritable(fullaccess[i].address,(fullaccess[i].size div 4096)*4096,false); {$endif} end; end; //load binaries if length(loadbinary)>0 then begin for i:=0 to length(loadbinary)-1 do begin testptr:=getAddressFromScript(loadbinary[i].address); if testptr<>0 then begin binaryfile:=tmemorystream.Create; try binaryfile.LoadFromFile(loadbinary[i].filename); if (CurrentDebuggerInterface is TGDBServerDebuggerInterface) and GDBWriteProcessMemoryCodeOnly then ok2:=TGDBServerDebuggerInterface(CurrentDebuggerInterface).writeBytes(testptr, binaryfile.Memory, binaryfile.size) else ok2:=writeprocessmemory(processhandle,pointer(testptr),binaryfile.Memory,binaryfile.Size,x); finally binaryfile.free; end; end else raise exception.create('Failure '); end; end; //fill in the addresses requested by dataForAACodePass2 and finish the compilation if dataForAACodePass2.cdata.cscript<>nil then begin dataForAACodePass2.cdata.address:=getAddressFromScript('ceinternal_autofree_ccode'); //warning: do not step over this with the debugger for i:=0 to length(dataForAACodePass2.cdata.references)-1 do begin dataForAACodePass2.cdata.references[i].address:=getAddressFromScript(dataForAACodePass2.cdata.references[i].name); if dataForAACodePass2.cdata.references[i].address=0 then begin OutputDebugString('Failure getting reference for '+dataForAACodePass2.cdata.references[i].name); end; end; if disableinfo<>nil then AutoassemblerCodePass2(dataForAACodePass2, disableinfo.ccodesymbols) else AutoassemblerCodePass2(dataForAACodePass2, nil); if disableinfo<>nil then begin if targetself then selfsymhandler.AddSymbolList(disableinfo.ccodesymbols) else symhandler.AddSymbolList(disableinfo.ccodesymbols); disableinfo.sourcecodeinfo:=dataForAACodePass2.cdata.sourceCodeInfo; if disableinfo.sourcecodeinfo<>nil then disableinfo.sourcecodeinfo.register; end else begin //else do not register the symbols (You can't disable them otherwise) freeandnil(dataForAACodePass2.cdata.sourceCodeInfo); end; //reassemble c-code reference for j:=0 to length(labels)-1 do begin if labels[j].afterccode then begin ok1:=false; for k:=0 to length(dataForAACodePass2.cdata.symbols)-1 do begin if labels[j].labelname=dataForAACodePass2.cdata.symbols[k].name then begin labels[j].address:=dataForAACodePass2.cdata.symbols[k].address; labels[j].defined:=true; ok1:=labels[j].address<>0; break; end; end; if not ok1 then raise exception.create('Failure getting the address for c-symbol '+labels[j].labelname); //todo: change to a function so both originallabel and this can use it for k:=0 to length(labels[j].references)-1 do begin a:=length(assembled[labels[j].references[k]].bytes); //original size of the assembled code s1:=replacetoken(assemblerlines[labels[j].references2[k]].line,labels[j].labelname,IntToHex(labels[j].address,8)); {$ifdef cpu64} if processhandler.is64Bit then assemble(s1,assembled[labels[j].references[k]].address,assembled[labels[j].references[k]].bytes) else {$endif} assemble(s1,assembled[labels[j].references[k]].address,assembled[labels[j].references[k]].bytes, apLong); b:=length(assembled[labels[j].references[k]].bytes); //new size setlength(assembled[labels[j].references[k]].bytes,a); //original size (original size is always bigger or equal than newsize) if (b0) {$ifdef onebytejumps}or (length(onebytejumps)>0){$endif} then InitializeAutoAssemblerExceptionHandler; if length(exceptionlist)>0 then for i:=length(exceptionlist)-1 downto 0 do //add it in the reverse order so the nested try/excepts come first AutoAssemblerExceptionHandlerAddExceptionRange(getAddressFromScript(exceptionlist[i].trylabel), getAddressFromScript(exceptionlist[i].exceptlabel)); {$ifdef onebytejumps} if length(onebytejumps)>0 then for i:=0 to length(onebytejumps)-1 do AutoAssemblerExceptionHandlerAddChangeRIPEntry(onebytejumps[i].originaddress, getAddressFromScript(onebytejumps[i].destinationlabel)); {$endif} if (length(exceptionlist)>0) {$ifdef onebytejumps}or (length(onebytejumps)>0){$endif} then AutoAssemblerExceptionHandlerApplyChanges; {$ifdef windows} connection:=getconnection; if connection<>nil then connection.beginWriteProcessMemory; //group all writes {$endif} //combine assembly lines j:=0; for i:=1 to length(assembled)-1 do begin if assembled[i].address=assembled[j].address+length(assembled[j].bytes) then //matches the previous entry begin //group k:=length(assembled[j].bytes); setlength(assembled[j].bytes, k+length(assembled[i].bytes)); copymemory(@assembled[j].bytes[k], @assembled[i].bytes[0], length(assembled[i].bytes)); assembled[j].createthreadandwait:=max(assembled[j].createthreadandwait, assembled[i].createthreadandwait); //should always pick i //mark it as empty setlength(assembled[i].bytes,0); assembled[i].address:=0; assembled[i].createthreadandwait:=-1; end else begin j:=i; //new block end; end; if (not SystemSupportsWritableExecutableMemory) and (not SkipVirtualProtectEx) and (ProcessID<>GetCurrentProcessId) then begin if (CurrentDebuggerInterface is TGDBServerDebuggerInterface) then TGDBServerDebuggerInterface(CurrentDebuggerInterface).suspendProcess else ntsuspendProcess(processhandle); end; for i:=0 to length(assembled)-1 do begin if length(assembled[i].bytes)=0 then continue; testptr:=assembled[i].address; op:=0; if SystemSupportsWritableExecutableMemory or SkipVirtualProtectEx then vpe:=(SkipVirtualProtectEx=false) and virtualprotectex(processhandle,pointer(testptr),length(assembled[i].bytes),PAGE_EXECUTE_READWRITE,op) else vpe:=(SkipVirtualProtectEx=false) and virtualprotectex(processhandle,pointer(testptr),length(assembled[i].bytes),PAGE_READWRITE,op); if vpe then outputdebugstring('autoassemble: original protection was '+op.ToString); if (CurrentDebuggerInterface is TGDBServerDebuggerInterface) and GDBWriteProcessMemoryCodeOnly then ok1:=TGDBServerDebuggerInterface(CurrentDebuggerInterface).writeBytes(testptr, @assembled[i].bytes[0], length(assembled[i].bytes)) else ok1:={$ifdef windows}WriteProcessMemoryWithCloakSupport{$else}WriteProcessMemory{$endif}(processhandle, pointer(testptr),@assembled[i].bytes[0],length(assembled[i].bytes),x); if vpe then virtualprotectex(processhandle,pointer(testptr),length(assembled[i].bytes),op,op2); if not ok1 then ok2:=false; if ok2 and (assembled[i].createthreadandwait<>-1) then begin //create threads for j:=0 to assembled[i].createthreadandwait do begin if createthreadandwait[j].position<>-1 then HandleCreateThreadAndWait(j); end; end; end; if (not SystemSupportsWritableExecutableMemory) and (not SkipVirtualProtectEx) and (ProcessID<>GetCurrentProcessId) then begin if (CurrentDebuggerInterface is TGDBServerDebuggerInterface) then TGDBServerDebuggerInterface(CurrentDebuggerInterface).resumeProcess else ntresumeProcess(processhandle); end; {$ifdef windows} if connection<>nil then //group all writes begin if connection.endWriteProcessMemory=false then ok2:=false; end; {$endif} //handle the unhandled createthreadandwait blocks for i:=0 to length(createthreadandwait)-1 do begin if createthreadandwait[i].position<>-1 then HandleCreateThreadAndWait(i); end; if not ok2 then begin {$ifndef jni} if popupmessages then showmessage(rsNotAllInstructionsCouldBeInjected) else begin if memrec<>nil then //there is an memrec provided, so also an ewxception handler raise exception.create(rsNotAllInstructionsCouldBeInjected); end; {$endif} end else begin if disableinfo<>nil then begin //see if all allocs are deallocated for i:=0 to length(disableinfo.allocs)-1 do begin //free the ceinternal_autofree entries (if they aren't already marked) if disableinfo.allocs[i].varname.StartsWith('ceinternal_autofree') then begin ok1:=false; for j:=0 to length(dealloc)-1 do begin if dealloc[j]=disableinfo.allocs[i].address then begin ok1:=true; break; end; end; if ok1=false then //not in the list yet, add it begin j:=length(dealloc); setlength(dealloc, j+1); dealloc[j]:=disableinfo.allocs[i].address; end; end; end; if (length(dealloc)>0) and (length(dealloc)=length(disableinfo.allocs)) then //free everything begin {$ifdef cpu64} baseaddress:=ptrUint($FFFFFFFFFFFFFFFF); {$else} baseaddress:=$FFFFFFFF; {$endif} for i:=0 to length(disableinfo.allocs)-1 do begin virtualfreeex(processhandle,pointer(dealloc[i]),0,MEM_RELEASE); if (targetself=false) and allocsAddToUnexpectedExceptionList then RemoveUnexpectedExceptionRegion(dealloc[i],0); { if ceallocarray[i].address0) and (AutoAssemblerExceptionHandlerHasEntries) then begin for i:=0 to length(disableinfo.exceptions)-1 do AutoAssemblerExceptionHandlerRemoveExceptionRange(disableinfo.exceptions[i]); AutoAssemblerExceptionHandlerApplyChanges; end; setlength(disableinfo.exceptions, length(exceptionlist)); for i:=0 to length(disableinfo.exceptions)-1 do disableinfo.exceptions[i]:=getAddressFromScript(exceptionlist[i].trylabel); end; //check the addsymbollist array and deletesymbollist array //first delete for i:=0 to length(deletesymbollist)-1 do symhandler.DeleteUserdefinedSymbol(deletesymbollist[i]); //now scan the addsymbollist array and add them to the userdefined list for i:=0 to length(addsymbollist)-1 do begin ok1:=false; for j:=0 to length(allocs)-1 do if uppercase(addsymbollist[i])=uppercase(allocs[j].varname) then begin try symhandler.DeleteUserdefinedSymbol(addsymbollist[i]); //delete old one so you can add the new one symhandler.AddUserdefinedSymbol(inttohex(allocs[j].address,8),addsymbollist[i], true); ok1:=true; except //don't crash when it's already defined or address=0 end; break; end; if not ok1 then for j:=0 to length(labels)-1 do if uppercase(addsymbollist[i])=uppercase(labels[j].labelname) then begin try symhandler.DeleteUserdefinedSymbol(addsymbollist[i]); //delete old one so you can add the new one symhandler.AddUserdefinedSymbol(inttohex(labels[j].address,8),addsymbollist[i], true); ok1:=true; except //don't crash when it's already defined or address=0 end; end; if not ok1 then for j:=0 to length(defines)-1 do if uppercase(addsymbollist[i])=uppercase(defines[j].name) then begin try symhandler.DeleteUserdefinedSymbol(addsymbollist[i]); //delete old one so you can add the new one symhandler.AddUserdefinedSymbol(defines[j].whatever, addsymbollist[i], true); ok1:=true; except end; end; end; //still here, so create threads if needed if length(createthread)>0 then begin for i:=0 to length(createthread)-1 do begin testptr:=getAddressFromScript(createthread[i]); ok1:=testptr<>0; if ok1 then //address found begin try threadhandle:=createremotethread(processhandle,nil,0,pointer(testptr),nil,0,bw); ok2:=threadhandle>0; if ok2 then closehandle(threadhandle); finally end; end; end; end; //^ thread creation //fill "allSymbols" if disableinfo<>nil then begin for i:=0 to length(labels)-1 do disableinfo.allsymbols.AddObject(labels[i].labelname, tobject(labels[i].address)); for i:=0 to length(allocs)-1 do disableinfo.allsymbols.AddObject(allocs[i].varname, tobject(allocs[i].address)); for i:=0 to length(kallocs)-1 do disableinfo.allsymbols.AddObject(kallocs[i].varname, tobject(kallocs[i].address)); for i:=0 to length(defines)-1 do begin testptr:=symhandler.getAddressFromName(defines[i].whatever,false,ok1); if ok1=false then disableinfo.allsymbols.AddObject(defines[i].name, tobject(testptr)); end; end; {$IFNDEF jni} if popupmessages then begin testPtr:=0; s1:=''; for i:=0 to length(globalallocs)-1 do begin if testPtr=0 then testPtr:=globalallocs[i].address; s1:=s1+#13#10+globalallocs[i].varname+'='+IntToHex(globalallocs[i].address,8); end; for i:=0 to length(allocs)-1 do begin if allocs[i].varname.StartsWith('ceinternal_') then continue; //don't show these if testPtr=0 then testPtr:=allocs[i].address; s1:=s1+#13#10+allocs[i].varname+'='+IntToHex(allocs[i].address,8); end; if length(kallocs)>0 then begin if testPtr=0 then testPtr:=kallocs[i].address; s1:=#13#10+rsTheFollowingKernelAddressesWhereAllocated+':'; for i:=0 to length(kallocs)-1 do s1:=s1+#13#10+kallocs[i].varname+'='+IntToHex(kallocs[i].address,8); end; // if messagedl if (testPtr=0) or (oldaamessage) then showmessage(rsTheCodeInjectionWasSuccessfull+s1) else begin if MessageDlg(rsTheCodeInjectionWasSuccessfull+s1+#13#10+rsGoTo+inttohex(testptr,8)+'?', mtInformation,[mbYes, mbNo], 0, mbno)=mrYes then begin memorybrowser.disassemblerview.selectedaddress:=testptr; memorybrowser.show; end; end; end; {$ENDIF} end; result:=ok2; if result and allocsAddToUnexpectedExceptionList and (not targetself) then begin for i:=0 to length(allocs)-1 do AddUnexpectedExceptionRegion(allocs[i].address,allocs[i].size); end; finally if dataForAACodePass2.cdata.cscript<>nil then freeandnil(dataForAACodePass2.cdata.cscript); for i:=0 to length(assembled)-1 do setlength(assembled[i].bytes,0); setlength(assembled,0); for i:=0 to length(readmems)-1 do if readmems[i].bytes<>nil then begin freememandnil(readmems[i].bytes); end; setlength(readmems,0); if tokens<>nil then freeandnil(tokens); {$IFNDEF jni} if pluginhandler<>nil then pluginhandler.handleAutoAssemblerPlugin(@currentlinep, 3,aaid); //tell the plugins to free their data if targetself then begin processhandler.processid:=oldprocessid; processhandler.processhandle:=oldhandle; symhandler:=oldsymhandler; end; {$ENDIF} if potentiallabels<>nil then freeandnil(potentiallabels); end; end; function getenableanddisablepos(code:tstrings;var enablepos,disablepos: integer): boolean; var i,j: integer; currentline: string; begin result:=false; enablepos:=-1; disablepos:=-1; for i:=0 to code.Count-1 do begin currentline:=code[i]; j:=pos('//',currentline); if j>0 then currentline:=copy(currentline,1,j-1); while (length(currentline)>0) and (currentline[1]=' ') do currentline:=copy(currentline,2,length(currentline)-1); while (length(currentline)>0) and (currentline[length(currentline)]=' ') do currentline:=copy(currentline,1,length(currentline)-1); if length(currentline)=0 then continue; if copy(currentline,1,2)='//' then continue; //skip if (uppercase(currentline))='[ENABLE]' then begin result:=true; //there's at least a enable section, so it's ok if enablepos<>-1 then begin enablepos:=-2; exit; end; enablepos:=i; end; if (uppercase(currentline))='[DISABLE]' then begin if disablepos<>-1 then begin disablepos:=-2; exit; end; disablepos:=i; end; end; end; procedure getEnableOrDisableScript(code: TStrings; newscript: tstrings; enablescript: boolean); { removes the enable or disable section from a script leaving only the outer code and the selected script routine } var i: integer; insideenable: boolean; insidedisable: boolean; begin insideenable:=false; insidedisable:=false; for i:=0 to code.Count-1 do begin if (uppercase(trim(code[i])))='[ENABLE]' then begin insideenable:=true; insidedisable:=false; continue; end; if (uppercase(trim(code[i])))='[DISABLE]' then begin insideenable:=false; insidedisable:=true; continue; end; // if ((not insideenable) and (not insidedisable)) or (insideenable and enablescript) or (insidedisable and not enablescript) then newscript.AddObject(code[i], code.Objects[i]); end; end; procedure stripCPUspecificCode(code: tstrings; strip32bit: boolean); var i: integer; s: string; inexcludedbitblock: boolean; begin inexcludedbitblock:=false; for i:=0 to code.Count-1 do begin s:=uppercase(Trim(code[i])); if s='[32-BIT]' then begin if strip32bit then inexcludedbitblock:=true; code[i]:=' '; end; if s='[/32-BIT]' then begin if strip32bit then inexcludedbitblock:=false; code[i]:=' '; end; if s='[64-BIT]' then begin if not strip32bit then inexcludedbitblock:=true; code[i]:=' '; end; if s='[/64-BIT]' then begin if not strip32bit then inexcludedbitblock:=false; code[i]:=' '; end; if inexcludedbitblock then code[i]:=' '; end; end; function autoassemble(code: Tstrings; popupmessages,enable,syntaxcheckonly, targetself: boolean; disableinfo: TDisableinfo=nil; memrec: TMemoryRecord=nil): boolean; overload; { targetself defines if the process that gets injected to is CE itself or the target process } var tempstrings: tstringlist; i,j: integer; currentline: string; enablepos,disablepos: integer; strip32bitcode: boolean; begin //add line numbers to the code for i:=0 to code.Count-1 do code.Objects[i]:=pointer(i+1); getenableanddisablepos(code,enablepos,disablepos); result:=false; if enablepos=-2 then begin raise EAssemblerException.create(rsYouCanOnlyHaveOneEnableSection); end; if disablepos=-2 then begin raise EAssemblerException.create(rsYouCanOnlyHaveOneDisableSection); end; tempstrings:=tstringlist.create; try if (enablepos=-1) and (disablepos=-1) then begin //everything tempstrings.AddStrings(code); end else begin if (enablepos=-1) then begin if not popupmessages then exit; raise EAssemblerException.create(rsYouHavnTSpecifiedAEnableSection); end; if (disablepos=-1) then begin if not popupmessages then exit; raise EAssemblerException.create(rsYouHavnTSpecifiedADisableSection); end; if enable then begin getEnableOrDisableScript(code, tempstrings, true); end else begin getEnableOrDisableScript(code, tempstrings,false); end; end; strip32bitcode:=processhandler.is64Bit; if targetself then strip32bitcode:={$ifdef cpu64}true{$else}false{$endif}; Stripcpuspecificcode(tempstrings, strip32bitcode); //todo: change to set for other types like arm result:=autoassemble2(tempstrings,popupmessages,syntaxcheckonly,targetself, disableinfo, memrec); finally tempstrings.Free; end; end; function autoassemble(code: tstrings;popupmessages: boolean):boolean; overload; begin result:=autoassemble(code,popupmessages,true,false,false); end; end.