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