procmod_layout_derive/
lib.rs1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{parse_macro_input, Data, DeriveInput, Fields, LitInt, Meta};
5
6#[proc_macro_derive(GameStruct, attributes(offset, pointer_chain))]
46pub fn derive_game_struct(input: TokenStream) -> TokenStream {
47 let input = parse_macro_input!(input as DeriveInput);
48 match impl_game_struct(&input) {
49 Ok(tokens) => tokens.into(),
50 Err(e) => e.to_compile_error().into(),
51 }
52}
53
54fn impl_game_struct(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
55 let name = &input.ident;
56
57 let fields = match &input.data {
58 Data::Struct(data) => match &data.fields {
59 Fields::Named(f) => &f.named,
60 _ => {
61 return Err(syn::Error::new(
62 Span::call_site(),
63 "GameStruct only supports structs with named fields",
64 ))
65 }
66 },
67 _ => {
68 return Err(syn::Error::new(
69 Span::call_site(),
70 "GameStruct can only be derived for structs",
71 ))
72 }
73 };
74
75 let mut field_reads = Vec::new();
76
77 for field in fields {
78 let field_name = field.ident.as_ref().unwrap();
79 let field_ty = &field.ty;
80
81 let offset = parse_offset_attr(field)?;
82 let chain = parse_pointer_chain_attr(field)?;
83
84 let read_expr = if let Some(offsets) = chain {
85 gen_pointer_chain_read(field_ty, offset, &offsets)
86 } else {
87 gen_direct_read(field_ty, offset)
88 };
89
90 field_reads.push(quote! {
91 #field_name: #read_expr
92 });
93 }
94
95 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
96
97 Ok(quote! {
98 impl #impl_generics #name #ty_generics #where_clause {
99 pub fn read(
101 __procmod_process: &::procmod_layout::Process,
102 __procmod_base: usize,
103 ) -> ::procmod_layout::Result<Self> {
104 Ok(Self {
105 #(#field_reads),*
106 })
107 }
108 }
109 })
110}
111
112fn parse_offset_attr(field: &syn::Field) -> syn::Result<u64> {
113 for attr in &field.attrs {
114 if attr.path().is_ident("offset") {
115 let lit: LitInt = attr.parse_args()?;
116 return lit.base10_parse();
117 }
118 }
119
120 Err(syn::Error::new_spanned(
121 field.ident.as_ref().unwrap(),
122 "missing #[offset(N)] attribute",
123 ))
124}
125
126fn parse_pointer_chain_attr(field: &syn::Field) -> syn::Result<Option<Vec<u64>>> {
127 for attr in &field.attrs {
128 if attr.path().is_ident("pointer_chain") {
129 match &attr.meta {
130 Meta::List(list) => {
131 let parsed: syn::punctuated::Punctuated<LitInt, syn::Token![,]> =
132 list.parse_args_with(syn::punctuated::Punctuated::parse_terminated)?;
133
134 if parsed.is_empty() {
135 return Err(syn::Error::new_spanned(
136 list,
137 "pointer_chain requires at least one offset",
138 ));
139 }
140
141 let offsets = parsed
142 .iter()
143 .map(|lit| lit.base10_parse())
144 .collect::<syn::Result<Vec<u64>>>()?;
145
146 return Ok(Some(offsets));
147 }
148 _ => {
149 return Err(syn::Error::new_spanned(
150 attr,
151 "expected #[pointer_chain(offset, ...)]",
152 ))
153 }
154 }
155 }
156 }
157 Ok(None)
158}
159
160fn gen_direct_read(ty: &syn::Type, offset: u64) -> proc_macro2::TokenStream {
161 let offset_lit = LitInt::new(&format!("{offset}"), Span::call_site());
162 quote! {
163 unsafe { __procmod_process.read::<#ty>(__procmod_base + #offset_lit)? }
164 }
165}
166
167fn gen_pointer_chain_read(
168 ty: &syn::Type,
169 base_offset: u64,
170 chain: &[u64],
171) -> proc_macro2::TokenStream {
172 let base_offset_lit = LitInt::new(&format!("{base_offset}"), Span::call_site());
173
174 let mut steps = Vec::new();
175
176 let first_var = syn::Ident::new("__ptr_0", Span::call_site());
178 steps.push(quote! {
179 let #first_var: usize = unsafe {
180 __procmod_process.read::<usize>(__procmod_base + #base_offset_lit)?
181 };
182 });
183
184 let last_idx = chain.len() - 1;
186 for (i, &offset) in chain[..last_idx].iter().enumerate() {
187 let prev_var = syn::Ident::new(&format!("__ptr_{i}"), Span::call_site());
188 let next_var = syn::Ident::new(&format!("__ptr_{}", i + 1), Span::call_site());
189 let offset_lit = LitInt::new(&format!("{offset}"), Span::call_site());
190 steps.push(quote! {
191 let #next_var: usize = unsafe {
192 __procmod_process.read::<usize>(#prev_var + #offset_lit)?
193 };
194 });
195 }
196
197 let final_var = syn::Ident::new(&format!("__ptr_{last_idx}"), Span::call_site());
199 let final_offset_lit = LitInt::new(&format!("{}", chain[last_idx]), Span::call_site());
200
201 quote! {
202 {
203 #(#steps)*
204 unsafe { __procmod_process.read::<#ty>(#final_var + #final_offset_lit)? }
205 }
206 }
207}