Remote Exec - An alternative to Streams

Started by habi, Jul 18, 2023, 02:48 PM

Previous topic - Next topic

habi

Remote Exec - An alternative to Streams
Remote Exec
  • What is it?
    It is a collection of plugin + server-script +  client-script.
  • How to use it?
    Put remoteexec04rel32/64 under 'plugins' directory, RemoteExec.nut, SendReply.nut, main.nut inside 'store/script' directory.
  • What are the function names used in client-side script?
    There is only one function ProcessData( Stream, array ) in RemoteExec.nut and two functions SendReply(token, result) and WriteDataToStream(data, Stream) in SendReply.nut
  • Did you reserve any global variable names?(client-side)
    No,just the function names seen above.

Function Documentation
1. SetRemoteValue( key, value )
In server, if you do SetRemoteValue( "a", 10 ). It is equivalent to a<-10 ( rawset ) on client side. Here 'Remote' means Client.
2. GetRemoteValue( "a" )
If you have a global variable named 'a' in client-scripts, this function fetches its value.
3. GetRemoteValueEx( obj, key )
If you have an object obj (table, class, instance) on client side and has a field key in obj, then this function fetches its value.

//Suppose a<-Vector( 1, 2, 3 ) in client-side.
//To get a.Z
GetRemoteValueEx( GetRemoteValue( "a" ), "Z" )
4. SetRemoteValueEx( obj, key, value )
Performs a rawset on object obj with key and value. I have coded it so that you can perform this on Player, Vehicle, etc classes too.

//Suppose a<-Vector( 1, 2, 3 ) client side
//To set a.Z = 0
SetRemoteValueEx( GetRemoteValue( "a" ), "Z", 0 )
5. CallRemoteFunc( func, arg1, arg2, ... )
Calls the func=GetRemoteValue("funcname") with arguments arg1, arg2 , etc.

//Suppose i want to call function foo("hello", 1, 100.0 )
CallRemoteFunc(GetRemoteValue("foo"),"hello", 1, 100.0)
6. CallRemoteFuncEx( func, env, arg1, arg2, ... )
Now you can specify the 'this' object with the function is called.

//This is very complicated and hence i am not providing an example here
RemoteExec ( ... )
RemoteExec( SetRemoteValue("a",100), FindPlayer(0), true, print)
The above six functions are to be used inside 'RemoteExec'.

RemoteExec( userdata, player, (should_return), (callback_func) )The last two parameters are used for getting the result back to server. They are optional. The callback_func must be a one-parameter function like print. I have managed to include a token with every RemoteExec and client sends the result with this token. Thus that function will be called when the token arrivesinvolves
Checking for error in callback_func
The global variable REMEXEC_ERROR will be set to 1 if client throwed error. This variable is to be used inside the callback as follows:
RemoteExec( GetRemoteValue( myvariable ), player, true, function(res)
{
if(!REMEXEC_ERROR)do_something(res);
});
Metamethods
The userdata returned by functions 2, 3,5-6, supports Get and Call metamethods on them.
long code:
CallRemoteFuncEx(GetRemoteValueEx(GetRemoteValue("Console"), "Print"), GetRemoteValue("Console"), "Hello World" )short code:
local Console=GetRemoteValue("Console");
RemoteExec( Console.Print("Helloworld"), player);
The metamethods add, sub, mul, div, modulo and unary minus are likewise defined. See the immediately following post after this for details.

Download
Download plugin ( store files included ) 51.3 KB below (updated Sep 29):
Installation - Client Side
store/script/main.nut looks like: (partial code, full files included in archive)
dofile("RemoteExec.nut");
dofile("SendReply.nut");
enum StreamType
{
    RemoteExecute=0x40ffffe1  //Used for executing
    RemoteExecuteSpecial=0x40ffffe2    //Used for execution and return result using token
    Reply=0x40ffffe3    //Used when sending result to server
    CompileString=0x40ffffe4   
    RPrint=0x40ffffe5
}


function Server::ServerData(stream) {
//Warning: We are reading Int and not byte !!!!
local i=stream.ReadInt();
if(stream.Error)
{
    return;
}
if(i==StreamType.CompileString)
{
    local script = compilestring(stream.ReadString());
    try{
        local result=script();
    }catch(e)
    {
       
    }
}else if(i==StreamType.RemoteExecute)
{
    try
    {
        local res=ProcessData(stream);
    }catch(e)
    {
        SendReply(-1, e, true);
    }
}else if(i==StreamType.RemoteExecuteSpecial)
{
    local token=stream.ReadInt();
    try
    {
        local res=ProcessData(stream);
        SendReply(token, res);
    }catch(e)
    {
        SendReply(token, e, true);
    }
}
}

Source-code of plugin (cpp): mediafire-link (updated Sep 29) or github

The function is able to return values like integer, float, string, null, boolean and arrays. However, classes and tables cannot be returned. In such cases the return value will be typeof(result).

If error occurs while executing them on client side, the error is catched and returned as string, which will be printed on server console "remexec (remote): Error message" and if return callback was specifed, the error message string will be the parameter.
Stream Information
The first bytes of the Streams (ServerData/ClientScriptData) uses the following:

ValueType
0x40ffffe1 - 0x40ffffe5  Integer

Default Callback
When parameter three is set to true, but no callback is given as fourth parameter, then plugin searches for following function in roottable and call it if scripted.
function onRemoteExecReply(token,result)
{
//token - internal number used for identifying streams
//result - the value returned from client
}

habi

Update:

Now, you can use
GetRemoteValue("a") + GetRemoteValue("b")
inside the RemoteExec function. (parameter one)
Let us see what this is:
GetRemoteValue("a")  will fetch the value of global variable a from client roottable to server.
GetRemoteValue("b") will fetch the  value of global variable b.

GetRemoteValue("a")+GetRemoteValue("b") will perform an addition on client environment whatever may be variables a and b.

Similary the operations +, -, *, / are valid. Unary minus (-) operator also works.

Rules:
1. The userdata (that returned by GetRemoteValue, CallRemoteFunc, etc) must be the left operand and the integer, float, etc must be on right. This is because of the property of metamethods _add, _sub, _mul, _div, _modulo. So
3 + GetRemoteValue("b") // is not valid GetRemoteValue("b") + 3 // is valid2. Concatenation cannot be performed.
GetRemoteValue("A") + " is the value of variable A" //leads to error CallRemoteFunc( GetRemoteValue("format"), "%d is the value of a", GetRemoteValue("a") )   //is valid
Complete Example
RemoteExec(CallRemoteFunc(GetRemoteValueEx(GetRemoteValue("GUI"),"GetScreenSize")),FindPlayer(0),true,
function(val){::print("The remote screenSize is "+val)});
output:
[SCRIPT]  The remote screenSize is (640, 480, 0)
RemoteExec(GetRemoteValueEx(CallRemoteFunc(GetRemoteValueEx(GetRemoteValue("GUI"),"GetScreenSize")),"Y")*0.96,FindPlayer(0),true,
function(val){::print("screenSize.Y * 0.96 is "+val)});
output:
[SCRIPT]  screenSize.Y * 0.96 is 460.8
Note: I don't know, but it happens once in a while passing wrong values and server crashes.

habi

update:
fixed crash occuring when trying to send clientscript data that is too long.
fixed another crash when reading data clientscript data.

i have added proper if statement to check size and index before reading them, like this:
case 's':
if (size < index + 3)return 0;//for reading len
len = swap2(*(uint16_t*)(data + index + 1));
//Check if len is less than size. otherwise it crash server.
if (size < index + 3 + len ) { return 0; }
sq->pushstring(v, (char*)data + index + 3, len); k = 1 + 2 + len; break;

PSL

Habi well done, I've been waiting a long time, I can't wait, if there are any questions, I will feedback here.


habi

#5
Update
#1: Found and fixed the bug. i have been 'memcpy' more than size and it was crashing server. it is now fixed
#2: Added _get and _call metamethods. See Example
local Console = GetRemoteValue("Console");
RemoteExec(  Console.Print("Greetings from Liberty City"), player )

the second example is little messy:
local RayTrace = GetRemoteValue("RayTrace");
RemoteExec(  RayTrace(Vector(-1073.56, 63.5817, 11.2329),Vector(-1081.5, 104.404, 11.263),15).Collided,player,true,print)
[SCRIPT]  trueWhen you do GetRemoteValue("getroottable")(), the _call metamethod is evoked and when you get like Console.Print, there _get metamethod will be evoked.

Big-Example
This is client side script i took from:
Quote from: Kelvin Garcia Mendoza on Nov 05, 2020, 06:09 PM_label <- null;

function Script::ScriptLoad()
{
 local
 screenSize          = ::GUI.GetScreenSize(),
 label               = ::GUILabel( ::VectorScreen( ( screenSize.X * 0.005 ), ( screenSize.Y * 0.96 ) ), ::Colour( 255, 255, 255 ), format( "Hey, %s. Welcome to the server!", ::World.FindLocalPlayer().Name ) );
 label.FontName      = "Verdana";
 label.FontSize      = ( screenSize.X * 0.2 );
 label.FontFlags     = GUI_FFLAG_BOLD;
 label.TextAlignment = GUI_ALIGN_LEFT;

 ::_label = label;
}
and when changed
function onPlayerJoin(player)
{
 local table= GetRemoteValue("getroottable")();
 local screenSize          = table.GUI.GetScreenSize();
 local label               = table.GUILabel( table.VectorScreen( ( screenSize.X * 0.005 ), ( screenSize.Y * 0.96 ) ), table.Colour( 255, 255, 255 ), table.format( "Hey, %s. Welcome to the server!", table.World.FindLocalPlayer().Name ) );
 
RemoteExec(SetRemoteValue("mylabel",label),player);

local mylabel=GetRemoteValue("mylabel"); //no neeed of '()' as it is not function
RemoteExec(SetRemoteValueEx(mylabel,"FontName","Verdana"),player);
RemoteExec(SetRemoteValueEx(mylabel,"FontSize", 20),player);

local consttable=GetRemoteValue("getconsttable")();
RemoteExec(SetRemoteValueEx(mylabel,"FontFlags",consttable.GUI_FFLAG_BOLD),player);
RemoteExec(SetRemoteValueEx(mylabel,"TextAlignment",consttable.GUI_ALIGN_RIGHT),player);
}
when called this result:


How is it..?

PS (for newbies, if anybody): You have to load remexec04rel32. Otherwise you get 'Error: the index 'GetRemoteValue' does not exist'

PSL

I got Error: spawn cmake ENOENT when I used vs code to convert dll.  I'm very sorry, I was working before and didn't have time to test, I may need to try to convert plugin by myself again.

habi

The error spawn cmake ENOENT is discussed here.

btw, are you compiling the plugin from source?

PSL

This is an area I have never touched, but I slowly carved, understand, I saw the tutorial on the network installed mingw64 and gcc, I configured the environment variables according to the tutorial, in cmd test gcc -v is OK, then I do not know what to do, in vs code or display the previous error, I'm really not good at this. And I don't know what source code is.

habi

#9
If on windows, download ninja-win zip from https://github.com/ninja-build/ninja/releases.
Put it in c:\ninja\
Add c:\ninja to PATH environment variable
Close VScode and open again.
Do Ctrl+Shift+P and click CMake:Reset


Again open Command Palette (Ctrl+Shift+P) and run CMake:Select a Kit. Choose GCC xx mingw-32 xx

Now you can successfully build plugin. To build :
From Command Palette run CMake: Select Variant and choose Release.
Then run CMake: Configure
And finally CMake: Build

If successfully, on windows explorer out/binaries you can see the plugin built by vscode.


Note: Ninja builds it on folder build originally. It is then copied to out/binaries

PSL

Great, thank you so much, with your help, I successfully generated the dll file.
Console display:  [MODULE]  Remote Exec __REMEXEC_VERSION__ by habi loaded.
I will use this plugin according to the information.

habi

#11
Sorry that was a later addition to source code which produced the strange result. It escaped my notice.
I will update the source and binaries.
Find this line (701) main. cpp
fputs("Remote Exec __REMEXEC_VERSION__ by
and replace __REMEXEC_VERSION__ by v1.1.1
fputs("Remote Exec v1.1.1 by

To make it work in linux at the same time, do the same to line ( 704 )

EDIT: Source and binaries updated. Added github link too.

PSL

Why did I return userdata 0x00~~?  Can you check it for me..
client:  abc<-Vector(1,2,3);
server: GetRemoteValueEx(GetRemoteValue("abc"),"Z");  return userdata 0x00~~
plugin dll: https://file.io/z0TLnJrM1LO4

habi

#13
GetRemoteValue always return 'userdata'.
print(GetRemoteValue(..)) will show 'userdata 0x..'
1. To obtain value from client in one step is not possible because server has to wait until result arrives.
2. Our main function is 'RemoteExec'.
See:
client:  abc<-Vector(1,2,3);
server:
QuoteRemoteExec( GetRemoteValueEx(GetRemoteValue("abc"),"Z"), FindPlayer(0), true, function(val){ print("The Z co-ordinate is "+val); } );

The parameter true tell client to send back the result.
The fourth parameter function will be called when the result arrives which take time.

you can replace 'one parameter function' with 'print' itself:
QuoteRemoteExec(GetRemoteValueEx(GetRemoteValue("abc"),"Z"), FindPlayer(0), true, print );
So 'print' will be called with the result:
[SCRIPT]  3
This replacement with 'print' will be useful for testing purposes.

The userdata returned is actually a C++ array whose length increases with number of GetRemoteValue used. If you use two GetRemoteValue, then the array contain almost double number of elements.
This pointer/array being used by plugin is not visible to squirrel.