arcdps_codegen/
lib.rs

1mod parse;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{quote, quote_spanned};
5use syn::{Expr, LitStr};
6
7// noinspection SpellCheckingInspection
8/// For documentation on how to use this, visit [`SupportedFields`]
9///
10/// [`SupportedFields`]: ./struct.SupportedFields.html
11#[proc_macro]
12pub fn arcdps_export(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
13    let input = syn::parse_macro_input!(item as parse::ArcDpsGen);
14    let sig = input.sig;
15    let build = std::env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION is not set") + "\0";
16    let build = LitStr::new(build.as_str(), Span::call_site());
17    let (raw_name, span) = if let Some(input_name) = input.name {
18        let name = input_name.value();
19        (name, input_name.span())
20    } else {
21        let name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME is not set");
22        (name, Span::call_site())
23    };
24    let name = LitStr::new(raw_name.as_str(), span);
25    let out_name = raw_name + "\0";
26    let out_name = LitStr::new(out_name.as_str(), span);
27
28    let (abstract_combat, cb_combat) = build_combat(input.raw_combat, input.combat);
29    let (abstract_combat_local, cb_combat_local) =
30        build_combat_local(input.raw_combat_local, input.combat_local);
31    let (abstract_imgui, cb_imgui) = build_imgui(input.raw_imgui, input.imgui);
32    let (abstract_options_end, cb_options_end) =
33        build_options_end(input.raw_options_end, input.options_end);
34    let (abstract_options_windows, cb_options_windows) =
35        build_options_windows(input.raw_options_windows, input.options_windows);
36    let (abstract_wnd_filter, cb_wnd_filter) =
37        build_wnd_filter(input.raw_wnd_filter, input.wnd_filter);
38    let (abstract_wnd_nofilter, cb_wnd_nofilter) =
39        build_wnd_nofilter(input.raw_wnd_nofilter, input.wnd_nofilter);
40
41    let export = quote! {
42        ArcDpsExport {
43            size: ::std::mem::size_of::<ArcDpsExport>(),
44            sig: #sig,
45            imgui_version: 18000,
46            out_build: #build.as_ptr(),
47            out_name: #out_name.as_ptr(),
48            combat: #cb_combat,
49            combat_local: #cb_combat_local,
50            imgui: #cb_imgui,
51            options_end: #cb_options_end,
52            options_windows: #cb_options_windows,
53            wnd_filter: #cb_wnd_filter,
54            wnd_nofilter: #cb_wnd_nofilter,
55        }
56    };
57
58    let init = if let Some(init) = input.init {
59        let span = syn::Error::new_spanned(&init, "").span();
60        quote_spanned! (span => unsafe { (#init as InitFunc)(__SWAPCHAIN) })
61    } else {
62        quote! {Ok(())}
63    };
64
65    let release = if let Some(release) = input.release {
66        let span = syn::Error::new_spanned(&release, "").span();
67        quote_spanned! (span => (#release as ReleaseFunc)())
68    } else {
69        quote! {}
70    };
71
72    let (abstract_extras_squad_update, extras_squad_update) = build_extras_squad_update(
73        input.raw_unofficial_extras_squad_update,
74        input.unofficial_extras_squad_update,
75    );
76    let (abstract_extras_chat_message, extras_chat_message) = build_extras_chat_message(
77        input.raw_unofficial_extras_chat_message,
78        input.unofficial_extras_chat_message,
79    );
80    let (abstract_extras_chat_message2, extras_chat_message2) = build_extras_chat_message2(
81        input.raw_unofficial_extras_chat_message2,
82        input.unofficial_extras_chat_message2,
83    );
84    let abstract_extras_init = build_extras_init(
85        input.raw_unofficial_extras_init,
86        input.unofficial_extras_init,
87        extras_squad_update,
88        extras_chat_message,
89        extras_chat_message2,
90        &out_name,
91    );
92
93    #[cfg(feature = "imgui")]
94    let sys_init = quote! {
95        use ::arcdps::imgui;
96
97        #[no_mangle]
98        // export -- arcdps looks for this exported function and calls the address it returns on client load
99        // if you need any of the ignored values, create an issue with your use case
100        pub unsafe extern "system" fn get_init_addr(
101            __arc_version: *mut c_char,
102            __imguictx: *mut imgui::sys::ImGuiContext,
103            __id3dptr: *mut c_void,
104            __arc_dll: *mut c_void,
105            __mallocfn: Option<unsafe extern "C" fn(sz: usize, user_data: *mut c_void) -> *mut c_void>,
106            __freefn: Option<unsafe extern "C" fn(ptr: *mut c_void, user_data: *mut c_void)>,
107        ) -> unsafe extern "system" fn() -> *const ArcDpsExport {
108            imgui::sys::igSetCurrentContext(__imguictx);
109            imgui::sys::igSetAllocatorFunctions(__mallocfn, __freefn, ::core::ptr::null_mut());
110            __CTX = Some(imgui::Context::current());
111            let __ctx = &raw const __CTX;
112            __UI = Some(imgui::Ui::from_ctx((*__ctx).as_ref().unwrap()));
113            __SWAPCHAIN = NonNull::new(__id3dptr);
114            ::arcdps::__init(__arc_version, __arc_dll, #name);
115            __load
116        }
117
118        static mut __CTX: Option<imgui::Context> = None;
119        static mut __UI: Option<imgui::Ui> = None;
120    };
121
122    #[cfg(not(feature = "imgui"))]
123    let sys_init = quote! {
124        #[no_mangle]
125        // export -- arcdps looks for this exported function and calls the address it returns on client load
126        // if you need any of the ignored values, create an issue with your use case
127        pub unsafe extern "system" fn get_init_addr(
128            __arc_version: *mut c_char,
129            _imguictx: *mut c_void,
130            __id3dptr: *mut c_void,
131            __arc_dll: *mut c_void,
132            _mallocfn: Option<unsafe extern "C" fn(sz: usize, user_data: *mut c_void) -> *mut c_void>,
133            _freefn: Option<unsafe extern "C" fn(ptr: *mut c_void, user_data: *mut c_void)>,
134        ) -> unsafe extern "system" fn() -> *const ArcDpsExport {
135            __SWAPCHAIN = NonNull::new(__id3dptr);
136            ::arcdps::__init(__arc_version, __arc_dll, #name);
137            __load
138        }
139    };
140
141    let res = quote! {
142        mod __arcdps_gen_export {
143            use super::*;
144            use ::std::os::raw::{c_char, c_void};
145            use ::std::ptr::NonNull;
146            use ::arcdps::ArcDpsExport;
147            use ::arcdps::{InitFunc, ReleaseFunc};
148
149            #abstract_combat
150            #abstract_combat_local
151            #abstract_imgui
152            #abstract_options_end
153            #abstract_options_windows
154            #abstract_wnd_filter
155            #abstract_wnd_nofilter
156            #abstract_extras_squad_update
157            #abstract_extras_chat_message
158            #abstract_extras_chat_message2
159            #abstract_extras_init
160
161            static __EXPORT: ArcDpsExport = #export;
162            static mut __EXPORT_ERROR: ArcDpsExport = ArcDpsExport {
163                    size: 0,
164                    sig: 0,
165                    imgui_version: 18000,
166                    out_build: #build.as_ptr(),
167                    out_name: #out_name.as_ptr(),
168                    combat: None,
169                    combat_local: None,
170                    imgui: None,
171                    options_end: None,
172                    options_windows: None,
173                    wnd_filter: None,
174                    wnd_nofilter: None,
175                };
176            static mut __ERROR_STRING: String = String::new();
177            static mut __SWAPCHAIN: Option<NonNull<c_void>> = None;
178
179            unsafe extern "system" fn __load() -> *const ArcDpsExport {
180                let mut __export = &raw const __EXPORT;
181                let __res: Result<(), Box<dyn ::std::error::Error>> = #init;
182                if let Err(__e) = __res {
183                    unsafe {
184                        __ERROR_STRING = __e.to_string() + "\0";
185                        __EXPORT_ERROR.size = &raw const __ERROR_STRING as _;
186                        __export = &raw const __EXPORT_ERROR;
187                    }
188                }
189
190                __export
191            }
192
193            unsafe extern "system" fn __unload() {
194                #release
195            }
196
197            #sys_init
198
199            #[no_mangle]
200            /* export -- arcdps looks for this exported function and calls the address it returns on client exit */
201            pub extern "system" fn get_release_addr() -> unsafe extern "system" fn() {
202                __unload
203            }
204        }
205    };
206    res.into()
207}
208
209fn build_extras_squad_update(
210    raw: Option<Expr>,
211    safe: Option<Expr>,
212) -> (TokenStream, Option<TokenStream>) {
213    let mut abstract_wrapper = quote! {};
214    let cb_safe = match (raw, safe) {
215        (Some(raw), _) => {
216            let span = syn::Error::new_spanned(&raw, "").span();
217            Some(quote_spanned!(span => Some(#raw as _) ))
218        }
219        (_, Some(safe)) => {
220            let span = syn::Error::new_spanned(&safe, "").span();
221            abstract_wrapper = quote_spanned!(span =>
222            unsafe extern "C" fn __abstract_extras_squad_update(__users: *const ::arcdps::RawUserInfo, __count: u64) {
223                let _ = #safe as ::arcdps::ExtrasSquadUpdateCallback;
224                let __users = ::std::slice::from_raw_parts(__users, __count as _);
225                let __users = __users.iter().map(::arcdps::helpers::convert_extras_user as ::arcdps::UserConvert);
226                #safe(__users)
227            });
228            Some(
229                quote_spanned!(span => Some(__arcdps_gen_export::__abstract_extras_squad_update as _) ),
230            )
231        }
232        _ => None,
233    };
234    (abstract_wrapper, cb_safe)
235}
236
237fn build_extras_chat_message(
238    raw: Option<Expr>,
239    safe: Option<Expr>,
240) -> (TokenStream, Option<TokenStream>) {
241    let mut abstract_wrapper = quote! {};
242    let cb_safe = match (raw, safe) {
243        (Some(raw), _) => {
244            let span = syn::Error::new_spanned(&raw, "").span();
245            Some(quote_spanned!(span => Some(#raw as _) ))
246        }
247        (_, Some(safe)) => {
248            let span = syn::Error::new_spanned(&safe, "").span();
249            abstract_wrapper = quote_spanned!(span =>
250            unsafe extern "C" fn __abstract_extras_chat_message(__msg: *const ::arcdps::RawSquadMessageInfo) {
251                let _ = #safe as ::arcdps::ExtrasChatMessageCallback;
252                let __msg = ::arcdps::helpers::convert_extras_squad_chat_message(&*__msg);
253                #safe(&__msg)
254            });
255            Some(
256                quote_spanned!(span => Some(__arcdps_gen_export::__abstract_extras_chat_message as _) ),
257            )
258        }
259        _ => None,
260    };
261    (abstract_wrapper, cb_safe)
262}
263
264fn build_extras_chat_message2(
265    raw: Option<Expr>,
266    safe: Option<Expr>,
267) -> (TokenStream, Option<TokenStream>) {
268    let mut abstract_wrapper = quote! {};
269    let cb_safe = match (raw, safe) {
270        (Some(raw), _) => {
271            let span = syn::Error::new_spanned(&raw, "").span();
272            Some(quote_spanned!(span => Some(#raw as _) ))
273        }
274        (_, Some(safe)) => {
275            let span = syn::Error::new_spanned(&safe, "").span();
276            abstract_wrapper = quote_spanned!(span =>
277                unsafe extern "C" fn __abstract_extras_chat_message2(__msg_type: ::arcdps::ChatMessageType, __msg: ::arcdps::RawChatMessageInfo2) {
278                let _ = #safe as ::arcdps::ExtrasChatMessage2Callback;
279                let __msg = ::arcdps::helpers::convert_extras_chat_message2(__msg_type, __msg);
280                #safe(&__msg)
281            });
282            Some(
283                quote_spanned!(span => Some(__arcdps_gen_export::__abstract_extras_chat_message2 as _) ),
284            )
285        }
286        _ => None,
287    };
288    (abstract_wrapper, cb_safe)
289}
290
291fn build_extras_init(
292    raw: Option<Expr>,
293    safe: Option<Expr>,
294    squad_update: Option<TokenStream>,
295    chat_message: Option<TokenStream>,
296    chat_message2: Option<TokenStream>,
297    name: &LitStr,
298) -> TokenStream {
299    let needs_init = squad_update.is_some() || chat_message.is_some();
300    let squad_cb = squad_update.unwrap_or(quote! { None });
301    let chat_cb = chat_message.unwrap_or(quote! { None });
302    let chat_cb2 = chat_message2.unwrap_or(quote! { None });
303
304    let basic_init = quote!(
305        if __addon.api_version != 2 {
306            return;
307        }
308        if __addon.max_info_version < 1 {
309            return;
310        }
311
312        fn __fill_v1(__sub: *mut ::arcdps::RawExtrasSubscriberInfo<::arcdps::InfoV1>) {
313            let __sub = unsafe { &mut *__sub };
314            __sub.header.info_version = 1;
315
316            __sub.subscriber_name = #name.as_ptr();
317            __sub.squad_update_callback = #squad_cb;
318            __sub.language_changed_callback = None;
319            __sub.key_bind_changed_callback = None;
320        }
321
322        fn __fill_v2(__sub: *mut ::arcdps::RawExtrasSubscriberInfo<::arcdps::InfoV2>) {
323            __fill_v1(__sub.cast());
324
325            let __sub = unsafe { &mut *__sub };
326            __sub.header.info_version = 2;
327
328            __sub.chat_message_callback = #chat_cb;
329        }
330
331        fn __fill_v3(__sub: *mut ::arcdps::RawExtrasSubscriberInfo<::arcdps::InfoV3>) {
332            __fill_v2(__sub.cast());
333
334            let __sub = unsafe { &mut *__sub };
335            __sub.header.info_version = 3;
336
337            __sub.chat_message_callback2 = #chat_cb2;
338        }
339
340        match __addon.max_info_version {
341            1 => __fill_v1(__sub.cast()),
342            2 => __fill_v2(__sub.cast()),
343            _ => __fill_v3(__sub.cast()),
344        }
345    );
346
347    let abstract_wrapper = match (raw, safe) {
348        (Some(raw), _) => {
349            let span = syn::Error::new_spanned(&raw, "").span();
350            quote_spanned!(span =>
351                let _ = #raw as ::arcdps::RawExtrasSubscriberInitSignature;
352
353                #raw(__addon, __sub)
354            )
355        }
356        (_, Some(safe)) => {
357            let span = syn::Error::new_spanned(&safe, "").span();
358            quote_spanned!(span =>
359                #basic_init
360
361                let _ = #safe as ::arcdps::ExtrasInitFunc;
362                let __user = ::arcdps::helpers::get_str_from_pc_char(__addon.self_account_name as _)
363                                .map(|n| n.trim_start_matches(':'));
364                let __version = ::arcdps::helpers::get_str_from_pc_char(__addon.string_version as _);
365
366                #safe(__user, __version)
367            )
368        }
369        _ if needs_init => basic_init,
370        _ => return quote! {},
371    };
372    use syn::spanned::Spanned;
373    quote_spanned!(abstract_wrapper.span() =>
374        #[no_mangle]
375        unsafe extern "system" fn arcdps_unofficial_extras_subscriber_init(
376                                    __addon: &::arcdps::RawExtrasAddonInfo,
377                                    __sub: *mut ::arcdps::RawExtrasSubscriberInfoHeader
378        ) {
379            #abstract_wrapper
380        }
381    )
382}
383
384fn build_wnd_filter(raw_wnd: Option<Expr>, wnd: Option<Expr>) -> (TokenStream, TokenStream) {
385    build_wnd(raw_wnd, wnd, quote! { __abstract_wnd_filter })
386}
387
388fn build_wnd_nofilter(raw_wnd: Option<Expr>, wnd: Option<Expr>) -> (TokenStream, TokenStream) {
389    build_wnd(raw_wnd, wnd, quote! { __abstract_wnd_nofilter })
390}
391
392fn build_wnd(
393    raw_wnd_filter: Option<Expr>,
394    wnd_filter: Option<Expr>,
395    func_name: TokenStream,
396) -> (TokenStream, TokenStream) {
397    let mut abstract_wnd_filter = quote! {};
398    let cb_wnd_filter = match (raw_wnd_filter, wnd_filter) {
399        (Some(raw), _) => {
400            let span = syn::Error::new_spanned(&raw, "").span();
401            quote_spanned!(span => Some(#raw as _) )
402        }
403        (_, Some(safe)) => {
404            let span = syn::Error::new_spanned(&safe, "").span();
405            abstract_wnd_filter = quote_spanned!(span =>
406            unsafe extern "C" fn #func_name (_h_wnd: *mut c_void, __u_msg: u32,
407                    __w_param: usize, __l_param: isize
408                ) -> u32 {
409                let _ = #safe as ::arcdps::WndProcCallback;
410                use ::arcdps::{WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP};
411                match __u_msg {
412                    WM_KEYDOWN | WM_KEYUP | WM_SYSKEYDOWN | WM_SYSKEYUP => {
413                        let __key_down = __u_msg & 1 == 0;
414                        let __prev_key_down = (__l_param >> 30) & 1 == 1;
415
416                        if #safe(__w_param, __key_down, __prev_key_down)
417                        {
418                            __u_msg
419                        } else {
420                            0
421                        }
422                    },
423                    _ => __u_msg,
424                }
425            });
426            quote_spanned!(span => Some(__arcdps_gen_export::#func_name as _) )
427        }
428        _ => quote! { None },
429    };
430    (abstract_wnd_filter, cb_wnd_filter)
431}
432
433fn build_options_windows(
434    raw_options_windows: Option<Expr>,
435    options_windows: Option<Expr>,
436) -> (TokenStream, TokenStream) {
437    let mut abstract_options_windows = quote! {};
438    let cb_options_windows = match (raw_options_windows, options_windows) {
439        (Some(raw), _) => {
440            let span = syn::Error::new_spanned(&raw, "").span();
441            quote_spanned!(span => Some(#raw as _) )
442        }
443        (_, Some(safe)) => {
444            let span = syn::Error::new_spanned(&safe, "").span();
445            abstract_options_windows = quote_spanned!(span =>
446            unsafe extern "C" fn __abstract_options_windows(__window_name: *mut c_char) -> bool {
447                let _ = #safe as ::arcdps::OptionsWindowsCallback;
448                let __ui = &raw const __UI;
449                let __ui = (*__ui).as_ref().unwrap();
450                #safe(__ui, ::arcdps::helpers::get_str_from_pc_char(__window_name))
451            });
452            quote_spanned!(span => Some(__arcdps_gen_export::__abstract_options_windows as _) )
453        }
454        _ => quote! { None },
455    };
456    (abstract_options_windows, cb_options_windows)
457}
458
459fn build_options_end(
460    raw_options_end: Option<Expr>,
461    options_end: Option<Expr>,
462) -> (TokenStream, TokenStream) {
463    let mut abstract_options_end = quote! {};
464    let cb_options_end = match (raw_options_end, options_end) {
465        (Some(raw), _) => {
466            let span = syn::Error::new_spanned(&raw, "").span();
467            quote_spanned!(span => Some(#raw as _) )
468        }
469        (_, Some(safe)) => {
470            let span = syn::Error::new_spanned(&safe, "").span();
471            abstract_options_end = quote_spanned!(span =>
472            unsafe extern "C" fn __abstract_options_end() {
473                let _ = #safe as ::arcdps::OptionsCallback;
474                let __ui = &raw const __UI;
475                let __ui = (*__ui).as_ref().unwrap();
476                #safe(__ui)
477            });
478            quote_spanned!(span => Some(__arcdps_gen_export::__abstract_options_end as _) )
479        }
480        _ => quote! { None },
481    };
482    (abstract_options_end, cb_options_end)
483}
484
485fn build_imgui(raw_imgui: Option<Expr>, imgui: Option<Expr>) -> (TokenStream, TokenStream) {
486    let mut abstract_imgui = quote! {};
487    let cb_imgui = match (raw_imgui, imgui) {
488        (Some(raw), _) => {
489            let span = syn::Error::new_spanned(&raw, "").span();
490            quote_spanned!(span => Some(#raw as _) )
491        }
492        (_, Some(safe)) => {
493            let span = syn::Error::new_spanned(&safe, "").span();
494            abstract_imgui = quote_spanned!(span =>
495            unsafe extern "C" fn __abstract_imgui(__loading: u32) {
496                let _ = #safe as ::arcdps::ImguiCallback;
497                let __ui = &raw const __UI;
498                let __ui = (*__ui).as_ref().unwrap();
499                #safe(__ui, __loading != 0)
500            });
501            quote_spanned!(span => Some(__arcdps_gen_export::__abstract_imgui as _) )
502        }
503        _ => quote! { None },
504    };
505    (abstract_imgui, cb_imgui)
506}
507
508fn build_combat_local(
509    raw_combat: Option<Expr>,
510    combat: Option<Expr>,
511) -> (TokenStream, TokenStream) {
512    build_cbt(raw_combat, combat, quote! { __abstract_combat_local })
513}
514
515fn build_combat(raw_combat: Option<Expr>, combat: Option<Expr>) -> (TokenStream, TokenStream) {
516    build_cbt(raw_combat, combat, quote! { __abstract_combat })
517}
518
519fn build_cbt(
520    raw_combat: Option<Expr>,
521    combat: Option<Expr>,
522    func_name: TokenStream,
523) -> (TokenStream, TokenStream) {
524    let mut abstract_combat = quote! {};
525    let cb_combat = match (raw_combat, combat) {
526        (Some(raw), _) => {
527            let span = syn::Error::new_spanned(&raw, "").span();
528            quote_spanned!(span => Some(#raw as _) )
529        }
530        (_, Some(safe)) => {
531            let span = syn::Error::new_spanned(&safe, "").span();
532            abstract_combat = quote_spanned!(span =>
533            unsafe extern "C" fn #func_name(
534                    __ev: Option<&::arcdps::CombatEvent>,
535                    __src: Option<&::arcdps::RawAgent>,
536                    __dst: Option<&::arcdps::RawAgent>,
537                    __skill_name: *mut c_char,
538                    __id: u64,
539                    __revision: u64,
540                ) {
541                    let _ = #safe as ::arcdps::CombatCallback;
542                    let __args = ::arcdps::helpers::get_combat_args_from_raw(__ev, __src, __dst, __skill_name);
543                    #safe(__args.ev, __args.src, __args.dst, __args.skill_name, __id, __revision)
544            });
545            quote_spanned!(span => Some(__arcdps_gen_export::#func_name as _) )
546        }
547        _ => quote! { None },
548    };
549    (abstract_combat, cb_combat)
550}