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
//! [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) support.
//!
//! *Requires the `"extras"` feature.*
pub mod callbacks;
pub mod exports;
pub mod keybinds;
pub mod message;
pub mod user;
mod globals;
pub use keybinds::{Control, Key, KeyCode, Keybind, KeybindChange, MouseCode};
pub use message::{ChannelType, ChatMessageInfo, ChatMessageInfoOwned};
pub use user::{UserInfo, UserInfoIter, UserInfoOwned, UserRole};
use crate::util::str_from_cstr;
use callbacks::{
RawExtrasChatMessageCallback, RawExtrasKeybindChangedCallback,
RawExtrasLanguageChangedCallback, RawExtrasSquadUpdateCallback,
};
use globals::EXTRAS_GLOBALS;
use std::{ops::RangeInclusive, os::raw::c_char};
use windows::Win32::Foundation::HMODULE;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Supported Unofficial Extras API version.
const API_VERSION: u32 = 2;
/// Supported [`ExtrasSubscriberInfo`] version range.
const SUB_INFO_RANGE: RangeInclusive<u32> = 1..=2;
/// Version with message callback addition.
const MESSAGE_CALLBACK: u32 = 2;
/// Helper to check compatibility.
#[inline]
fn check_compat(api_version: u32, sub_info_version: u32) -> bool {
api_version == API_VERSION && SUB_INFO_RANGE.contains(&sub_info_version)
}
/// Information about the [Unofficial Extras](https://github.com/Krappa322/arcdps_unofficial_extras_releases) addon.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ExtrasAddonInfo {
/// Version of the API.
///
/// Gets incremented whenever a function signature or behavior changes in a breaking way.
///
/// Current version is `2`.
pub api_version: u32,
/// Highest known version of the [`ExtrasSubscriberInfo`] struct.
///
/// Also determines the size of the subscriber info buffer in the init call.
/// The buffer is only guaranteed to have enough space for known [`ExtrasSubscriberInfo`] versions.
///
/// Current version is `2`.
pub max_info_version: u32,
/// String version of the Unofficial Extras addon.
///
/// Gets changed on every release.
pub string_version: Option<&'static str>,
}
impl ExtrasAddonInfo {
/// Checks compatibility with the Unofficial Extras addon.
pub fn is_compatible(&self) -> bool {
check_compat(self.api_version, self.max_info_version)
}
/// Whether the Unofficial Extras addon supports the chat message callback.
pub fn supports_chat_message_callback(&self) -> bool {
self.max_info_version >= MESSAGE_CALLBACK
}
}
impl From<RawExtrasAddonInfo> for ExtrasAddonInfo {
fn from(raw: RawExtrasAddonInfo) -> Self {
Self {
api_version: raw.api_version,
max_info_version: raw.max_info_version,
string_version: unsafe { str_from_cstr(raw.string_version) },
}
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct RawExtrasAddonInfo {
/// Version of the API.
///
/// Gets incremented whenever a function signature or behavior changes in a breaking way.
///
/// Current version is `2`.
pub api_version: u32,
/// Highest known version of the [`ExtrasSubscriberInfo`] struct.
///
/// Also determines the size of the subscriber info buffer in the init call.
/// The buffer is only guaranteed to have enough space for known [`ExtrasSubscriberInfo`] versions.
///
/// Current version is `2`.
pub max_info_version: u32,
/// String version of the Unofficial Extras addon.
///
/// Gets changed on every release.
/// The string is valid for the entire lifetime of the Unofficial Extras DLL.
pub string_version: *const c_char,
/// Account name of the logged-in player, including leading `':'`.
///
/// The string is only valid for the duration of the init call.
pub self_account_name: *const c_char,
/// The handle to the Unofficial Extras module.
///
/// Use this to call the exports of the DLL.
pub extras_handle: HMODULE,
}
impl RawExtrasAddonInfo {
/// Checks compatibility with the Unofficial Extras addon.
pub fn is_compatible(&self) -> bool {
check_compat(self.api_version, self.max_info_version)
}
/// Whether the Unofficial Extras addon supports the message callback.
pub fn supports_chat_message_callback(&self) -> bool {
self.max_info_version >= MESSAGE_CALLBACK
}
}
/// Subscriber header shared across different versions.
#[derive(Debug)]
#[repr(C)]
pub struct ExtrasSubscriberInfoHeader {
/// The version of the following info struct
/// This has to be set to the version you want to use.
pub info_version: u32,
/// Unused padding.
pub unused1: u32,
}
/// Information about a subscriber to updates from Unofficial Extras.
#[derive(Debug)]
#[repr(C)]
pub struct ExtrasSubscriberInfo {
/// Header shared across different versions.
pub header: ExtrasSubscriberInfoHeader,
/// Name of the addon subscribing to the changes.
///
/// Must be valid for the lifetime of the subscribing addon.
/// Set to `nullptr` if initialization fails.
pub subscriber_name: *const c_char,
/// Called whenever anything in the squad changes.
///
/// Only the users that changed are sent.
/// If a user is removed from the squad, it will be sent with `role` set to [`UserRole::None`]
pub squad_update_callback: Option<RawExtrasSquadUpdateCallback>,
/// Called whenever the language is changed.
///
/// Either by Changing it in the UI or by pressing the Right Ctrl (default) key.
/// Will also be called directly after initialization, with the current language, to get the startup language.
pub language_changed_callback: Option<RawExtrasLanguageChangedCallback>,
/// Called whenever a keybind is changed.
///
/// By changing it in the ingame UI, by pressing the translation shortcut or with the Presets feature of this plugin.
/// 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.
pub keybind_changed_callback: Option<RawExtrasKeybindChangedCallback>,
/// Called whenever a chat message is sent in your party/squad.
pub chat_message_callback: Option<RawExtrasChatMessageCallback>,
}
impl ExtrasSubscriberInfo {
/// Subscribes to unofficial extras callbacks after checking for compatibility.
///
/// Unsupported callbacks will be skipped.
///
/// Name needs to be null-terminated.
pub unsafe fn subscribe(
&mut self,
extras_addon: &RawExtrasAddonInfo,
name: &'static str,
squad_update: Option<RawExtrasSquadUpdateCallback>,
language_changed: Option<RawExtrasLanguageChangedCallback>,
keybind_changed: Option<RawExtrasKeybindChangedCallback>,
chat_message: Option<RawExtrasChatMessageCallback>,
) {
if extras_addon.is_compatible() {
// initialize globals
EXTRAS_GLOBALS.init(
extras_addon.extras_handle,
str_from_cstr(extras_addon.string_version),
);
// we simply use the max version
self.header.info_version = extras_addon.max_info_version;
self.subscriber_name = name.as_ptr() as *const c_char;
self.squad_update_callback = squad_update;
self.language_changed_callback = language_changed;
self.keybind_changed_callback = keybind_changed;
// only attempt to write message callback if supported
if extras_addon.supports_chat_message_callback() {
self.chat_message_callback = chat_message;
}
}
}
}