mirror of
https://github.com/cheat-engine/cheat-engine
synced 2026-08-15 02:26:08 -04:00
388 lines
12 KiB
ObjectPascal
388 lines
12 KiB
ObjectPascal
unit firstscanhandler;
|
|
|
|
{
|
|
First scan handler is a class that will help with scanning the results of the
|
|
first scan.
|
|
It'll read the results of the first scan and provides an inteface for the
|
|
scanroutines for quick lookup of the previous value of a specific address
|
|
}
|
|
|
|
|
|
{
|
|
function BinSearchEntry(Strings: TStrings; address: dword; var Pivot: integer): integer;
|
|
var
|
|
First: Integer;
|
|
Last: Integer;
|
|
Found: Boolean;
|
|
begin
|
|
try
|
|
First := 0; //Sets the first item of the range
|
|
Last := Strings.Count-1; //Sets the last item of the range
|
|
Found := False; //Initializes the Found flag (Not found yet)
|
|
Result := -1; //Initializes the Result
|
|
|
|
while (First <= Last) and (not Found) do
|
|
begin
|
|
|
|
//Gets the middle of the selected range
|
|
Pivot := (First + Last) div 2;
|
|
//Compares the String in the middle with the searched one
|
|
if TMemoryAllocEvent(strings.Objects[Pivot]).BaseAddress = address then
|
|
begin
|
|
Found := True;
|
|
Result := Pivot;
|
|
end
|
|
//If the Item in the middle has a bigger value than
|
|
//the searched item, then select the first half
|
|
else if TMemoryAllocEvent(strings.Objects[Pivot]).BaseAddress > address then
|
|
Last := Pivot - 1
|
|
//else select the second half
|
|
else
|
|
First := Pivot + 1;
|
|
end;
|
|
except
|
|
outputdebugstring('Exception in BinSearchEntry');
|
|
end;
|
|
end; }
|
|
|
|
|
|
interface
|
|
|
|
uses windows,classes,sysutils,syncobjs;
|
|
|
|
type TFirstscantype= (fs_advanced,fs_addresslist);
|
|
type TValueType= (vt_byte,vt_word, vt_dword, vt_single, vt_double, vt_int64, vt_all);
|
|
|
|
|
|
type TFirstScanHandler = class
|
|
private
|
|
firstscanmemoryfile: TFileStream;
|
|
firstscanaddress: tmemorystream;
|
|
firstscantype: tfirstscantype;
|
|
|
|
firstscanmemory: pointer;
|
|
LoadedFromList: array of boolean;
|
|
LoadedFromListMREW: TMultiReadExclusiveWriteSynchronizer;
|
|
maxnumberofregions: integer;
|
|
|
|
|
|
procedure cleanup;
|
|
function loadIfNotLoadedRegion(p: pointer): pointer;
|
|
public
|
|
function getfirstscanbyte(address: dword): byte;
|
|
function getfirstscanword(address: dword): word;
|
|
function getfirstscandword(address: dword): dword;
|
|
function getfirstscansingle(address: dword): single;
|
|
function getfirstscandouble(address: dword): double;
|
|
function getfirstscanint64(address: dword): int64;
|
|
function getpointertoaddress(address:dword;valuetype:tvaluetype): pointer;
|
|
|
|
constructor create;
|
|
destructor destroy; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses cefuncproc, Math;
|
|
|
|
type TArrMemoryRegion= array [0..0] of TMemoryRegion;
|
|
|
|
|
|
function TFirstScanHandler.loadIfNotLoadedRegion(p: pointer): pointer;
|
|
{
|
|
Will load in a section from the memory file
|
|
p is a pointer in the memory buffer as if it was completly loaded
|
|
This will effectivly decrease reads to the file. Of course, there is still
|
|
unused memory which is kinda a waste, but it's the most efficient way
|
|
}
|
|
var index: integer;
|
|
base: pointer;
|
|
begin
|
|
result:=p;
|
|
|
|
{
|
|
adding a multireadexclusivewrite or not...
|
|
might result in memory being written multiple times to exactly the same value
|
|
but besides that no real problem.
|
|
decision: no need to block other threads. Besides, the way threadjobs are made
|
|
all have a seperate region to scan, so usually shouldn't have much overlap
|
|
}
|
|
if not LoadedFromList[(dword(p)-dword(firstscanmemory)) shr 12] then
|
|
begin
|
|
//not loaded yet, load this section
|
|
index:=(dword(p)-dword(firstscanmemory)) shr 12;
|
|
|
|
base:=pointer(dword(firstscanmemory)+(index shl 12));
|
|
firstscanmemoryfile.Seek((index shl 12),soFromBeginning);
|
|
|
|
//read 8KB (2 entries)
|
|
firstscanmemoryfile.Read(base^,$2000);
|
|
|
|
loadedfromlistMREW.BeginWrite;
|
|
LoadedFromList[index]:=true;
|
|
LoadedFromList[index+1]:=true;
|
|
loadedfromlistMREW.EndWrite;
|
|
end;
|
|
end;
|
|
|
|
function TFirstScanHandler.getpointertoaddress(address:dword;valuetype:tvaluetype): pointer;
|
|
var j: integer;
|
|
pm: ^TArrMemoryRegion;
|
|
pa: PDwordArray;
|
|
pab: PBitAddressArray;
|
|
p: pbyte;
|
|
p1: PByteArray;
|
|
p2: PWordArray;
|
|
p3: PDwordArray;
|
|
p4: PSingleArray;
|
|
p5: PDoubleArray;
|
|
p6: PInt64Array;
|
|
|
|
first,last: integer;
|
|
|
|
pivot: integer;
|
|
begin
|
|
result:=nil;
|
|
|
|
//5.4: change routine to only read in pages of 4KB if it wasn't paged in yet (does require a page table like list of course)
|
|
|
|
p:=pointer(dword(firstscanaddress.Memory)+7);
|
|
|
|
|
|
if firstscantype=fs_advanced then
|
|
begin
|
|
{the addressfile exists out of a list of memoryregions started with the text
|
|
REGION or NORMAL, so skip the first 7 bytes
|
|
}
|
|
pm:=pointer(dword(firstscanaddress.Memory)+7);
|
|
|
|
//find the region this address belongs to
|
|
//the region list should be sorted
|
|
|
|
|
|
first:=0;
|
|
last:=maxnumberofregions-1;
|
|
|
|
while (First <= Last) do
|
|
begin
|
|
|
|
//Gets the middle of the selected range
|
|
Pivot := (First + Last) div 2;
|
|
//Compares the String in the middle with the searched one
|
|
if InRange(address, pm[pivot].BaseAddress, pm[pivot].BaseAddress + pm[pivot].MemorySize) then
|
|
begin
|
|
//found it
|
|
result:=loadifnotloadedRegion(pointer(dword(pm[pivot].startaddress)+(address-pm[pivot].baseaddress)));
|
|
exit;
|
|
end
|
|
//If the Item in the middle has a bigger value than
|
|
//the searched item, then select the first half
|
|
else if pm[pivot].baseaddress > address then
|
|
Last := Pivot - 1
|
|
//else select the second half
|
|
else
|
|
First := Pivot + 1;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
pa:=pointer(p);
|
|
pab:=pointer(p);
|
|
p1:=firstscanmemory;
|
|
p2:=firstscanmemory;
|
|
p3:=firstscanmemory;
|
|
p4:=firstscanmemory;
|
|
p5:=firstscanmemory;
|
|
p6:=firstscanmemory;
|
|
|
|
if (valuetype <> vt_all) then
|
|
begin
|
|
//addresslist is a list of dword
|
|
|
|
j:=(firstscanaddress.Size-7) div sizeof(dword); //max number of addresses , no same as first for binary
|
|
|
|
//the list is sorted so do a quickscan
|
|
first:=0;
|
|
last:=j-1;
|
|
|
|
while (First <= Last) do
|
|
begin
|
|
|
|
//Gets the middle of the selected range
|
|
Pivot := (First + Last) div 2;
|
|
//Compares the String in the middle with the searched one
|
|
if address=pa[pivot] then
|
|
begin
|
|
//found it
|
|
case valuetype of
|
|
vt_byte : result:=loadifnotloadedregion(@p1[pivot]);
|
|
vt_word : result:=loadifnotloadedregion(@p2[pivot]);
|
|
vt_dword : result:=loadifnotloadedregion(@p3[pivot]);
|
|
vt_single: result:=loadifnotloadedregion(@p4[pivot]);
|
|
vt_double: result:=loadifnotloadedregion(@p5[pivot]);
|
|
vt_int64: result:=loadifnotloadedregion(@p6[pivot]);
|
|
end;
|
|
exit;
|
|
end
|
|
//If the Item in the middle has a bigger value than
|
|
//the searched item, then select the first half
|
|
else if pa[pivot] > address then
|
|
Last := Pivot - 1
|
|
//else select the second half
|
|
else
|
|
First := Pivot + 1;
|
|
end;
|
|
|
|
|
|
exit; //not found
|
|
end
|
|
else
|
|
begin
|
|
//addresslist is a list of 2 dwords, address and vartype, what kind of vartype is not important
|
|
|
|
j:=(firstscanaddress.Size-7) div sizeof(tbitaddress); //max number of addresses , no same as first for binary
|
|
|
|
//the list is sorted so do a quickscan
|
|
first:=0;
|
|
last:=j-1;
|
|
|
|
while (First <= Last) do
|
|
begin
|
|
|
|
//Gets the middle of the selected range
|
|
Pivot := (First + Last) div 2;
|
|
//Compares the String in the middle with the searched one
|
|
if address=pab[pivot].address then
|
|
begin
|
|
//found it
|
|
result:=loadifnotloadedregion(@p6[pivot]); //8 byte entries, doesnt have to match the same type, since it is the same 8 byte value that's stored
|
|
exit;
|
|
end
|
|
//If the Item in the middle has a bigger value than
|
|
//the searched item, then select the first half
|
|
else if pab[pivot].address > address then
|
|
Last := Pivot - 1
|
|
//else select the second half
|
|
else
|
|
First := Pivot + 1;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
raise exception.create('Failure in finding '+inttohex(address,8)+' in the first scan results');
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscanbyte(address: dword): byte;
|
|
begin
|
|
result:=pbyte(getpointertoaddress(address,vt_byte))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscanword(address: dword): word;
|
|
begin
|
|
result:=pword(getpointertoaddress(address,vt_word))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscandword(address: dword): dword;
|
|
begin
|
|
result:=pdword(getpointertoaddress(address,vt_dword))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscansingle(address: dword): single;
|
|
begin
|
|
result:=psingle(getpointertoaddress(address,vt_single))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscandouble(address: dword): double;
|
|
begin
|
|
result:=pdouble(getpointertoaddress(address,vt_double))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
function TFirstScanHandler.getfirstscanint64(address: dword): int64;
|
|
begin
|
|
result:=pint64(getpointertoaddress(address,vt_int64))^; //tries to read nil is not found, which should never happen, so I should get a bug report if it does
|
|
end;
|
|
|
|
|
|
constructor TFirstScanHandler.create;
|
|
var datatype: string[6];
|
|
pm: ^TArrMemoryRegion;
|
|
i: integer;
|
|
p: dword;
|
|
begin
|
|
try
|
|
try
|
|
|
|
firstscanmemoryfile:=Tfilestream.Create(CheatEngineDir+'MEMORYFIRST.TMP',fmOpenRead or fmsharedenynone);
|
|
firstscanmemory:=virtualalloc(nil, firstscanmemoryfile.Size+$2000, mem_commit, page_readwrite);
|
|
|
|
//make an array to store the previous memory in blocks of 4KB
|
|
setlength(loadedfromlist, 2+(firstscanmemoryfile.Size shr 12)); //+2 to keep some extra room, so less checking
|
|
zeromemory(@loadedfromlist[0], length(loadedfromlist));
|
|
|
|
loadedfromlistMREW:=TMultiReadExclusiveWriteSynchronizer.create;
|
|
except
|
|
raise exception.Create('No first scan data files found');
|
|
end;
|
|
|
|
firstscanaddress:=tmemorystream.Create;
|
|
try
|
|
firstscanaddress.LoadFromFile(cheatenginedir+'ADDRESSESFIRST.TMP');
|
|
except
|
|
raise exception.Create('No first scan data files found');
|
|
end;
|
|
|
|
firstscanaddress.ReadBuffer(datatype,7);
|
|
if datatype='REGION' then
|
|
begin
|
|
firstscantype:=fs_advanced;
|
|
maxnumberofregions:=(firstscanaddress.Size-7) div sizeof(TMemoryRegion); //max number of regions
|
|
|
|
//fill in startaddress elements
|
|
pm:=pointer(dword(firstscanaddress.Memory)+7);
|
|
p:=dword(firstscanmemory);
|
|
for i:=0 to maxnumberofregions-1 do
|
|
begin
|
|
pm[i].startaddress:=pointer(p);
|
|
inc(p, pm[i].MemorySize);
|
|
end;
|
|
|
|
end
|
|
else
|
|
begin
|
|
firstscantype:=fs_addresslist;
|
|
end;
|
|
except
|
|
on e: exception do
|
|
begin
|
|
//clean up and raise the exception
|
|
cleanup;
|
|
|
|
raise exception.Create(e.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TFirstScanHandler.destroy;
|
|
begin
|
|
cleanup;
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TFirstScanHandler.cleanup;
|
|
{Cleanup routine, for use by create when failure and destroy}
|
|
begin
|
|
if firstscanmemory<>nil then
|
|
begin
|
|
virtualfree(firstscanmemory,0,MEM_RELEASE);
|
|
firstscanmemory:=nil;
|
|
end;
|
|
|
|
freeandnil(loadedfromlistMREW);
|
|
freeandnil(firstscanaddress);
|
|
freeandnil(firstscanmemoryfile);
|
|
end;
|
|
|
|
end.
|
|
|