Xbox + Rust

Created:

In my last blog post, I uncovered my old Xbox. Briefly, I mentioned my desire to create a demo for the console. I can say I'm now working towards that goal. But, I couldn't make it easy though.

NXDK

The New Xbox Development Kit (NXDK) aims to build an open-source SDK for the original Xbox. This allows new games to be created for the console without using Microsoft's proprietary SDK.

The project is currently being developed in C. This proves difficult, given that I'm a die hard Rust fanatic, I rather not learn C nor make a project with it. This corundum guided me towards one solution: Rust bindings for NXDK.

Rust Bindings

Surely, someone else smarter than me has created bindings, right? Sadly, this is only kind of the case. GitHub user antangelo started this endeavor a bit more than two years ago in 2022. They were able to get a simple hello world program up and running. However, they didn't get much further than this. A majority of the SDK remains unbinded.

This presents two options: either use the fully ready to use C/C++ SDK, or continue development on the Rust bindings. You probably can guess which plan I choose to go with.

Rust has a trick up it's sleeve. There's a macro that takes a C header file and creates unsafe functions out of them. These can be used to implement a more idiomatic Rust API built on the unsafe C functions. For example, this is what the following C function would get converted to:

int addOne(int value) {
    return value + 1;
}
unsafe fn addOne(value: i32) -> i32 {}

Importantly, this isn't converting the actual underlying code of the functions. This just allows you Rust code to interact with the C functions. You would then want to create a safe function that grantees safety. In this case it would be really simple.

fn add_one(value: i32) -> i32 {
    let ret;

    unsafe {
        ret = addOne(value);
    }

    ret
}

Forking The Bindings

I created a fork in order to start implementing new functions. My first goal was the Hardware Abstraction Layer (HAL). This includes very low-level functions such as debug printout in the terminal. Debug print and video initialization functions where already setup.

The first header I decided to implement was the LED functions. There are only two of them. Focusing on just one of them, here you can see a real example of implementing a safe API for Rust. The C function takes 4 colors which are actually just signed 32-bit integers. This can seem misleading though. The colors green, orange, red, and off are represented by the numbers 0-3. So every other input is undefined behavior, something Rust programers frown upon. In order to resolve this undefined behavior, I created an enum. While it just represents a signed 32-bit integer, the Rust API has no way of giving unexpected inputs, resolving the undefined behavior.

use nxdk_sys::hal::led::*;

#[derive(Debug, Default)]
enum LEDColor {
    #[default]
    Off = _XLEDColor_XLED_OFF as isize,
    Green = _XLEDColor_XLED_GREEN as isize,
    Red = _XLEDColor_XLED_RED as isize,
    Orange = _XLEDColor_XLED_ORANGE as isize,
}

fn xset_custom_led(color1: LEDColor, color2: LEDColor, color3: LEDColor, color4: LEDColor) {
    unsafe {
        XSetCustomLED(color1 as i32, color2 as i32, color3 as i32, color4 as i32);
    }
}

As a sanity check, I implemented some of the debug functions to confirm I was doing everything correctly. I was able to confirm the debug_print_binary function in XEMU.

use nxdk_rs::hal::{video::*, debug::*};

let msg = format!("Hello from {}!\n", "Rust");

xvideo_set_mode(640, 480, 32, RefreshRate::Default);
debug_print_str(&msg);

debug_print_binary(0b1010);

The Xbox's terminal output with two lines of text. The first reads "Hello from Rust!". The second has seven groups of four zeros followed by a group of one-zero-one-zero.

I've continued this process to other HAL headers, but many are still unimplemented. Anyways, the HAL isn't the only important code that needs Rust bindings. SDL2 is included in the SDK. It's by far the most complicated and important piece of it. SDL opens up the ability to create 3D/hardware-accelerated graphics with DirectX. Also, it handles user input and many other aspects important to a game engine. Without it, making a video game—or demo—becomes very difficult.

What's Next

I'm still not the most knowledgeable on this topic. I'll need to look more into if it's possible to use the SDL2 crate or if I'll need to recreate the bindings myself.

Currently, I'm going through the process of upgrading the 8GB HDD to a 1TB SDD. This is proving harder than expected, so who knows when there will be a post up on that. If I get it working, expect to see a post about it. In the process of that, I also got FTP setup, but that's for another time.

Another side project I'm working on is fixing my old broken controllers. Many of the cables went bad. I just desoldered the cables and am waiting for replacements to be shipped. I should be able to start working on them within the next two weeks or so.