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}