cheat-engine/Cheat Engine/groupscancommandparser.pas

317 lines
7.1 KiB
ObjectPascal

unit groupscancommandparser;
{
This unit contains the class that reads a groupscan command and parses it. The results can be looked at afterwards
}
{$mode delphi}
interface
uses
Classes, SysUtils, cefuncproc, CustomTypeHandler, strutils, commonTypeDefs;
type
TGroupscanCommandParser=class
private
calculatedBlocksize: integer;
procedure parseToken(s: string);
public
FloatSettings: TFormatSettings;
elements: array of record
wildcard: boolean;
offset: integer;
vartype: TVariabletype;
uservalue: string;
valueint: qword;
valuefloat: double;
customtype: TCustomType;
bytesize: integer;
command: string;
picked: boolean;
end;
blocksize: integer;
blockalignment: integer;
outOfOrder: boolean;
typeAligned: boolean;
procedure parse(command: string);
constructor create(command: string='');
end;
implementation
procedure TGroupscanCommandParser.parseToken(s: string);
var i,j,k: integer;
command,value: string;
ctn: string;
bracketcount: integer;
nextchar: integer;
begin
//deal with custom types with a ':' and don't mess up strings
bracketcount:=0;
for i:=1 to length(s)-1 do
begin
case s[i] of
':': if bracketcount=0 then break; //found it
'(': inc(bracketcount);
')': dec(bracketcount);
end;
end;
if i=length(s) then exit;
command:=uppercase(copy(s,1,i-1));
value:=copy(s,i+1, length(s));
if command='BA' then
blockalignment:=strtoint(value)
else
if command='BS' then
blocksize:=strtoint(value)
else
if command='OOO' then
begin
outOfOrder:=true;
typeAligned:=value='A';
end;
if (length(command)>=1) and (command[1] in ['1','2','4','8','F','D','C','S','W']) then
begin
j:=length(elements);
setlength(elements, j+1);
elements[j].offset:=calculatedBlocksize;
elements[j].command:=command;
elements[j].picked:=false;
nextchar:=2;
case command[1] of
'1':
begin
elements[j].vartype:=vtByte;
elements[j].bytesize:=1;
end;
'2':
begin
elements[j].vartype:=vtWord;
elements[j].bytesize:=2;
end;
'4':
begin
elements[j].vartype:=vtDWord;
elements[j].bytesize:=4;
end;
'8':
begin
elements[j].vartype:=vtQword;
elements[j].bytesize:=8;
end;
'F':
begin
elements[j].vartype:=vtSingle;
elements[j].bytesize:=4;
end;
'D':
begin
elements[j].vartype:=vtDouble;
elements[j].bytesize:=8;
end;
'C':
begin
//custom type
elements[j].vartype:=vtCustom;
i:=pos('(', command);
for k:=length(command) downto i do
begin
if command[k]=')' then break;
end;
ctn:=copy(command, i+1, k-i-1);
elements[j].customtype:=GetCustomTypeFromName(ctn);
if elements[j].customtype<>nil then
elements[j].bytesize:=elements[j].customtype.bytesize
else
raise exception.create('Custom type not recognized: '+ctn);
nextchar:=k+1;
end;
'S':
begin
//remove quote part from value
if (value<>'') and (value[1]='''') and (value[length(value)]='''') then
value:=copy(value, 2, length(value)-2);
if (length(command)>=2) and (command[2]='U') then
begin
elements[j].vartype:=vtUnicodeString;
elements[j].bytesize:=length(value)*2;
nextchar:=3;
end
else
begin
elements[j].vartype:=vtString;
elements[j].bytesize:=length(value);
end;
end;
'W': //wildcard of random size (implemented as a string of specific size with wildcard value)
begin
elements[j].vartype:=vtString;
elements[j].bytesize:=strtoint(value);
value:=''; //set as a wildcard
end;
else
raise exception.create('Invalid groupscan command');
end;
if length(command)>=nextchar then
begin
case command[nextchar] of
'P': elements[j].picked:=true; //elements marked picked will be added when doubleclicked in the addresslist
else
raise exception.create('Invalid groupscan command');
end;
end;
elements[j].uservalue:=value;
inc(calculatedBlocksize, elements[j].bytesize);
elements[j].wildcard:=(value='') or ((not (elements[j].vartype in [vtString, vtUnicodeString])) and (value = '*'));
if not elements[j].wildcard then
begin
case elements[j].vartype of
vtByte..vtQword, vtCustom: elements[j].valueint:=StrToQWordEx(value);
vtSingle, vtDouble:
begin
try
elements[j].valuefloat:=StrToFloat(value, FloatSettings);
except
if FloatSettings.DecimalSeparator='.' then
FloatSettings.DecimalSeparator:=',' else
FloatSettings.DecimalSeparator:='.';
end;
end;
end;
end;
end;
end;
procedure TGroupscanCommandParser.parse(command: string);
var start, i: integer;
s: string;
inquote: boolean;
inbraces: boolean;
haspick: boolean;
begin
//reset/init
blockalignment:=4; //default
blocksize:=-1;
calculatedBlocksize:=0;
setlength(elements,0);
FloatSettings:=DefaultFormatSettings;
start:=1;
inquote:=false;
inbraces:=false;
for i:=1 to length(command) do
if (command[i] in [' ','''','(',')']) or (i=length(command)) then
begin
if command[i]='''' then
begin
inquote:=not inquote;
if i<length(command) then //there's more, first check that (it's wrong, whatever comes after here if it's not a space though)
continue;
end;
if inquote then continue;
//not inside a quote
if command[i]='(' then //check if custom type name
begin
inbraces:=true;
continue;
end
else
if command[i]=')' then
begin
inbraces:=false;
continue;
end;
if inbraces then continue;
//not inside braces or a quote, handle the token
s:=trim(copy(command, start, i+1-start));
start:=i;
parseToken(s);
end;
if blocksize=-1 then //check if set by the user, if not (or the user is a complete retard that sets size to -1)
blocksize:=calculatedBlocksize;
if outOfOrder then
for i:=0 to length(elements)-1 do
if elements[i].wildcard then
raise exception.create('Wildcards/Empty are not allowed for Out of Order scans');
haspick:=false;
for i:=0 to length(elements)-1 do
if elements[i].picked then
begin
haspick:=true;
break;
end;
if haspick=false then
begin
//mark ALL elements as picked
for i:=0 to length(elements)-1 do
elements[i].picked:=true;
end;
end;
constructor TGroupscanCommandParser.create(command: string='');
begin
parse(command);
end;
end.