1use crate::{
2 hook::{HookArgs, HookTransformer},
3 pure_virtual::{PureVirtualFn, VirtualArgs},
4};
5use proc_macro::TokenStream;
6use proc_macro_crate::{FoundCrate, crate_name};
7use proc_macro2::TokenStream as TokenStream2;
8use quote::quote;
9use syn::{DeriveInput, FnArg, ItemFn, parse_macro_input, visit_mut::VisitMut};
10
11fn forge_crate() -> TokenStream2 {
12 match crate_name("mhgu-forge") {
13 Ok(FoundCrate::Itself) => quote! { crate },
14 Ok(FoundCrate::Name(_)) | Err(_) => quote! { ::forge },
15 }
16}
17
18mod hook;
19#[cfg(feature = "config")]
20mod ini;
21mod pure_virtual;
22
23#[proc_macro_attribute]
33pub fn entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
34 let inner = parse_macro_input!(item as ItemFn);
35 let inner_name = &inner.sig.ident;
36
37 let expanded = quote! {
38 #[inline(always)]
39 #inner
40
41 #[unsafe(no_mangle)]
42 pub extern "C" fn forge_onLoad(params: *mut ::forge::sys::init::PluginInitParams) {
43 unsafe {
44 (*params).required_version = ::forge::REQUIRED_VERSION;
45 }
46 }
47
48 #[unsafe(no_mangle)]
49 pub extern "C" fn forge_onInit(params: *mut ::forge::sys::init::PluginInitParams) {
50 ::forge::log::init().expect("Failed to initialize logger");
51 #inner_name();
52 }
53 };
54
55 expanded.into()
56}
57
58#[cfg(feature = "imgui")]
68#[proc_macro_attribute]
69pub fn imgui_render(_attr: TokenStream, item: TokenStream) -> TokenStream {
70 let inner = parse_macro_input!(item as ItemFn);
71 let inner_name = &inner.sig.ident;
72
73 let expanded = quote! {
74 #[inline(always)]
75 #inner
76
77 #[unsafe(no_mangle)]
78 pub extern "C" fn forge_onImGuiRender() {
79 #inner_name();
80 }
81 };
82
83 expanded.into()
84}
85
86#[cfg(feature = "imgui")]
97#[proc_macro_attribute]
98pub fn imgui_free_render(_attr: TokenStream, item: TokenStream) -> TokenStream {
99 let inner = parse_macro_input!(item as ItemFn);
100 let inner_name = &inner.sig.ident;
101
102 let expanded = quote! {
103 #[inline(always)]
104 #inner
105
106 #[unsafe(no_mangle)]
107 pub extern "C" fn forge_onImGuiFreeRender() {
108 #inner_name();
109 }
110 };
111
112 expanded.into()
113}
114
115#[proc_macro_attribute]
152pub fn hook(attr: TokenStream, item: TokenStream) -> TokenStream {
153 let args = parse_macro_input!(attr as HookArgs);
154 let func = parse_macro_input!(item as ItemFn);
155
156 let offset = &args.offset;
157 let func_name = &func.sig.ident;
158 let inputs = &func.sig.inputs;
159 let output = &func.sig.output;
160
161 let param_types: Vec<TokenStream2> = inputs
162 .iter()
163 .map(|arg| match arg {
164 FnArg::Typed(pat_type) => {
165 let ty = &pat_type.ty;
166 quote! { #ty }
167 }
168 FnArg::Receiver(_) => {
169 panic!("#[forge::hook] does not support `self` parameters")
170 }
171 })
172 .collect();
173
174 let ret_type = match output {
175 syn::ReturnType::Default => quote! { () },
176 syn::ReturnType::Type(_, ty) => quote! { #ty },
177 };
178
179 let fn_ptr_type = quote! { unsafe extern "C" fn(#(#param_types),*) -> #ret_type };
180
181 let mut body = func.block.clone();
182 HookTransformer.visit_block_mut(&mut body);
183
184 let expanded = quote! {
185 pub mod #func_name {
186 #[allow(unused_imports)]
187 use super::*;
188
189 pub const OFFSET: u32 = #offset as u32;
191
192 static mut __HOOK: ::core::mem::MaybeUninit<::forge::sys::hook::Hook> =
193 ::core::mem::MaybeUninit::uninit();
194 static mut __ORIGINAL: *const ::core::ffi::c_void = ::core::ptr::null();
195
196 pub unsafe extern "C" fn __detour(#inputs) #output {
197 let __forge_original: #fn_ptr_type = unsafe {
198 ::core::mem::transmute(__ORIGINAL)
199 };
200 let __forge_context = unsafe { ::forge::sys::hook::forge_hook_getContext() };
201 #body
202 }
203
204 pub unsafe fn __install(base: u32) {
205 unsafe {
206 __HOOK.write(::forge::sys::hook::forge_hook_create(
207 (base + OFFSET) as *const ::core::ffi::c_void,
208 __detour as *const ::core::ffi::c_void,
209 ::core::ptr::addr_of_mut!(__ORIGINAL),
210 ));
211 }
212 }
213
214 pub unsafe fn __install_with_ctx(base: u32, ctx: *const ::core::ffi::c_void) {
215 unsafe {
216 __HOOK.write(::forge::sys::hook::forge_hook_createWithContext(
217 (base + OFFSET) as *const ::core::ffi::c_void,
218 __detour as *const ::core::ffi::c_void,
219 ::core::ptr::addr_of_mut!(__ORIGINAL),
220 ctx,
221 ));
222 }
223 }
224
225 pub unsafe fn __update_ctx(ctx: *const ::core::ffi::c_void) {
227 unsafe {
228 let result = ::forge::sys::hook::forge_hook_updateContext(
229 __HOOK.as_mut_ptr(),
230 ctx,
231 );
232 debug_assert_eq!(result, 0, "forge_hook_updateContext failed");
233 }
234 }
235 }
236 };
237
238 expanded.into()
239}
240
241#[proc_macro_attribute]
257pub fn pure_virtual(attr: TokenStream, item: TokenStream) -> TokenStream {
258 let args = parse_macro_input!(attr as VirtualArgs);
259 let func = parse_macro_input!(item as PureVirtualFn);
260
261 let func_name = &func.sig.ident;
262 let inputs = &func.sig.inputs;
263 let output = &func.sig.output;
264 let visibility = &func.vis;
265
266 let has_self = !inputs.is_empty()
267 && match &inputs[0] {
268 FnArg::Receiver(receiver) => receiver.reference.is_some(),
269 _ => false,
270 };
271
272 if !has_self {
273 panic!("Functions marked with #[forge::pure_virtual] must have `&self` as their first parameter");
274 }
275
276 let param_types: Vec<TokenStream2> = inputs
277 .iter()
278 .map(|arg| match arg {
279 FnArg::Receiver(receiver) => {
280 let ty = &receiver.ty;
281 quote! { #ty }
282 }
283 FnArg::Typed(pat_type) => {
284 let ty = &pat_type.ty;
285 quote! { #ty }
286 }
287 })
288 .collect();
289
290 let ret_type = match output {
291 syn::ReturnType::Default => quote! { () },
292 syn::ReturnType::Type(_, ty) => quote! { #ty },
293 };
294
295 let fn_ptr_type = quote! { unsafe extern "C" fn(#(#param_types),*) -> #ret_type };
296
297 let index = &args.index;
298 let param_names: Vec<TokenStream2> = inputs
299 .iter()
300 .map(|arg| match arg {
301 FnArg::Receiver(_) => quote! { self },
302 FnArg::Typed(pat_type) => {
303 let pat = &pat_type.pat;
304 quote! { #pat }
305 }
306 })
307 .collect();
308
309 let forge = forge_crate();
310 let expanded = quote! {
311 #visibility fn #func_name(#inputs) #output {
312 let vtable = #forge::sys::cpp::HasVtable::vtable_ptr(self);
313 let addr = unsafe { #forge::sys::cpp::HasVtable::get_virtual_function(self, #index) };
314 let func: #fn_ptr_type = unsafe {
315 ::core::mem::transmute(addr)
316 };
317 unsafe { func(#(#param_names),*) }
318 }
319 };
320
321 expanded.into()
322}
323
324#[proc_macro_derive(HasVtable)]
325pub fn has_vtable_derive(input: TokenStream) -> TokenStream {
326 let input = parse_macro_input!(input as DeriveInput);
327 let type_name = &input.ident;
328 let forge = forge_crate();
329
330 let expanded = quote! {
331 impl #forge::sys::cpp::HasVtable for #type_name {
332 fn vtable_ptr(&self) -> *const *const ::core::ffi::c_void {
333 unsafe {
334 let ptr = self as *const Self as *const *const *const ::core::ffi::c_void;
335 *ptr
336 }
337 }
338 }
339 };
340
341 expanded.into()
342}
343
344#[proc_macro_derive(Object)]
345pub fn mt_object_derive(input: TokenStream) -> TokenStream {
346 let input = parse_macro_input!(input as DeriveInput);
347 let type_name = &input.ident;
348 let forge = forge_crate();
349
350 let expanded = quote! {
351 impl #forge::sys::cpp::HasVtable for #type_name {
352 fn vtable_ptr(&self) -> *const *const ::core::ffi::c_void {
353 unsafe {
354 let ptr = self as *const Self as *const *const *const ::core::ffi::c_void;
355 *ptr
356 }
357 }
358 }
359
360 impl #forge::mt::object::Object for #type_name {}
361 };
362
363 expanded.into()
364}
365
366#[proc_macro_derive(CacheDti)]
367pub fn cache_dti_derive(input: TokenStream) -> TokenStream {
368 let input = parse_macro_input!(input as DeriveInput);
369 let type_name = &input.ident;
370 let type_name_str = type_name.to_string();
371 let forge = forge_crate();
372
373 let expanded = quote! {
374 impl #forge::mt::dti::CacheDti for #type_name {
375 fn dti() -> Option<&'static #forge::mt::dti::MtDti> {
376 static DTI: core::sync::atomic::AtomicPtr<#forge::mt::dti::MtDti> = core::sync::atomic::AtomicPtr::new(core::ptr::null_mut());
377 let mut ptr = DTI.load(core::sync::atomic::Ordering::Relaxed);
378 if ptr.is_null() {
379 ptr = #forge::mt::dti::MtDti::find(#type_name_str).map_or(core::ptr::null_mut(), |d| d as *const _ as *mut _);
380 DTI.store(ptr, core::sync::atomic::Ordering::Relaxed);
381 }
382
383 if ptr.is_null() {
384 None
385 } else {
386 Some(unsafe { &*ptr })
387 }
388 }
389 }
390 };
391
392 expanded.into()
393}
394
395#[cfg(feature = "config")]
422#[proc_macro_derive(IniDeserialize, attributes(section, alias))]
423pub fn ini_deserialize_derive(input: TokenStream) -> TokenStream {
424 let input = parse_macro_input!(input as DeriveInput);
425 ini::ini_deserialize_derive(input).into()
426}
427
428#[cfg(feature = "config")]
447#[proc_macro_derive(IniSerialize, attributes(section, alias))]
448pub fn ini_serialize_derive(input: TokenStream) -> TokenStream {
449 let input = parse_macro_input!(input as DeriveInput);
450 ini::ini_serialize_derive(input).into()
451}