arcdps\extras/
mod.rs

1//! [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) support.
2//!
3//! *Requires the `"extras"` feature.*
4
5pub mod callbacks;
6pub mod exports;
7pub mod keybinds;
8pub mod message;
9pub mod user;
10
11mod globals;
12mod version;
13
14use crate::{util::str_from_cstr, RawExtrasSquadChatMessageCallback};
15use callbacks::{
16    RawExtrasChatMessageCallback, RawExtrasKeybindChangedCallback,
17    RawExtrasLanguageChangedCallback, RawExtrasSquadUpdateCallback,
18};
19pub use keybinds::{Control, Key, KeyCode, Keybind, KeybindChange, MouseCode};
20pub use message::{
21    ChannelType, Message, NpcMessage, NpcMessageOwned, SquadMessage, SquadMessageOwned,
22};
23pub use user::{UserInfo, UserInfoIter, UserInfoOwned, UserRole};
24
25use globals::ExtrasGlobals;
26use std::os::raw::c_char;
27use windows::Win32::Foundation::HMODULE;
28
29pub use version::ExtrasVersion;
30
31#[cfg(feature = "serde")]
32use serde::{Deserialize, Serialize};
33
34/// Information about the [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) addon.
35#[derive(Debug, Clone)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37pub struct ExtrasAddonInfo {
38    /// Version of the API.
39    ///
40    /// Gets incremented whenever a function signature or behavior changes in a breaking way.
41    pub api_version: u32,
42
43    /// Highest known version of the [`ExtrasSubscriberInfo`] struct.
44    ///
45    /// Also determines the size of the subscriber info buffer in the init call.
46    /// The buffer is only guaranteed to have enough space for known [`ExtrasSubscriberInfo`] versions.
47    pub max_info_version: u32,
48
49    /// String version of the Unofficial Extras addon.
50    ///
51    /// Gets changed on every release.
52    pub string_version: Option<&'static str>,
53}
54
55impl ExtrasAddonInfo {
56    /// Returns the corresponding [`ExtrasVersion`].
57    #[inline]
58    pub fn version(&self) -> ExtrasVersion {
59        ExtrasVersion::new(self.api_version, self.max_info_version)
60    }
61}
62
63impl From<RawExtrasAddonInfo> for ExtrasAddonInfo {
64    fn from(raw: RawExtrasAddonInfo) -> Self {
65        Self {
66            api_version: raw.api_version,
67            max_info_version: raw.max_info_version,
68            string_version: unsafe { str_from_cstr(raw.string_version) },
69        }
70    }
71}
72
73#[derive(Debug, Clone)]
74#[repr(C)]
75pub struct RawExtrasAddonInfo {
76    /// Version of the API.
77    ///
78    /// Gets incremented whenever a function signature or behavior changes in a breaking way.
79    pub api_version: u32,
80
81    /// Highest known version of the [`ExtrasSubscriberInfo`] struct.
82    ///
83    /// Also determines the size of the subscriber info buffer in the init call.
84    /// The buffer is only guaranteed to have enough space for known [`ExtrasSubscriberInfo`] versions.
85    pub max_info_version: u32,
86
87    /// String version of the Unofficial Extras addon.
88    ///
89    /// Gets changed on every release.
90    /// The string is valid for the entire lifetime of the Unofficial Extras DLL.
91    pub string_version: *const c_char,
92
93    /// Account name of the logged-in player, including leading `':'`.
94    ///
95    /// The string is only valid for the duration of the init call.
96    pub self_account_name: *const c_char,
97
98    /// The handle to the Unofficial Extras module.
99    ///
100    /// Use this to call the exports of the DLL.
101    pub extras_handle: HMODULE,
102}
103
104impl RawExtrasAddonInfo {
105    /// Returns the corresponding [`ExtrasVersion`].
106    #[inline]
107    pub fn version(&self) -> ExtrasVersion {
108        ExtrasVersion::new(self.api_version, self.max_info_version)
109    }
110}
111
112/// Subscriber header shared across different versions.
113#[derive(Debug)]
114#[repr(C)]
115pub struct ExtrasSubscriberInfoHeader {
116    /// The version of the following info struct.
117    /// This has to be set to the version you want to use.
118    pub info_version: u32,
119
120    /// Unused padding.
121    pub unused1: u32,
122}
123
124/// Information about a subscriber to updates from Unofficial Extras.
125#[derive(Debug)]
126#[repr(C)]
127pub struct ExtrasSubscriberInfo {
128    /// Header shared across different versions.
129    pub header: ExtrasSubscriberInfoHeader,
130
131    /// Name of the addon subscribing to the changes.
132    ///
133    /// Must be valid for the lifetime of the subscribing addon.
134    /// Set to `nullptr` if initialization fails.
135    pub subscriber_name: *const c_char,
136
137    /// Called whenever anything in the squad changes.
138    ///
139    /// Only the users that changed are sent.
140    /// If a user is removed from the squad, it will be sent with `role` set to [`UserRole::None`]
141    pub squad_update_callback: Option<RawExtrasSquadUpdateCallback>,
142
143    /// Called whenever the language is changed.
144    ///
145    /// Either by Changing it in the UI or by pressing the Right Ctrl (default) key.
146    /// Will also be called directly after initialization, with the current language, to get the startup language.
147    pub language_changed_callback: Option<RawExtrasLanguageChangedCallback>,
148
149    /// Called whenever a keybind is changed.
150    ///
151    /// By changing it in the ingame UI, by pressing the translation shortcut or with the Presets feature of this plugin.
152    /// It is called for every keybind separately.
153    ///
154    /// After initialization this is called for every current keybind that exists.
155    /// If you want to get a single keybind, at any time you want, call the exported function.
156    pub keybind_changed_callback: Option<RawExtrasKeybindChangedCallback>,
157
158    /// Called whenever a chat message is sent in your party/squad.
159    pub squad_chat_message_callback: Option<RawExtrasSquadChatMessageCallback>,
160
161    /// Called on different chat messages.
162    pub chat_message_callback: Option<RawExtrasChatMessageCallback>,
163}
164
165impl ExtrasSubscriberInfo {
166    /// Subscribes to unofficial extras callbacks after checking for compatibility.
167    ///
168    /// Unsupported callbacks will be skipped.
169    ///
170    /// # Safety
171    /// Info needs to point to a valid to interpret as subscriber infos with minimum version of [`MIN_SUB_INFO_VERSION`].
172    /// It is passed as pointer to prevent UB by creating a reference to the wrong type.
173    ///
174    /// Name needs to be null-terminated.
175    #[allow(clippy::too_many_arguments)]
176    pub unsafe fn subscribe(
177        sub: *mut Self,
178        extras_addon: &RawExtrasAddonInfo,
179        name: &'static str,
180        squad_update: Option<RawExtrasSquadUpdateCallback>,
181        language_changed: Option<RawExtrasLanguageChangedCallback>,
182        keybind_changed: Option<RawExtrasKeybindChangedCallback>,
183        squad_chat_message: Option<RawExtrasSquadChatMessageCallback>,
184        chat_message: Option<RawExtrasChatMessageCallback>,
185    ) {
186        let version = extras_addon.version();
187        if let Some(sub_info_version) = version.get_version_to_use() {
188            // initialize globals
189            ExtrasGlobals::init(
190                extras_addon.extras_handle,
191                str_from_cstr(extras_addon.string_version),
192            );
193
194            (*sub).header.info_version = sub_info_version;
195            (*sub).subscriber_name = name.as_ptr() as *const c_char;
196            (*sub).squad_update_callback = squad_update;
197            (*sub).language_changed_callback = language_changed;
198            (*sub).keybind_changed_callback = keybind_changed;
199
200            // only attempt to write additional callbacks if supported
201            if version.supports_squad_chat_message() {
202                (*sub).squad_chat_message_callback = squad_chat_message;
203            }
204            if version.supports_chat_message2() {
205                (*sub).chat_message_callback = chat_message;
206            }
207        }
208    }
209}