Skip to main content

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<(), Option<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//! # Initializing manually
65//!
66//! When not using the [`export!`] macro, Arc, ImGui, and DirectX information has to be initialized manually.
67//! Accessing Arc information/exports or ImGui without initializing them will **panic**.
68//!
69//! ```ignore
70//! unsafe {
71//!     arcdps::init_arc(arc_handle, arc_version);
72//!     arcdps::init_imgui(imgui_ctx, malloc, free);
73//!     arcdps::init_dxgi(id3d);
74//! }
75//! ```
76//! The bindings can also attempt to initialize Arc by searching modules in the current process.
77//!
78//! ```ignore
79//! if let Err(err) = unsafe { arcdps::search_and_init_arc() } {
80//!     log::error!("Failed to find ArcDPS: {err}");
81//! }
82//! ```
83
84#![allow(clippy::missing_safety_doc)]
85
86pub mod callbacks;
87pub mod evtc;
88pub mod exports;
89
90#[cfg(feature = "extras")]
91pub mod extras;
92
93#[cfg(feature = "log")]
94pub mod log;
95
96#[cfg(feature = "panic")]
97mod panic;
98
99mod globals;
100mod util;
101
102#[cfg(feature = "codegen")]
103pub use arcdps_codegen::export;
104
105pub use crate::globals::{
106    arc::{init_arc, search_and_init_arc, search_arc_handle},
107    dxgi::{d3d11_device, dxgi_swap_chain, init_dxgi},
108    imgui::{imgui_context, init_imgui, with_ui},
109};
110pub use crate::util::strip_account_prefix;
111pub use arcdps_imgui as imgui;
112pub use evtc::{
113    Affinity, Agent, AgentOwned, Attribute, BuffCategory, CombatResult, CustomSkill, Event,
114    Language, Profession, Specialization, StateChange,
115};
116
117#[cfg(feature = "panic")]
118pub use crate::panic::init_panic_hook;
119
120use callbacks::*;
121
122#[cfg(feature = "extras")]
123use extras::callbacks::*;
124
125/// Reference on what fields are currently supported by the [`export!`] macro.
126///
127/// This struct is not used anywhere.
128pub struct SupportedFields {
129    /// Name of the plugin.
130    pub name: &'static str,
131
132    /// Unique signature of the plugin.
133    ///
134    /// Pick a random number that is not used by other modules.
135    pub sig: u32,
136
137    /// Callback for plugin load.
138    ///
139    /// May return an error with an optional error message to signal load failure.
140    pub init: Option<InitFunc>,
141
142    /// Callback for plugin unload.
143    pub release: Option<ReleaseFunc>,
144
145    /// Callback for plugin unload.
146    // TODO: higher level abstraction?
147    pub update_url: Option<UpdateUrlFunc>,
148
149    /// Raw WndProc callback.
150    pub raw_wnd_nofilter: Option<RawWndProcCallback>,
151
152    /// Raw ImGui callback.
153    pub raw_imgui: Option<RawImguiCallback>,
154
155    /// Raw options callback.
156    pub raw_options_end: Option<RawOptionsCallback>,
157
158    /// Raw combat callback.
159    pub raw_combat: Option<RawCombatCallback>,
160
161    /// Raw filtered WndProc callback.
162    pub raw_wnd_filter: Option<RawWndProcCallback>,
163
164    /// Raw options windows callback.
165    pub raw_options_windows: Option<RawOptionsWindowsCallback>,
166
167    /// Raw local combat callback.
168    pub raw_combat_local: Option<RawCombatCallback>,
169
170    /// Callback for key presses.
171    ///
172    /// Returning `true` will allow ArcDPS and GW2 to receive the key press.
173    /// First parameter indicates the [virtual key code](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
174    /// Second parameter is `true` if the key was pressed and `false` when released.
175    /// Third parameter is `true` if the key was down before this event occurred, for example by holding it down.
176    pub wnd_nofilter: Option<WndProcCallback>,
177
178    /// Callback for area combat events.
179    ///
180    /// May be called asynchronously, use `id` to keep track of order.
181    /// First event id will be `2`.
182    ///
183    /// 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.
184    /// Not all statechanges are present in the realtime API, see [`StateChange`] for details.
185    ///
186    /// No `event` and `src.elite == 0` indicates a tracking change.
187    /// Player was added when `src.prof != 0`, otherwise removed.
188    /// When added `dst.name` contains the account name,
189    /// `dst.id` the instance id,
190    /// `dst.prof` the [`Profession`],
191    /// `dst.elite` the elite [`Specialization`],
192    /// `dst.is_self` whether the added player is self (local player),
193    /// `src.team` the team and `dst.team` the subgroup.
194    ///
195    /// No `event` and `src.elite != 0` indicates a target change.
196    /// `src.id` will contain the new target.
197    ///
198    /// *Note that Arc's realtime combat API comes with an intentional delay and filtering.*
199    pub combat: Option<CombatCallback>,
200
201    /// Callback for standalone UI creation.
202    ///
203    /// Provides an [`imgui::Ui`] for drawing.
204    /// The second parameter is `true` whenever the player is **not** in character select, loading screens or forced cameras.
205    pub imgui: Option<ImguiCallback>,
206
207    /// Callback for plugin settings UI creation.
208    ///
209    /// Provides an [`imgui::Ui`] for drawing.
210    pub options_end: Option<OptionsCallback>,
211
212    /// Callback for local combat events.
213    ///
214    /// Same as [`combat`](Self::combat) but for events from chat log.
215    pub combat_local: Option<CombatCallback>,
216
217    /// Callback for filtered key presses.
218    ///
219    /// Same as [`wnd_nofilter`](Self::wnd_nofilter) but filtered to only notify when modifiers are pressed.
220    pub wnd_filter: Option<WndProcCallback>,
221
222    /// Callback for options windows.
223    ///
224    /// Called for each window checkbox in ArcDPS settings.
225    /// Last call will always be with [`None`].
226    /// Does not draw the checkbox if returning `true`.
227    pub options_windows: Option<OptionsWindowsCallback>,
228
229    /// Raw extras init callback.
230    ///
231    /// *Requires the `"extras"` feature.*
232    #[cfg(feature = "extras")]
233    pub raw_extras_init: Option<RawExtrasSubscriberInit>,
234
235    /// Raw extras squad update callback.
236    ///
237    /// *Requires the `"extras"` feature.*
238    #[cfg(feature = "extras")]
239    pub raw_extras_squad_update: Option<RawExtrasSquadUpdateCallback>,
240
241    /// Raw extras language changed callback.
242    ///
243    /// *Requires the `"extras"` feature.*
244    #[cfg(feature = "extras")]
245    pub raw_extras_language_changed: Option<RawExtrasLanguageChangedCallback>,
246
247    /// Raw extras keybind changed callback.
248    ///
249    /// *Requires the `"extras"` feature.*
250    #[cfg(feature = "extras")]
251    pub raw_extras_keybind_changed: Option<RawExtrasKeybindChangedCallback>,
252
253    /// Raw extras chat message callback.
254    ///
255    /// *Requires the `"extras"` feature.*
256    #[cfg(feature = "extras")]
257    pub raw_extras_chat_message: Option<RawExtrasChatMessageCallback>,
258
259    /// Initialization callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
260    ///
261    /// Can be called before or after ArcDPS [`init`](Self::init).
262    /// Receives information about the Unofficial Extras addon and the current player account name as parameters.
263    ///
264    /// *Requires the `"extras"` feature.*
265    #[cfg(feature = "extras")]
266    pub extras_init: Option<ExtrasInitFunc>,
267
268    /// Squad update callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
269    ///
270    /// Called whenever anything in the squad changes.
271    /// Only the users that changed are sent.
272    /// If a user was removed from the squad, their `role` will be set to [`UserRole::None`](crate::extras::UserRole::None).
273    ///
274    /// *Requires the `"extras"` feature.*
275    #[cfg(feature = "extras")]
276    pub extras_squad_update: Option<ExtrasSquadUpdateCallback>,
277
278    /// Language changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
279    ///
280    /// Called whenever the language is changed, either by changing it in the UI or by pressing the translation key (Right Ctrl by default).
281    ///
282    /// Will be called directly after initialization, with the current language, to get the startup language.
283    ///
284    /// *Requires the `"extras"` feature.*
285    #[cfg(feature = "extras")]
286    pub extras_language_changed: Option<ExtrasLanguageChangedCallback>,
287
288    /// Keybind changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
289    ///
290    /// Called whenever a keybind is changed, either by changing it in the ingame UI or with the presets feature of Unofficial Extras.
291    /// It is called for every keybind separately.
292    ///
293    /// After initialization this is called for every current keybind that exists.
294    /// If you want to get a single keybind, at any time you want, call the exported function.
295    ///
296    /// *Requires the `"extras"` feature.*
297    #[cfg(feature = "extras")]
298    pub extras_keybind_changed: Option<ExtrasKeybindChangedCallback>,
299
300    /// Squad chat message callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
301    ///
302    /// Called whenever a chat message is sent in your party/squad.
303    ///
304    /// *Requires the `"extras"` feature.*
305    #[cfg(feature = "extras")]
306    pub extras_squad_chat_message: Option<ExtrasSquadChatMessageCallback>,
307
308    /// Chat message callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
309    ///
310    /// Called on different chat messages.
311    ///
312    /// *Requires the `"extras"` feature.*
313    #[cfg(feature = "extras")]
314    pub extras_chat_message: Option<ExtrasChatMessageCallback>,
315}
316
317/// Exports for usage in macros.
318#[doc(hidden)]
319pub mod __macro {
320    pub use crate::{
321        globals::imgui::{FreeFn, IMGUI_VERSION, MallocFn, with_ui},
322        util::{str_from_cstr, str_to_wide, strip_account_prefix},
323    };
324    pub use std::ffi::{c_char, c_void};
325    pub use windows::Win32::{
326        Foundation::{HMODULE, HWND, LPARAM, WPARAM},
327        UI::WindowsAndMessaging::{WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP},
328    };
329
330    use crate::{
331        exports::has_e3_log_file,
332        globals::{dxgi::init_dxgi, imgui::init_imgui},
333        imgui, init_arc,
334    };
335
336    #[cfg(feature = "panic")]
337    use crate::panic::init_panic_hook;
338
339    #[cfg(feature = "log")]
340    use crate::{exports::has_e8_log_window, log::ArcDpsLogger};
341
342    /// Internally used function to initialize with information received from Arc.
343    #[inline]
344    #[allow(clippy::too_many_arguments)]
345    pub unsafe fn init(
346        arc_version: *const c_char,
347        arc_handle: HMODULE,
348        imgui_ctx: *mut imgui::sys::ImGuiContext,
349        malloc: Option<MallocFn>,
350        free: Option<FreeFn>,
351        imgui_version: u32,
352        id3d: *mut c_void,
353        name: &'static str,
354    ) {
355        // arc exports have to be retrieved before panic hook & logging
356        unsafe { init_arc(arc_handle, arc_version) };
357
358        // only set panic hook if log file export was found
359        if has_e3_log_file() {
360            #[cfg(feature = "panic")]
361            init_panic_hook(name);
362
363            // only set logger if log file & window exports were found
364            #[cfg(feature = "log")]
365            if has_e8_log_window() {
366                let result = log::set_boxed_logger(Box::new(ArcDpsLogger::new(name)));
367                if result.is_ok() {
368                    log::set_max_level(log::LevelFilter::Trace);
369                }
370            }
371        }
372
373        // only initialize imgui if versions match
374        if imgui_version == IMGUI_VERSION {
375            unsafe { init_imgui(imgui_ctx, malloc, free) };
376        }
377
378        // initialize dxgi always
379        unsafe { init_dxgi(id3d) };
380    }
381}