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 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
//! ArcDPS exports.
//!
//! Calling an export before ArcDPS calls `init` will cause a panic.
mod has;
pub mod raw;
pub use self::has::*;
use crate::{
evtc::{Event, Profession},
globals::ARC_GLOBALS,
imgui::sys::ImVec4,
};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
ffi::{CString, NulError, OsString},
mem::MaybeUninit,
ops::RangeInclusive,
os::windows::prelude::*,
path::PathBuf,
slice,
};
use windows::Win32::Foundation::HMODULE;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "strum")]
use strum::{Display, EnumCount, EnumIter, IntoStaticStr, VariantNames};
/// Retrieves the ArcDPS version as string.
#[inline]
pub fn version() -> Option<&'static str> {
unsafe { ARC_GLOBALS.version }
}
/// Retrieves the config path from ArcDPS.
///
/// # Examples
/// ```no_run
/// use std::fs;
/// use arcdps::exports;
///
/// # fn foo() -> Option<()> {
/// let config_path = exports::config_path()?;
/// let config_dir = config_path.parent()?;
/// fs::write(config_dir.join("foo.txt"), "lorem ipsum");
/// # None }
/// ```
#[inline]
pub fn config_path() -> Option<PathBuf> {
let ptr = unsafe { raw::e0_config_path() };
if !ptr.is_null() {
// calculate length
let mut len = 0;
while unsafe { *ptr.offset(len) } != 0 {
len += 1;
}
// transform data
let slice = unsafe { slice::from_raw_parts(ptr, len as usize) };
let string = OsString::from_wide(slice);
Some(PathBuf::from(string))
} else {
None
}
}
/// Logs a message to ArcDPS' log file `arcdps.log`.
///
/// Returns an error if the passed message could not be converted to a C-compatible string.
///
/// # Examples
/// ```no_run
/// use arcdps::exports;
///
/// exports::log_to_file("message from my plugin");
/// ```
#[inline]
pub fn log_to_file(message: impl Into<String>) -> Result<(), NulError> {
let string = CString::new(message.into())?;
unsafe { raw::e3_log_file(string.as_ptr()) };
Ok(())
}
/// ArcDPS core UI color.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum CoreColor {
Transparent,
White,
LightWhite,
LightGrey,
LightYellow,
LightGreen,
LightRed,
LightTeal,
MediumGrey,
DarkGrey,
Num,
}
/// ArcDPS color type.
pub type Color = [f32; 4];
/// Current ArcDPS color settings.
///
/// Use the associated functions to access individual colors.
///
/// This holds pointers to color information in memory until dropped.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Colors {
raw: [*mut ImVec4; 5],
}
impl Colors {
/// Range of valid subgroups.
const SUB_RANGE: RangeInclusive<usize> = 0..=15;
/// Reads a color from the raw color array.
///
/// The first index is the index of the subarray.
/// The second index is the index of the color within the subarray.
///
/// This will return [`None`] if the pointer retrieved from ArcDPS is null or was not initialized.
///
/// This is unsafe since indexing the raw color array is only valid with specific indices.
/// Incorrect indices may cause undefined behavior.
unsafe fn read_color(&self, first_index: usize, second_index: usize) -> Option<Color> {
let ptr = self.raw[first_index];
if !ptr.is_null() {
let color = *ptr.add(second_index);
Some(color.into())
} else {
None
}
}
/// Returns the base color for a specific [`CoreColor`].
///
/// This will return [`None`] if ArcDPS did not yield the requested color when the [`Colors`] struct was retrieved.
#[inline]
pub fn core(&self, color: CoreColor) -> Option<Color> {
unsafe { self.read_color(0, color as usize) }
}
/// Returns the base color for a specific [`Profession`].
///
/// This will return [`None`] if ArcDPS did not yield the requested color when the [`Colors`] struct was retrieved.
#[inline]
pub fn prof_base(&self, prof: Profession) -> Option<Color> {
unsafe { self.read_color(1, prof as usize) }
}
/// Returns the highlight color for a specific [`Profession`].
///
/// This will return [`None`] if ArcDPS did not yield the requested color when the [`Colors`] struct was retrieved.
#[inline]
pub fn prof_highlight(&self, prof: Profession) -> Option<Color> {
unsafe { self.read_color(2, prof as usize) }
}
/// Returns the base color for a specific subgroup.
///
/// This will return [`None`] if ArcDPS did not yield the requested color when the [`Colors`] struct was retrieved.
/// Also returns [`None`] if the subgroup is out of the game subgroup range.
#[inline]
pub fn sub_base(&self, sub: usize) -> Option<Color> {
// range check
if Self::SUB_RANGE.contains(&sub) {
unsafe { self.read_color(3, sub) }
} else {
None
}
}
/// Returns the highlight color for a specific subgroup.
///
/// This will return [`None`] if ArcDPS did not yield the requested color when the [`Colors`] struct was retrieved.
/// Also returns [`None`] if the subgroup is out of the game subgroup range.
#[inline]
pub fn sub_highlight(&self, sub: usize) -> Option<Color> {
// range check
if Self::SUB_RANGE.contains(&sub) {
unsafe { self.read_color(4, sub) }
} else {
None
}
}
}
/// Retrieves the color settings from ArcDPS.
///
/// # Examples
/// ```no_run
/// use arcdps::{Profession, exports};
///
/// let colors = exports::colors();
/// let guard = colors.prof_base(Profession::Guardian);
/// ```
#[inline]
pub fn colors() -> Colors {
// zeroing this is important for our null pointer checks later
let mut colors = MaybeUninit::zeroed();
unsafe { raw::e5_colors(colors.as_mut_ptr()) };
Colors {
raw: unsafe { colors.assume_init() },
}
}
/// Current ArcDPS UI settings.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UISettings {
/// Whether the UI is hidden.
pub hidden: bool,
/// Whether the UI is always drawn.
///
/// When `false`, the UI is hidden during loading screens & character select.
pub draw_always: bool,
/// Whether pressing the modifiers are required to move windows.
pub modifiers_move_lock: bool,
/// Whether pressing the modifiers are required to click windows.
pub modifiers_click_lock: bool,
/// Whether windows should close with the `ESC` key.
pub close_with_esc: bool,
}
/// Retrieves the UI settings from ArcDPS.
///
/// # Examples
/// ```no_run
/// use arcdps::exports;
///
/// let ui_settings = exports::ui_settings();
/// if !ui_settings.hidden {
/// # let ui: arcdps::imgui::Ui = todo!();
/// ui.text("hello world");
/// }
/// ```
pub fn ui_settings() -> UISettings {
let raw = unsafe { raw::e6_ui_settings() };
UISettings {
hidden: raw & 1 == 1,
draw_always: (raw >> 1) & 1 == 1,
modifiers_move_lock: (raw >> 2) & 1 == 1,
modifiers_click_lock: (raw >> 3) & 1 == 1,
close_with_esc: (raw >> 4) & 1 == 1,
}
}
/// Current ArcDPS modifier keybinds as virtual key ids.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Modifiers {
/// Virtual key id of the first modifier used by ArcDPS.
pub modifier1: u16,
/// Virtual key id of the second modifier used by ArcDPS.
pub modifier2: u16,
/// Virtual key id of the "multi" modifier used by ArcDPS.
pub modifier_multi: u16,
}
/// Retrieves the modifier keybinds from ArcDPS.
///
/// # Examples
/// ```no_run
/// use arcdps::exports;
///
/// let modifiers = exports::modifiers();
/// let multi = modifiers.modifier_multi;
/// ```
#[inline]
pub fn modifiers() -> Modifiers {
let raw = unsafe { raw::e7_modifiers() };
Modifiers {
modifier1: raw as u16,
modifier2: (raw >> 16) as u16,
modifier_multi: (raw >> 32) as u16,
}
}
/// Logs a message to ArcDPS' logger window.
///
/// Text can be colored in a HTML-like way: `<c=#aaaaaa>colored text</c>`.
///
/// Returns an error if the passed message could not be converted to a C-compatible string.
///
/// # Examples
/// ```no_run
/// use arcdps::exports;
///
/// exports::log_to_window("message from my plugin");
/// ```
#[inline]
pub fn log_to_window(message: impl Into<String>) -> Result<(), NulError> {
let string = CString::new(message.into())?;
unsafe { raw::e8_log_window(string.as_ptr()) };
Ok(())
}
/// Adds an [`Event`] to ArcDPS' event processing.
///
/// `is_statechange` will be set to [`StateChange::Extension`](crate::StateChange::Extension), padding will be set to contain `sig`.
/// Event will end up processed like ArcDPS events and logged to EVTC.
#[inline]
pub fn add_event(event: &Event, sig: u32) {
unsafe { raw::e9_add_event(event, sig) }
}
/// Adds an [`Event`] to ArcDPS' event processing.
///
/// `is_statechange` will be set to [`StateChange::ExtensionCombat`](crate::StateChange::ExtensionCombat), padding will be set to contain `sig`.
/// Event will end up processed like ArcDPS events and logged to EVTC.
///
/// Contrary to [`add_event`], the `skill_id` is treated as skill id and will be added to the EVTC skill table.
#[inline]
pub fn add_event_combat(event: &Event, sig: u32) {
unsafe { raw::e10_add_event_combat(event, sig) }
}
/// Requests to load an extension (plugin/addon).
///
/// ArcDPS will `LoadLibrary` the `handle` to increment the reference count, call `get_init_addr` and call its returned function.
/// Returns [`AddExtensionResult`] indicating success or failure.
///
/// This uses version 2 (`addextension2`) of the extension API.
#[inline]
pub fn add_extension(handle: HMODULE) -> AddExtensionResult {
unsafe { raw::add_extension(handle) }
.try_into()
.expect("unexpected add extension result")
}
/// Result of an [`add_extension`] operation.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPrimitive, TryFromPrimitive,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "strum",
derive(Display, EnumCount, EnumIter, IntoStaticStr, VariantNames)
)]
#[repr(u32)]
pub enum AddExtensionResult {
/// Extension was loaded successfully.
Ok,
/// Extension-specific error.
ExtensionError,
/// ImGui version did not match.
ImGuiError,
/// Obsolete ArcDPS module.
Obsolete,
/// An extension with the same `sig` already exists.
SigExists,
/// Extension did not provide callback function table.
NoExport,
/// Extension did not provide an `init` function.
NoInit,
/// Failed to load extension module with `LoadLibrary`.
///
/// Safe to call `GetLastError`.
LoadError,
}
/// Requests to free a loaded extension (plugin/addon).
///
/// ArcDPS will call `get_release_addr` and its returned function.
/// Upon returning from [`free_extension`] there will be no more pending callbacks.
/// However, the caller must ensure to callbacks are executing before freeing.
/// Returns the [`HMODULE`] handle of the module if the extension was found.
///
/// This uses version 2 (`freeextension2`) of the extension API.
#[inline]
pub fn free_extension(sig: u32) -> Option<HMODULE> {
match unsafe { raw::free_extension(sig) } {
HMODULE(0) => None,
handle => Some(handle),
}
}