arcdps/
lib.rs

1//! Bindings for [ArcDPS](https://www.deltaconnected.com/arcdps/) plugins.
2//!
3//! # Usage
4//! Plugins export information for ArcDPS via the [`export!`] macro.
5//! To see which fields are supported by it, have a look at [`SupportedFields`].
6//!
7//! ```no_run
8//! # mod test {
9//! use std::error::Error;
10//! use arcdps::{Agent, Event, StateChange};
11//!
12//! arcdps::export! {
13//!     name: "Example Plugin",
14//!     sig: 0x12345678, // change this to a random number
15//!     init,
16//!     combat: custom_combat_name,
17//! }
18//!
19//! fn init() -> Result<(), String> {
20//!     // may return an error to indicate load failure
21//!     Ok(())
22//! }
23//!
24//! fn custom_combat_name(
25//!     event: Option<&Event>,
26//!     src: Option<&Agent>,
27//!     dst: Option<&Agent>,
28//!     skill_name: Option<&str>,
29//!     id: u64,
30//!     revision: u64,
31//! ) {
32//!     if let Some(event) = event {
33//!         if let StateChange::EnterCombat = event.get_statechange() {
34//!             // source agent has entered combat
35//!         }
36//!     }
37//! }
38//! # }
39//! ```
40//!
41//! # Unofficial Extras
42//! [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) support is hidden behind the `extras` feature flag.
43//!
44//! ```no_run
45//! # mod test {
46//! use arcdps::extras::{UserInfoIter, UserRole};
47//!
48//! arcdps::export! {
49//!     name: "Example Plugin",
50//!     sig: 123,
51//!     extras_squad_update,
52//! }
53//!
54//! fn extras_squad_update(users: UserInfoIter) {
55//!     for user in users {
56//!         if let UserRole::SquadLeader | UserRole::Lieutenant = user.role {
57//!             // user can place markers
58//!         }
59//!     }
60//! }
61//! # }
62//! ```
63
64#![allow(clippy::missing_safety_doc)]
65
66pub mod callbacks;
67pub mod evtc;
68pub mod exports;
69
70#[cfg(feature = "extras")]
71pub mod extras;
72
73#[cfg(feature = "log")]
74pub mod log;
75
76mod globals;
77mod panic;
78mod util;
79
80pub use arcdps_codegen::export;
81pub use arcdps_imgui as imgui;
82pub use evtc::{
83    Activation, Affinity, Agent, AgentOwned, Attribute, BuffCategory, BuffCycle, BuffRemove,
84    CustomSkill, Event, Language, Profession, Specialization, StateChange, Strike,
85};
86pub use globals::{d3d11_device, d3d_version, dxgi_swap_chain};
87pub use util::strip_account_prefix;
88
89use callbacks::*;
90
91#[cfg(feature = "extras")]
92use extras::callbacks::*;
93
94/// Reference on what fields are currently supported by the [`export!`] macro.
95///
96/// This struct is not used anywhere.
97pub struct SupportedFields {
98    /// Name of the plugin.
99    pub name: &'static str,
100
101    /// Unique signature of the plugin.
102    ///
103    /// Pick a random number that is not used by other modules.
104    pub sig: u32,
105
106    /// Callback for plugin load.
107    pub init: Option<InitFunc>,
108
109    /// Callback for plugin unload.
110    pub release: Option<ReleaseFunc>,
111
112    /// Callback for plugin unload.
113    // TODO: higher level abstraction?
114    pub update_url: Option<UpdateUrlFunc>,
115
116    /// Raw WndProc callback.
117    pub raw_wnd_nofilter: Option<RawWndProcCallback>,
118
119    /// Raw ImGui callback.
120    pub raw_imgui: Option<RawImguiCallback>,
121
122    /// Raw options callback.
123    pub raw_options_end: Option<RawOptionsCallback>,
124
125    /// Raw combat callback.
126    pub raw_combat: Option<RawCombatCallback>,
127
128    /// Raw filtered WndProc callback.
129    pub raw_wnd_filter: Option<RawWndProcCallback>,
130
131    /// Raw options windows callback.
132    pub raw_options_windows: Option<RawOptionsWindowsCallback>,
133
134    /// Raw local combat callback.
135    pub raw_combat_local: Option<RawCombatCallback>,
136
137    /// Callback for key presses.
138    ///
139    /// Returning `true` will allow ArcDPS and GW2 to receive the key press.
140    /// First parameter indicates the [virtual key code](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
141    /// Second parameter is `true` if the key was pressed and `false` when released.
142    /// Third parameter is `true` if the key was down before this event occurred, for example by holding it down.
143    pub wnd_nofilter: Option<WndProcCallback>,
144
145    /// Callback for area combat events.
146    ///
147    /// May be called asynchronously, use `id` to keep track of order.
148    /// First event id will be `2`.
149    ///
150    /// At least one participant will be a party/squad member or minion of, or a buff applied by squad in the case of buff remove.
151    /// Not all statechanges are present in the realtime API, see [`StateChange`] for details.
152    ///
153    /// No `event` and `src.elite == 0` indicates a tracking change.
154    /// Player was added when `src.prof != 0`, otherwise removed.
155    /// When added `dst.name` contains the account name,
156    /// `dst.id` the instance id,
157    /// `dst.prof` the [`Profession`],
158    /// `dst.elite` the elite [`Specialization`],
159    /// `dst.is_self` whether the added player is self (local player),
160    /// `src.team` the team and `dst.team` the subgroup.
161    ///
162    /// No `event` and `src.elite != 0` indicates a target change.
163    /// `src.id` will contain the new target.
164    ///
165    /// *Note that Arc's realtime combat API comes with an intentional delay and filtering.*
166    pub combat: Option<CombatCallback>,
167
168    /// Callback for standalone UI creation.
169    ///
170    /// Provides an [`imgui::Ui`] for drawing.
171    /// The second parameter is `true` whenever the player is **not** in character select, loading screens or forced cameras.
172    pub imgui: Option<ImguiCallback>,
173
174    /// Callback for plugin settings UI creation.
175    ///
176    /// Provides an [`imgui::Ui`] for drawing.
177    pub options_end: Option<OptionsCallback>,
178
179    /// Callback for local combat events.
180    ///
181    /// Same as [`combat`](Self::combat) but for events from chat log.
182    pub combat_local: Option<CombatCallback>,
183
184    /// Callback for filtered key presses.
185    ///
186    /// Same as [`wnd_nofilter`](Self::wnd_nofilter) but filtered to only notify when modifiers are pressed.
187    pub wnd_filter: Option<WndProcCallback>,
188
189    /// Callback for options windows.
190    ///
191    /// Called for each window checkbox in ArcDPS settings.
192    /// Last call will always be with [`None`].
193    /// Does not draw the checkbox if returning `true`.
194    pub options_windows: Option<OptionsWindowsCallback>,
195
196    /// Raw extras init callback.
197    ///
198    /// *Requires the `"extras"` feature.*
199    #[cfg(feature = "extras")]
200    pub raw_extras_init: Option<RawExtrasSubscriberInit>,
201
202    /// Raw extras squad update callback.
203    ///
204    /// *Requires the `"extras"` feature.*
205    #[cfg(feature = "extras")]
206    pub raw_extras_squad_update: Option<RawExtrasSquadUpdateCallback>,
207
208    /// Raw extras language changed callback.
209    ///
210    /// *Requires the `"extras"` feature.*
211    #[cfg(feature = "extras")]
212    pub raw_extras_language_changed: Option<RawExtrasLanguageChangedCallback>,
213
214    /// Raw extras keybind changed callback.
215    ///
216    /// *Requires the `"extras"` feature.*
217    #[cfg(feature = "extras")]
218    pub raw_extras_keybind_changed: Option<RawExtrasKeybindChangedCallback>,
219
220    /// Raw extras chat message callback.
221    ///
222    /// *Requires the `"extras"` feature.*
223    #[cfg(feature = "extras")]
224    pub raw_extras_chat_message: Option<RawExtrasChatMessageCallback>,
225
226    /// Initialization callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
227    ///
228    /// Can be called before or after ArcDPS [`init`](Self::init).
229    /// Receives information about the Unofficial Extras addon and the current player account name as parameters.
230    ///
231    /// *Requires the `"extras"` feature.*
232    #[cfg(feature = "extras")]
233    pub extras_init: Option<ExtrasInitFunc>,
234
235    /// Squad update callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
236    ///
237    /// Called whenever anything in the squad changes.
238    /// Only the users that changed are sent.
239    /// If a user was removed from the squad, their `role` will be set to [`UserRole::None`](crate::extras::UserRole::None).
240    ///
241    /// *Requires the `"extras"` feature.*
242    #[cfg(feature = "extras")]
243    pub extras_squad_update: Option<ExtrasSquadUpdateCallback>,
244
245    /// Language changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
246    ///
247    /// Called whenever the language is changed, either by changing it in the UI or by pressing the translation key (Right Ctrl by default).
248    ///
249    /// Will be called directly after initialization, with the current language, to get the startup language.
250    ///
251    /// *Requires the `"extras"` feature.*
252    #[cfg(feature = "extras")]
253    pub extras_language_changed: Option<ExtrasLanguageChangedCallback>,
254
255    /// Keybind changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
256    ///
257    /// Called whenever a keybind is changed, either by changing it in the ingame UI or with the presets feature of Unofficial Extras.
258    /// It is called for every keybind separately.
259    ///
260    /// After initialization this is called for every current keybind that exists.
261    /// If you want to get a single keybind, at any time you want, call the exported function.
262    ///
263    /// *Requires the `"extras"` feature.*
264    #[cfg(feature = "extras")]
265    pub extras_keybind_changed: Option<ExtrasKeybindChangedCallback>,
266
267    /// Squad chat message callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
268    ///
269    /// Called whenever a chat message is sent in your party/squad.
270    ///
271    /// *Requires the `"extras"` feature.*
272    #[cfg(feature = "extras")]
273    pub extras_squad_chat_message: Option<ExtrasSquadChatMessageCallback>,
274
275    /// Chat message callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
276    ///
277    /// Called on different chat messages.
278    ///
279    /// *Requires the `"extras"` feature.*
280    #[cfg(feature = "extras")]
281    pub extras_chat_message: Option<ExtrasChatMessageCallback>,
282}
283
284/// Exports for usage in macros.
285#[doc(hidden)]
286pub mod __macro {
287    pub use crate::{
288        globals::{FreeFn, MallocFn},
289        util::{str_from_cstr, str_to_wide, strip_account_prefix},
290    };
291    pub use std::os::raw::{c_char, c_void};
292    pub use windows::Win32::{
293        Foundation::{HMODULE, HWND, LPARAM, WPARAM},
294        UI::WindowsAndMessaging::{WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP},
295    };
296
297    use crate::{
298        exports::has_e3_log_file,
299        globals::{init_dxgi, init_imgui, ArcGlobals, IG_CONTEXT},
300        imgui,
301        panic::init_panic_hook,
302    };
303    use std::mem::ManuallyDrop;
304
305    #[cfg(feature = "log")]
306    use crate::{exports::has_e8_log_window, log::ArcDpsLogger};
307
308    /// Internally used function to initialize with information received from Arc.
309    #[inline]
310    #[allow(clippy::too_many_arguments)]
311    pub unsafe fn init(
312        arc_version: *const c_char,
313        arc_handle: HMODULE,
314        imgui_ctx: *mut imgui::sys::ImGuiContext,
315        malloc: Option<MallocFn>,
316        free: Option<FreeFn>,
317        id3d: *const c_void,
318        d3d_version: u32,
319        name: &'static str,
320    ) {
321        // arc exports have to be retrieved before panic hook & logging
322        ArcGlobals::init(arc_handle, str_from_cstr(arc_version));
323
324        // only set panic hook if log file export was found
325        if has_e3_log_file() {
326            init_panic_hook(name);
327
328            // only set logger if log file & window exports were found
329            #[cfg(feature = "log")]
330            if has_e8_log_window() {
331                let result = log::set_boxed_logger(Box::new(ArcDpsLogger::new(name)));
332                if result.is_ok() {
333                    log::set_max_level(log::LevelFilter::Trace);
334                }
335            }
336        }
337
338        // initialize imgui & dxgi
339        init_imgui(imgui_ctx, malloc, free);
340        init_dxgi(id3d, d3d_version, name);
341    }
342
343    /// Internally used function to retrieve the [`imgui::Ui`].
344    #[inline]
345    pub unsafe fn ui() -> ManuallyDrop<imgui::Ui<'static>> {
346        ManuallyDrop::new(imgui::Ui::from_ctx(
347            &IG_CONTEXT.get().expect("imgui not initialized").0,
348        ))
349    }
350}