InSim

Note: this is a work in progress

InSim is a protocol that allows an external program to communicate with Live for Speed. It allows you to create a socket connection with the game and to send and receive packets of data. These packets can be used to control LFS, send various commands, and request information. InSim.NET is an InSim library written for the .NET framework that prevents you from having to deal with sockets and packets of bytes directly. It is designed to be simple to use and to as close to InSim as possible, as well as being fast and flexible.

These tutorials are provided in the C# programming language, although InSim.NET supports all .NET languages, including dynamic languages such as IronPython.

Hello, InSim!

Let's cut to chase and show some code! This is the simplest InSim program, that simply sends the message 'Hello, InSim!' to the game chat.

using System;
using InSimDotNet;
using InSimDotNet.Packets;

class Program {
    static void Main() {
        // Create new InSim object
        InSim insim = new InSim();

        // Initailize InSim
        insim.Initialize(new InSimSettings {
            Host = "127.0.0.1", // Host where LFS is runing
            Port = 29999, // Port to connect to LFS through
            Admin = String.Empty, // Optional game admin password
        });

        // Send message to LFS
        insim.Send("/msg Hello, InSim!");

        // Stop program from exiting
        Console.ReadLine();
    }
}

This simple example creates an InSim object, initializes the InSim system (with the specified settings), then sends the message 'Hello, InSim!' to appear in the game chat. Lastly, as InSim.NET uses a background thread to manage the connection with LFS, we need to stop the program from exiting too early and closing our thread, so in this instance we add a call to Console.ReadKey() to tell it to wait until the user closes the program.

In our previous example we used the InSim.Send(string) conveince method to send a message packet to LFS, however we can create and send the packet ourselves. Let's do that next!

InSim insim = new InSim();

insim.Initialize(new InSimSettings {
    Host = "127.0.0.1",
    Port = 29999,
    Admin = String.Empty,
});

// Create message packet
IS_MST mst = new IS_MST();
mst.Msg = "/msg Hello, InSim!"; // Set message contents

// Send message packet to LFS
insim.Send(mst);

Console.ReadLine();

We create an IS_MST (MeSage Type) packet object, set the message 'Hello, InSim!', then send the packet to LFS. There are lots of different packets we can send and we'll see plenty more later. Exactly how you go about constructing packets is up to you, but the following is an example of the syntax preferered by me, and what I'll continue to use for the rest of this tutorial.

//  A single line for simple packets
insim.Send(new IS_MST { Msg = "/msg Hello, InSim!" });

// Or spread over multiple lines for more complex ones
insim.Send(new IS_TINY { 
    ReqI = 255,
    SubT = TinyType.TINY_NCN,
});

Heavy abuse of C# 3.0's object initializers to some, no-doubt! All this of course is a matter of taste, and as always the most important thing is not what style you use, but rather that you pick one and stick to it!

Receiving Packets

We've seen how to send packets, but how do we go about receiving them? InSim.NET uses an event driven model for packets sent by LFS, that allows you to specify a method to be called when a specific packet is received. Let's look at an example that shows how to receive the IS_MSO (MeSsage Out) packet and output the message content to the screen.

static void Main() {
    InSim insim = new InSim();

    // Bind handler for IS_MSO packet event.
    insim.Bind<IS_MSO>(MessageOut);

    insim.Initialize(new InSimSettings {
        Host = "127.0.0.1",
        Port = 29999,
        Admin = String.Empty,
    });

    Console.ReadLine();
}

// Method called whenever an IS_MSO packet is received.
static void MessageOut(InSim insim, IS_MSO mso) {
    // Print out the message content.
    Console.WriteLine(mso.Msg);
}

We use the InSim.Bind<TPacket>(Action<InSim, TPacket>) method to bind a callback for the IS_MSO packet. Now whenever an IS_MSO packet is received it will be assembled and passed to the bound method. The first parameter of the handler is a reference to the InSim instance that received the packet, which is useful if you have multiple InSim connections and need to tell them apart. It is also possible to bind multiple handlers for the same packet, so for instance the following is perfectly acceptable:

static void Main() {
    using (InSim insim = new InSim()) {
        insim.Bind<IS_MSO>(MessageOut1);
        insim.Bind<IS_MSO>(MessageOut2);
        insim.Bind<IS_MSO>(MessageOut3);
    }
}

static void MessageOut1(InSim insim, IS_MSO mso) { }
static void MessageOut2(InSim insim, IS_MSO mso) { }
static void MessageOut3(InSim insim, IS_MSO mso) { }

Each handler will be called in turn, in the order they were first bound. You can also unbind handlers, if you are done with a packet.

insim.Unbind<IS_MSO>(MessageOut);

Or check to see if a handler has already been bound.

if (insim.IsBound<IS_MSO>(MessageOut)) {
    // Handler has been bound.
}

There is also an alternative method for binding packets, that allows you to specify the type of packet from the PacketType enumeration.

insim.Bind(PacketType.ISP_MSO, MessageOut);

This was added to allow languages which don't support generics, such as IronPython, to bind packet callbacks. Either way of binding handlers is fine, although the first way is the one generally used most often.

In addition to binding individual handlers, it is also possible to attach an event-handler to the InSim.PacketReceived event, which is raised every time a packet is received.

static void Main() {
    using (InSim insim = new InSim()) {
        insim.PacketReceived += insim_PacketReceived;
    }
}

static void insim_PacketReceived(object sender, PacketEventArgs e) {
    if (e.Packet.Type == PacketType.ISP_MSO) {
        IS_MSO mso = (IS_MSO)e.Packet;
    }
}

The PacketReceived event is raised before the individual bindings are called and you can cancel any bindings from being raised for that packet by setting the PacketEventArgs.IsHandled boolean to true. Receiving packets in this way is useful for debugging or if you want apply an operation to all packets, however it is not recommended for most practices due to being much less efficient that using individual bindings*.

* The reason for this is an implementation detail, as when using individual packet bindings the InSim.NET networking code is smart enough to discard any packets which are not needed, thereby saving resources. However when an event-handler is attached to the PacketReceived event, all packets are constructed whether they are needed or not. As in all things programming you should do whatever seems easiest and if you find you have a performance problem later on you can come back and fix it. Frankly decoding packets is stupid-fast anyway.

I mentioned debugging before, as with the PacketReceived event and a little reflection you can get a pretty good idea of what LFS is sending you.

static void Main() {
    using (InSim insim = new InSim()) {
        insim.PacketReceived += insim_PacketReceived;

        insim.Initialize(new InSimSettings {
            Host = "127.0.0.1",
            Port = 29999,
            Admin = String.Empty,
        });

        Console.ReadLine();
    }
}

static void insim_PacketReceived(object sender, PacketEventArgs e) {
    // Use reflection to dump the packet values to the output.
    foreach (var property in e.Packet.GetType().GetProperties()) {
        Console.WriteLine("{0}: {1}", property.Name, property.GetValue(e.Packet, null));
    }
    Console.WriteLine();
}

We can simply use a touch of reflection to write the value of each packet property to the console. Think of it as a basic InSimSniffer, in twenty lines of code. Incidentally this is pretty much all InSimSniffer does anyway. ;p

Car Updates

Of course no InSim library would be worth its salt without support for IS_MCI and IS_NLP car position updates. To enable these you must specify the correct options when initializing InSim.

static void Main() {
    using (InSim insim = new InSim()) {
        // Bind event for MCI update
        insim.Bind<IS_MCI>(MultiCarInfo);

        insim.Initialize(new InSimSettings {
            Host = "127.0.0.1",
            Port = 29999,
            Admin = String.Empty,
            Flags = InSimFlags.ISF_MCI, // Enable MCI updates
            Interval = 1000, // The interval at which updates are sent (milliseconds)
        });

        Console.ReadLine();
    }
}

static void MultiCarInfo(InSim insim, IS_MCI mci) {
    // Loop through each car in the packet
    foreach (CompCar car in mci.Info) {
        // Handle car.
    }
}

Each IS_MCI packet contains up to eight cars, if more than eight cars are on track then multiple IS_MCI packets are sent. It is also possible to specify a separate UDP port for incoming MCI and NLP packets, but more on that below.

InSim and UDP

In our previous examples we have been using InSim.NET in TCP mode, but the library also supports UDP connections with LFS. In general TCP should be preferred, as it's more reliable than UDP and also supports more connections (up to eight in TCP mode, as opposed to UDP's measly one). However if you do need to connect to InSim using UDP, the library does support it.

The following code shows a reworking of the basic 'Hello, InSim!' example, but that specifies a UDP socket for use with LFS instead of the default TCP.

using (InSim insim = new InSim()) {
    // Set socket as UDP.
    insim.Socket = new UdpSocket();

    insim.Initialize(new InSimSettings {
        Host = "127.0.0.1",
        Port = 29999,
        Admin = String.Empty,
    });

    insim.Send("/msg Hello, InSim!");

    Console.ReadLine();
}

As you can see, simply by setting the InSim.Socket property to an instance UdpSocket, we can create a UDP connection with LFS. Whether connecting to InSim using UDP or TCP, it's possible to specify a separate UDP port for car position updates. This is useful as a performance enhancement, as even though UDP is unreliable for many tasks, when dealing with car updates reliability is not a high concern, as if a packet is dropped another one will be along in a few milliseconds. Let's have a look at an example of using UDP for receiving IS_NLP NodeLap updates.

static void Main() {
    using (InSim insim = new InSim()) {

        // Bind NLP packet handler.
        insim.Bind<IS_NLP>(NodeLap);

        insim.Initialize(new InSimSettings {
            Host = "127.0.0.1",
            Port = 29999,
            Admin = String.Empty,
            UdpPort = 30000, // Initialize UDP to port 30000
            Flags = InSimFlags.ISF_NLP, // Enable NLP updates
            Interval = 1000, // Set update interval to one second
        });

        Console.ReadLine();
    }
}

static void NodeLap(InSim insim, IS_NLP nlp) {
    foreach (NodeLap lap in nlp.Info) {
        // Handle NodeLap.
    }
}

In this example we use the default TCP connection for InSim updates, but specify UDP port 30000 for IS_NLP. Internally InSim.NET will initialize a separate UDP socket to handle these packets, but externally both TCP and UDP should work together seamlessly.

Handling Errors

InSim.NET uses a background thread to handle sending and receiving packets, which means that any exceptions that occur will not be detected by your program's main thread. To allow for that InSim.NET catches any background exceptions and raises a InSim.InSimError event, containing the exception object. As packet events are raised from the background thread, this means that any exceptions that occur in the packet-handlers will only be caught by InSimError, and not by the .NET Framework runtime (which is what you'd normally expect).

In the following example we demonstrate how errors in InSim.NET work by throwing our own Exception when the IS_VER version packet is received (VER is sent when InSim is first initialized). As the Version packet handler was called from the background receive thread this exception will not be caught for us by the .NET Framework, instead we need to hook up an event to InSim.InSimError which notifies us of any background exceptions.

using System;
using InSimDotNet;
using InSimDotNet.Packets;

class Program {
    static void Main() {
        InSim insim = new InSim();
        insim.Bind<IS_VER>(Version);

        // Hook event-handler to InSimError event.
        insim.InSimError += insim_InSimError;

        insim.Initialize(new InSimSettings {
            Host = "127.0.0.1",
            Port = 29999,
            Admin = String.Empty,
        });

        Console.ReadLine();
    }

    static void Version(InSim insim, IS_VER ver) {
        // Throw an exception on the background thread.
        throw new Exception("this exception has occured on the background " +
                            "thread and so will be caught by InSimError");
    }

    static void insim_InSimError(object sender, InSimErrorEventArgs e) {
        // When the above exception is thrown, it will be picked up by the 
        // InSim.InSimError event.
        Console.WriteLine("InSim Error: {0}", e.Exception.Message);
    }
}

Basically what this means is that you should always hook up an event to InSim.InSimError to catch and log any exceptions that occur. If you find your program closing for an unknown reason, check to make sure that a background exception is not being thown.

When an exception is caught on the background thread InSim.NET disconnects from LFS. The reason for this is that once an exception has occurred it is no longer possible to make any assumption about the state of the background thread, and the best course of action is to allow it to terminate ungracefully.

As well as raising the InSim.InSimError event, InSim.NET also writes the stacktrace for the exception to the Debug Output window in Visual Studio.

Helpers

Using PacketFactory to make your own packets

Last edited Mar 31, 2014 at 8:41 PM by AlexMcBride, version 10

Comments

No comments yet.