#reverse-engineering #halo #game-hacking #halo-infinite

infinite-rs

Simple and fast deserialization library for Halo Infinite

17 releases (6 breaking)

new 0.7.1 Dec 13, 2024
0.6.2 Dec 10, 2024
0.6.0 Nov 29, 2024

#210 in Encoding

Download history 158/week @ 2024-08-23 116/week @ 2024-08-30 238/week @ 2024-09-06 49/week @ 2024-09-13 12/week @ 2024-09-20 8/week @ 2024-09-27 3/week @ 2024-10-04 2/week @ 2024-10-11 269/week @ 2024-10-18 29/week @ 2024-10-25 5/week @ 2024-11-01 2/week @ 2024-11-08 646/week @ 2024-11-15 333/week @ 2024-11-22 252/week @ 2024-11-29 354/week @ 2024-12-06

1,585 downloads per month

Unlicense OR MIT

485KB
9K SLoC

C++ 8K SLoC // 0.2% comments Rust 1.5K SLoC // 0.1% comments Shell 16 SLoC Batch 3 SLoC

infinite-rs

Simple and fast deserialization library for Halo Infinite.

This crate currently is in early-development. Please let me know via Github issues about any issues you encounter using this project.

Crates.io Documentation License

Documentation

  • Documentation on this project can be found at docs.rs.

Examples/Usage

Getting Started: Loading a Module file

Modules are the file format that store "tags" in Halo Infinite. These files are used to store all the assets in the game, including models, textures, metadata, and more. infinite-rs provides a simple interface to load these tags, starting with loading the module files themselves.

use infinite_rs::{ModuleFile, Result};

fn load_modules() -> Result<()> {
    // Reads to the module file given a file path.
    // These are the main archive files used in Halo Infinite.
    // Note: the path can be anything that implements AsRef<Path>.
    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;
    Ok(())
}

Loading a tag file

After we have loaded a module file, we can now use the read_tag function to load a specific tag by index from the module file. This populates the data_stream and tag_info properties in a module entry that we can use later.

The read_tag_from_id function is also available to load a tag by its global ID, returning the index in which it was found in the module file.

use infinite_rs::{ModuleFile, Result};

fn load_tags() -> Result<()> {
    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;

    // Load a specific tag from the module file.
    let tag_index = 0;
    let id = module.read_tag(tag_index)?;
    // We can now access the data stream and tag info.
    if id.is_some() {
        let tag_data = module.files[tag_index as usize].data_stream.as_ref().unwrap();
        let tag_info = module.files[tag_index as usize].tag_info.as_ref().unwrap();
    }
    Ok(())
}

Creating a custom structure and reading it

infinite-rs also allows you to read data directly into structures, using the read_metadata function. This functionality requires the derive feature.

Defining Structures

To define a structure that can be read from a tag data stream, you must first derive the TagStructure trait. To ensure proper padding and alignment, you can use the data attribute to specify the size of the structure in bytes. Each field also must contain a data attribute specifying the offset in bytes from the start of the structure.

[!TIP] Padding between fields are automatically calculated. Any data between two offsets are skipped.

use infinite_rs_derive::TagStructure;
use infinite_rs::tag::types::common_types::{
    AnyTag, FieldReference,
};

#[derive(Default, Debug, TagStructure)]
#[data(size(0x88))] // Size can be any u64 value.
struct MaterialTag {
    #[data(offset(0x00))] // Offset can be any u64 value within the range of the size.
    any_tag: AnyTag,
    #[data(offset(0x10))]
    material_shader: FieldReference,
}

Reading structures

use infinite_rs_derive::TagStructure;
use infinite_rs::tag::types::common_types::{
    AnyTag, FieldReference,
};
use infinite_rs::{ModuleFile, Result};

#[derive(Default, Debug, TagStructure)]
#[data(size(0x88))] // Size can be any u64 value.
struct MaterialTag {
    #[data(offset(0x00))] // Offset can be any u64 value within the range of the size.
    any_tag: AnyTag,
    #[data(offset(0x10))]
    material_shader: FieldReference,
}

fn load_tags() -> Result<()> {
    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;

    // We now want to find the material tags in the module file.
    let material_indices = module.files.iter()
        .enumerate()
        .filter(|(_, file)| file.tag_group == "mat ")
        .map(|(index, _)| index)
        .collect::<Vec<_>>();

    // And for each material tag, we want to read the metadata associated.
    for index in material_indices {
        // We first have to populate data_stream and tag_info.
        let id = module.read_tag(index as u32)?;
        
        if id.is_some() {
            let mut mat = MaterialTag::default();
            // We pass in our structure as a generic parameter.
            module.files[index].read_metadata(&mut mat)?;
            // We can now access the fields in our structure.
            // For instance, `any_tag.internal_struct.tag_id` is always equal to the tag id of our file.
            assert_eq!(module.files[index].tag_id, mat.any_tag.internal_struct.tag_id);
        }
    }
    Ok(())
}

Reading enums and flags

infinite-rs also supports the usage of enums and flags as fields, available on the common types: FieldCharEnum, FieldShortEnum, FieldLongEnum, FieldLongFlags, FieldWordFlags and FieldByteFlags.

For enums, this requires TryFromPrimitive to be implemented. For flags, you can use the bitflags crate.

use infinite_rs_derive::TagStructure;
use infinite_rs::tag::types::common_types::{FieldShortEnum, FieldWordFlags};
use num_enum::TryFromPrimitive;
use bitflags::bitflags;

#[derive(Default, Debug, TryFromPrimitive)]
#[repr(u16)]
enum Variants {
    #[default]
    One,
    Two,
    Three
}

bitflags! {
    #[derive(Default, Debug)]
    struct FlagVariants : u16 {
        const ONE = 0b00;
        const TWO = 0b01;
        const THREE = 0b10;
    }
}

#[derive(Default, Debug, TagStructure)]
#[data(size(16))]
struct ExampleStruct {
    #[data(offset(0))]
    variants: FieldShortEnum<Variants>,
    #[data(offset(2))]
    variant_flags: FieldWordFlags<FlagVariants>
}

Credits

  • libinfinite by Coreforge, which this project is mostly based on.
  • Reclaimer by Gravemind2401, which helped me get familiar with Blam file formats.
  • AusarDocs by Shockfire, a very useful resource on Ausar/Slipspace file formats.
  • Kraken by Wolvenkit team, a re-implementation of Oodle Kraken, removing the need for any binary blobs being required for decompression.
  • TagFramework by Codename Atriox, which was a common reference point for Slipspace internals.
  • red4lib by rfuzzo, acting as the main inspiration for this project.
  • HIRT by urium1186, which was very useful in debugging and verifying output from this project.

Dependencies

~0.4–1.7MB
~35K SLoC