2025-03-31 23:33:07 -06:00
2025-03-31 23:33:07 -06:00
2025-03-04 23:14:00 -07:00
2024-11-22 00:11:48 -07:00
2021-08-25 14:54:52 -04:00
2021-08-25 14:54:52 -04:00
2025-03-31 23:33:07 -06:00

nod Build Status Latest Version Api Rustdoc Rust Version

Library for reading and writing Nintendo Optical Disc (GameCube and Wii) images.

Originally based on the C++ library nod, but with extended format support and many additional features.

Currently supported file formats:

  • ISO (GCM)
  • WIA / RVZ
  • WBFS (+ NKit 2 lossless)
  • CISO (+ NKit 2 lossless)
  • NFS (Wii U VC, read-only)
  • GCZ
  • TGC

CLI tool

This crate includes a command-line tool called nodtool.

Download the latest release from the releases page, or install it using Cargo:

cargo install --locked nodtool

info

Displays information about a disc image.

nodtool info /path/to/game.iso

extract

Extracts the contents of a disc image to a directory.

nodtool extract /path/to/game.iso [outdir]

For Wii U VC titles, use content/hif_000000.nfs:

nodtool extract /path/to/game/content/hif_000000.nfs [outdir]

convert

Converts a disc image to any supported format.

See nodtool convert --help for more information.

nodtool convert /path/to/game.iso /path/to/game.rvz

verify

Verifies a disc image against an internal Redump database.

nodtool verify /path/to/game.iso

Library example

Opening a disc image and reading a file:

use std::io::Read;

use nod::{
    common::PartitionKind,
    read::{DiscOptions, DiscReader, PartitionOptions},
};

// Open a disc image and the first data partition.
let disc =
    DiscReader::new("path/to/file.iso", &DiscOptions::default()).expect("Failed to open disc");
let mut partition = disc
    .open_partition_kind(PartitionKind::Data, &PartitionOptions::default())
    .expect("Failed to open data partition");

// Read partition metadata and the file system table.
let meta = partition.meta().expect("Failed to read partition metadata");
let fst = meta.fst().expect("File system table is invalid");

// Find a file by path and read it into a string.
if let Some((_, node)) = fst.find("/MP3/Worlds.txt") {
    let mut s = String::new();
    partition
        .open_file(node)
        .expect("Failed to open file stream")
        .read_to_string(&mut s)
        .expect("Failed to read file");
    println!("{}", s);
}

Converting a disc image to raw ISO:

use nod::read::{DiscOptions, DiscReader, PartitionEncryption};

let options = DiscOptions {
    partition_encryption: PartitionEncryption::Original,
    // Use 4 threads to preload data as the disc is read. This can speed up sequential reads,
    // especially when the disc image format uses compression.
    preloader_threads: 4,
};
// Open a disc image.
let mut disc = DiscReader::new("path/to/file.rvz", &options).expect("Failed to open disc");

// Create a new output file.
let mut out = std::fs::File::create("output.iso").expect("Failed to create output file");
// Read directly from the DiscReader and write to the output file.
// NOTE: Any copy method that accepts `Read` and `Write` can be used here,
// such as `std::io::copy`. This example utilizes `BufRead` for efficiency,
// since `DiscReader` has its own internal buffer.
nod::util::buf_copy(&mut disc, &mut out).expect("Failed to write data");

Converting a disc image to RVZ:

use std::fs::File;
use std::io::{Seek, Write};
use nod::common::{Compression, Format};
use nod::read::{DiscOptions, DiscReader, PartitionEncryption};
use nod::write::{DiscWriter, DiscWriterWeight, FormatOptions, ProcessOptions};

let open_options = DiscOptions {
    partition_encryption: PartitionEncryption::Original,
    // Use 4 threads to preload data as the disc is read. This can speed up sequential reads,
    // especially when the disc image format uses compression.
    preloader_threads: 4,
};
// Open a disc image.
let disc = DiscReader::new("path/to/file.iso", &open_options)
    .expect("Failed to open disc");
// Create a new output file.
let mut output_file = File::create("output.rvz")
    .expect("Failed to create output file");

let options = FormatOptions {
    format: Format::Rvz,
    compression: Compression::Zstandard(19),
    block_size: Format::Rvz.default_block_size(),
};
// Create a disc writer with the desired output format.
let mut writer = DiscWriter::new(disc, &options)
    .expect("Failed to create writer");

// Ideally we'd base this on the actual number of CPUs available.
// This is just an example.
let num_threads = match writer.weight() {
    DiscWriterWeight::Light => 0,
    DiscWriterWeight::Medium => 4,
    DiscWriterWeight::Heavy => 12,
};
let process_options = ProcessOptions {
    processor_threads: num_threads,
    // Enable checksum calculation for the _original_ disc data.
    // Digests will be stored in the output file for verification, if supported.
    // They will also be returned in the finalization result.
    digest_crc32: true,
    digest_md5: false, // MD5 is slow, skip it
    digest_sha1: true,
    digest_xxh64: true,
};
// Start processing the disc image.
let finalization = writer.process(
    |data, _progress, _total| {
        output_file.write_all(data.as_ref())?;
        // One could display progress here, if desired.
        Ok(())
    },
    &process_options
)
.expect("Failed to process disc image");

// Some disc writers calculate data during processing.
// If the finalization returns header data, seek to the beginning of the file and write it.
if !finalization.header.is_empty() {
    output_file.rewind()
        .expect("Failed to seek");
    output_file.write_all(finalization.header.as_ref())
        .expect("Failed to write header");
}
output_file.flush().expect("Failed to flush output file");

// Display the calculated digests.
println!("CRC32: {:08X}", finalization.crc32.unwrap());
// ...

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Description
Rust crate for reading GameCube and Wii disc images
Readme 2.2 MiB
Languages
Rust 100%