cheat-engine/Cheat Engine/frmstructurecompareunit.pas
2020-12-10 23:39:40 +01:00

2720 lines
69 KiB
ObjectPascal
Executable File

unit frmstructurecompareunit;
{$mode delphi}
interface
uses
{$ifdef darwin}
macport,
{$else}
windows,
{$endif}
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, ComCtrls,
cefuncproc, newkernelhandler, frmStringMapUnit, MemFuncs, AvgLvlTree, Menus,
bigmemallochandler, math, maps, oldRegExpr, symbolhandler, commonTypeDefs,
pagemap, syncobjs2, syncobjs, Clipbrd, betterControls;
const compareversion=1;
type
{ TfrmStructureCompare }
TPointerpath=array of dword;
TDiffkind=(dkDontCare, dkMustBeDifferent, dkMustBeSame);
type
PMappedRegion=^TMappedRegion;
TMappedRegion=record
baseaddress: ptruint; //if a new section overlaps, append to this
size: ptruint; //if a new section ovrlaps increase this
previous: PMappedRegion;
next: PMappedRegion;
end;
PPointerListEntry=^TPointerListEntry;
TPointerListEntry=record
address: ptruint;
pointsto: ptruint;
previous: PPointerListEntry;
next: PPointerListEntry;
end;
type TPointerRecord=packed record
level: integer;
offset: TDwordArray;
end;
PPointerRecord=^TPointerRecord;
TPointerfileReader=class
private
files: array of record
f: Tfilestream;
startindex: qword;
lastindex: qword;
end;
entrysize: integer;
fcount: qword;
pointerfileLevelwidth: integer;
bufferindex: integer;
buffersize: integer;
pointerrecords: PPointerRecord; //actually an array...
stringbuf: pchar;
widestringbuf: pwidechar;
pointermap: TMap;
ffilename: string;
fhexadecimal: boolean;
function getByteFromAddress(address: ptruint; var error: boolean): byte;
function getWordFromAddress(address: ptruint; var error: boolean): word;
function getDWordFromAddress(address: ptruint; var error: boolean): dword;
function getQWordFromAddress(address: ptruint; var error: boolean): qword;
function getSingleFromAddress(address: ptruint; var error: boolean): single;
function getDoubleFromAddress(address: ptruint; var error: boolean): double;
function getPointerFromAddress(address: ptruint; var error: boolean): ptruint;
procedure writeRecord(filestream: TfileStream; precord: PPointerRecord); //for sorting
public
vartype: TVariableType;
procedure clearPointerCache;
function getPointerRec(index: qword): PPointerRecord;
function getAddressFromPointerRecord(p: ppointerrecord; baseaddress: ptruint; shadow: ptruint; shadowsize: integer): ptruint;
function getStringFromPointerRecord(p: ppointerrecord; address: ptruint; shadow: ptruint; shadowsize: integer): string;
function getStringAndAddress(index: qword; var address: ptruint; out p: PPointerRecord; shadow: ptruint; shadowsize: integer): string;
procedure sort(offsetnr: integer);
procedure CloseFile;
procedure LoadFile(filename: string);
constructor create(filename: string);
destructor destroy; override;
property levelWidth: integer read pointerfileLevelwidth;
property count: qword read fcount;
property filename: string read ffilename;
property hexadecimal: boolean read fhexadecimal write fhexadecimal;
end;
TAddressWithShadow=record
address: ptruint;
shadow: ptruint;
shadowsize: ptruint;
end;
TAddressWithShadowList=array of TAddressWithShadow;
TStructureCompareWorkItem=record
currentLevel: integer;
path: TPointerPath;
LookingForList: array of ptruint;
NotLookingForList: array of ptruint;
end;
TfrmStructureCompare=class;
TStructCompareRescan=class(tthread)
private
LFList: TAddressWithShadowList;
NLFList: TAddressWithShadowList;
outputfile: tfilestream;
pointerfilereader: TPointerfileReader;
outputfilename: string;
oldpointerfilename: string;
results: TMemorystream;
fcount: qword;
fCurrentPosition: qword;
fMaxPosition: qword;
sameonly: boolean;
lastwrite: qword;
ownerFrmStructureCompare: TfrmStructureCompare;
comparesize: integer;
procedure addPointer(p: PPointerRecord);
procedure flushresults;
public
progressbar: TProgressBar;
errorstring:string;
procedure execute; override;
constructor create(comparesize: integer; sameonly: boolean; const LFL: TAddressWithShadowList; const NLFL: TAddressWithShadowList; oldpointerfilename: string; outputfilename: string; ownerFrmStructureCompare: TfrmStructureCompare );
destructor destroy; override;
property count: qword read fcount;
property currentPosition: qword read fCurrentPosition;
property maxPosition: qword read fMaxPosition;
end;
TStructCompareController=class;
TStructCompareScanner=class(TThread)
private
owner: TStructCompareController;
is64bittarget: boolean;
maxlevel: integer;
structsize: integer;
alignment: integer;
LookingForShadowList: array of TAddressWithShadow;
NotLookingForShadowList: array of TAddressWithShadow;
memoryblockLF: array of PByteArray;
memoryblockNLF: array of PByteArray;
blocksize: integer;
newwi: TStructureCompareWorkItem;
readmemorycache: array of record
pageindex: ptruint;
data: pointer;
lasttimeaccess: qword;
end;
filename: string;
lastwrite: qword;
results: TMemorystream;
resultfile: TFileStream;
sameonly: boolean;
procedure flushresults;
procedure writeResult(path: TPointerpath; level: integer);
function readMemory(address: ptruint; destination: pointer; size: integer): boolean;
function getWorkItem(var wi: TStructureCompareWorkItem): boolean; //gets a workitem from the queue, waits if empty
procedure addNewWorkItemToQueue; //add the new workitem to the queue (handles it itself if the que is full)
procedure HandleWorkItem(wi: TStructureCompareWorkItem);
public
foundcount: integer;
idle: boolean;
procedure execute; override;
destructor destroy; override;
end;
TStructCompareController=class(tthread)
private
LFList: TAddressWithShadowList;
NLFList: TAddressWithShadowList;
structsize: ptruint;
maxlevel: integer;
foundcount: integer;
//levelblock: array of Puint64Array; //I think this is obsolete
// block64: array of Pint64Array;
sameonly: boolean;
results: TMemorystream;
resultfile: tfilestream;
lastwrite: dword;
alignment: integer;
mustbeinregion: boolean;
pointerstart: ptruint;
pointerstop: ptruint;
owner: TfrmStructureCompare;
is64bittarget: boolean;
public
workqueue: array [0..63] of TStructureCompareWorkItem;
workqueueCS: TCriticalSection;
workqueueSemaphore: TSemaphore;
workqueuepos: integer;
workers: array of TStructCompareScanner;
memorymap: TPageMap;
memorymapCS: TCriticalSection;
filename: string;
configfile: tfilestream;
errorstring: string;
procedure execute; override;
constructor create(alignment: integer; sameonly: boolean; const LFL: TAddressWithShadowList; const NLFL: TAddressWithShadowList; structsize: integer; maxlevel: integer; filename: string; owner: TfrmStructureCompare);
destructor destroy; override;
end;
TfrmStructureCompare = class(TForm)
btnAddAddressLF: TButton;
btnAddAddressNLF: TButton;
btnNewScan: TButton;
btnScan: TButton;
cbHexadecimal: TCheckBox;
cbSameOnly: TCheckBox;
comboType: TComboBox;
edtAlignsize: TEdit;
edtMaxLevel: TEdit;
edtStructsize: TEdit;
FindDialog1: TFindDialog;
gbLF: TGroupBox;
gbNLF: TGroupBox;
scImageList: TImageList;
lblAlign: TLabel;
lblInfo: TLabel;
lblMaxLevel: TLabel;
lblStructsize: TLabel;
lblvds: TLabel;
lvResults: TListView;
MainMenu1: TMainMenu;
MenuItem1: TMenuItem;
MenuItem2: TMenuItem;
miMaxAddresses: TMenuItem;
miDeleteAddress: TMenuItem;
miShadow: TMenuItem;
miCut: TMenuItem;
miCopy: TMenuItem;
miPaste: TMenuItem;
MenuItem9: TMenuItem;
miFind: TMenuItem;
miFindNext: TMenuItem;
miNewScan: TMenuItem;
miOpen: TMenuItem;
MenuItem4: TMenuItem;
MenuItem7: TMenuItem;
miClearCache: TMenuItem;
MenuItem5: TMenuItem;
MenuItem6: TMenuItem;
OpenDialog1: TOpenDialog;
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Panel4: TPanel;
Panel5: TPanel;
Panel9: TPanel;
pnlNLF: TPanel;
pnlLF: TPanel;
Panel7: TPanel;
pmPointerfile: TPopupMenu;
pmAddressPopup: TPopupMenu;
SaveDialog1: TSaveDialog;
sbLF: TScrollBox;
sbNLF: TScrollBox;
Splitter1: TSplitter;
statusupdater: TTimer;
tRefresher: TTimer;
procedure btnAddAddressClick(Sender: TObject);
procedure btnScanClick(Sender: TObject);
procedure cbHexadecimalChange(Sender: TObject);
procedure comboTypeChange(Sender: TObject);
procedure FindDialog1Find(Sender: TObject);
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure gbNLFClick(Sender: TObject);
procedure lvResultsColumnClick(Sender: TObject; Column: TListColumn);
procedure lvResultsCustomDrawSubItem(Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean);
procedure lvResultsData(Sender: TObject; Item: TListItem);
procedure lvResultsDblClick(Sender: TObject);
procedure miCopyClick(Sender: TObject);
procedure miCutClick(Sender: TObject);
procedure miDeleteAddressClick(Sender: TObject);
procedure miFindClick(Sender: TObject);
procedure miMaxAddressesClick(Sender: TObject);
procedure miNewScanClick(Sender: TObject);
procedure miOpenClick(Sender: TObject);
procedure MenuItem7Click(Sender: TObject);
procedure miClearCacheClick(Sender: TObject);
procedure miPasteClick(Sender: TObject);
procedure miShadowClick(Sender: TObject);
procedure pmAddressPopupPopup(Sender: TObject);
procedure statusupdaterTimer(Sender: TObject);
procedure tRefresherTimer(Sender: TObject);
private
{ private declarations }
scanner: TStructCompareController;
rescanner: TStructCompareRescan;
pointerfilereader: TPointerfilereader;
btncancel: TButton;
maxaddresses: integer;
function mapCompare(Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
function pointerCompare(Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
procedure cleanup;
procedure OpenPointerfile(filename: string);
procedure scanDone;
procedure rescanDone;
procedure scanError;
function getStringFromPointer(address: ptruint; offsets: TDwordArray; level, bytesize: integer; unicode: boolean; var a: ptruint): string;
procedure setGUIStateEnabled(state: boolean);
procedure reloadlistviewcolumns;
procedure AddressEditChange(Sender: TObject);
procedure btnCancelClick(sender: TObject);
public
{ public declarations }
edtLF: TList;
edtNLF: TList;
donotfreeonclose: boolean;
procedure disableGui;
procedure enablegui;
procedure addAddress(address: ptruint; shadow: qword; shadowsize: integer; group: integer);
end;
var
frmStructureCompare: TfrmStructureCompare;
implementation
{ TfrmStructureCompare }
uses frmStructPointerRescanUnit, MemoryBrowserFormUnit, ProcessHandlerUnit,
Parsers, addressedit, PointerscanresultReader, DPIHelper, ceregistry;
resourcestring
rsGeneratingStringmap = 'Generating stringmap';
rsGeneratedScanning = 'Generated. Scanning...';
rsThisAddressIsNotAccessible = 'This address is not accessible';
rsStop = 'Stop';
rsTerminating = 'Terminating...';
rsAreYouSureYo = 'Are you sure you wish to start a new scan?';
rsScan = 'Scan';
rsScanningFoun = 'Scanning... Found %s';
rsSPSUNotYetImplemented = 'Not yet implemented';
rsSPSUFUUUU = 'FUUUU';
rsSPSUFUUUUU = 'FUUUUU';
rsSPSUException = 'Exception:';
rsSPSUOffset = 'Offset ';
rsSPSUAddress = 'Address';
rsSPSUAddress2 = 'Address 2';
rsSPSURescan = 'Rescan';
rsSPSUFound = 'Found:';
rsSPSUScanDoneFound = 'Scan done! Found ';
rsSPSUErrorduringScanNoScanresults = 'Error during scan. No scanresults available';
rsInvalidGroup = 'Group %d address %s (%s) is not valid';
rsTooSmallAlign = 'Alignsize must be greater than 0';
rsSPSInvalidstruct = 'Invalid structure pointerfile';
rsSPSNeedNewCE = 'You''ll need a newer CE version to open this file';
rsSPSNoError = 'No error';
rsSPSUnlock = 'Unlock (%.8x-%.8x)';
rsSPSLock = 'Lock';
rsStructureCompare = 'Structure Compare';
rsMaxAddressesShown = 'Max addresses shown :';
rsMaxReached = '...<max reached>...';
rsSameAddress = 'This will result in 0 results as address %s appears multiple times';
//----------TPointerfileReader---------
type
TShadow=class
address: ptruint;
size: ptruint;
donotfree: boolean;
end;
function TPointerfilereader.getByteFromAddress(address: ptruint; var error: boolean): byte;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 1, x);
error:=error or (x<>1);
end;
function TPointerfilereader.getWordFromAddress(address: ptruint; var error: boolean): word;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 2, x);
error:=error or (x<>2);
end;
function TPointerfilereader.getDWordFromAddress(address: ptruint; var error: boolean): dword;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 4, x);
error:=error or (x<>4);
end;
function TPointerfilereader.getQWordFromAddress(address: ptruint; var error: boolean): qword;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 8, x);
error:=error or (x<>8);
end;
function TPointerfilereader.getSingleFromAddress(address: ptruint; var error: boolean): single;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 4, x);
error:=error or (x<>4);
end;
function TPointerfilereader.getDoubleFromAddress(address: ptruint; var error: boolean): double;
var x: ptruint;
begin
error:=not readprocessmemory(processhandle, pointer(address), @result, 8, x);
error:=error or (x<>8);
end;
function TPointerfilereader.getPointerFromAddress(address: ptruint; var error: boolean): ptruint;
var x: ptruint;
begin
result:=0;
error:=not readprocessmemory(processhandle, pointer(address), @result, processhandler.pointersize, x);
error:=error or (x<>processhandler.pointersize);
end;
procedure TPointerfilereader.clearPointerCache;
begin
pointermap.Clear;
end;
function TPointerfileReader.getPointerRec(index: qword): PPointerRecord;
var
blocksize: integer;
i: integer;
relativeindex: qword;
found: boolean;
pos: int64;
begin
if index>fcount then exit(nil);
result:=nil;
if (buffersize=0) or (not InRangeQ(index, bufferindex, bufferindex+buffersize-1)) then
begin
//find the file this index belongs to
found:=false;
for i:=0 to length(files)-1 do
if InRangeQ(index, files[i].startindex, files[i].lastindex) then
begin
relativeindex:=index-files[i].startindex;
blocksize:=files[i].lastindex-files[i].startindex+1-relativeindex;
blocksize:=min(blocksize, 4096);
pos:=relativeindex*entrysize;
files[i].f.position:=pos;
if files[i].f.Read(pointerrecords^, entrysize*blocksize)=entrysize*blocksize then
begin
bufferindex:=index;
buffersize:=blocksize;
end else exit(nil);
found:=true;
break;
end;
if not found then exit(nil);
end;
result:=PPointerRecord(ptruint(pointerrecords)+(index-bufferindex)*entrysize);
end;
function TPointerfilereader.getAddressFromPointerRecord(p: ppointerrecord; baseaddress: ptruint; shadow: ptruint; shadowsize: integer): ptruint;
var address: ptruint;
a: ptruint;
i: integer;
x: ptruint;
dp: pptruint;
begin
result:=0;
if p=nil then exit;
address:=baseaddress+p.offset[0];
if (shadow<>0) and inrangeq(address, baseaddress, baseaddress+shadowsize) then
address:=address+(shadow-baseaddress);
for i:=1 to p.level do
begin
dp:=pointermap.GetDataPtr(address);
if dp<>nil then
begin
address:=dp^;
if address=0 then
exit; //unreadable
end
else
begin
a:=0;
if not readprocessmemory(processhandle, pointer(address), @a, processhandler.pointersize, x) then
a:=0;
if (shadow<>0) and (inrangeq(a, baseaddress, baseaddress+shadowsize)) then
a:=a+(shadow-baseaddress);
pointermap.Add(address, a);
address:=a;
if a=0 then
exit; //unreadable
end;
address:=address+p.offset[i]
end;
result:=address;
end;
function TPointerfileReader.getStringFromPointerRecord(p: ppointerrecord; address: ptruint; shadow: ptruint; shadowsize: integer): string;
var i,j: integer;
x: ptruint;
e: boolean;
begin
e:=false;
result:='';
address:=getAddressFromPointerRecord(p, address, shadow, shadowsize);
case vartype of
vtByte, vtWord, vtDword, vtQword, vtSingle, vtDouble, vtPointer:
begin
e:=false;
case vartype of
vtByte: if fhexadecimal then result:=inttohex(getByteFromAddress(address,e),2) else result:=inttostr(getByteFromAddress(address,e));
vtWord: if fhexadecimal then result:=inttohex(getWordFromAddress(address,e),4) else result:=inttostr(getWordFromAddress(address,e));
vtDword: if fhexadecimal then result:=inttohex(getDwordFromAddress(address,e),8) else result:=inttostr(getDwordFromAddress(address,e));
vtQword: if fhexadecimal then result:=inttohex(getQwordFromAddress(address,e),16) else result:=inttostr(getQwordFromAddress(address,e));
vtSingle: result:=format('%.3f',[getSingleFromAddress(address,e)]);
vtDouble: result:=format('%.3f',[getDoubleFromAddress(address,e)]);
vtPointer: result:=IntToHex(getPointerFromAddress(address,e), processhandler.pointersize*2);
end;
end;
vtString:
begin
//string
i:=16;
if readprocessmemory(processhandle, pointer(address), stringbuf, i, x) then
begin
stringbuf[i]:=#0;
stringbuf[i+1]:=#0;
result:=stringbuf;
end;
end;
vtUnicodeString:
begin
//string
i:=16;
if readprocessmemory(processhandle, pointer(address), stringbuf, i, x) then
begin
stringbuf[i]:=#0;
stringbuf[i+1]:=#0;
result:=widestringbuf;
end;
end
else
result:=rsSPSUNotYetImplemented;
end;
if e then
result:='???';
end;
function TPointerfileReader.getStringAndAddress(index: qword; var address: ptruint; out p: PPointerRecord; shadow: ptruint; shadowsize: integer): string;
begin
result:='';
address:=0;
p:=getPointerRec(index);
if p<>nil then
begin
result:=getStringFromPointerRecord(p, address, shadow, shadowsize);
address:=getAddressFromPointerRecord(p, address, shadow, shadowsize);
end;
end;
procedure TPointerfileReader.writeRecord(filestream: TfileStream; precord: PPointerRecord);
begin
filestream.WriteBuffer(precord^,entrysize);
end;
procedure TPointerfileReader.sort(offsetnr: integer); //sorts more on level than on offsetvalues
var
configfile: TFilestream=nil;
results: Array of Tfilestream;
resultcount: integer;
tempfilename: string;
i: qword;
j: integer;
pr: PPointerRecord;
fl: Tstringlist;
s1,s2: string;
begin
if count=0 then exit;
if offsetnr>pointerfilelevelwidth then exit;
tempfilename:=filename+'.sorting';
configfile:=tfilestream.create(tempfilename, fmCreate);
configfile.WriteByte($ec); //HEADER
configfile.WriteByte(compareversion);
configfile.WriteDWord(pointerfileLevelwidth);
configfile.free;
configfile:=nil;
setlength(results,pointerfileLevelwidth);
resultcount:=pointerfileLevelwidth;
for j:=0 to length(results)-1 do
results[j]:=tfilestream.create(tempfilename+'.results.'+inttostr(j+1), fmCreate);
i:=0;
while (i <= count-1) do
begin
pr:=getPointerRec(i);
writeRecord(results[pr^.level], pr);
inc(i);
end;
for j:=0 to length(results)-1 do
begin
results[j].free;
results[j]:=nil
end;
setlength(results,0);
closeFile;
fl:=tstringlist.create;
findAllResultFilesForThisPtr(filename,fl);
for j:=0 to fl.Count-1 do
DeleteFile(fl[j]);
DeleteFile(filename);
fl.free;
RenameFile(tempfilename, filename);
for j:=1 to resultcount do
begin
s1:=tempfilename+'.results.'+inttostr(j);
s2:=filename+'.results.'+inttostr(j);
if RenameFile(s1, s2)=false then
OutputDebugString('Failed to rename sorted results');
end;
loadFile(filename);
end;
procedure TPointerFileReader.closeFile;
var i: integer;
begin
if pointerrecords<>nil then
FreeMemAndNil(pointerrecords);
if stringbuf<>nil then
FreeMemAndNil(stringbuf);
widestringbuf:=nil;
if pointermap<>nil then
freeandnil(pointermap);
for i:=0 to length(files)-1 do
begin
if files[i].f<>nil then
freeandnil(files[i].f);
end;
setlength(files,0);
fcount:=0;
entrysize:=0;
end;
procedure TPointerfileReader.LoadFile(filename: string);
var
f: Tstringlist=nil;
configfile: TFilestream=nil;
i,j: integer;
begin
ffilename:=filename;
try
configfile:=TFileStream.Create(filename, fmOpenRead);
if configfile.ReadByte<>$ec then raise exception.create(rsSPSInvalidstruct);
if configfile.readbyte>compareversion then raise exception.create(rsSPSNeedNewCE);
pointerfileLevelwidth:=configfile.ReadDWord;
entrysize:=sizeof(pointerfileLevelwidth)+pointerfileLevelwidth*sizeof(dword);
f:=tstringlist.create;
findAllResultFilesForThisPtr(ffilename, f);
setlength(files, f.count);
for i:=0 to f.count-1 do
files[i].f:=nil;
fcount:=0;
try
for i:=0 to f.count-1 do
begin
files[i].f:=tfilestream.create(f[i], fmOpenRead);
files[i].startindex:=fcount;
if files[i].f.Size>0 then
begin
inc(fcount, files[i].f.Size div entrysize);
files[i].lastindex:=fcount-1;
end
else
freeandnil(files[i].f);
end;
i:=0;
while i<length(files) do
begin
if files[i].f=nil then
begin
for j:=i to length(files)-2 do
files[j]:=files[j+1];
setlength(files, length(files)-1);
end
else
inc(i);
end;
except
for i:=0 to f.count-1 do
if files[i].f<>nil then
freeandnil(files[i].f);
raise;
end;
getmem(pointerrecords, entrysize*4096);
getmem(stringbuf, 512);
widestringbuf:=pwidechar(stringbuf);
pointermap:=TMap.Create(ituPtrSize,sizeof(pointer))
finally
if f<>nil then
freeandnil(f);
if configfile<>nil then
freeandnil(configfile);
end;
end;
constructor TPointerfileReader.create(filename: string);
begin
loadFile(filename);
end;
destructor TPointerfileReader.destroy;
var i: integer;
begin
closefile;
//cleanup the maps
inherited destroy;
end;
//--------------TStructCompareRescan----------------
procedure TStructCompareRescan.flushResults;
begin
lastwrite:=GetTickCount;
outputfile.WriteBuffer(results.Memory^, results.position);
results.position:=0;
end;
procedure TStructCompareRescan.addPointer(p: PPointerRecord);
begin
inc(fcount);
results.WriteBuffer(p^, pointerfilereader.entrysize);
if ((gettickcount64-lastwrite)>5*60*1000) or (results.Position>=(15*1024*1024)) then
flushResults;
end;
procedure TStructCompareRescan.execute;
var index, i,j: integer;
p: PPointerRecord;
passed: boolean;
g1: array of pointer;
g2: array of pointer;
g1same, g2same: boolean;
a: ptruint;
x: ptruint;
r: tstringlist=nil;
begin
setlength(g1, length(LFList));
setlength(g2, length(NLFList));
for i:=0 to length(g1)-1 do
getmem(g1[i], comparesize);
for i:=0 to length(g2)-1 do
getmem(g2[i], comparesize);
try
try
for index:=0 to pointerfilereader.count-1 do
begin
fCurrentPosition:=index;
p:=pointerfilereader.getPointerRec(index);
if p=nil then continue;
passed:=true;
for j:=0 to length(LFList)-1 do
begin
a:=pointerfilereader.getAddressFromPointerRecord(p,LFList[j].address,LFList[j].shadow, LFList[j].shadowsize);
if a=0 then
begin
passed:=false;
break;
end;
if not readprocessmemory(processhandle, pointer(a),g1[j],comparesize, x) then
begin
passed:=false;
break;
end;
end;
if not passed then continue;
for j:=0 to length(NLFList)-1 do
begin
a:=pointerfilereader.getAddressFromPointerRecord(p,NLFList[j].address,NLFList[j].shadow, NLFList[j].shadowsize);
if a=0 then
begin
passed:=false;
break;
end;
if not readprocessmemory(processhandle, pointer(a),g2[j],comparesize, x) then
begin
passed:=false;
break;
end;
end;
if not passed then continue;
//still here so readable
//compare the groups
g1same:=true;
g2same:=true;
for i:=0 to length(g2)-1 do
begin
//check if these values are in g1. If so, not valid
for j:=0 to length(g1)-1 do
begin
//first iteration checks if g1 is the same or not
if (i=0) and (j>0) and (CompareMem(g1[0], g1[j],comparesize)=false) then
begin
g1same:=false;
if sameonly then
begin
passed:=false;
break;
end;
end;
if CompareMem(g2[i], g1[j], comparesize) then
begin
passed:=false;
break;
end;
end;
if passed=false then break;
//also check if g2 is the same during this loop
if (i>0) and (CompareMem(g2[0], g2[i],comparesize)=false) then
begin
g2same:=false;
if sameonly then
begin
passed:=false;
break;
end;
end;
end;
if passed and ((g1same=false) and (g2same=false)) then
passed:=false;
if passed then //add it
addPointer(p);
//check for forced exit
if terminated then exit;
end;
flushresults;
if outputfile<>nil then
freeandnil(outputfile);
if results<>nil then
freeandnil(results);
if pointerfilereader<>nil then
freeandnil(pointerfilereader);
//delete all old files
r:=tstringlist.create;
findAllResultFilesForThisPtr(outputfilename, r);
for i:=0 to r.count-1 do
DeleteFile(r[i]);
freeandnil(r);
if deletefile(outputfilename)=false then
OutputDebugString('Failure deleting '+outputfilename);
RenameFile(outputfilename+'.temp', outputfilename);
RenameFile(outputfilename+'.results.0.temp', outputfilename+'.results.0');
Queue(ownerFrmStructureCompare.rescandone);
finally
if r<>nil then
freeandnil(r);
if outputfile<>nil then
freeandnil(outputfile);
if results<>nil then
freeandnil(results);
if pointerfilereader<>nil then
freeandnil(pointerfilereader);
for i:=0 to length(g1)-1 do
FreeMemAndNil(g1[i]);
for i:=0 to length(g2)-1 do
FreeMemAndNil(g2[i]);
end;
except
on e:exception do
begin
errorstring:=e.message;
Queue(ownerFrmStructureCompare.scanerror);
end;
end;
end;
constructor TStructCompareRescan.create(comparesize: integer; sameonly: boolean; const LFL: TAddressWithShadowList; const NLFL: TAddressWithShadowList; oldpointerfilename: string; outputfilename: string; ownerFrmStructureCompare: TfrmStructureCompare );
var
i: integer;
regflags: tregexprflags;
lw: integer;
configfile: tfilestream;
begin
progressbar:=tprogressbar.create(ownerFrmStructureCompare);
progressbar.align:=alBottom;
progressbar.parent:=ownerFrmStructureCompare;
self.comparesize:=comparesize;
setlength(LFList, length(LFL));
for i:=0 to length(LFL)-1 do
LFList[i]:=LFL[i];
setlength(NLFList, length(NLFL));
for i:=0 to length(NLFL)-1 do
NLFList[i]:=NLFL[i];
self.oldpointerfilename:=oldpointerfilename;
pointerfilereader:=TPointerfileReader.create(oldpointerfilename);
fmaxPosition:=pointerfilereader.count;
self.outputfilename:=outputfilename;
self.ownerFrmStructureCompare:=ownerFrmStructureCompare;
self.sameonly:=sameonly;
pointerfilereader.clearPointercache;
//build the configfile
configfile:=TFileStream.Create(outputfilename+'.temp', fmCreate);
configfile.WriteByte($ec); //header (not to be confused with pointerscan)
configfile.WriteByte(compareversion);
configfile.WriteDWord(pointerfilereader.levelWidth);
configfile.free;
outputfile:=Tfilestream.create(outputfilename+'.results.0.temp', fmcreate);
lastwrite:=GetTickCount64;
results:=TMemoryStream.Create;
results.size:=16*1024*1024;
inherited create(suspended);
end;
destructor TStructCompareRescan.destroy;
begin
if pointerfilereader<>nil then
freeandnil(pointerfilereader);
if outputfile<>nil then
freeandnil(outputfile);
if results<>nil then
freeandnil(results);
if progressbar<>nil then
freeandnil(progressbar);
inherited destroy;
end;
//--------------TStructCompareScanner------------------
procedure TStructCompareScanner.flushresults;
begin
resultfile.WriteBuffer(results.Memory^, results.position);
results.position:=0;
lastwrite:=GetTickCount;
end;
procedure TStructCompareScanner.writeResult(path: TPointerpath; level: integer);
begin
results.WriteBuffer(level, sizeof(level));
results.WriteBuffer(path[0], sizeof(path[0])*(maxlevel+1));
if (getTickCount-lastwrite>5*60*1000) or (results.Position>=(15*1024*1024)) then
flushResults;
inc(foundcount);
end;
function TStructCompareScanner.readMemory(address: ptruint; destination: pointer; size: integer): boolean;
var
pi: PPageInfo;
pageindex: ptruint;
i: integer;
x: ptruint;
readok: boolean;
data: PByteArray;
offset: word;
block: integer;
begin
while size>0 do
begin
pageindex:=address shr 12;
offset:=address and $fff;
pi:=owner.memorymap.GetPageInfo(pageindex);
if pi=nil then
begin
getmem(data,4096);
x:=0;
readok:=ReadProcessMemory(processhandle, pointer(pageindex shl 12), data, 4096, x);
if (not readok) or (x<>4096) then
begin
//unreadable memory or buggy rpm hook
FreeMemAndNil(data);
exit(false)
end;
owner.memorymapCS.Enter;
try
owner.memorymap.Add(pageindex, data);
finally
owner.memorymapCS.Leave;
end;
end
else
data:=pi^.data;
block:=4096-offset;
if block>size then
block:=size;
copymemory(destination,@data[offset],block);
dec(size,block);
destination:=pointer(ptruint(destination)+block);
end;
result:=true;
end;
function TStructCompareScanner.getWorkItem(var wi: TStructureCompareWorkItem): boolean;
{pre: wi has the proper array lengths setup}
var i: integer;
begin
result:=false;
if owner.workqueueSemaphore.tryAcquire=false then
begin
//the list is empty, wait
idle:=true;
owner.workqueueSemaphore.Acquire;
end;
idle:=false;
owner.workqueueCS.Enter;
try
if owner.workqueuepos>0 then
begin
wi.currentLevel:=owner.workqueue[owner.workqueuepos-1].currentLevel;
for i:=0 to length(wi.LookingForList)-1 do
wi.LookingForList[i]:=owner.workqueue[owner.workqueuepos-1].LookingForList[i];
for i:=0 to length(wi.NotLookingForList)-1 do
wi.NotLookingForList[i]:=owner.workqueue[owner.workqueuepos-1].NotLookingForList[i];
for i:=0 to length(wi.path)-1 do
wi.path[i]:=owner.workqueue[owner.workqueuepos-1].path[i];
dec(owner.workqueuepos);
result:=true;
end;
finally
owner.workqueueCS.Leave;
end;
if terminated then result:=false;
end;
procedure TStructCompareScanner.addNewWorkItemToQueue;
var
addedtoqueue: boolean=false;
i: integer;
begin
if owner.workqueuepos<60 then
begin
if owner.workqueueCS.tryenter then
begin
try
if (owner.workqueuepos<64) then
begin
for i:=0 to length(newwi.NotLookingForList)-1 do
owner.workqueue[owner.workqueuepos].NotLookingForList[i]:=newwi.NotLookingForList[i];
for i:=0 to length(newwi.LookingForList)-1 do
owner.workqueue[owner.workqueuepos].LookingForList[i]:=newwi.LookingForList[i];
for i:=0 to length(newwi.path)-1 do
owner.workqueue[owner.workqueuepos].path[i]:=newwi.path[i];
owner.workqueue[owner.workqueuepos].currentLevel:=newwi.currentLevel;
inc(owner.workqueuepos);
addedtoqueue:=true;
owner.workqueueSemaphore.Release;
end;
finally
owner.workqueueCS.Leave;
end;
end;
end;
if (not addedtoqueue) and (not terminated) then
handleWorkItem(newwi); //do it yourself
end;
procedure TStructCompareScanner.handleWorkItem(wi: TStructureCompareWorkItem);
var
i,j: integer;
a: ptruint;
pi: PPageInfo;
pageindex: integer;
pos: integer;
valid: boolean;
t1same: boolean;
t2same: boolean;
potentialpointer: boolean;
allreadablepointers: boolean;
p: ptruint;
begin
//do stuff
//matching rules: (looking for is table 1, not looking for is table 2)
//if any value of table 2 is in table 1, then not valid
//if above passes then
//if table 1 fields are all the same and table 2 fields are all the same: match
//if table 1 fields are all the same and table 2 fields are not all the same: match
//if table 1 fields are all different and table 2 fields are all the same: match
//if no match and ALL fields represent an address then add it as a new workitem (fields where the player is null and the enemy is filled in have no further use besides checking if it's valid or not)
pos:=0;
for i:=0 to wi.currentLevel-1 do
newwi.path[i]:=wi.path[i];
while pos<structsize do
begin
for i:=0 to length(memoryblockLF)-1 do
begin
a:=wi.LookingForList[i]+pos;
if (LookingForShadowList[i].shadow<>0) and inrangeq(a, LookingForShadowList[i].address, LookingForShadowList[i].address+LookingForShadowList[i].shadowsize) then
begin
//make it a relative address in the shadow copy
a:=a-LookingForShadowList[i].address+LookingForShadowList[i].shadow;
if readMemory(a, memoryblockLF[i], blocksize)=false then
begin
inc(pos,alignment);
continue;
end;
end
else
if readMemory(a, memoryblockLF[i], blocksize)=false then
exit; //unreadable
end;
for i:=0 to length(memoryblockNLF)-1 do
begin
a:=wi.NotLookingForList[i]+pos;
if (NotLookingForShadowList[i].shadow<>0) and inrangeq(a, NotLookingForShadowList[i].address, NotLookingForShadowList[i].address+NotLookingForShadowList[i].shadowsize) then
begin
//make it a relative address in the shadow copy
a:=(a-NotLookingForShadowList[i].address)+NotLookingForShadowList[i].shadow;
if readMemory(a, memoryblockNLF[i], blocksize)=false then
begin
inc(pos,alignment);
continue;
end;
end
else
if readMemory(a, memoryblockNLF[i], blocksize)=false then
exit; //unreadable
end;
//do the check
valid:=true;
t1same:=true;
t2same:=true;
potentialpointer:=true;
for i:=0 to length(memoryblockNLF)-1 do
begin
//check if these values are in memoryblockLF. If so, not valid
for j:=0 to length(memoryblockLF)-1 do
begin
//use the first iteration to see of LF is all the same nor not
if (i=0) and (j>0) and (CompareMem(memoryblockLF[0], memoryblockLF[j],alignment)=false) then
begin
t1same:=false;
if sameonly then
begin
valid:=false;
break;
end;
end;
if CompareMem(memoryblockNLF[i], memoryblockLF[j], alignment) then
begin
//a value in table2 matches table1
valid:=false;
potentialpointer:=false;
break;
end;
end;
if valid=false then break;
if (i>0) and (CompareMem(memoryblockNLF[0], memoryblockNLF[i],alignment)=false) then
begin
t2same:=false;
if sameonly then
begin
valid:=false;
break;
end;
end;
end;
if valid then
if (t1same=false) and (t2same=false) then valid:=false;
if valid then
begin
//found a possible identifier to use
wi.path[wi.currentLevel]:=pos;
writeResult(wi.path,wi.currentLevel);
end;
if (potentialpointer) then
begin
allreadablepointers:=(wi.currentLevel<maxlevel) and potentialpointer;
if allreadablepointers then
begin
for i:=0 to length(memoryblockLF)-1 do
begin
p:=0;
if is64bittarget then
begin
newwi.LookingForList[i]:=pqword(memoryblockLF[i])^;
end
else
begin
newwi.LookingForList[i]:=pdword(memoryblockLF[i])^;
end;
if readMemory(newwi.LookingForList[i], @p, 1)=false then
begin
allreadablepointers:=false;
break;
end;
end;
end;
if allreadablepointers then
begin
for i:=0 to length(memoryblockNLF)-1 do
begin
p:=0;
if is64bittarget then
begin
newwi.NotLookingForList[i]:=pqword(memoryblockNLF[i])^;
end
else
begin
newwi.NotLookingForList[i]:=pdword(memoryblockNLF[i])^;
end;
if readMemory(newwi.NotLookingForList[i], @p, 1)=false then
begin
allreadablepointers:=false;
break;
end;
end;
end;
if allreadablepointers then
begin
newwi.currentLevel:=wi.currentLevel+1;
newwi.path[wi.currentLevel]:=pos;
addNewWorkItemToQueue;
end;
end;
inc(pos,alignment);
end;
//profit
end;
procedure TStructCompareScanner.execute;
var
wi: TStructureCompareWorkItem;
i: integer;
begin
//initialize the temp and new workitem object (saves on allocs)
results:=tmemorystream.create;
lastwrite:=gettickcount64;
resultfile:=TFileStream.Create(filename,fmCreate);
setlength(newwi.LookingForList,length(LookingForShadowList));
setlength(newwi.NotLookingForList,length(NotLookingForShadowList));
setlength(newwi.path,maxlevel+1); //for the final offset
setlength(memoryblockLF,length(LookingForShadowList));
setlength(memoryblockNLF,length(NotLookingForShadowList));
blocksize:=max(alignment, processhandler.pointersize);
for i:=0 to length(memoryblockLF)-1 do
getmem(memoryblockLF[i],blocksize);
for i:=0 to length(memoryblockNLF)-1 do
getmem(memoryblockNLF[i],blocksize);
setlength(readmemorycache,length(LookingForShadowList)+length(NotLookingForShadowList));
for i:=0 to length(readmemorycache)-1 do
begin
readmemorycache[i].pageindex:=0;
readmemorycache[i].lasttimeaccess:=0;
readmemorycache[i].data:=nil;
end;
try
setlength(wi.path,maxlevel+1);
setlength(wi.LookingForList,length(LookingForShadowList));
setlength(wi.NotLookingForList,length(NotLookingForShadowList));
while not terminated and GetWorkItem(wi) do
HandleWorkItem(wi);
except
on e:exception do
OutputDebugString('TStructCompareScanner.Error:'+e.message);
end;
idle:=true;
flushresults;
freeandnil(results);
freeandnil(resultfile);
end;
destructor TStructCompareScanner.destroy;
var i: integer;
begin
for i:=0 to length(memoryblockLF)-1 do
FreeMemAndNil(memoryblockLF[i]);
for i:=0 to length(memoryblockNLF)-1 do
FreeMemAndNil(memoryblockNLF[i]);
if results<>nil then
freeandnil(results);
if resultfile<>nil then
freeandnil(resultfile);
inherited destroy;
end;
//--------------TStructCompareController---------------
procedure TStructCompareController.execute;
var
count: integer;
pointerpath: TPointerpath;
i,j: integer;
t: TStructCompareScanner;
released: boolean;
allidle: boolean;
begin
try
errorstring:=rsSPSNoError;
memorymap:=TPageMap.create;
memorymapCS:=TCriticalSection.create;;
try
count:=GetCPUCount;
workqueueCS:=TCriticalSection.Create;
workqueueSemaphore:=TSemaphore.create(64, true);
configfile:=tfilestream.create(filename, fmCreate);
setlength(workers, count);
for i:=0 to length(workers)-1 do
begin
workers[i]:=TStructCompareScanner.Create(true);
workers[i].LookingForShadowList:=LFList;
workers[i].NotLookingForShadowList:=NLFList;
workers[i].maxlevel:=maxlevel;
workers[i].structsize:=structsize;
workers[i].alignment:=alignment;
workers[i].is64bittarget:=is64bittarget;
workers[i].filename:=filename+'.results.'+inttostr(i);
workers[i].sameonly:=sameonly;
workers[i].owner:=self;
workers[i].Start;
end;
//allocate space for the workitems
for i:=0 to 63 do
begin
setlength(workqueue[i].path, maxlevel+1);
for j:=0 to maxlevel do
workqueue[i].path[j]:=0;
setlength(workqueue[i].LookingForList, length(LFList));
setlength(workqueue[i].NotLookingForList, length(NLFList));
end;
//input the first workitem
for i:=0 to length(LFList)-1 do
workqueue[0].LookingForList[i]:=LFList[i].address;
for i:=0 to length(NLFList)-1 do
workqueue[0].NotLookingForList[i]:=NLFList[i].address;
workqueue[0].currentLevel:=0;
workqueuepos:=1;
workqueueSemaphore.Release; //the first worker will now add new items to the queue launching other workers
//create the results config file (pointerscan file, not compressed config)
configfile.WriteByte($ec); //header (not to be confused with pointerscan)
configfile.WriteByte(compareversion);
configfile.WriteDword(maxlevel+1); //number of offsets in each row (with level 0 that is 1 offset)
freeandnil(configfile);
allidle:=false;
released:=false;
while (not terminated) and ((not allidle) or (workqueuepos>0)) do
begin
j:=0;
allidle:=true;
for i:=0 to length(workers)-1 do
begin
inc(j,workers[i].foundcount);
if workers[i].idle=false then
allidle:=false;
end;
foundcount:=j;
end;
finally
for i:=0 to length(workers)-1 do
workers[i].Terminate;
while workqueueSemaphore.Release<>-1 do;
for i:=0 to length(workers)-1 do
begin
workers[i].WaitFor;
workers[i].Free;
end;
setlength(workers,0);
//reached the end, tell the main thread that the scan is done
if configfile<>nil then
freeandnil(configfile);
Queue(owner.scandone);
freeandnil(workqueueCS);
freeandnil(workqueueSemaphore);
freeandnil(memorymapCS);
freeandnil(memorymap);
end;
except
on e:exception do
begin
errorstring:=e.message;
Queue(owner.scanerror);
end;
end;
end;
constructor TStructCompareController.create(alignment: integer; sameonly: boolean; const LFL: TAddressWithShadowList; const NLFL: TAddressWithShadowList; structsize: integer; maxlevel: integer; filename: string; owner: TfrmStructureCompare);
var
i: integer;
r: tstringlist;
begin
setlength(LFList, length(LFL));
for i:=0 to length(LFL)-1 do
LFList[i]:=LFL[i];
setlength(NLFList, length(NLFL));
for i:=0 to length(NLFL)-1 do
NLFList[i]:=NLFL[i];
self.is64bittarget:=processhandler.is64Bit;
self.structsize:=structsize;
self.maxlevel:=maxlevel;
self.alignment:=alignment;
self.mustbeinregion:=mustbeinregion;
self.owner:=owner;
self.filename:=filename;
self.sameonly:=sameonly;
r:=tstringlist.create;
findAllResultFilesForThisPtr(filename, r);
for i:=0 to r.count-1 do
DeleteFile(r[i]);
r.free;
inherited create(false); //let's get started...
end;
destructor TStructCompareController.destroy;
var i: integer;
begin
if resultfile<>nil then
freeandnil(resultfile);
inherited destroy;
end;
//----------------------------
function TfrmStructureCompare.getStringFromPointer(address: ptruint; offsets: TDwordArray; level, bytesize: integer; unicode: boolean; var a: ptruint): string;
var i: integer;
x: ptruint;
b: pchar;
wb: pwidechar absolute b;
begin
result:='';
a:=address+offsets[0];
for i:=1 to level do
begin
if readprocessmemory(processhandle, pointer(a), @a, processhandler.pointersize, x) then
begin
a:=a+offsets[i];
end
else
begin
result:='???';
exit;
end;
end;
getmem(b, bytesize+2);
if ReadProcessMemory(processhandle, pointer(a), b, bytesize, x) then
begin
b[bytesize]:=#0;
b[bytesize+1]:=#0;
if unicode then
result:=wb
else
result:=b;
end;
FreeMemAndNil(b);
end;
procedure TfrmStructureCompare.lvResultsColumnClick(Sender: TObject;
Column: TListColumn);
begin
if pointerfilereader<>nil then
begin
if (pointerfilereader.count>10000000) and (MessageDlg('There are over 10000000 entries in this list. This may take a while. Are you sure?', mtConfirmation,[mbyes,mbno],0)<>mryes) then exit;
pointerfilereader.sort(column.Index);
end;
end;
procedure TfrmStructureCompare.lvResultsData(Sender: TObject; Item: TListItem);
var
i: integer;
s,s2: string;
a: ptruint;
a2: ptruint;
p: PPointerRecord;
count: integer;
procedure HandleAddress(edt: TAddressEdit);
var
shadow: ptruint;
shadowsize: integer;
begin
a:=edt.address;
if edt.tag<>0 then
begin
shadow:=tshadow(edt.tag).address;
shadowsize:=tshadow(edt.tag).size;
end
else
begin
shadow:=0;
shadowsize:=0;
end;
if not edt.invalidAddress then
begin
s:=pointerfilereader.getStringFromPointerRecord(p, a, shadow, shadowsize);
a:=pointerfilereader.getAddressFromPointerRecord(p, a, shadow, shadowsize);
//check if a is in the shadow, and if so, change it back (for visual purpose)
if (shadow<>0) and inrangeq(a,shadow,shadow+shadowsize-1) then
a:=(a-shadow)+edt.address;
item.SubItems.Add(inttohex(a,8)+' : '+s)
end
else
begin
item.subitems.add(' ');
end;
end;
begin
if pointerfilereader<>nil then
begin
item.data:=nil;
p:=pointerfilereader.getPointerRec(item.index);
if p<>nil then
begin
item.caption:=inttohex(p.offset[0],1);
for i:=1 to p.level do
item.SubItems.add(inttohex(p.offset[i],1));
for i:=p.level+1 to pointerfilereader.levelWidth-1 do
item.SubItems.Add(' ');
count:=0;
for i:=0 to edtLF.count-1 do
begin
inc(count);
if count>maxAddresses then
begin
item.subitems.add(rsMaxReached);
exit;
end;
HandleAddress(TAddressEdit(edtLF[i]));
end;
for i:=0 to edtNLF.count-1 do
begin
inc(count);
if count>maxAddresses then
begin
item.subitems.add(rsMaxReached);
exit;
end;
HandleAddress(TAddressEdit(edtNLF[i]));
end;
end;
end;
end;
procedure TfrmStructureCompare.lvResultsDblClick(Sender: TObject);
var
p: tpoint;
i,j: integer;
x: integer;
a: ptruint;
begin
if (lvResults.Selected<>nil) and (pointerfilereader<>nil) then
begin
if edtLF.count>1 then
a:=TAddressEdit(edtLF[0]).address
else
if edtNLF.count>1 then
a:=TAddressEdit(edtNLF[0]).address
else
exit;
//get the mousecursor
p:=lvResults.ScreenToClient(mouse.CursorPos);
x:=0;
for i:=0 to lvResults.Columns.Count-1 do
begin
if (p.x>x) and (p.x<x+lvResults.Column[i].Width) then
begin
//found the columns
j:=i-pointerfilereader.pointerfileLevelwidth;
if j>=0 then
begin
if j>=edtLF.count then
begin
dec(j, edtLF.count);
a:=TAddressEdit(edtNLF[j]).address;
end
else
a:=TAddressEdit(edtLF[j]).address;
end;
break;
end;
inc(x, lvResults.Column[i].Width);
end;
MemoryBrowser.hexview.address:=pointerfilereader.getAddressFromPointerRecord(pointerfilereader.getPointerRec(lvResults.Selected.Index), a, 0, 0);
end;
end;
procedure TfrmStructureCompare.miCopyClick(Sender: TObject);
var
e: TAddressEdit;
i: integer;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
for i:=0 to edtLF.Count-1 do
if TAddressEdit(edtLF[i]).Focused then
begin
e:=edtLF[i];
break;
end;
for i:=0 to edtNLF.Count-1 do
if TAddressEdit(edtNLF[i]).Focused then
begin
e:=edtNLF[i];
break;
end;
if e=nil then exit;
if e.SelLength>0 then
e.CopyToClipboard;
end;
procedure TfrmStructureCompare.miCutClick(Sender: TObject);
var
e: TAddressEdit;
i: integer;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
for i:=0 to edtLF.Count-1 do
if TAddressEdit(edtLF[i]).Focused then
begin
e:=edtLF[i];
break;
end;
for i:=0 to edtNLF.Count-1 do
if TAddressEdit(edtNLF[i]).Focused then
begin
e:=edtNLF[i];
break;
end;
if e=nil then exit;
if e.SelLength>0 then
begin
if e.ReadOnly then
e.CopyToClipboard
else
e.CutToClipboard;
end;
end;
procedure TfrmStructureCompare.miDeleteAddressClick(Sender: TObject);
var
e: TAddressEdit;
i: integer;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
i:=edtLF.IndexOf(e);
if i<>-1 then
begin
if edtLF.count=1 then exit;
edtLF.delete(i);
end;
i:=edtNLF.IndexOf(e);
if i<>-1 then
begin
if edtNLF.Count=1 then exit;
edtNLF.delete(i);
end;
e.free;
end;
procedure TfrmStructureCompare.miFindClick(Sender: TObject);
begin
finddialog1.execute;
end;
procedure TfrmStructureCompare.miMaxAddressesClick(Sender: TObject);
var
s: string;
i: integer;
begin
s:=inttostr(maxaddresses);
if InputQuery(rsStructureCompare, rsMaxAddressesShown, s) then
begin
maxaddresses:=strtoint(s);
if maxaddresses<0 then
maxaddresses:=0;
miMaxAddresses.caption:=format(rsMaxAddressesShown+' %d',[maxaddresses]);
end;
end;
procedure TfrmStructureCompare.OpenPointerfile(filename: string);
begin
cleanup;
pointerfilereader:=TPointerfileReader.Create(filename);
comboType.OnChange(comboType);
cbHexadecimal.OnChange(cbHexadecimal);
reloadlistviewcolumns;
lvResults.items.count:=dword(min(dword(1000000), dword(pointerfilereader.count)));
lblInfo.caption:=inttostr(pointerfilereader.count);
end;
procedure TfrmStructureCompare.scanerror;
begin
if rescanner<>nil then
messagedlg(rescanner.errorstring,mtError,[mbok],0);
if scanner<>nil then
messagedlg(scanner.errorstring,mtError,[mbok],0);
cleanup;
end;
procedure TfrmStructureCompare.rescanDone;
begin
if rescanner<>nil then
lblInfo.caption:=rsSPSUFound+inttostr(rescanner.count);
cleanup;
beep;
btnScan.Visible:=true;
btnNewScan.Visible:=true;
OpenPointerfile(SaveDialog1.FileName);
if btncancel<>nil then
freeandnil(btncancel);
EnableGui;
beep;
end;
procedure TfrmStructureCompare.scanDone;
begin
if scanner<>nil then
lblInfo.caption:=rsSPSUFound+inttostr(scanner.foundcount);
cleanup;
OpenPointerfile(SaveDialog1.FileName);
btnScan.visible:=true;
btnScan.caption:=rsSPSURescan;
btnScan.tag:=1;
btnNewScan.visible:=true;
btnNewScan.enabled:=true;
if btncancel<>nil then
freeandnil(btncancel);
EnableGui;
beep;
end;
function TfrmStructureCompare.mapCompare(Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
begin
result:=CompareValue(PMappedRegion(Data1).baseaddress, PMappedRegion(Data2).baseaddress);
end;
function TfrmStructureCompare.pointerCompare(Tree: TAvgLvlTree; Data1, Data2: Pointer): integer;
begin
result:=CompareValue(PPointerListEntry(Data1).address, PPointerListEntry(Data2).address);
end;
procedure TfrmStructureCompare.cleanup;
var r: TAvgLvlTreeNode;
p,m: PMappedRegion;
begin
statusupdater.enabled:=false;
if rescanner<>nil then
begin
rescanner.terminate;
rescanner.waitfor;
freeandnil(rescanner);
end;
if scanner<>nil then
begin
scanner.terminate;
scanner.WaitFor;
freeandnil(scanner);
end;
lvResults.items.count:=0;
while lvResults.ColumnCount>0 do
lvResults.Columns.Delete(0);
if pointerfilereader<>nil then
freeandnil(pointerfilereader);
end;
procedure TfrmStructureCompare.btnCancelClick(sender: TObject);
begin
if scanner<>nil then
scanner.Terminate;
if rescanner<>nil then
rescanner.terminate;
btncancel.enabled:=false;
end;
procedure TfrmStructureCompare.btnScanClick(Sender: TObject);
var baseaddress: ptruint;
baseaddress2: ptruint;
structsize: integer;
maxlevel: integer;
alignsize: integer;
pointerstart: ptruint;
pointerstop: ptruint;
diffkind: TDiffkind;
vartype: TVariableType;
shadow, shadow2: ptruint;
shadowsize, shadowsize2: ptruint;
oldpointerfile: string;
i,j: integer;
LF: array of TAddressWithShadow;
NLF: array of TAddressWithShadow;
s: string;
begin
if pointerfilereader<>nil then
oldpointerfile:=pointerfilereader.filename
else
oldpointerfile:='';
setlength(LF, edtLF.count);
for i:=0 to edtLF.count-1 do
begin
lf[i].address:=TAddressEdit(edtLF[i]).address;
if TAddressEdit(edtLF[i]).tag<>0 then
begin
lf[i].shadow:=TShadow(TAddressEdit(edtLF[i]).tag).Address;
lf[i].shadowsize:=TShadow(TAddressEdit(edtLF[i]).tag).Size;
end
else
begin
lf[i].shadow:=0;
lf[i].shadowsize:=0;
end;
if TAddressEdit(edtLF[i]).invalidAddress then raise exception.create(Format(rsInvalidGroup, [1, inttostr(i+1), TAddressEdit(edtLF[i]).text]));
for j:=0 to i-1 do
if (lf[j].address=lf[i].address) and (lf[i].shadow=lf[j].shadow) then raise exception.create(Format(rsSameAddress, [TAddressEdit(edtLF[i]).text]));
end;
setlength(NLF, edtNLF.count);
for i:=0 to edtNLF.count-1 do
begin
nlf[i].address:=TAddressEdit(edtNLF[i]).address;
if TAddressEdit(edtNLF[i]).tag<>0 then
begin
nlf[i].shadow:=TShadow(TAddressEdit(edtNLF[i]).tag).Address;
nlf[i].shadowsize:=TShadow(TAddressEdit(edtNLF[i]).tag).Size;
end
else
begin
nlf[i].shadow:=0;
nlf[i].shadowsize:=0;
end;
if TAddressEdit(edtNLF[i]).invalidAddress then raise exception.create(Format(rsInvalidGroup, [2, inttostr(i+1), TAddressEdit(edtNLF[i]).text]));
for j:=0 to i-1 do
begin
if (nlf[j].address=nlf[i].address) and (nlf[i].shadow=nlf[j].shadow) then
begin
s:=TAddressEdit(edtNLF[i]).text;
raise exception.create(Format(rsSameAddress, [s]));
end;
end;
for j:=0 to length(lf)-1 do
begin
if (lf[j].address=nlf[i].address) and (lf[i].shadow=nlf[j].shadow) then
begin
s:=TAddressEdit(edtNLF[i]).text;
raise exception.create(Format(rsSameAddress, [s]));
end;
end;
end;
maxlevel:=strtoint(edtMaxLevel.text);
structsize:=strtoint(edtStructsize.text);
alignsize:=strtoint(edtAlignsize.text);
vartype:=vtPointer;
if alignsize<=0 then raise exception.create(rsTooSmallAlign);
if savedialog1.execute=false then exit;
cleanup;
disableGui;
if btnscan.tag=1 then
begin
if oldpointerfile<>'' then
rescanner:=TStructCompareRescan.create(alignsize,cbSameOnly.checked, lf,nlf, oldpointerfile, savedialog1.filename, self)
else
rescandone;
end
else
begin
scanner:=TStructCompareController.create(alignsize,cbSameOnly.checked, lf,nlf, structsize, maxlevel, savedialog1.filename, self);
statusupdater.enabled:=true;
end;
btnscan.visible:=false;
btnNewScan.visible:=false;
btncancel:=TButton.create(self);
btncancel.parent:=panel9;
btncancel.caption:=rsStop;
btncancel.AutoSize:=true;
btncancel.OnClick:=btnCancelClick;
end;
procedure TfrmStructureCompare.cbHexadecimalChange(Sender: TObject);
begin
if pointerfilereader<>nil then
pointerfilereader.hexadecimal:=cbHexadecimal.checked;
lvResults.Refresh;
end;
procedure TfrmStructureCompare.reloadlistviewcolumns;
var
nr: integer;
lc: TListColumn;
i: integer;
begin
if pointerfilereader=nil then
begin
if lvResults.Columns.Count>0 then
lvResults.Columns.Clear;
end
else
begin
nr:=0;
for i:=0 to pointerfilereader.levelWidth-1 do
begin
if lvResults.Columns.count<=nr then
begin
lc:=lvResults.Columns.Add;
lc.MinWidth:=2;
lc.Width:=70;
end
else
lc:=lvResults.Column[nr];
lc.Caption:=rsSPSUOffset+inttostr(i);
inc(nr);
end;
for i:=0 to edtLF.Count-1 do
begin
if lvResults.Columns.count<=nr then
begin
lc:=lvResults.Columns.Add;
lc.MinWidth:=2;
lc.Width:=120;
end
else
lc:=lvResults.Column[nr];
lc.Caption:='G1:'+TAddressEdit(edtLF[i]).Text;
inc(nr);
end;
for i:=0 to edtNLF.Count-1 do
begin
if lvResults.Columns.count<=nr then
begin
lc:=lvResults.Columns.Add;
lc.MinWidth:=2;
lc.Width:=120;
end
else
lc:=lvResults.Column[nr];
lc.Caption:='G2:'+TAddressEdit(edtNLF[i]).Text;
inc(nr);
end;
while lvResults.columns.count>nr do
lvResults.Columns.Delete(lvResults.columns.count-1);
end;
end;
procedure TfrmStructureCompare.AddressEditChange(Sender: TObject);
begin
reloadlistviewcolumns;
lvResults.Refresh;
end;
procedure TfrmStructureCompare.btnAddAddressClick(Sender: TObject);
var e: TaddressEdit;
begin
e:=TAddressEdit.Create(self);
e.OnChange:=AddressEditChange;
e.PopupMenu:=pmAddressPopup;
if (sender as Tbutton).tag=0 then
begin
e.parent:=pnlLF;
edtLF.Add(e);
end
else
begin
e.parent:=pnlNLF;
edtNLF.Add(e);
end;
end;
procedure TfrmStructureCompare.comboTypeChange(Sender: TObject);
begin
if pointerfilereader<>nil then
begin
case comboType.itemindex of
0: pointerfilereader.vartype:=vtString;
1: pointerfilereader.vartype:=vtUnicodeString;
2: pointerfilereader.vartype:=vtByte;
3: pointerfilereader.vartype:=vtWord;
4: pointerfilereader.vartype:=vtDword;
5: pointerfilereader.vartype:=vtQword;
6: pointerfilereader.vartype:=vtSingle;
7: pointerfilereader.vartype:=vtDouble;
8: pointerfilereader.vartype:=vtPointer;
end;
lvResults.Refresh;
end;
end;
procedure TfrmStructureCompare.FindDialog1Find(Sender: TObject);
var
i,start: integer;
s: string;
FindText: string;
cs: boolean;
begin
FindText:=finddialog1.FindText;
if FindText='' then exit;
miFindNext.enabled:=true;
cs:=frMatchCase in finddialog1.Options;
if not cs then findtext:=uppercase(findtext);
for i:=lvResults.ItemIndex+1 to lvResults.Items.Count-1 do
begin
s:=lvResults.items[i].SubItems.Text;
if not cs then s:=uppercase(s);
if pos(FindText, s)>0 then
begin
lvResults.ItemIndex:=i;
lvResults.Items[i].MakeVisible(false);
exit;
end;
end;
beep;
end;
procedure TfrmStructureCompare.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
cleanup;
if donotfreeonclose then exit;
if self<>frmStructureCompare then closeaction:=cafree;
end;
procedure TfrmStructureCompare.FormCreate(Sender: TObject);
begin
edtLF:=Tlist.create;
edtNLF:=Tlist.create;
autosize:=false;
LoadFormPosition(self);
maxAddresses:=cereg.readInteger('Structure Compare Max Addresses', 32);
end;
procedure TfrmStructureCompare.FormDestroy(Sender: TObject);
begin
edtLF.free;
edtNLF.free;
SaveFormPosition(self);
cereg.writeInteger('Structure Compare Max Addresses', maxaddresses);
end;
procedure TfrmStructureCompare.FormShow(Sender: TObject);
var w,b,mh: integer;
begin
//panel1.Constraints.MinHeight:=btnNewScan.Top+btnNewScan.Height+lblInfo.Height+4;
if edtLF.Count=0 then
btnAddAddressLF.click;
if edtNLF.count=0 then
btnAddAddressNLF.click;
AdjustComboboxSize(comboType, canvas);
// gbLF.Constraints.MinWidth:=GetEditBoxMargins(tedit(edtLF[0]))+canvas.TextWidth(' XXXXXXXXXXXXXXXX ');
w:=GetEditBoxMargins(edtMaxLevel)+canvas.TextWidth(' XXXXXXXXXXXXXXXX '); ;
b:=tedit(edtLF[0]).ClientToParent(point(0,0),gbLF).x;
gbLF.Constraints.MinWidth:=w+b;
gbNLF.Constraints.MinWidth:=w+b;
gbLF.ClientWidth:=w+b;
gbNLF.ClientWidth:=w+b;
Constraints.MinWidth:=panel7.left+panel7.Width;
mh:=panel9.top+panel9.height+lblinfo.height;
panel3.Constraints.MinHeight:=mh;
end;
procedure TfrmStructureCompare.gbNLFClick(Sender: TObject);
begin
end;
procedure TfrmStructureCompare.lvResultsCustomDrawSubItem(
Sender: TCustomListView; Item: TListItem; SubItem: Integer;
State: TCustomDrawState; var DefaultDraw: Boolean);
var i: integer;
begin
defaultdraw:=true;
if pointerfilereader<>nil then
begin
i:=subitem-pointerfilereader.levelWidth;
if i>=0 then
begin
sender.Canvas.Font.Color:=clBlue;
if i>=edtLF.count then
begin
sender.Canvas.Font.Color:=clRed;
end;
end;
end;
end;
procedure TfrmStructureCompare.miNewScanClick(Sender: TObject);
begin
if MessageDlg(rsAreYouSureYo, mtConfirmation, mbYesNo, 0)=mryes then
begin
cleanup;
btnScan.tag:=0;
btnScan.caption:=rsScan;
btnScan.Left:=(panel1.ClientWidth div 2)-(btnscan.Width div 2);
EnableGui;
edtMaxLevel.Enabled:=true;
edtStructsize.enabled:=true;
lblMaxLevel.enabled:=true;
lblStructsize.enabled:=true;
btnNewScan.Visible:=false;
end;
end;
procedure TfrmStructureCompare.miOpenClick(Sender: TObject);
begin
if (scanner=nil) and (rescanner=nil) and OpenDialog1.Execute then
begin
OpenPointerfile(opendialog1.filename);
enablegui;
btnScan.caption:=rsSPSURescan;
btnScan.tag:=1;
btnNewScan.visible:=true;
btnNewScan.enabled:=true;
end;
end;
procedure TfrmStructureCompare.MenuItem7Click(Sender: TObject);
begin
with TfrmStructureCompare.create(Application) do show;
end;
procedure TfrmStructureCompare.miClearCacheClick(Sender: TObject);
begin
if pointerfilereader<>nil then
pointerfilereader.clearPointerCache;
lvResults.Refresh;
end;
procedure TfrmStructureCompare.miPasteClick(Sender: TObject);
var e: TAddressEdit;
i: integer;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
for i:=0 to edtLF.Count-1 do
if TAddressEdit(edtLF[i]).Focused then
begin
e:=edtLF[i];
break;
end;
for i:=0 to edtNLF.Count-1 do
if TAddressEdit(edtNLF[i]).Focused then
begin
e:=edtNLF[i];
break;
end;
if e=nil then exit; //?
if e.readonly then exit;
if Clipboard.AsText<>'' then
e.PasteFromClipboard;
end;
procedure TfrmStructureCompare.miShadowClick(Sender: TObject);
var
e: TAddressEdit;
a: ptruint;
temp: pointer;
size: integer;
x: ptruint;
s: tshadow;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
if e=nil then exit;
a:=e.address;
if e.invalidAddress then exit;
if e.tag<>0 then
begin
s:=TShadow(e.tag);
if s.donotfree=false then
VirtualFreeEx(processhandle, pointer(TShadow(e.tag).address), 0, MEM_DECOMMIT);
s.free;
e.tag:=0;
e.ReadOnly:=false;
lvResults.Refresh;
exit;
end;
size:=strtoint(edtStructsize.Text);
getmem(temp, size);
if ReadProcessMemory(processhandle, pointer(a), temp, size,x) then
begin
a:=ptruint(VirtualAllocEx(processhandle, nil, size,mem_Commit or mem_Reserve, PAGE_READWRITE));
if a<>0 then
begin
WriteProcessMemory(processhandle, pointer(a),temp,size,x);
s:=tshadow.create;
s.address:=a;
s.size:=size;
e.tag:=ptruint(s);
e.readonly:=true;
end;
end;
FreeMemAndNil(temp);
end;
procedure TfrmStructureCompare.pmAddressPopupPopup(Sender: TObject);
var e: TAddressEdit;
begin
e:=TAddressEdit(pmAddressPopup.PopupComponent);
miPaste.enabled:=Clipboard.AsText<>'';
miCut.enabled:=e.SelLength>0;
miCopy.enabled:=e.SelLength>0;
miShadow.enabled:=(e.invalidAddress=false) or (e.tag<>0);
if e.tag<>0 then
miShadow.Caption:=format(rsSPSUnlock,[TShadow(e.tag).address, TShadow(e.tag).address+TShadow(e.tag).size])
else
miShadow.caption:=rsSPSLock;
end;
procedure TfrmStructureCompare.statusupdaterTimer(Sender: TObject);
var
scannerprogress: double;
scannerTotal: double;
i: integer;
x: integer;
begin
{
if (rescanner<>nil) and (pointerfilereader<>nil) then
progressbar1.position:=trunc((rescanner.currentPosition / pointerfilereader.count) * 1000);
}
if scanner<>nil then
begin
lblInfo.caption:=Format(rsScanningFoun, [inttostr(scanner.foundcount)]);
{
scannerTotal:=power(scanner.structsize, scanner.maxlevel+1);
scannerprogress:=0;
for i:=0 to scanner.maxlevel-1 do
begin
x:=scanner.progress[i];
scannerprogress:=scannerprogress+x*power(scanner.structsize, (scanner.maxlevel+1)-i-1);
end;
progressbar1.position:=trunc((scannerprogress / scannertotal) * 1000);
}
end
else
if rescanner<>nil then
begin
if rescanner.progressbar<>nil then
rescanner.progressbar.position:=(rescanner.currentPosition*100) div rescanner.maxPosition;
lblinfo.caption:=Format(rsScanningFoun, [inttostr(rescanner.count)])
end;
end;
procedure TfrmStructureCompare.tRefresherTimer(Sender: TObject);
begin
lvResults.refresh;
end;
procedure TfrmStructureCompare.setGUIStateEnabled(state: boolean);
begin
lblvds.enabled:=state;
comboType.enabled:=state;
btnNewScan.enabled:=state;
btnScan.enabled:=state;
end;
procedure TfrmStructureCompare.disableGui;
begin
setGUIStateEnabled(false);
end;
procedure TfrmStructureCompare.enableGui;
begin
setGUIStateEnabled(true);
end;
procedure TfrmStructureCompare.addAddress(address: ptruint; shadow: qword; shadowsize: integer; group: integer);
var
e: TAddressEdit;
s: Tshadow;
begin
if group=0 then
begin
btnAddAddressLF.click;
e:=TAddressEdit(edtLF[edtLF.count-1]);
end
else
begin
btnAddAddressNLF.click;
e:=TAddressEdit(edtNLF[edtNLF.count-1]);
end;
e.Text:=inttohex(address,8);
if shadow<>0 then
begin
s:=tshadow.create;
s.address:=shadow;
s.size:=shadowsize;
s.donotfree:=true;
e.tag:=ptruint(s);
e.ReadOnly:=true;
end;
end;
initialization
{$I frmstructurecompareunit.lrs}
end.