Skip to main content

infinite_rs_derive/
lib.rs

1#![warn(clippy::pedantic)]
2#![allow(clippy::missing_errors_doc)]
3#![allow(clippy::missing_panics_doc)]
4#![allow(clippy::module_name_repetitions)]
5#![warn(clippy::all)]
6
7use std::collections::HashMap;
8
9use quote::quote;
10use syn::{DataStruct, DeriveInput};
11
12#[derive(deluxe::ExtractAttributes)]
13#[deluxe(attributes(data))]
14struct TagStructureAttributes {
15    size: u64,
16}
17
18#[derive(deluxe::ExtractAttributes, Clone)]
19#[deluxe(attributes(data))]
20struct TagStructureFieldAttributes {
21    offset: u64,
22    count: Option<u64>,
23}
24
25fn extract_struct_field_attributes(
26    ast: &mut DeriveInput,
27) -> deluxe::Result<HashMap<String, TagStructureFieldAttributes>> {
28    let mut field_attributes = HashMap::new();
29    if let syn::Data::Struct(data) = &mut ast.data {
30        for field in &mut data.fields {
31            let field_name = field.ident.as_ref().unwrap().to_string();
32            let attributes: TagStructureFieldAttributes = deluxe::extract_attributes(field)?;
33            field_attributes.insert(field_name, attributes);
34        }
35    }
36    Ok(field_attributes)
37}
38
39fn extract_field_maps(
40    field_attributes: &HashMap<String, TagStructureFieldAttributes>,
41) -> (Vec<String>, Vec<u64>) {
42    field_attributes
43        .clone()
44        .into_iter()
45        .map(|(field, attrs)| (field, attrs.offset))
46        .unzip()
47}
48
49fn generate_field_reads(
50    data: &DataStruct,
51    field_attributes: &HashMap<String, TagStructureFieldAttributes>,
52) -> Vec<proc_macro2::TokenStream> {
53    data.fields
54        .iter()
55        .map(|field| {
56            let field_name = &field.ident;
57            let offset = field_attributes
58                .get(&field_name.as_ref().unwrap().to_string())
59                .unwrap()
60                .offset;
61            if let syn::Type::Path(type_path) = &field.ty {
62                if let Some(segment) = type_path.path.segments.last() {
63                    if segment.ident == "FieldArray" {
64                        let count = field_attributes
65                            .get(&field_name.as_ref().unwrap().to_string())
66                            .unwrap()
67                            .count
68                            .unwrap();
69                        return quote! {
70                            reader.seek(std::io::SeekFrom::Start(main_offset + #offset))?;
71                            self.#field_name.read(reader, #count)?;
72                        };
73                    }
74                }
75            }
76            quote! {
77                reader.seek(std::io::SeekFrom::Start(main_offset + #offset))?;
78                self.#field_name.read(reader)?;
79            }
80        })
81        .collect()
82}
83
84fn generate_field_blocks(
85    data: &DataStruct,
86    field_attributes: &HashMap<String, TagStructureFieldAttributes>,
87) -> Vec<proc_macro2::TokenStream> {
88    data.fields.iter().filter_map(|field| {
89        if let syn::Type::Path(type_path) = &field.ty {
90            if let Some(segment) = type_path.path.segments.last() {
91                let field_name = &field.ident;
92                match segment.ident.to_string().as_str() {
93                    "FieldBlock" => {
94                        let offset = field_attributes.get(&field_name.as_ref().unwrap().to_string()).unwrap().offset;
95                        Some(quote! {
96                            self.#field_name.load_blocks(source_index, adjusted_base + #offset, reader, tag_file)?;
97                        })
98                    },
99                    "FieldTagResource" => {
100                        let offset = field_attributes.get(&field_name.as_ref().unwrap().to_string()).unwrap().offset;
101                        Some(quote! {
102                            self.#field_name.load_resource(adjusted_base + #offset, reader, tag_file)?;
103                        })
104                    },
105                    "FieldArray" => {
106                        let offset = field_attributes.get(&field_name.as_ref().unwrap().to_string()).unwrap().offset;
107                        Some(quote! {
108                            self.#field_name.load_blocks(reader, source_index, adjusted_base + #offset, tag_file)?;
109                        })
110                    },
111                    "FieldData" => {
112                        Some(quote! {
113                            self.#field_name.load_data(reader, source_index, parent_index, tag_file)?;
114                        })
115                    },
116                    _ => None
117                }
118            } else {
119                None
120            }
121        } else {
122            None
123        }
124    }).collect()
125}
126fn tag_structure_derive2(
127    input: proc_macro2::TokenStream,
128) -> deluxe::Result<proc_macro2::TokenStream> {
129    let mut ast: DeriveInput = syn::parse2(input)?;
130    let TagStructureAttributes { size } = deluxe::extract_attributes(&mut ast)?;
131    let field_attributes: HashMap<String, TagStructureFieldAttributes> =
132        extract_struct_field_attributes(&mut ast)?;
133    let ident: &syn::Ident = &ast.ident;
134    let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
135
136    let syn::Data::Struct(data) = &ast.data else {
137        panic!("TagStructure can only be derived for structs")
138    };
139    let (name, field_offset) = extract_field_maps(&field_attributes);
140
141    let field_reads = generate_field_reads(data, &field_attributes);
142    let field_blocks = generate_field_blocks(data, &field_attributes);
143
144    Ok(quote! {
145        impl #impl_generics infinite_rs::module::file::TagStructure for #ident #type_generics #where_clause {
146            fn size(&mut self) -> u64 {
147                #size
148            }
149            fn read<R: infinite_rs::common::extensions::BufReaderExt>(&mut self, reader: &mut R) -> infinite_rs::Result<()> {
150                let main_offset = reader.stream_position()?;
151                #(#field_reads)*
152                reader.seek(std::io::SeekFrom::Start(main_offset + self.size()))?;
153                Ok(())
154            }
155
156            fn offsets(&self) -> std::collections::HashMap<&'static str, u64> {
157                let field_names = [#(#name),*];
158                let field_offsets = [#(#field_offset),*];
159
160                let map: std::collections::HashMap<&'static str, u64> = field_names.iter().zip(field_offsets.iter()).map(|(&name, &offset)| (name, offset)).collect();
161                map
162            }
163
164            fn load_field_blocks<R: std::io::BufRead + std::io::Seek + infinite_rs::common::extensions::BufReaderExt>(
165                &mut self,
166                source_index: i32,
167                parent_index: usize,
168                adjusted_base: u64,
169                reader: &mut R,
170                tag_file: &infinite_rs::tag::loader::TagFile,
171            ) -> infinite_rs::Result<()> {
172                #(#field_blocks)*
173                Ok(())
174            }
175        }
176    })
177}
178
179#[proc_macro_derive(TagStructure, attributes(data))]
180/// For implementing Tag Structures as described in documentation.
181pub fn tag_structure_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182    tag_structure_derive2(input.into()).unwrap().into()
183}