[][src]Crate cheats

cheats is a shell backend for games. Basically, it helps you invoke code with a provided string line.

The library is not yet production-ready. It has a very simple implementation of developer console and might lack some features you might desire.

Shell Grammar

There are many conventions about how to cheat. Grand Theft Auto series receive sequential keypresses and invokes functionality. Age of Empires II has a simple textbox to invoke a cheat but its cheats do not have any arguments.

In this library, cheats, the developer console of Valve games such as Half-Life, Counter-Strike, Portal, Left 4 Dead, has been an inspiration and it is implemented in such a way.

// this is a comment
cl_hello // without arg
cl_hello Eray # with arg

How to Use

First, you need to initialize a Shell instance.

use cheats::Shell;

let mut shell = Shell::new();

Registering/Unregistering A Code

In order to register a cheat code, first, you need to define what it will do upon invoking. There is a Invokable trait that you can implement to do that.

use std::io::Write;
use cheats::code::Invokable;

struct ClHello; // An empty struct to implement.

impl Invokable for ClHello {
    fn invoke(
        &self,
        args: &str, // args if given, can be an empty string
        mut stdout: Box<&mut dyn Write>, // stdout to write
        mut stderr: Box<&mut dyn Write>, // stderr to write
    ) {
        match args.is_empty() { // is `args` empty?
            true => { // if so...
                // write to `stderr` to inform that args were empty
                // you do not have to do that, it is given for demonstration purposes
                stderr
                    .write(b"Args are empty.")
                    .expect("Could not write to stderr.");
                // since no args were given, write "Hello, world!" to `stdout`
                stdout
                    .write(b"Hello, world!")
                    .expect("Could not write to stdout.");
            }
            false => { // if not...
                // build a message saying the name
                let msg: String = format!("Hello, {}!", args);
                // write `msg` to `stdout`
                stdout
                    .write(msg.as_bytes()) // mind `as_bytes`
                    .expect("Could not write to stdout.");
            }
        }
    }
}

Mind Invokable receives args as plain &str. Parsing of arguments is not handled by this library. Also, no args will result in an empty &str.

Now that you have an Invokable, you can register it:

// we need to box our Invokable struct
// that's because `register` method on `Shell` requires `Box<Invokable>`
let invokable = Box::new(ClHello);
shell.register("cl_hello", invokable).expect("Could not register the code.");

register method returns ShellError in case:

You can also unregister an existing code:

shell.unregister("cl_hello").expect("Could not unregister the code.");

unregister method returns ShellError::CodeDoesNotExist if, well, the code with given name is not registered before.

Filtering Codes

Naturally, you'd like to filter code names as the user types to your shell. Shell instance has a method named filter_names to help you filter codes. You need:

  • query: A query to filter code names.
  • starts_with: If true, filters code names using starts_with, else uses contains.
// assuming you have `cl_hello`, `sv_foo`, `sv_foobar`

let sv_codes: Vec<&str> = shell.filter_names("sv", true).collect();
assert_eq!(sv_codes, ["sv_foo", "sv_foobar"]);

let foo_codes: Vec<&str> = shell.filter_names("foo", false).collect();
assert_eq!(foo_codes, ["sv_foo", "sv_foobar"]),

While, in this case, the Vec of code names are ordered, it might not be in larger examples. In this case, you can sort a Vec by using sort on it.

let sv_codes: Vec<&str> = shell.filter_names("sv", true).collect();
sv_codes.sort();

Note that filter_names method actually returns an Iterator, which, then, you can collect into a Vec<&str>.

Running Script

You can run a cheat code line by doing:

shell.run("cl_hello").expect("Could not run the code.");
shell.run("cl_hello Eray").expect("Could not run the code.");

Running a single line is cool but consider loading a script in runtime. You can pass a file content to run method. An example:

use std::fs;

// read the file
// it does not have to have .script extension, this is just an example
let content: String = fs::read_to_string("path/to/file.script")
    .expect("Could not read the file.");
// convert from String to &str
let content_str: &content[..];
// run
shell.run(content_str).expect("Could not run the code.");

Reading Output

Of course, a shell is nothing without output. Shell has two attributes:

  • stdout: Standard output.
  • stderr: Standard output for errors.

These attributes are actually a custom ReadWrite trait objects, which means you can read from or write to them.

You usually would like to write to these channels while in Invokable trait's invoke method because these are referenced in there so that you can use them.

You can read from stdout or stderr as below:

// you can do the same with `stderr`
let output: String = {
    let ref mut stdout = shell.stdout; // take a reference to stdout
    let mut stdout_bytes: Vec<u8> = vec![]; // create a vector buffer for bytes
    stdout
        .read_to_end(&mut stdout_bytes) // read until the end
        .expect("Could not read stdout.");
    String::from_iter(stdout_bytes.into_iter().map(|b| b as char)) // map u8 bytes to char
};

Modules

code

Structs

Shell

A shell for a game.

Enums

ShellError

Traits

ReadWrite

Type Definitions

ShellResult