infinite_rs/
lib.rs

1#![warn(clippy::pedantic)]
2#![warn(clippy::cargo)]
3#![warn(clippy::complexity)]
4#![warn(clippy::absolute_paths)]
5#![warn(clippy::missing_safety_doc)]
6#![warn(clippy::all)]
7#![warn(rustdoc::redundant_explicit_links)]
8#![warn(clippy::needless_doctest_main)]
9#![warn(clippy::default_constructed_unit_structs)]
10#![allow(clippy::missing_errors_doc)]
11#![allow(clippy::module_name_repetitions)]
12#![allow(rustdoc::private_intra_doc_links)]
13/*!
14Simple and fast deserialization library for Halo Infinite.
15
16## Getting Started: Loading a Module file
17Modules 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.
18
19```rust
20use infinite_rs::{ModuleFile, Result};
21
22fn load_modules() -> Result<()> {
23    // Create new instance of a Module file.
24    // These are the main archive files used in Halo Infinite.
25    // Note: the path can be anything that implements AsRef<Path>.
26    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;
27    Ok(())
28}
29```
30
31## Loading a tag file
32After we have loaded a module file, we can now use the [`read_tag`](`ModuleFile::read_tag`) function to load a specific tag by index from the module file. This populates the [`data_stream`](`crate::module::file::ModuleFileEntry::data_stream`) and [`tag_info`](`crate::module::file::ModuleFileEntry::tag_info`) properties in a module entry that we can use later.
33
34The [`read_tag_from_id`](`ModuleFile::read_tag_from_id`) function is also available to load a tag by its global ID.
35
36```rust
37use infinite_rs::{ModuleFile, Result};
38
39fn load_tags() -> Result<()> {
40    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;
41
42    // Load a specific tag from the module file.
43    let tag_index = 0;
44    let tag = module.read_tag(tag_index)?;
45    if let Some(tag) = tag {
46        // We can now access the data stream and tag info.
47        let tag_data = tag.data_stream.as_ref().unwrap();
48        let tag_info = tag.tag_info.as_ref().unwrap();
49    }
50    Ok(())
51}
52```
53
54## Creating a custom structure and reading it
55`infinite-rs` also allows you to read data directly into structures, using the [`read_metadata`](`crate::module::file::ModuleFileEntry::read_metadata) function. This functionality requires the `derive` feature.
56
57### Defining Structures
58To define a structure that can be read from a tag data stream, you must first derive the [`TagStructure`](`crate::module::file::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.
59
60*Padding between fields is automatically calculated. Any data between two offsets are skipped.*
61
62```rust,no_run
63use infinite_rs::TagStructure;
64use infinite_rs::tag::types::common_types::{
65    AnyTag, FieldReference,
66};
67
68#[derive(Default, Debug, TagStructure)]
69#[data(size(0x88))] // Size can be any u64 value.
70struct MaterialTag {
71    #[data(offset(0x00))] // Offset can be any u64 value within the range of the size.
72    any_tag: AnyTag,
73    #[data(offset(0x10))]
74    material_shader: FieldReference,
75}
76```
77
78### Reading structures
79
80```rust,no_run
81use infinite_rs::tag::types::common_types::{
82    AnyTag, FieldReference,
83};
84use infinite_rs::{ModuleFile, Result, TagStructure};
85
86#[derive(Default, Debug, TagStructure)]
87#[data(size(0x88))] // Size can be any u64 value.
88struct MaterialTag {
89    #[data(offset(0x00))] // Offset can be any u64 value within the range of the size.
90    any_tag: AnyTag,
91    #[data(offset(0x10))]
92    material_shader: FieldReference,
93}
94
95fn load_tags() -> Result<()> {
96    let mut module = ModuleFile::from_path("C:/XboxGames/Halo Infinite/Content/deploy/any/globals-rtx-new.module")?;
97
98    // We now want to find the material tags in the module file.
99    let material_indices = module.files.iter()
100        .enumerate()
101        .filter(|(_, file)| file.tag_group == "mat ")
102        .map(|(index, _)| index)
103        .collect::<Vec<_>>();
104
105    // And for each material tag, we want to read the metadata associated.
106    for index in material_indices {
107        // We first have to populate data_stream and tag_info.
108        let tag = module.read_tag(index as u32)?;
109        if let Some(tag) = tag {
110            let mat = tag.read_metadata::<MaterialTag>()?;
111            // We can now access the fields in our structure.
112            // For instance, `any_tag.internal_struct.tag_id` is always equal to the tag id of our file.
113            assert_eq!(tag.tag_id, mat.any_tag.internal_struct.tag_id);
114        }
115    }
116    Ok(())
117}
118```
119
120### Reading enums and flags
121`infinite-rs` also supports the usage of enums and flags as fields, available on the common types: `FieldCharEnum`, `FieldShortEnum`, `FieldLongEnum`, `FieldLongFlags`, `FieldWordFlags` and `FieldByteFlags`.
122
123For enums, this requires [`TryFromPrimitive`](`num_enum::TryFromPrimitive`) to be implemented.
124For flags, you can use the [`bitflags`] crate.
125
126```rust,no_run
127use infinite_rs::tag::types::common_types::{FieldShortEnum, FieldWordFlags};
128use infinite_rs::TagStructure;
129use num_enum::TryFromPrimitive;
130use bitflags::bitflags;
131
132#[derive(Default, Debug, TryFromPrimitive)]
133#[repr(u16)]
134enum Variants {
135    #[default]
136    One,
137    Two,
138    Three
139}
140
141bitflags! {
142    #[derive(Default, Debug)]
143    struct FlagVariants : u16 {
144        const ONE = 0b00;
145        const TWO = 0b01;
146        const THREE = 0b10;
147    }
148}
149
150#[derive(Default, Debug, TagStructure)]
151#[data(size(16))]
152struct ExampleStruct {
153    #[data(offset(0))]
154    variants: FieldShortEnum<Variants>,
155    #[data(offset(2))]
156    variant_flags: FieldWordFlags<FlagVariants>
157}
158```
159
160## Credits
161- [libinfinite](https://github.com/Coreforge/libInfinite) by Coreforge, which this project is mostly based on.
162- [Reclaimer](https://github.com/Gravemind2401/Reclaimer) by Gravemind2401, which helped me get familiar with Blam file formats.
163- [AusarDocs](https://github.com/ElDewrito/AusarDocs) by Shockfire, a very useful resource on Ausar/Slipspace file formats.
164- [Kraken](https://github.com/WolvenKit/kraken) by Wolvenkit team, a re-implementation of Oodle Kraken, removing the need for any binary blobs being required for decompression.
165- [TagFramework](https://github.com/Codename-Atriox/TagFramework) by Codename Atriox, which was a common reference point for Slipspace internals.
166- [red4lib](https://github.com/rfuzzo/red4lib) by rfuzzo, acting as the main inspiration for this project.
167- [HIRT](https://github.com/urium1186/HIRT) by urium1186, which was very useful in debugging and verifying output from this project.
168
169*/
170
171pub mod common;
172pub mod module;
173pub mod tag;
174
175#[doc(inline)]
176pub use crate::common::errors::{Error, Result};
177#[doc(inline)]
178pub use crate::{module::loader::ModuleFile, tag::loader::TagFile};
179
180#[cfg(feature = "derive")]
181extern crate infinite_rs_derive;
182
183#[cfg(feature = "derive")]
184pub use infinite_rs_derive::TagStructure;