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