Extracting an ISO from CUE/BIN

Recently, I felt like playing one of my old childhood favorites - Nerf Arena Blast. I found a copy in CUE/BIN format, and instead of downloading Ye Olde Alcohol 120% or something to handle it, I decided to figure out how to turn it into a mountable ISO myself. Turns out it's pretty simple! The key understanding for me was that I didn't have to set up the ISO filesystem from the binary myself. Instead, it already existed within a track of the binary, and I merely needed to extract the correct bytes. Here's how...

First, let's try renaming the .bin to .iso. This results in a file corrupted error when trying to mount it. The obvious next step is to look at the CUE:

FILE "ARENABLAST.bin" BINARY
    TRACK 01 MODE1/2352
        INDEX 01 00:00:00
    TRACK 02 AUDIO
        INDEX 01 34:53:13
    TRACK 03 AUDIO
        INDEX 01 39:44:31
    TRACK 04 AUDIO
        INDEX 01 44:13:13
    TRACK 05 AUDIO
        INDEX 01 49:17:03
    TRACK 06 AUDIO
        INDEX 01 55:09:74
    TRACK 07 AUDIO
        INDEX 01 60:31:57

Clearly this describes the overall layout of the binary, and a trip to Wikipedia confirms this:

The .bin files are raw sector-by-sector binary copies of tracks in the original discs. These binary .bin files usually contain all 2,352 bytes from each sector in an optical disc, including control headers and error correction data in the case of CD-ROMs (unlike ISO images of CD-ROMs, which store only the user data). However, the TRACK command in a cue sheet file can be used to refer to binary disc images that contain only the user data of each sector, by indicating the specific CD mode of the tracks from which the image was created (which is necessary to know the size of the user data in each sector).

The binary's size of 704,567,472 bytes cleanly divides by 2,352, so that seems to make sense. Then, some more detail about the tracks:

A CD can have multiple tracks, which can contain computer data, audio, or video. File systems such as ISO 9660 are stored inside one of these tracks. Since ISO images are expected to contain a binary copy of the file system and its contents, there is no concept of a "track" inside an ISO image, since a track is a container for the contents of an ISO image. This means that CDs with multiple tracks can't be stored inside a single ISO image; at most, an ISO image will contain the data inside one of those multiple tracks, and only if it is stored inside a standard file system.

So at this point, I think I need the "user data" from the first track. The mode (MODE1/2352) describes how that track is structured:

CD-ROM Mode 1: 12 (Sync pattern) 3 (Address) 1 (Mode, 0x01) 2,048 (Data) 4 (Error detection) 8 (Reserved, zero) 276 (Error correction)

This seems like enough to get started!

let mut file = fs::read("ARENABLAST.bin").unwrap();

This returns a Vec<u8> containing the entire file. However, I only need the bytes of the first track. Presumably it starts at byte 0, but what byte does it end at? Looking at the CUE again:

TRACK 01 MODE1/2352
    INDEX 01 00:00:00
TRACK 02 AUDIO
    INDEX 01 34:53:13

Track 2 starts at 34:53:13, but isn't that a timestamp? Sort of!

INDEX Indicates an index (position) within the current FILE. The position is specified in mm:ss:ff (minute-second-frame) format. There are 75 such frames per second of audio. In the context of cue sheets "frames" refer to CD sectors, despite a different, lower-level structure in CDs also being known as frames.

So 34:53:13 is track 2's starting sector (2,352 byte block). Converting it out:

(34 min * 60 sec/min * 75 frames/sec) 
+ (53 sec * 75 frames/sec) 
+ (13 frames) 
= 156988

Which means that track 1 must end at sector 156987.

file.truncate(156987 * 2352);

Then I need the user data from the remaining sectors.

let sectors = file.chunks(2352);

This gives me an iterator over the remaining bytes a sector at a time, which is easier than iterating over every byte. As the mode structure shows, there's 16 bytes of header information, then the following 2,048 bytes are the user data I'm looking for. Let's copy those to a new file.

let mut out_file = File::create("ARENABLAST.iso").unwrap();
for sector in sectors {
    out_file.write_all(&sector[16..2064]).unwrap();
}

Moment of truth...

mounting

success!

It worked!