#live #speed #insim #lfs #for

insim

Rust implementation of the Live For Speed InSim protocol

8 releases

✓ Uses Rust 2018 edition

0.4.2 Apr 28, 2019
0.4.0 Nov 5, 2018
0.3.2 Aug 19, 2018
0.3.1 Jul 22, 2018
0.1.0 Sep 18, 2017

#5 in Games

50 downloads per month

MIT license

221KB
5.5K SLoC

About InSim-Rust

This project aims to make a simple, clean implementation of the Live for Speed InSim game protocol. With this network-based protocol, applications can connect to LFS servers and control behavior or add new interactive features, changing the game experience.

With this API, you may now start writing a InSim application using the Rust programming language quickly with most technical aspects out of the way, and enjoy the safety and lighter runtime requirements that comes with Rust.

Features

  • Simple, self-explanatory API that feels high level by hiding most network code
  • TCP connection with support for secondary UDP channel (for IS_MCI and others)
  • Documentation for important parts of the crate
  • Complete build script which generates the serialization, structures and constants directly from the InSim file provided with LFS, with minimal patching
  • Good support for LFS's multi code pages conversion to and from UTF-8
  • Supports InSim version 8 (LFS 0.6T and 0.6U)
  • Good test coverage of the crate to ensure its stability
  • OutGauge is supported, but OutSim isn't (very simple to implement, look at the OutGauge code!)
  • Been used to run production InSims on servers, so good stability is proven

Usage

Here is a quick explanation on how insim-rs expects you to use the crate:

  • First, fill in a IS_ISI struct (in_sim_ver is not needed) and connect to the server with InSim::connect
    • Create any packet variable by filling in all the fields or by calling ::default() which sets everything to zero
    • To enable UDP, fill in the udp_port field. The InSim will use the same IP as your TCP connection
  • In your logic loop of choice, make calls to recv in order to parse packets that the server sends to your TCP connection
    • To read UDP packets, call recv_udp instead
    • By default, it will block until it receives something; use set_timeout to change this behavior
    • You also have to manually reply to TINY_NONE packets, unless you use the recv_keep_alive function which will do it for you
    • InSim implements Iterator for your convenience

Remarks:

  • Do not block in recv_udp! You won't be able to respond to TINY_NONE ping packets from LFS and you will be timed out
  • InSim is neither Send or Sync because of the use of network structures inside. You have roughly two ways to handle this:
    • Avoid blocking IO and keep the InSim running on your main thread
    • Spawn a new thread and connect the InSim in it, then use a channel to move packets to your main thread (packets are within a Packet enum which is thread-safe and free of lifetimes)
  • If you want to empty your UDP sockets from a very large pile of unprocessed packets, call flush_udp
  • Because of the use of a generator and its simplicity, it doesn't know which enums goes with each struct fields; this means you may have to cast some of the constants to the desired type (they default to u32).

Features

There's a feature named zero-req-i that you may enable for your crate. It detects ReqI struct fields that must always be set to zero. For those structs, the field will be removed by the generator. It however disables some older tests that wrongfully had this field set to one.

There's also a unrecommended feature named gen-in-sources. For users of IDEs like IntelliJ IDEA with the Rust plugin, you'll find that include! causes any included generated code to not be highlighted in your code. For this purpose, you may enable this feature to have insim-rs generate it in its source files instead. This feature isn't enabled by default because crates.io forbids this behavior. This basically bypasses the restriction to publish this crate online. Use with care.

An example

This code prints the number of cars on track and the IS_MCI's first car info in kph:

extern crate insim;

use std::time::Duration;
use insim::{InSim, Error, packets::*};

fn main() {
    let mut isi = IS_ISI::default();
    isi.udp_port = 48999;
    isi.flags |= ISF_MCI as u16;
    isi.interval = 500;
    isi.req_i = 1;
    isi.i_name = "RustTest";
    
    let mut insim = InSim::connect("127.0.0.1:12345", None, isi).unwrap().0;
    insim.set_timeout(Some(Duration::from_millis(100))).unwrap();
    
    loop {
        // Even though we use no packets from the TCP connection, this will automatically
        // send the keep-alive packets back to the game.
        let _ = match insim.recv_keep_alive() {
            Ok(_) | Err(Error::TimedOut) => (),
            _ => panic!("recv_keep_alive")
        };
        
        let mci = match insim.recv_udp() {
            Ok(Some(Packet::MCI(p))) => p,
            Ok(Some(_)) => continue,
            Err(Error::TimedOut) => continue,
            _ => panic!("recv_udp")
        };
        println!("Num cars: {}", mci.num_c);
        
        let speed = mci.info[0].speed as f32 * 0.01098632812;
        // 100.0 / 32768.0 * 60.0 * 60.0 / 1000.0
        println!("Speed (km/h): {}", speed as u32);
    }
}

FAQ

This is mostly empty, feel free to grow it up by making a PR!

  1. What does the IO error Failed to fill whole buffer mean? It usually indicates you got the admin password wrong.

Dependencies

~4MB
~140K SLoC