The online racing simulator
New InSim packet size byte and mod info
Hello programmers.

Mod support is coming soon and there are a couple of changes in InSim (updated InSim.txt attached)


Size byte

Most notably, the size byte is now size divided by four. So the value '1' means the packet is size 4 bytes, etc. This does no harm as packet sizes were always a multiple of 4, and now we can have much bigger packets, up to 1020 in size.

I have made use of these bigger packets in a couple of places:

// IS_AXM maximum objects increased to 60 (was 30) - see AXM_MAX_OBJECTS
// IS_MCI maximum cars increased to 16 (was 8) - see MCI_MAX_CARS

(Layout editor now now allows 60 objects to be moved at once)

- The new INSIM_VERSION is 9 (this uses the new size bytes)
- You can still connect using InSim 8 and LFS will use the old size bytes in that case

So that change, while simple in concept, may or may not be quite quick for you to implement, depending on how your InSim program is structured.


Skin ID for MOD vehicles in the CName fields:

You may notice the Skin ID of a mod has 6 characters. So how will this fit into a 3-byte CName? Well, the Skin ID is actually a 6 digit hexadecimal number (a 4 byte integer with zero in the high byte). So if a CName doesn't match any of the official cars, it must be a mod's Skin ID.

Some more logic relating to Skin ID:

If ALL THREE bytes of CName are ASCII A-Z, a-z, 0-9 then it must be an official car. The characters represent a plain text string. Mods are not issued with Skin ID where all three characters are ASCII A-Z, a-z, 0-9.

If not an official car, you can interpret the 4 bytes as an unsigned integer, which when displayed in hexadecimal, will be the mod's skin ID.

A note on byte order:

An official skin prefix has the form ABC0 (0 being the NULL terminator)

If this was reinterpreted as a Skin ID, A would be the low byte, B the middle byte and C the high byte of the 3 byte integer.

Here is a little C code that may explain things more clearly if you like C. Smile


CODE:

// FROM HEADER FILE

typedef const char *ccs;

inline int is_ascii_char(char c) // checks if character c is 0-9 / A-Z / a-z
{
if (c >= '0' && c <= '9') return 1;
if (c >= 'A' && c <= 'Z') return 1;
if (c >= 'a' && c <= 'z') return 1;
return 0;
}

int id_is_valid (unsigned int id);
int is_official_prefix (ccs prefix);
ccs expand_prefix (ccs prefix);
ccs contract_prefix (ccs prefix);


// FROM C FILE

char expanded_prefix [8];
char contracted_prefix [4];

int id_is_valid(unsigned id) // return 1 if the supplied id is valid as a mod's skin ID
{
if ((id & 0xff)==0 || // 1st char is zero
(id & 0xff00)==0 || // 2nd char is zero
(id & 0xff0000)==0 || // 3rd char is zero
id & 0xff000000) // 4th char is non-zero
{
return 0; // invalid
}

if (is_ascii_char(id & 0xff) &&
is_ascii_char((id & 0xff00) >> 8) &&
is_ascii_char((id & 0xff0000) >> 16))
{
return 0; // all 3 characters are 0-9 / A-Z / a-z -> reserve this id for official cars!
}

return 1; // ok
}

int is_official_prefix(ccs prefix)
{
return is_ascii_char(prefix[0]) && is_ascii_char(prefix[1]) && is_ascii_char(prefix[2]) && prefix[3]==0;
}

ccs expand_prefix(ccs prefix) // fill a static buffer "char expanded_prefix[8]" and return a pointer to it
{
memset(expanded_prefix, 0, 8);

if (prefix && prefix[3]==0) // valid prefix supplied
{
if (is_official_prefix(prefix))
{
*(unsigned *)expanded_prefix = *(unsigned *)prefix; // official car - direct copy
}
else // mod
{
sprintf(expanded_prefix, "%06X", *(unsigned *)prefix); // regard the prefix as an unsigned integer and expand it as a 6-digit hexadecimal string
}
}

return expanded_prefix;
}

ccs contract_prefix(ccs prefix) // fill a static buffer "char contracted_prefix[4]" and return a pointer to it
{
if (is_official_prefix(prefix)) // official car - direct copy
{
*(unsigned *)contracted_prefix = *(unsigned *)prefix;
}
else // mod - convert the 6-digit hexadecimal string into an unsigned integer
{
unsigned id;
if (sscanf(prefix, "%X", &id) && id_is_valid(id))
{
*(unsigned *)contracted_prefix = id;
}
else // not valid
{
*(unsigned *)contracted_prefix = 0;
}
}

return contracted_prefix;
}

Attached files
InSim.txt - 88.4 KB - 203 views
Hi, regarding CName:
does this mean that mod's skinID bytes (hex chars) can never be in range of alphanumeric (0x30-39, 0x41-5A, 0x61-7A)?
for example skinId 58525A would translate to "XRZ" and would be neither valid skinId neither an official car?
Hi, for a MOD they cannot *all* be alphanumeric.

If all three are alphanumeric, it may be an official car, or a *future* official car.

E.g.1: XRZ must be an official car we don't know about yet.

E.g.2: _A3 can't be an official car (one byte is not alphanumeric) so it must be mod Skin ID 33415F

EDIT: I noticed your (XRZ = 58525A) is the wrong way round. If XRZ was a mod ID (which it can't be, as all are alphanumeric) then it would be number 5A5258 (because of Intel byte order).
I guess those two won't work later then?
IS_PLC // PLayer Cars
IS_HCP // HandiCaPs

Possible solution would be to breakdown the hardcoded cars-section into an array so we can send parameters for any possible car/mod id. We'd have to send multiple packets then, depending on the number of installed mods, but at least it would be a way to do it.
Quote from Scawen :EDIT: I noticed your (XRZ = 58525A) is the wrong way round. If XRZ was a mod ID (which it can't be, as all are alphanumeric) then it would be number 5A5258 (because of Intel byte order).

Alright this seems pretty straightforward

Quote from chucknorris :Possible solution would be to breakdown the hardcoded cars-section into an array so we can send parameters for any possible car/mod id. We'd have to send multiple packets then, depending on the number of installed mods, but at least it would be a way to do it.

With the new packet sizes there should be more than enough space for sending allowed car list (4x120 bytes)
and HCP could have updated CarHCP struct with a car ID, totalling to 6x120 bytes

EDIT: if we could also sneak in a PLID somewhere in there and set those to each player individually, that would be even better! Big grin
Would it be possible to get the "nice" name of the mod car (RB4 GT T5 RESCUE~002 in this case) from server through inSim? Or at least a RET API connection to current mod list?

Also I don't remember server sending 2 consecutive NPL packets (one when "join" is clicked, and another on vehicle spawned)


is there a specific reason for that?
It could be possible to add something to get the full car name from the server but it'll be a while before that reaches high priority. I think Victor has set up something using http (or something - I'll ask him).

About two consecutive NPL - I guess you aren't using ISF_REQ_JOIN ? Does the first one appear as if it is a join request? I'm just looking for clues. I've got a lot to try and catch up on so it's hard for me to test each bug. Would it be possible to confirm if this is new and doesn't happen in the old version?
Quote from xspeedasx :Would it be possible to get the "nice" name of the mod car (RB4 GT T5 RESCUE~002 in this case) from server through inSim? Or at least a RET API connection to current mod list?

Yes! I've been working on a new RESTful API to cater for this and other, future functionality.
I'll be writing full documentation for this soon, but I'll write the highlights here. If you are familiar with Oauth2 authentication you should be able to knock something up fairly quickly.

First, register your application at https://www.lfs.net/account/api .
You can then request a Bearer token with your client ID and client secret at id.lfs.net. Quick example:

// Request a Bearer token by using the client_credentials grant type.
// This requires client_id and client_secret.
// One can simply fetch that by POSTing those values. See below for example.

$accessTokenUrl = 'https://id.lfs.net/oauth2/access_token';
$accessTokenPost = [
'grant_type' => 'client_credentials',
'client_id' => 'dfffniwufhnfr823nhr',
'client_secret' => '4n8rrrn3ycnf48ycf8ny4r',
];

$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($accessTokenPost),
'ignore_errors' => true,
]
]);
$result = file_get_contents($accessTokenUrl, false, $context);
if ($result)
{
$json = json_decode($result);
print_r($json);
}
else
{
var_dump($http_response_header);
var_dump($result);
}

With the obtained Bearer token, you can use our new API at https://api.lfs.net . Quick example:

$bearerToken = 'abcdefetc';
$apiUrl = 'https://api.lfs.net/vehiclemod';
$opts = array('http' =>
array(
'method' => 'GET',
'header' => 'Authorization: Bearer '.$bearerToken,
)
);

$result = file_get_contents(
$apiUrl,
false,
stream_context_create($opts)
);

if ($result)
{
header('Content-Type: application/json');
echo $result;
}
else
{
var_dump($http_response_header);
var_dump($result);
}

That gives you a list of all vehicle mods. The schema of a VehicleModSummary object:

{
"data": {
"id": string // Vehicle Mod identifier. AKA Skin ID, in HEX format
"name": string // Vehicle Mod name
"descriptionShort": string // Short description
"description": string // Description
"userId": int // Uploader user ID
"userName": string // Uploader user Name
"wip": boolean // Work In Progress
"publishedAt": int // Unixtimestamp of publish date
"numDownloads": int // Number of downloads
"curUsage": int // Number of racers using this mod online right now
"rating": float // Rating, from 0 - 5
"numRatings": int // Number of people who rated
"version": int // Vehicle Mod Version
"lastDownloadedAt": int // Unixtimestamp of last download date
"class": int // Vehicle class
"ev": boolean // Electric Vehicle if true
}
}
}

"class":
0: Object
1: Touring car
2: Saloon car
3: Buggy
4: Formula
5: GT
6: Kart
7: Bike
8: Van
9: Truck
10: Formula 1
11: Formula SAE

You can also get more detailed by performing a GET /vehiclemod/{id} which will return:
{
"data": {
"id": string // Vehicle Mod identifier. AKA Skin ID, in HEX format
"name": string // Vehicle Mod name
"descriptionShort": string // Short description
"description": string // Description
"userId": int // Uploader user ID
"userName": string // Uploader user Name
"wip": boolean // Work In Progress
"publishedAt": int // Unixtimestamp of publish date
"numDownloads": int // Number of downloads
"curUsage": int // Number of racers using this mod online right now
"rating": float // Rating, from 0 - 5
"numRatings": int // Number of people who rated
"version": int // Vehicle Mod Version
"lastDownloadedAt": int // Unixtimestamp of last download date
"class": int // Vehicle class
"ev": boolean // Electric Vehicle if true
"vehicle": {
"iceCc": int // ICE cc
"iceNumCylinders": int // ICE number of cylinders
"iceLayout": int // ICE engine layout
"evRedLine": float // EV redline
"drive": int // Drive
"shiftType": int // Shift type
"power": float // Power in kW
"maxPowerRpm": int // Max power at RPM
"torque": float // Torque in Nm
"maxTorqueRpm": int // Max torque at RPM
"mass": float // Total mass of vehicle in kg
"bhp": float // BHP
"powerWeightRatio": float // Power to weight ratio
"bhpTon": float // BHP per ton
"fuelTankSize": float // Fuel tank size. If "ev" = true, its unit is kWh, otherwise litres
}
}
}

"class":
0: Object
1: Touring car
2: Saloon car
3: Buggy
4: Formula
5: GT
6: Kart
7: Bike
8: Van
9: Truck
10: Formula 1
11: Formula SAE

"iceLayout":
0: inline
1: flat
2: V

"drive":
0: None
1: Rear wheel drive
2: Front wheel drive
3: All wheel drive

"shiftType":
0: None
1: H-pattern gearbox
2: Motorbike
3: Sequential
4: Sequential with ignition cut
5: Paddle
6: Electric motor
7: Centrifugal clutch

Hope that gets you going already.
well the html approach works but is kind of dirty (parsing html and doing xpath searches is quite slow).
I'm also wondering how paging will work when there will be more approved mods.

The NPL was my total oversight Big grin it was as you said - a Join request with numP=0 - I took parts of my old inSim and was building on top of that so I've missed that ISF_REQ_JOIN was set.
hmm but it's json? No xpath stuff needed.
Anyway, don't let us hijack the insim topic Smile

edit - oh maybe you mean you are now parsing a web page - yeah .. that's not ideal Smile
Quote from Victor :hmm but it's json? No xpath stuff needed.
Anyway, don't let us hijack the insim topic Smile

edit - oh maybe you mean you are now parsing a web page - yeah .. that's not ideal Smile

Sorry, didn't see your post about REST Smile I'll give it a try!
@the_angry_angle We're gonna need a bigger boat.
I was wondering if it would be helpful to send the actual car name along in InSim packets.

The server already can relate car name to Skin ID.

My suggestion is to either
1) make bigger version of existing packets, with full car names included as well as Skin ID.
or
2) provide simple packets to ask the host to convert between Skin ID and car name.

What the game server actually could do, for case (2) above:

- For any given car name could look up the Skin ID and return the Skin ID if that car name exists as an uploaded mod.
- For a given Skin ID, it could return the car name. But for this one it only knows the car names for Skin ID of players in the race.

Would any of that be helpful? It seems better to avoid web queries if possible.
Is option 1 instead of that CName to Hex convertion?
That whould be a cleaner solution indeed.
Quote from Scawen :I was wondering if it would be helpful to send the actual car name along in InSim packets.

The server already can relate car name to Skin ID.

My suggestion is to either
1) make bigger version of existing packets, with full car names included as well as Skin ID.
or
2) provide simple packets to ask the host to convert between Skin ID and car name.

What the game server actually could do, for case (2) above:

- For any given car name could look up the Skin ID and return the Skin ID if that car name exists as an uploaded mod.
- For a given Skin ID, it could return the car name. But for this one it only knows the car names for Skin ID of players in the race.

Would any of that be helpful? It seems better to avoid web queries if possible.

web queries are much slower than using direct TCP/UDP to the server (and also requires the OAuth flow/token management flow) so getting that data from lfs server would be ideal.

(Web API can still be used when integrating insim control/monitoring into websites)

* 1st option would be much easier for insim developers to quickstart their apps with human-readable car names (for leaderboards, car allowing ui options, other tasks that would require car names).

But on the other hand - it would increase bandwidth with (mostly) unnecessary data - if mod car names don't change it would be more efficient to store those values in a cache.

* which covers the 2nd option - it would be more efficient to request car names from server when they are actually needed (with a new IS_TINY probably) and can be cache them locally. But that requires having a more structured insim app, which most of the tinkerers don't bother to make

so that's that Big grin any other thoughts?

EDIT: In any case, it would be possible to implement caching/requesting into the library itself (thinking about InSim.NET) and return extended NPL, RES, etc. packets which would include actual car name (CFullName for example).
Quote from Bass-Driver :Is option 1 instead of that CName to Hex convertion?
That whould be a cleaner solution indeed.

No, the 3 bytes to ASCII representation would remain. Option (1) is about adding an extra 32 byte full car name field to IS_SLC, IS_NPL and IS_RES. xspeedasx put some good thoughts on that in the previous post.

But on your question:
I'm not sure if there is any real problem converting the 3-byte CName to a 6 character string.

In C it's really one line of code:
sprintf(expanded_prefix, "%06X", *(unsigned *)prefix);

There are a lot of ways to do it, but all we are doing is converting 3 bytes to 3 2-digit hexadecimal numbers. With all the complicated stuff you guys do, I don't imagine much trouble with this.
Can the mods be renamed?
in that case, getting the freshest name always would be more preferred than managing staleness of a cache or having a separate call.
Looks like the REST API will be the way to get info on the car's configuration and performance. Will it be possible to use the same API to get the same information about the standard cars, or will I need to look that up by hand and hardcode it in tables?
Quote from xspeedasx :Can the mods be renamed?
in that case, getting the freshest name always would be more preferred than managing staleness of a cache or having a separate call.

No, the mods can't be renamed after it's been created.
Quote from Victor :Yes! I've been working on a new RESTful API to cater for this and other, future functionality.

Are you also going to be moving the LFS World API to use OAuth?

Also, I love that PHP code example. Everyone uses curl and it's so very much not needed when the built in file_get_contents works so well (and doesn't leak memory like curl did.)
Longer term, yes I'd like to add stats and stuff to the API as well, so we have everything in one place.

And yep for simple stuff like this you don't need curl! Smile
Is "powerWightRatio" going to be permanent?

Also "rating" is float rather than int :-)
Fixed!

FGED GREDG RDFGDR GSFDG