infinite_rs/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
#![warn(clippy::complexity)]
#![warn(clippy::absolute_paths)]
#![warn(clippy::missing_safety_doc)]
#![warn(clippy::all)]
#![warn(rustdoc::redundant_explicit_links)]
#![warn(clippy::needless_doctest_main)]
#![warn(clippy::default_constructed_unit_structs)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(rustdoc::private_intra_doc_links)]
/*!
Simple and fast deserialization library for Halo Infinite.

## 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.

```rust
use infinite_rs::{ModuleFile, Result};

fn load_modules() -> Result<()> {
    // Create new instance of a Module file.
    // 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`](`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.

The [`read_tag_from_id`](`ModuleFile::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.

```rust
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)?;
    if id.is_some() {
        // We can now access the data stream and tag info.
        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`](`crate::module::file::ModuleFileEntry::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`](`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.

*Padding between fields is automatically calculated. Any data between two offsets are skipped.*

```rust
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

```rust
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`](`num_enum::TryFromPrimitive`) to be implemented.
For flags, you can use the [`bitflags`] crate.

```rust
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](https://github.com/Coreforge/libInfinite) by Coreforge, which this project is mostly based on.
- [Reclaimer](https://github.com/Gravemind2401/Reclaimer) by Gravemind2401, which helped me get familiar with Blam file formats.
- [AusarDocs](https://github.com/ElDewrito/AusarDocs) by Shockfire, a very useful resource on Ausar/Slipspace file formats.
- [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.
- [TagFramework](https://github.com/Codename-Atriox/TagFramework) by Codename Atriox, which was a common reference point for Slipspace internals.
- [red4lib](https://github.com/rfuzzo/red4lib) by rfuzzo, acting as the main inspiration for this project.
- [HIRT](https://github.com/urium1186/HIRT) by urium1186, which was very useful in debugging and verifying output from this project.

*/

pub mod common;
pub mod module;
pub mod tag;

#[doc(inline)]
pub use crate::common::errors::{Error, Result};
#[doc(inline)]
pub use crate::{module::loader::ModuleFile, tag::loader::TagFile};