The online racing simulator
LFSLapper V7.0.9.0 Test #6
Hello everyone.

After days being frustrated to get the new insimpacket sizes not to work. I can finally start release something that actually works.
Thanks to all who gave me advice.

I will try to add the new stuff Scawen is releasing during the test phases of the game.
Every new test version of LFSLapper will be uploaded in the first post.


NOTE: LFSLapper V7.0.9.0 DOES NOT WORK in LFS version V or older!!!

EDIT: Released Testversion #6 , Works on LFS W47 and higher

Download the test version and let me know if something isn't working. I might take a couple of days to fix stuff. Schwitz
============================================================================

+---------------------------------------------------------------+
|Changes from 7.0.8.1 to 7.0.9.0 TEST #6
+---------------------------------------------------------------+
=================================================
New:
=================================================

1: [Playervar]: "SkinName". GetPlayerVar("SkinName");
2: [LapperFunction]: SetListAllowedMods(); Set allowed mods. Max 120 mods allowed


CASE "!setmodlist":
SetListAllowedMods("4A1C57,FA2989,39CEEB,238F06");
#SetAllowedMods(""); #Allow all mods.
BREAK;

3: [LapperFunction]: GetListAllowedMods(); Get list of allowed mods.

CASE "!getmodlist":
$List = getlistallowedmods();
$NrOfMods = ToNum($List["NrOfAllowedMods"]);

privmsg("^7===== ^3List of Allowed mods ^7(".$NrOfMods.") ^3mods ^7=====");

IF($NrOfMods == 0) THEN
privmsg("^3All mods are allowed!");
ELSE
FOR ( $i = 0; $i < $NrOfMods ; $i = $i + 1)
privmsg("^7Mod ^0[^3".$i+1."^0]^7: ".$List[$i,"SkinID"]);
ENDFOR
ENDIF
BREAK;

4: LFS Rest API (request info about a certain mod) Test #1
-Register your LFSLapper @ https://www.lfs.net/account/api
-Fill in your ClientID and ClientSecret in myInc.LPR (includes/myinc.lpr)
-2 differend ways to request modinfo , by username(must be on track!!!) or by SkinID

$modinfo = getmoddedcarinfo("Bass-Driver");
or
$modinfo = getmoddedcarinfo("E25059");
-Variables in testscript below or in (includes/optional/lfsrestapi_Info.lpr)
=================================================
Changed:
=================================================

1: LFSLapper Insim version changed to version 9. Only compatible with LFS W43 and higher

2: Update: Event OnObjectHit() New argument to get contactspeed when hit an object:
$ContactSpeed

3: PlayerVar: GetPlayerVar("Car"); , Will now display the SkinID, if you've selected a mod.

4: Updated trackList.cfg
=================================================
Fix:
=================================================

1: Some autoX objects didnt match with their objectindex number
2: [Lappercrash] when loading more than 30 objects at once
3: [Event]: OnPlayerSelectCar() didnt displayed the name of the car correctly
4: [Event]: Several Events like (OnDist, OnDriftScore) didnt execute when more than 8 players on the track
Caused by MCI packets when more than 8 player are on the track


Attached files
LFSLapper V7.0.9.0 Test #6.zip - 5.8 MB - 169 views
lfsrestapi_Info.txt - 7.6 KB - 141 views
Quote from Bass-Driver :I cannot get the modded carnames to work. Somehow when i convert the characters to Hex, there is always one char that doesn't convert well. I have been looking on the web about converting Strings into bytes and than to Hex and trying several methods, but with no luck.

Hmm what are you trying to do here?

Taking a guess: you would like to get the name of a mod.

InSim only uses the hex vehicle mod identifiers to indicate which car a person is using. That identifier is nothing more than a random string. It's not the actual mod name. The mod name isn't shown anywhere in InSim but it can be looked up via our new API. I've written a quick note about that here: https://www.lfs.net/forum/post/1969337#post1969337
You could cache those ids and names hard because they never change. So once looked up, you can store them locally and never have to look them up again. But, it does require you to add Oauth2 and API lookups to your program.
I'm not sure if in the future InSim will show the actual name of a mod.

But maybe that's not what you meant at all Smile
Quote from Bass-Driver :I cannot get the modded carnames to work. Somehow when i convert the characters to Hex, there is always one char that doesn't convert well. I have been looking on the web about converting Strings into bytes and than to Hex and trying several methods, but with no luck.

Hey, take a look at this method, I think it is pretty straightforward when you have the 4 bytes of CName in a packet:
// length is not needed here, since CName is known to be 4
static string pakGetCName(byte[] pak, int first)
{
var buf = new byte[4];
Array.Copy(pak, first, buf, 0, 4);
if (isAlphaNumeric(buf[0]) && isAlphaNumeric(buf[1]) && isAlphaNumeric(buf[2]))
{
return LfsEncoding.Current.GetString(pak, first, len);
}

return buf[2].ToString("X2") + buf[1].ToString("X2") + buf[0].ToString("X2");

bool isAlphaNumeric(byte b)
{
if (b >= '0' && b <= '9') return true;
if (b >= 'A' && b <= 'Z') return true;
if (b >= 'a' && b <= 'z') return true;
return false;
}
}

Quote from Bass-Driver :Also the new packetsizes is a real struggle for me. No idea what todo here. I have been checking insim.Net codes what they have done and trying the same way, based on their code. Also with no luck.

all you have to do is:
[reading packets] when you read in the first byte of the packet - multiply it by 4
[sending packets] same as reading, when writing the packet to insim connection, divide the size by 4

Also, its actually a good idea to have the size passed separately or in your case (I've glanced over the lapper code a little bit) it can be calculated from the buffer length (divided by 4)


EDIT: Updated code snipped to the one that should fit your code
Quote from Victor :Hmm what are you trying to do here?

Taking a guess: you would like to get the name of a mod.

Indeed, i tried to get the 6 chars name of the mod.

First get the chars/official carname from the NPL insim packet, which gives me some weird symbols if it is a mod. But that is fine.

Then i checked the web for some methods to convert it to HEX like the link below.

https://stackoverflow.com/questions/16999604/convert-string-to-hex-string-in-c-sharp

Some methods did work. And the output was almost the same as the modded car (Skin ID), but some chars didn't convert well, which gives me different HEX values.
Quote from xspeedasx :

Feel so stupid right now. After adding all those packets in LFSLapper. With no experience in C# lol.
Trying to learn and understand the working of the code.

But some examples:

Sending a packet: I did this

public byte[] OCO(byte OCOAction, byte Index, byte Identifier, byte Data)
{
byte[] packet = new byte[8];
packet[0] = 8 * 4;
packet[1] = (byte)TypePack.ISP_OCO;
packet[2] = 0;
packet[3] = 0;
packet[4] = OCOAction;
packet[5] = Index;
packet[6] = Identifier;
packet[7] = Data;

return packet;
}

Reading a packet: What do i do here??

// OK For Insim 7
//Admin Command Report ( added @ 16-10-2017)
public class ACR // Size 12, 16, 20... 72 depending on Text
{
public readonly int PacketSize; // 12, 16, 20... 72 depending on Text
public readonly int ReqI; // 0
public readonly int UCID; // Unique Connection ID
public readonly int Admin; // set if user is an admin
public readonly int Result; // 1 - processed / 2 - rejected / 3 - unknown command
public readonly int Sp3; // Spare 3
public readonly string Text; // 4, 8, 12... 64 characters - last byte is zero

public ACR(byte[] packet)
{
PacketSize = pakGetByte(packet * 4, 1); //1 Byte (multiply 'packet' by 4) ??
ReqI = pakGetByte(packet, 2); //1 Byte
UCID = pakGetByte(packet, 4); //1 Byte
Admin = pakGetByte(packet, 5); //1 Byte
Result = pakGetByte(packet, 6); //1 Byte
Sp3 = pakGetByte(packet, 7); //1 Byte
Text = pakGetString(packet, 8, 64); // 4, 8, 12... 64 characters - last byte is zero
}
}

well the
packet[0] = 8 * 4;

should be 2 (divided by 4 so that sizes up to 1020 can fit into 0-255 possible values).
packet[0] = 8 / 4;

and the size after reading can be calculated like this:

public ACR(byte[] packet)
{
PacketSize = pakGetByte(packet, 1) * 4;

Quote from xspeedasx :well the
packet[0] = 8 * 4;

should be 2 (divided by 4 so that sizes up to 1020 can fit into 0-255 possible values).
packet[0] = 8 / 4;

and the size after reading can be calculated like this:

public ACR(byte[] packet)
{
PacketSize = pakGetByte(packet, 1) * 4;


Ahh oke, i will try that.
Are there packets that doesn't require the divide/multiply stuff?

Thank you all for the help.
Quote from Bass-Driver :Are there packets that doesn't require the divide/multiply stuff?

in InSim 9 all packet sizes are modified Smile
Oke after adding alot of missing lines in the packets that represnts the packetsizes/type/req/ etc and cleaning the code a bit. I have been started to add the multiply/divide stuff to the packetsizes.
Also i've set the insimversion to 9.

The insim wont even start and tells me that the adminpass is wrong.
It seems to go wrong with getting info from the IS_VER insimpacket.

Is there something else i need to change with packets,like the TCP/UDP packets for example. Because LFS might also send/receive packets with differend bytesizes with the new insim version?

EDIT: I disabled some Try{} Catch{} to make lapper crash. The app closes within a second, so had to be quick to make a screenshot of it.

Attached images
Error.jpg
Attached files
InSim4.txt - 97.6 KB - 143 views
I did manage to Connect LFSlapper with the server by multiply some values with 4 in the TCP section of the insim. Most of the code is not commented or is in french.
so that is some progress.

Still have some issues with reading the correct packets and their values.
Good news Omg omg omg

It were a few frustrating days, because i had no idea what todo with the new insim packet sizes etc. But i finally managed to get LFSLapper working on LFS version W43. Atleast it does load LFSLapper and the things it must do.

Grab the first test version in the first post.

Thank you all who helped me. Much appreciated Smile
Good work / lekker bezig!
@ Van Sterberkt : Thank you


New Testversion available

+---------------------------------------------------------------+
|Changes from 7.0.9.0 TEST #1 to 7.0.9.0 TEST #2
+---------------------------------------------------------------+
=================================================
New:
=================================================
1: New Playervar: "SkinName". GetPlayerVar("SkinName");
=================================================
Changed:
=================================================
1: PlayerVar: GetPlayerVar("Car"); , Will now display the SkinID, if you've selected a mod.

New Testversion available : Testversion #3
Download in first post.

This version contains 2 functions to set/get allowed modded cars.

=================================================
New:
=================================================

1: [LapperFunction]: SetListAllowedMods(); Set allowed mods. Max 120 mods allowed


<?php 
        
CASE "!setmodlist":
                
SetListAllowedMods("4A1C57,FA2989,39CEEB,238F06"); 
                
#SetListAllowedMods(""); #Allow all mods.
        
BREAK;
        
?>


2: [LapperFunction]: GetListAllowedMods(); Get list of allowed mods.


<?php 
        
CASE "!getmodlist":
            
$List getlistallowedmods();
            
$NrOfMods ToNum($List["NrOfAllowedMods"]);
            
            
privmsg("^7===== ^3List of Allowed mods ^7(".$NrOfMods.") ^3mods ^7=====");
            
            IF(
$NrOfMods == 0THEN
                privmsg
("^3All mods are allowed!");
            ELSE    
                FOR ( 
$i 0$i $NrOfMods $i $i 1)
                    
privmsg("^7Mod ^0[^3".$i+1."^0]^7: ".$List[$i,"SkinID"]);
                ENDFOR
            ENDIF    
        BREAK;
?>

Hello,

New Testversion available: Test #4
Download in first post.

Contains a Lappercrash fix and 1 updated file.


=================================================
Changed:
=================================================
1: Updated trackList.cfg

=================================================
Fixed:
=================================================
1: Lappercrash when loading more than 30 objects at once


New testversion available: Testversion #5
Download available in first post.

Contains a few fixes regarding to Insim Packets and carnames (SkinID)

=================================================
Fix:
================================================
1: [Event]: OnPlayerSelectCar() didnt displayed the name of the car correctly
2: [Event]: Several Events like (OnDist(), OnDriftScore()) didn't execute when more than 8 players on the track
Caused by MCI packet manager didn't allow more than 8 players on the track.

For the past days i have been trying to add the 'LFS REST API' into LFSLapper.
I used the code from xspeedasx from this post: https://www.lfs.net/forum/post/1970141#post1970141

I've managed to get data from the API, so that is a step forward.
But i get stuck on reading the actual data.

i've been reading about Json objects, async tasks etc, but nothing has helped me much. So i have no clue what todo anymore.

There were 2 options: stop working on the sourcecode of LFSLapper or asking for help.
Well i going to ask for help as a non-programmer. No idea why i'm still working on the sourcecode of LFSLapper with all the complexity Smile, because this is beyond my level.
Everything i did in the sourcecode was help from the code itself or from the web like stackoverflow.com etc

Anyway, could someone send me in the right direction on what todo.
Thank you.



<?php 
//scriptFunction.cs
/* Function i call from a LFSLapper Script, this is fine.
CASE "!gm":
    GetModdedCarInfo();
BREAK;*/

public void GetModdedCarInfo(GLScript.unionVal valArrayList args)
        {
            
infoPlayer currInfoPlayer newCfg.getCurrInfoPlayer();
            
string ident val.nameFunction;

            var list = 
LFSRestAPI.GetModdedCars(newCfg.varsLapper.RestAPIClientIDnewCfg.varsLapper.RestAPIClientSecret);
           
        }

//LFSRestAPI.cs

using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System;
using Configurator;

namespace 
LapperThreads
{
    public class 
LFSRestAPI
    
{
        public static 
async Task<List<ModCarEntry>> GetModdedCars(string RestclientIDstring RestclientSecret)
        { 
            
/*just a few warning messages*/
            
if (string.IsNullOrWhiteSpace(RestclientSecret))
            {
                
Console.WriteLine("ClientSecret Empty");
            }
            if (
string.IsNullOrWhiteSpace(RestclientID))
            {
                
Console.WriteLine("ClientID Empty");
            }

            var 
client = new HttpClient();
            var 
content = new FormUrlEncodedContent(new Dictionary<stringstring>
            {
                {
"client_secret",  RestclientSecret },
                {
"client_id"RestclientID },
                {
"grant_type""client_credentials"}
            });
            var 
tokenResponse await client.PostAsync("https://id.lfs.net/oauth2/access_token"content);
            var 
tokenText await tokenResponse.Content.ReadAsStringAsync();
            var 
tokenObject JsonConvert.DeserializeObject<TokenResponse>(tokenText);
            var 
accessToken tokenObject.access_token;

            
client.DefaultRequestHeaders.Add("Authorization""Bearer " accessToken);

            var 
carEntriesResponse await client.GetStringAsync("https://api.lfs.net/vehiclemod/");
            return 
JsonConvert.DeserializeObject<ModCarEntriesResponse>(carEntriesResponse).Data;
        }

        public class 
TokenResponse
        
{
            public 
string token_type getset; }
            public 
int expires_in getset; }
            public 
string access_token getset; }
        }

        public class 
ModCarEntriesResponse
        
{
            public List<
ModCarEntryData getset; }
        }

        public class 
ModCarEntry
        
{
            public 
string id getset; }
            public 
string name getset; }
            public 
string descriptionShort getset; }
            public 
string description getset; }
            public 
int userId getset; }
            public 
string userName getset; }
            public 
bool wip getset; }
            public 
int publishedAt getset; }
            public 
int numDownloads getset; }
            public 
int curUsage getset; }
            public 
float rating getset; }
            public 
int numRatings getset; }
            public 
int version getset; }
            public 
int lastDownloadedAt getset; }
            public 
int _class getset; }
            public 
bool ev getset; }
        }
}
}
?>

If the LFSLapper code is pretty old and doesn't use async anywhere else, the easiest route is to use the non-async calls instead, e.g.

var tokenResponse = client.Post("https://id.lfs.net/oauth2/access_token", content);

And remove the async/await/Task stuff from your GetModdedCars()
Quote from Bass-Driver :I've managed to get data from the API, so that is a step forward.
But i get stuck on reading the actual data.

Hi, what exactly is the problem that you're having? After reading through your code I'm pretty sure it should work (except idk how httpclient does behave while being reused for getting api key and data, maybe have 2 separate httpclients?).

If you get Task instead of actual data and can't use await in your synchronous code, you can synchronize the execution by adding .Wait() at the end:
Quote :
var list = LFSRestAPI.GetModdedCars(newCfg.varsLapper.RestAPIClientID, newCfg.varsLapper.RestAPIClientSecret).Wait();


@PeterN:
i do use Async for the Discord API, so that is working as intented.

@xspeedasx:
ye sorry,i was not clear enough about my "challenge" i'm dealing here.

As you can see i created a var named 'list'. I'm trying to read the objects (modded cars i assume) from 'list'.

Tried this method i have found on the web.

<?php 
using Newtonsoft
.Json.Linq;
JArray NewArray JArray.Parse(list.ToString());
            foreach (
JObject Car in NewArray)
            {
                
Console.WriteLine($"{Car["id"]} -> {Car["name"]}");
            }
?>

and trying to get the amount of mods: (this crashes Lapper)


<?php 
JArray NewArray 
JArray.Parse(list.ToString());
            
int nrofmods NewArray.Count;
?>

Also i have tried other methods aswell, but no luck.
There is so much information about Json, i have no idea where to look at, when you are not familiar with Json etc.

I'm sure its something simple i havent seen (most of the time).

Thanks in advanced.
Do you need to be using JSON there?

With the GetModdedCars() call you have a List<ModCarEntry>. You can use System.Text.Json to Serialize to string and Deserialize back to List<ModCarEntry> without doing anything with JArray/JObject.

Or you can just store each ModCarEntry in a database table if you a DB is involved.
oke, i clearly missed a step in the process. Thank you for that.
I got a bit further now. But somehow i seems to be crashing at the foreach loop.
with the next error message: Object reference not set to an instance of an object.

i used this webpage as an example.
https://www.codegrepper.com/co ... each+object+in+array+json
and
https://social.msdn.microsoft. ... ?forum=winappswithcsharp

and
https://social.msdn.microsoft. ... s?forum=aspgettingstarted

/* get data */
var list = LFSRestAPI.GetModdedCars(newCfg.varsLapper.RestAPIClientID, newCfg.varsLapper.RestAPIClientSecret);

/* Serialize data to string */
string json = JsonConvert.SerializeObject(list, new JsonSerializerSettings());
//Console.WriteLine(json); // this works yippie:)

============================================================================
/* Deserialize string to object */
LFSRestAPI.ModCarEntriesResponse Cars = JsonConvert.DeserializeObject<LFSRestAPI.ModCarEntriesResponse>(json);

foreach (var car in Cars.Data)
{
Console.WriteLine("" + car.name);
}[/php]



====================================================================

/* Deserialize string to object */
var Cars = JsonConvert.DeserializeObject<List<LFSRestAPI.ModCarEntry>>(json);

foreach (var car in Cars)
{
Console.WriteLine("" + car.name);
}[/php]

edit:
tried this aswell
===============================================================
List<LFSRestAPI.ModCarEntry> cars = JsonConvert.DeserializeObject<List<LFSRestAPI.ModCarEntry>>(json);
foreach (LFSRestAPI.ModCarEntry car in cars)
{
Console.WriteLine("field0: " + car.id);
}

I don't understand why are you doing all that de/serialize stuff again when it's already taken care of in the GetModdedCars method and the returned list is already ready to use.

This works perfectly fine for me:

<?php 
var await REST_API.GetModdedCars();

                            for (
int i 0v.Counti++)
                            {
                                    
Console.WriteLine(v[i].name);
                            }
?>

Well, thats what i was thinking. Because i've did that at the first try. and because it did give me an error, i did think it wasnt the right way to do it.

but somehow it give me an error. i also removed 'await'

The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

with await removed:

'Task<List<LFSRestAPI.ModCarEntry>>' does not contain a definition for 'Count' and no extension method 'Count' accepting a first argument of type 'Task<List<LFSRestAPI.ModCarEntry>>' could be found (are you missing a using directive or an assembly reference?)

but which assembly??

LFSLapper is using net.framework 4.8


<?php 
public void getmoddedcarinfo(GLScript.unionVal valArrayList args)
        {

            
infoPlayer currInfoPlayer newCfg.getCurrInfoPlayer();
            
string ident val.nameFunction;

            var 
await LFSRestAPI.GetModdedCars(newCfg.varsLapper.RestAPIClientIDnewCfg.varsLapper.RestAPIClientSecret);

            for (
int i 0v.Counti++)
            {
                
Console.WriteLine(v[i].name);
            }
        }
?>

ok installing the system.text.json assembly made my projectfile corrupt.
cool :/

Edit: Thank god i created a backup of the sourcecode, before i changed the Net framework version from 4.6.1 to 4.8
Attached files
LFSLapper V7.0.9.0 Dev #6.zip - 5.8 MB - 121 views
1
This thread is closed

FGED GREDG RDFGDR GSFDG