The last post was nothing more than an introduction to the project. In this post we get down to some initial coding, although it’s not really anything N64 specific. We focus on command line parsing and loading roms.
The Command Line
So what needs to be provided through the command line is two things: the PIF ROM and the cartridge ROM (in the future we might just emulate the PIF ROM as well). These are just binary blobs which needs to be read into memory and used later at runtime. We’ll come back to what roles they play in the Nintento 64 ecosystem in the next part.
I’m thinking the command line should be something like rustn64 --pif mypif.bin myrom.z64
and there are a few ways to parse the command line. You could simply read the raw arguments from the command line using std::argv. This is perfectly fine but usually you want to have some validation. What if there are to few arguments? What if the argument is of the wrong format? Should we be able to handle flags vs. values?
On *nix there is getopt but first, it’s a C library which means we have to interface it through Rust and second, it’s not available on Windows of course. Note that there are crates which already have implemented the interface to getopt in Rust.
There are a number of crates for parsing the command line and the option I’ve gone for is to use the clap crate. In src/main.rs
we find the snippet that reads the command line:
Here we specify the application’s name (RustN64), the version (using a neat trick so that by a macro, the version gets picked up at compile time to match the version in Cargo.toml), author and a short description. Then the two arguments are specified where the first one is the PIF file and the second the game cart ROM.
Besides the fact that we get a convenient way of specifying the command line arguments, we also get a help flag for free:
… and a version flag:
Reading Files in Rust
I also wanted to show a pretty nice way on how files are read in Rust. Basically most of the file operations return an std::io:Result
(std::io::File::open
for instance) which takes two generic arguments - the first is the result of the operation and the second is an std::io::Error
in case of failure. Note that there is no such thing as exceptions in Rust, you typically return multiple values instead.
The Result type can chain operations together and short circuit if an error occurs. This is what the read_bin()
method looks like in src/main.rs
:
If an error were to occur in either File::open()
or File::read_to_end
we’d have the same error handler in the match expression. Pretty sweet!
What more is that you can use the try!
macro which expands to a match statement that returns early if an error occurs. The code above could be written as follows:
This makes for really concise file handling while still maintaining fault tolerance.
Next Part
In the next part we’ll finally get to some more interesting stuff where we’ll have a look at how we will model the CPU. Hopefully we’ll be able to execute the PIF ROM.