1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
//! Bindings for [ArcDPS](https://www.deltaconnected.com/arcdps/) plugins.
//!
//! # Usage
//! Plugins export information for ArcDPS via the [`export!`] macro.
//! To see which fields are supported by it, have a look at [`SupportedFields`].
//!
//! ```no_run
//! # mod test {
//! use std::error::Error;
//! use arcdps::{Agent, Event, StateChange};
//!
//! arcdps::export! {
//!     name: "Example Plugin",
//!     sig: 0x12345678, // change this to a random number
//!     init,
//!     combat: custom_combat_name,
//! }
//!
//! fn init() -> Result<(), String> {
//!     // may return an error to indicate load failure
//!     Ok(())
//! }
//!
//! fn custom_combat_name(
//!     event: Option<&Event>,
//!     src: Option<&Agent>,
//!     dst: Option<&Agent>,
//!     skill_name: Option<&str>,
//!     id: u64,
//!     revision: u64,
//! ) {
//!     if let Some(event) = event {
//!         if let StateChange::EnterCombat = event.get_statechange() {
//!             // source agent has entered combat
//!         }
//!     }
//! }
//! # }
//! ```
//!
//! # Unofficial Extras
//! [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) support is hidden behind the `extras` feature flag.
//!
//! ```no_run
//! # mod test {
//! use arcdps::extras::{UserInfoIter, UserRole};
//!
//! arcdps::export! {
//!     name: "Example Plugin",
//!     sig: 123,
//!     extras_squad_update,
//! }
//!
//! fn extras_squad_update(users: UserInfoIter) {
//!     for user in users {
//!         if let UserRole::SquadLeader | UserRole::Lieutenant = user.role {
//!             // user can place markers
//!         }
//!     }
//! }
//! # }
//! ```

#![allow(clippy::missing_safety_doc)]

pub mod callbacks;
pub mod evtc;
pub mod exports;

#[cfg(feature = "extras")]
pub mod extras;

#[cfg(feature = "log")]
pub mod log;

mod globals;
mod panic;
mod util;

pub use arcdps_codegen::export;
pub use arcdps_imgui as imgui;
pub use evtc::{
    Activation, Affinity, Agent, AgentOwned, Attribute, BuffCategory, BuffCycle, BuffRemove,
    CustomSkill, Event, Language, Profession, Specialization, StateChange, Strike,
};
pub use globals::{d3d11_device, d3d_version, dxgi_swap_chain};
pub use util::strip_account_prefix;

use callbacks::*;

#[cfg(feature = "extras")]
use extras::callbacks::*;

/// Reference on what fields are currently supported by the [`export!`] macro.
///
/// This struct is not used anywhere.
pub struct SupportedFields {
    /// Name of the plugin.
    pub name: &'static str,

    /// Unique signature of the plugin.
    ///
    /// Pick a random number that is not used by other modules.
    pub sig: u32,

    /// Callback for plugin load.
    pub init: Option<InitFunc>,

    /// Callback for plugin unload.
    pub release: Option<ReleaseFunc>,

    /// Callback for plugin unload.
    // TODO: higher level abstraction?
    pub update_url: Option<UpdateUrlFunc>,

    /// Raw WndProc callback.
    pub raw_wnd_nofilter: Option<RawWndProcCallback>,

    /// Raw ImGui callback.
    pub raw_imgui: Option<RawImguiCallback>,

    /// Raw options callback.
    pub raw_options_end: Option<RawOptionsCallback>,

    /// Raw combat callback.
    pub raw_combat: Option<RawCombatCallback>,

    /// Raw filtered WndProc callback.
    pub raw_wnd_filter: Option<RawWndProcCallback>,

    /// Raw options windows callback.
    pub raw_options_windows: Option<RawOptionsWindowsCallback>,

    /// Raw local combat callback.
    pub raw_combat_local: Option<RawCombatCallback>,

    /// Callback for key presses.
    ///
    /// Returning `true` will allow ArcDPS and GW2 to receive the key press.
    /// First parameter indicates the [virtual key code](https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes).
    /// Second parameter is `true` if the key was pressed and `false` when released.
    /// Third parameter is `true` if the key was down before this event occurred, for example by holding it down.
    pub wnd_nofilter: Option<WndProcCallback>,

    /// Callback for area combat events.
    ///
    /// May be called asynchronously, use `id` to keep track of order.
    /// First event id will be `2`.
    ///
    /// 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.
    /// Not all statechanges are present in the realtime API, see [`StateChange`] for details.
    ///
    /// No `event` and `src.elite == 0` indicates a tracking change.
    /// Player was added when `src.prof != 0`, otherwise removed.
    /// When added `dst.name` contains the account name,
    /// `dst.id` the instance id,
    /// `dst.prof` the [`Profession`],
    /// `dst.elite` the elite [`Specialization`],
    /// `dst.is_self` whether the added player is self (local player),
    /// `src.team` the team and `dst.team` the subgroup.
    ///
    /// No `event` and `src.elite != 0` indicates a target change.
    /// `src.id` will contain the new target.
    ///
    /// *Note that Arc's realtime combat API comes with an intentional delay and filtering.*
    pub combat: Option<CombatCallback>,

    /// Callback for standalone UI creation.
    ///
    /// Provides an [`imgui::Ui`] for drawing.
    /// The second parameter is `true` whenever the player is **not** in character select, loading screens or forced cameras.
    pub imgui: Option<ImguiCallback>,

    /// Callback for plugin settings UI creation.
    ///
    /// Provides an [`imgui::Ui`] for drawing.
    pub options_end: Option<OptionsCallback>,

    /// Callback for local combat events.
    ///
    /// Same as [`combat`](Self::combat) but for events from chat log.
    pub combat_local: Option<CombatCallback>,

    /// Callback for filtered key presses.
    ///
    /// Same as [`wnd_nofilter`](Self::wnd_nofilter) but filtered to only notify when modifiers are pressed.
    pub wnd_filter: Option<WndProcCallback>,

    /// Callback for options windows.
    ///
    /// Called for each window checkbox in ArcDPS settings.
    /// Last call will always be with [`None`].
    /// Does not draw the checkbox if returning `true`.
    pub options_windows: Option<OptionsWindowsCallback>,

    /// Raw extras init callback.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub raw_extras_init: Option<RawExtrasSubscriberInit>,

    /// Raw extras squad update callback.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub raw_extras_squad_update: Option<RawExtrasSquadUpdateCallback>,

    /// Raw extras language changed callback.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub raw_extras_language_changed: Option<RawExtrasLanguageChangedCallback>,

    /// Raw extras keybind changed callback.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub raw_extras_keybind_changed: Option<RawExtrasKeybindChangedCallback>,

    /// Raw extras chat message callback.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub raw_extras_chat_message: Option<RawExtrasChatMessageCallback>,

    /// Initialization callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
    ///
    /// Can be called before or after ArcDPS [`init`](Self::init).
    /// Receives information about the Unofficial Extras addon and the current player account name as parameters.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub extras_init: Option<ExtrasInitFunc>,

    /// Squad update callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
    ///
    /// Called whenever anything in the squad changes.
    /// Only the users that changed are sent.
    /// If a user was removed from the squad, their `role` will be set to [`UserRole::None`](crate::extras::UserRole::None).
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub extras_squad_update: Option<ExtrasSquadUpdateCallback>,

    /// Language changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
    ///
    /// Called whenever the language is changed, either by changing it in the UI or by pressing the translation key (Right Ctrl by default).
    ///
    /// Will be called directly after initialization, with the current language, to get the startup language.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub extras_language_changed: Option<ExtrasLanguageChangedCallback>,

    /// Keybind changed callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
    ///
    /// Called whenever a keybind is changed, either by changing it in the ingame UI or with the presets feature of Unofficial Extras.
    /// It is called for every keybind separately.
    ///
    /// After initialization this is called for every current keybind that exists.
    /// If you want to get a single keybind, at any time you want, call the exported function.
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub extras_keybind_changed: Option<ExtrasKeybindChangedCallback>,

    /// Chat message callback for [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases).
    ///
    /// Called whenever a chat message is sent in your party/squad
    ///
    /// *Requires the `"extras"` feature.*
    #[cfg(feature = "extras")]
    pub extras_chat_message: Option<ExtrasChatMessageCallback>,
}

/// Exports for usage in macros.
#[doc(hidden)]
pub mod __macro {
    pub use crate::{
        globals::{FreeFn, MallocFn},
        util::{str_from_cstr, str_to_wide, strip_account_prefix},
    };
    pub use std::os::raw::{c_char, c_void};
    pub use windows::Win32::{
        Foundation::{HMODULE, HWND, LPARAM, WPARAM},
        UI::WindowsAndMessaging::{WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP},
    };

    use crate::{
        exports::has_e3_log_file,
        globals::{init_dxgi, init_imgui, ARC_GLOBALS, IG_UI},
        imgui,
        panic::init_panic_hook,
    };

    #[cfg(feature = "log")]
    use crate::{exports::has_e8_log_window, log::ArcDpsLogger};

    /// Internally used function to initialize with information received from Arc.
    #[inline]
    #[allow(clippy::too_many_arguments)]
    pub unsafe fn init(
        arc_version: *const c_char,
        arc_handle: HMODULE,
        imgui_ctx: *mut imgui::sys::ImGuiContext,
        malloc: Option<MallocFn>,
        free: Option<FreeFn>,
        id3d: *const c_void,
        d3d_version: u32,
        name: &'static str,
    ) {
        // arc exports have to be retrieved before panic hook & logging
        ARC_GLOBALS.init(arc_handle, str_from_cstr(arc_version));

        // only set panic hook if log file export was found
        if has_e3_log_file() {
            init_panic_hook(name);

            // only set logger if log file & window exports were found
            #[cfg(feature = "log")]
            if has_e8_log_window() {
                let result = log::set_boxed_logger(Box::new(ArcDpsLogger::new(name)));
                if result.is_ok() {
                    log::set_max_level(log::LevelFilter::Trace);
                }
            }
        }

        // initialize imgui & dxgi
        init_imgui(imgui_ctx, malloc, free);
        init_dxgi(id3d, d3d_version, name);
    }

    /// Internally used function to retrieve the [`imgui::Ui`].
    #[inline]
    pub unsafe fn ui() -> &'static imgui::Ui<'static> {
        IG_UI.as_ref().expect("imgui ui not initialized")
    }
}