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