PDA

View Full Version : [LAPE] VariantInvoke() and exceeding it's limits



bg5
03-25-2015, 11:14 PM
Introduction:

VariantInvoke is powerfull tool inbuilt in Simba, which lets you to pass, as an argument, one function to another function and call it repeatedly inside it's body. It's LAPE equivalent of PascalScript's CallFunc().

Syntax:

function VariantInvoke('functionToInvoke': string; parametersOfFunction :TVariantArray) : Variant;

Variant is very neat thing - it's basically a type, which can contain value of any basic type , such as: integer, byte ,string ,extended. Also, you can assign any basic type to Variant directly, without any casting (so be carefull here):
var i:integer;
var v : variant;
var s :string;
begin
v := 50;
i := v; // no problem
s := v; // compiler won't complain but you will get here error or access violation
end;
So array of Variant ( = TVariantArray) can contain many values of different type, such as:
var va : TVariantArray;
i : integer;
begin
va := ['Hello', 0.35 , 76543, i ];
end;

In VariantInvoke we use TVariantArray to store all arguments of function we call. And the result of VariantInvoke() is stored in Variant. Let's see an example, how it works:


function MyNumber ( s :string; a,b :integer ) : integer;
begin
writeln(s +' args: ' + tostr(a)+ ' '+ tostr(b));
result := a*b;
end;

procedure VariantTest(f:string;va:TVariantArray;howmany : integer);
var i,r : integer;
begin
for i := 0 to howmany-1 do
begin
va[1] := va[1] * 2;
r := VariantInvoke(f,va);
writeln('Result : '+tostr(r));
end;
end;

begin
VariantTest('MyNumber',['Hello',5,7],6);
end.

It will print:

Hello args: 10 7
Result : 70
Hello args: 20 7
Result : 140
Hello args: 40 7
Result : 280
Hello args: 80 7
Result : 560
Hello args: 160 7
Result : 1120
Hello args: 320 7
Result : 2240

Limits:

As I've said already Variant can store only basic types. That makes it useless with functions dealing with TPoints ,TPointArrays, TIntegerArrays etc. ( all arrays, records , custom types at all), which are quite frequent in SRL. Furthermore, due to way how VariantInvoke works ( it runs through code before compiler, I won't go further on this topic) it can't invoke functions, which are methods of types ,such as:

customType.anyFunc();
tabBackpack.isFull();
TRSGameTabs.isTabVisible();
TSomething.anything.objectX.getMyValue();

which is a big pain, because SRL-6 and many other libraries are object-oriented.

Reassuming, there are 3 limits of VariantInvoke():


Functions, which return non-basic type
Functions, which have non-basic types as parameters
Type.function()


In the next part I will show and explain how to go around each of those, so you can use it freely with SRL and other libs.

bg5
03-25-2015, 11:15 PM
VariantInvoke() with functions, which return non-basic type

Let's consider a function abc() : TPoint ,which we want to pass to function test() :


function abc() : TPoint;
begin
Result := Point(300,200);
end;


procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
var i : integer;
p : TPoint;
begin
for i := 0 to howmany-1 do
begin
p := VariantInvoke(FuncToInvoke,args);
writeln(p);
end;
end;

begin
test('abc',[],10);
end.

Of course compiler will say something predictable:

Error: Can't assign "Variant" to "record [0]Int32; [4]Int32; end" at line 14

Indeed variant can't store TPoint, but it can store..integer containing adress of our TPoint. So here is a trick !


STEP 1
We need to make a wrapper of abc() returning address of result instead of result itself :
function abc_Wrap(addr_result : integer) : integer;
begin
TPoint(Pointer(a_result)^) := abc();
{ this syntax can be confusing to you so I can explain a bit: first we cast our address to pointer.
Pointer is basically nothing else then 'cell' in memory containing address of another 'cell' in memory,
in this case our variable of TPoint type. So we grab the variable to which Pointer is pointing and
change it's value to result of original function. }
Result := addr_result;
end;

STEP 2
Oh wait, we are dealing with an address of something, but where is that thing? Indeed we need some place in memory, where the result will be stored. So we declare variable in global scope and add it above our wrapper:


var abc_Wrap_Result : TPoint;
function abc_Wrap(addr_result : integer) : integer;
begin
TPoint(Pointer(a_result)^) := abc();
Result := addr_result;
end;


STEP 3

We need to change test() function to handle wrapper:

procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
var i, ap : integer;
p : TPoint;
begin
for i := 0 to howmany-1 do
begin
ap := VariantInvoke(FuncToInvoke,args); // = @p (address of our TPoint) ; I would prefer to do it in one line, but compiler would cry for some reason
p := TPoint(Pointer(ap)^);
writeln(p);
end;
end;

STEP 4

Everything altogether:

function abc() : TPoint;
begin
Result := Point(300,200);
end;

var abc_Wrap_Result : TPoint;
function abc_Wrap(a_result : integer) : integer;
begin
TPoint(Pointer(a_result)^) := abc();
Result := a_result;
end;

procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
var i,ap : integer;
p : TPoint;
begin
for i := 0 to howmany-1 do
begin
ap := VariantInvoke(FuncToInvoke,args);
p := TPoint(Pointer(ap)^);
writeln(p);
end;
end;

begin
test('abc_Wrap',[integer(@abc_Wrap_Result)],10);
end.




VariantInvoke() with methods

Look:

type TMyType = record
P : TPoint;
end;

function TMyType.getX(really : boolean) : integer;
begin
if really then
Result := self.P.x;
end;

procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
var i,ap : integer;

begin
for i := 0 to howmany-1 do
begin
ap := VariantInvoke(FuncToInvoke,args);
writeln(ap);
end;
end;

var
mt : TMyType;
begin
mt.P := Point(123,999);
test('mt.getX',[TRUE],10);
end.

Compiler won't notice anything wrong, just as like as you put any random function name, but it won't work, so we need to fix it. As you propably guess it will be done in very similar way as above, except we don't need to declare any variable, because our object already exists and the name of my wrapper will be more ugly :) Also we don't modify test() function, because this function returns basic type. So there is only one step ! :

program new;

type TMyType = record
P : TPoint;
end;

function TMyType.getX(really : boolean) : integer;
begin
if really then
Result := self.P.x;
end;

procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
var i,ap : integer;
begin
for i := 0 to howmany-1 do
begin
ap := VariantInvoke(FuncToInvoke,args);
writeln(ap);
end;
end;

// nothing have changed till here
function Wrap_TMyType_getP(TMyTypeAdress: integer; really : boolean) : integer;
begin
Result := TMyType(Pointer(TMyTypeAdress)^).getX(really);
end;
var
mt : TMyType;
begin
mt.P := Point(123,999);
test('Wrap_TMyType_getP',[integer(@mt),TRUE],10);
end.


VariantInvoke() with functions,which have non-basic types as parameters

later...

bg5
03-26-2015, 01:03 AM
Tracking Mouse
{************************************************* ******************************
Procedure windTrackingMouse();
By: Flight & bg5
Description (Flight): Mouse movement based on distance to determine speed. Default slowed
speed at 20% from destination, if Double is set to true then slowed
speed also starts at origin and ends 80% to destination.

Description (bg5): Variation of Flight's brakeWindMouse from aerolib. Calls function
(returning address to TPoint) every refreshingDelay seconds to update destination.
That function must be wrapped properly!
All changes are marked with //*
************************************************** *****************************}
Procedure windTrackingMouse(xs, ys, gravity, wind, minWait, maxWait,
targetArea: extended; double: boolean;
funcRetTPointAdr:string; args:TVariantArray; refreshingDelay :integer ); //*
var
T,rD : Timer; //*
xe, ye , ap :integer; //*
Pe :TPoint; //*
veloX,veloY,windX,windY,veloMag,dist,randomDist,la stDist,D: extended;
lastX,lastY,MSP,W,TDist: integer;
sqrt2,sqrt3,sqrt5,PDist,maxStep,dModA,dModB,nModA, nModB: extended;
begin
ap := VariantInvoke(funcRetTPointAdr,args); // *
Pe := TPoint(Pointer(ap)^); // *
xe := Pe.X; //*
ye := Pe.y; //*
rD.start(); //*

MSP := MouseSpeed;
sqrt2:= sqrt(2);
sqrt3:= sqrt(3);
sqrt5:= sqrt(5);

TDist := Distance(Round(xs), Round(ys), Round(xe), Round(ye));
if (TDist < 1) then
TDist := 1;

dModA := 0.88;
dModB := 0.95;

if (TDist > 220) then
begin
nModA := 0.08;
nModB := 0.04;
end else if (TDist <= 220) then
begin
nModA := 0.20;
nModB := 0.10;
end;



T.start();
repeat
if (T.timeElapsed() > 10000) then
break;
if (rd.timeElapsed() > refreshingDelay) then //*
begin
ap := VariantInvoke(funcRetTPointAdr,args); // *
Pe := TPoint(Pointer(ap)^); // *
xe := Pe.X; //*
ye := Pe.y; //*
rD.start(); //*
end;


dist:= hypot(xs - xe, ys - ye);
wind:= minE(wind, dist);
if (dist < 1) then
dist := 1;
PDist := (dist/TDist);
if (PDist < 0.01) then
PDist := 0.01;

if Double then
begin
if (PDist <= dModA) then
begin
D := (Round((Round(dist)*0.3))/5);
if (D < 20) then
D := 20;

end else if (PDist > dModA) then
begin
if (PDist < dModB) then
D := RandomRange(5, 8)
else if (PDist >= dModB) then
D := RandomRange(3, 4);
end;
end;

if (PDist >= nModA) then
begin
D := (Round((Round(dist)*0.3))/5);
if (D < 20) then
D := 20;
end else if (PDist < nModA) then
begin
if (PDist >= nModB) then
D := RandomRange(5, 8)
else if (PDist < nModB) then
D := RandomRange(3, 4);
end;

if (D <= Round(dist)) then
maxStep := D
else
maxStep := Round(dist);

if dist >= targetArea then
begin
windX:= windX / sqrt3 + (random(round(wind) * 2 + 1) - wind) / sqrt5;
windY:= windY / sqrt3 + (random(round(wind) * 2 + 1) - wind) / sqrt5;
end else
begin
windX:= windX / sqrt2;
windY:= windY / sqrt2;
end;

veloX:= veloX + windX;
veloY:= veloY + windY;
veloX:= veloX + gravity * (xe - xs) / dist;
veloY:= veloY + gravity * (ye - ys) / dist;

if (hypot(veloX, veloY) > maxStep) then
begin
randomDist:= maxStep / 2.0 + random(round(maxStep) div 2);
veloMag:= sqrt(veloX * veloX + veloY * veloY);
veloX:= (veloX / veloMag) * randomDist;
veloY:= (veloY / veloMag) * randomDist;
end;

lastX:= Round(xs);
lastY:= Round(ys);
xs:= xs + veloX;
ys:= ys + veloY;

if (lastX <> Round(xs)) or (lastY <> Round(ys)) then
moveMouse(Round(xs), Round(ys));

W := (Random((Round(100/MSP)))*6);
if (W < 5) then
W := 5;
if Double then
if (PDist > dModA) then
W := Round(W*2.5)
else
W := Round(W*1.2);
wait(W);
lastdist:= dist;
until(hypot(xs - xe, ys - ye) < 1)

if (Round(xe) <> Round(xs)) or (Round(ye) <> Round(ys)) then
MoveMouse(Round(xe), Round(ye));

MouseSpeed := MSP;
end;

{************************************************* ******************************
By: Flight & bg5
Description(Flight): Makes use of BrakeWindMouse; mouse speed decreases at 15% to
destination point. If Double is set to true then speed starts
slow and increases through the first 15% of the path.
Description (bg5): Variation of Flight's brakeMMouse from aerolib. Calls function
(returning address to TPoint) every refreshingDelay seconds to update destination.
That function must be wrapped properly!
All changes are marked with //*
************************************************** *****************************}
Procedure TrackMMouse(double: Boolean; //* randomization of destination point not needed
funcRetTPointAdr:string; args:TVariantArray; refreshingDelay :integer); //*
var
randSpeed : extended;
X,Y,MS : integer;
begin
MS := MouseSpeed;
randSpeed := (random(MouseSpeed) / 2.0 + MouseSpeed) / 10.0;
getMousePos(X, Y);

windTrackingMouse(X, Y, //*
8, 3, 10.0/randSpeed, 15.0/randSpeed, 10.0*randSpeed, double,
funcRetTPointAdr,args,refreshingDelay); //*
MouseSpeed := MS;
end;

Example of use: (remember you can use ANY detecting function which returns TPoint with Tracking Mouse);

TReflectGroundItem.GetMSPoint() wrapped for Tracking Mouse
var res_Wrap_TReflectGroundItem_GetMSPoint : TPoint;
function Wrap_TReflectGroundItem_GetMSPoint(adr_self, adr_result : integer) : integer;
begin
TPoint(Pointer(adr_result)^) := TReflectGroundItem(Pointer(adr_self)^).GetMSPoint( );
Result := adr_result;
end;


Putting them together:

function TReflectGroundItem.Grab2() :boolean;
begin
{...}

TrackMMouse(TRUE,'Wrap_TReflectGroundItem_GetMSPoi nt',
[integer(@self),integer(@res_Wrap_TReflectGroundIte m_GetMSPoint)], 100);
Reflect.Mouse.Click(Mouse_Right);
if Reflect.Text.ChooseOption(self.GetName,50) then
begin
Result := TRUE;
break;
end;
{..}

end;


Effect:

with MouseSpeed := 10;
watch?v=i_ppsOGrPjo


Credits to:
Olly - for little help with code.
Flight , elfyyy - for function

Clutch
04-15-2015, 04:53 PM
Sweet guide was looking for something like this after my talk with Obscurity;