Skip to main content

eos_rs/
lib.rs

1//! # eos-rs
2//!
3//! Safe(ish) Rust wrappers around the Epic Online Services (EOS) C SDK.
4//!
5//! This crate is designed as an ergonomic layer over [`eos_sys`], while still
6//! allowing escape hatches to raw handles for APIs that are not yet wrapped.
7//!
8//! ## Chapter 1: Installation
9//!
10//! Add dependency:
11//!
12//! ```toml
13//! [dependencies]
14//! eos-rs = "0.1"
15//! ```
16//!
17//! Provide EOS SDK location when building:
18//!
19//! - `EOS_SDK_DIR=/path/to/EOS-SDK`
20//!
21//! Expected layout inside `EOS_SDK_DIR`:
22//!
23//! - `Include/`
24//! - `Lib/`
25//!
26//! On Windows, ship `EOSSDK-Win64-Shipping.dll` with your app.
27//!
28//! ## Chapter 2: SDK Lifecycle
29//!
30//! EOS has a global lifecycle:
31//!
32//! 1. [`initialize`]
33//! 2. Create a [`Platform`]
34//! 3. Call [`Platform::tick`] regularly
35//! 4. Drop `Platform`
36//! 5. [`shutdown`]
37//!
38//! ## Chapter 3: Quick Start
39//!
40//! ```no_run
41//! use eos_rs::{initialize, shutdown, InitializeOptions, Platform, PlatformOptions};
42//!
43//! fn main() -> Result<(), Box<dyn std::error::Error>> {
44//!     initialize(InitializeOptions {
45//!         product_name: "my-game".to_string(),
46//!         product_version: "0.1.0".to_string(),
47//!     })?;
48//!
49//!     let platform = Platform::create(PlatformOptions {
50//!         product_id: "product-id".to_string(),
51//!         sandbox_id: "sandbox-id".to_string(),
52//!         deployment_id: "deployment-id".to_string(),
53//!         client_id: "client-id".to_string(),
54//!         client_secret: "client-secret".to_string(),
55//!         is_server: false,
56//!         encryption_key: None,
57//!         override_country_code: None,
58//!         override_locale_code: None,
59//!     })?;
60//!
61//!     // Call once per frame/tick in your game loop.
62//!     platform.tick();
63//!
64//!     drop(platform);
65//!     shutdown()?;
66//!     Ok(())
67//! }
68//! ```
69//!
70//! ## Chapter 4: Interfaces and Raw Escape Hatch
71//!
72//! [`Platform`] exposes typed accessors for EOS interfaces (`auth()`, `connect()`,
73//! `lobby()`, `p2p()`, and more). Each wrapper provides `raw_handle()` so you can
74//! call any function from [`sys`] directly when needed.
75//!
76//! ## Chapter 5: Callback Model
77//!
78//! EOS async APIs are callback-based. `eos-rs` wraps selected callbacks with Rust
79//! closures and owns callback context allocations until EOS invokes them.
80//!
81//! ## Chapter 6: Owned EOS Objects
82//!
83//! EOS returns many heap/handle values that require `*_Release`. This crate defines
84//! RAII wrappers for those objects; dropping the wrapper calls the matching release
85//! API automatically.
86//!
87//! ## Chapter 7: Safety Notes
88//!
89//! This crate significantly improves safety over raw FFI, but it is not fully safe:
90//!
91//! - EOS threading requirements still apply.
92//! - EOS callback contracts still apply.
93//! - `raw_handle()` access can bypass invariants.
94//!
95//! Prefer wrapped methods and RAII types where available.
96//!
97use std::ffi::{CStr, CString};
98use std::marker::PhantomData;
99use std::ptr::{null, null_mut, NonNull};
100use std::sync::OnceLock;
101
102pub use eos_sys as sys;
103
104#[derive(thiserror::Error, Debug)]
105pub enum Error {
106    #[error("EOS error: {0:?}")]
107    Eos(sys::EOS_EResult),
108    #[error("nul byte in string")]
109    Nul(#[from] std::ffi::NulError),
110    #[error("null pointer from EOS SDK")]
111    Null,
112}
113
114pub type Result<T> = std::result::Result<T, Error>;
115
116fn ok(res: sys::EOS_EResult) -> Result<()> {
117    if res == sys::EOS_EResult_EOS_Success {
118        Ok(())
119    } else {
120        Err(Error::Eos(res))
121    }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum LoginStatus {
126    NotLoggedIn,
127    UsingLocalProfile,
128    LoggedIn,
129    Other(sys::EOS_ELoginStatus),
130}
131
132impl LoginStatus {
133    fn from_raw(status: sys::EOS_ELoginStatus) -> Self {
134        match status {
135            x if x == sys::EOS_ELoginStatus_EOS_LS_NotLoggedIn => Self::NotLoggedIn,
136            x if x == sys::EOS_ELoginStatus_EOS_LS_UsingLocalProfile => Self::UsingLocalProfile,
137            x if x == sys::EOS_ELoginStatus_EOS_LS_LoggedIn => Self::LoggedIn,
138            x => Self::Other(x),
139        }
140    }
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub enum NatType {
145    Unknown,
146    Open,
147    Moderate,
148    Strict,
149    Other(sys::EOS_ENATType),
150}
151
152impl NatType {
153    fn from_raw(v: sys::EOS_ENATType) -> Self {
154        match v {
155            x if x == sys::EOS_ENATType_EOS_NAT_Unknown => Self::Unknown,
156            x if x == sys::EOS_ENATType_EOS_NAT_Open => Self::Open,
157            x if x == sys::EOS_ENATType_EOS_NAT_Moderate => Self::Moderate,
158            x if x == sys::EOS_ENATType_EOS_NAT_Strict => Self::Strict,
159            x => Self::Other(x),
160        }
161    }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum RelayControl {
166    NoRelays,
167    AllowRelays,
168    ForceRelays,
169    Other(sys::EOS_ERelayControl),
170}
171
172impl RelayControl {
173    fn from_raw(v: sys::EOS_ERelayControl) -> Self {
174        match v {
175            x if x == sys::EOS_ERelayControl_EOS_RC_NoRelays => Self::NoRelays,
176            x if x == sys::EOS_ERelayControl_EOS_RC_AllowRelays => Self::AllowRelays,
177            x if x == sys::EOS_ERelayControl_EOS_RC_ForceRelays => Self::ForceRelays,
178            x => Self::Other(x),
179        }
180    }
181
182    fn to_raw(self) -> sys::EOS_ERelayControl {
183        match self {
184            Self::NoRelays => sys::EOS_ERelayControl_EOS_RC_NoRelays,
185            Self::AllowRelays => sys::EOS_ERelayControl_EOS_RC_AllowRelays,
186            Self::ForceRelays => sys::EOS_ERelayControl_EOS_RC_ForceRelays,
187            Self::Other(v) => v,
188        }
189    }
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub enum PacketReliability {
194    UnreliableUnordered,
195    ReliableUnordered,
196    ReliableOrdered,
197}
198
199impl PacketReliability {
200    fn to_raw(self) -> sys::EOS_EPacketReliability {
201        match self {
202            Self::UnreliableUnordered => sys::EOS_EPacketReliability_EOS_PR_UnreliableUnordered,
203            Self::ReliableUnordered => sys::EOS_EPacketReliability_EOS_PR_ReliableUnordered,
204            Self::ReliableOrdered => sys::EOS_EPacketReliability_EOS_PR_ReliableOrdered,
205        }
206    }
207}
208
209#[derive(Clone, Debug)]
210pub struct PacketQueueInfo {
211    pub incoming_max_size_bytes: u64,
212    pub incoming_current_size_bytes: u64,
213    pub incoming_current_packet_count: u64,
214    pub outgoing_max_size_bytes: u64,
215    pub outgoing_current_size_bytes: u64,
216    pub outgoing_current_packet_count: u64,
217}
218
219#[derive(Clone, Debug)]
220pub enum LobbySearchValue {
221    Bool(bool),
222    Int64(i64),
223    Double(f64),
224    String(String),
225}
226
227#[derive(Clone, Debug)]
228pub struct ReceivedPacket {
229    pub peer_id: ProductUserId,
230    pub socket_name: String,
231    pub channel: u8,
232    pub data: Vec<u8>,
233}
234
235pub fn result_to_string(result: sys::EOS_EResult) -> &'static str {
236    // SAFETY: EOS guarantees this pointer is non-null, static, and valid UTF-8-ish C string.
237    let ptr = unsafe { sys::EOS_EResult_ToString(result) };
238    if ptr.is_null() {
239        return "EOS_Unknown";
240    }
241    unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("EOS_InvalidUtf8")
242}
243
244fn make_socket_id(socket_name: &str) -> Result<sys::EOS_P2P_SocketId> {
245    let c = CString::new(socket_name)?;
246    let bytes = c.as_bytes_with_nul();
247    if bytes.len() > sys::EOS_P2P_SOCKETID_SOCKETNAME_SIZE as usize {
248        return Err(Error::Null);
249    }
250    let mut out = sys::EOS_P2P_SocketId {
251        ApiVersion: sys::EOS_P2P_SOCKETID_API_LATEST as i32,
252        SocketName: [0; sys::EOS_P2P_SOCKETID_SOCKETNAME_SIZE as usize],
253    };
254    for (idx, b) in bytes.iter().copied().enumerate() {
255        out.SocketName[idx] = b as i8;
256    }
257    Ok(out)
258}
259
260fn socket_name_from_raw(socket: &sys::EOS_P2P_SocketId) -> String {
261    let ptr = socket.SocketName.as_ptr();
262    unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()
263}
264
265static INITIALIZED: OnceLock<()> = OnceLock::new();
266
267pub struct InitializeOptions {
268    pub product_name: String,
269    pub product_version: String,
270}
271
272pub fn initialize(opts: InitializeOptions) -> Result<()> {
273    if INITIALIZED.get().is_some() {
274        return Ok(());
275    }
276
277    let product_name = CString::new(opts.product_name)?;
278    let product_version = CString::new(opts.product_version)?;
279
280    let init_opts = sys::EOS_InitializeOptions {
281        ApiVersion: sys::EOS_INITIALIZE_API_LATEST as i32,
282        AllocateMemoryFunction: None,
283        ReallocateMemoryFunction: None,
284        ReleaseMemoryFunction: None,
285        ProductName: product_name.as_ptr(),
286        ProductVersion: product_version.as_ptr(),
287        Reserved: null_mut(),
288        SystemInitializeOptions: null_mut(),
289        OverrideThreadAffinity: null_mut(),
290    };
291
292    // SAFETY: EOS expects pointers valid for the duration of the call.
293    let res = unsafe { sys::EOS_Initialize(&init_opts) };
294    ok(res)?;
295    let _ = INITIALIZED.set(());
296    Ok(())
297}
298
299pub fn shutdown() -> Result<()> {
300    if INITIALIZED.get().is_none() {
301        return Ok(());
302    }
303    let res = unsafe { sys::EOS_Shutdown() };
304    ok(res)
305}
306
307#[derive(Clone, Copy, Debug)]
308pub struct EpicAccountId(sys::EOS_EpicAccountId);
309
310#[derive(Clone, Copy, Debug)]
311pub struct ProductUserId(sys::EOS_ProductUserId);
312
313#[derive(Clone, Copy, Debug)]
314pub struct ContinuanceToken(sys::EOS_ContinuanceToken);
315
316impl ContinuanceToken {
317    pub fn raw(self) -> sys::EOS_ContinuanceToken {
318        self.0
319    }
320
321    pub fn from_login_callback(info: &sys::EOS_Connect_LoginCallbackInfo) -> Option<Self> {
322        if info.ContinuanceToken.is_null() {
323            None
324        } else {
325            Some(Self(info.ContinuanceToken))
326        }
327    }
328}
329
330#[derive(Clone, Debug)]
331pub struct CreateLobbyParams {
332    pub max_lobby_members: u32,
333    pub permission_level: sys::EOS_ELobbyPermissionLevel,
334    pub presence_enabled: bool,
335    pub allow_invites: bool,
336    pub bucket_id: String,
337    pub disable_host_migration: bool,
338    pub enable_rtc_room: bool,
339    pub enable_join_by_id: bool,
340    pub rejoin_after_kick_requires_invite: bool,
341}
342
343impl Default for CreateLobbyParams {
344    fn default() -> Self {
345        Self {
346            max_lobby_members: 8,
347            permission_level: sys::EOS_ELobbyPermissionLevel_EOS_LPL_PUBLICADVERTISED,
348            presence_enabled: true,
349            allow_invites: true,
350            bucket_id: "default".to_string(),
351            disable_host_migration: false,
352            enable_rtc_room: false,
353            enable_join_by_id: false,
354            rejoin_after_kick_requires_invite: false,
355        }
356    }
357}
358
359impl EpicAccountId {
360    pub fn from_string(s: &str) -> Result<Self> {
361        let s = CString::new(s)?;
362        let raw = unsafe { sys::EOS_EpicAccountId_FromString(s.as_ptr()) };
363        let id = Self(raw);
364        if id.is_valid() {
365            Ok(id)
366        } else {
367            Err(Error::Null)
368        }
369    }
370
371    pub fn to_string(self) -> Result<String> {
372        let mut buf = vec![0i8; (sys::EOS_EPICACCOUNTID_MAX_LENGTH + 1) as usize];
373        let mut len = buf.len() as i32;
374        let res = unsafe { sys::EOS_EpicAccountId_ToString(self.0, buf.as_mut_ptr(), &mut len) };
375        ok(res)?;
376        let s = unsafe { CStr::from_ptr(buf.as_ptr()) }
377            .to_string_lossy()
378            .into_owned();
379        Ok(s)
380    }
381
382    pub fn is_valid(self) -> bool {
383        unsafe { sys::EOS_EpicAccountId_IsValid(self.0) != 0 }
384    }
385
386    pub fn raw(self) -> sys::EOS_EpicAccountId {
387        self.0
388    }
389}
390
391impl ProductUserId {
392    pub fn from_string(s: &str) -> Result<Self> {
393        let s = CString::new(s)?;
394        let raw = unsafe { sys::EOS_ProductUserId_FromString(s.as_ptr()) };
395        let id = Self(raw);
396        if id.is_valid() {
397            Ok(id)
398        } else {
399            Err(Error::Null)
400        }
401    }
402
403    pub fn to_string(self) -> Result<String> {
404        let mut buf = vec![0i8; (sys::EOS_PRODUCTUSERID_MAX_LENGTH + 1) as usize];
405        let mut len = buf.len() as i32;
406        let res = unsafe { sys::EOS_ProductUserId_ToString(self.0, buf.as_mut_ptr(), &mut len) };
407        ok(res)?;
408        let s = unsafe { CStr::from_ptr(buf.as_ptr()) }
409            .to_string_lossy()
410            .into_owned();
411        Ok(s)
412    }
413
414    pub fn is_valid(self) -> bool {
415        unsafe { sys::EOS_ProductUserId_IsValid(self.0) != 0 }
416    }
417
418    pub fn raw(self) -> sys::EOS_ProductUserId {
419        self.0
420    }
421}
422
423pub struct Platform(NonNull<sys::EOS_PlatformHandle>);
424
425unsafe impl Send for Platform {}
426unsafe impl Sync for Platform {}
427
428pub struct PlatformOptions {
429    pub product_id: String,
430    pub sandbox_id: String,
431    pub deployment_id: String,
432    pub client_id: String,
433    pub client_secret: String,
434    pub is_server: bool,
435    pub encryption_key: Option<String>,
436    pub override_country_code: Option<String>,
437    pub override_locale_code: Option<String>,
438}
439
440impl Platform {
441    pub fn create(opts: PlatformOptions) -> Result<Self> {
442        let product_id = CString::new(opts.product_id)?;
443        let sandbox_id = CString::new(opts.sandbox_id)?;
444        let deployment_id = CString::new(opts.deployment_id)?;
445        let client_id = CString::new(opts.client_id)?;
446        let client_secret = CString::new(opts.client_secret)?;
447        let encryption_key = match opts.encryption_key {
448            Some(s) => Some(CString::new(s)?),
449            None => None,
450        };
451        let override_country_code = match opts.override_country_code {
452            Some(s) => Some(CString::new(s)?),
453            None => None,
454        };
455        let override_locale_code = match opts.override_locale_code {
456            Some(s) => Some(CString::new(s)?),
457            None => None,
458        };
459
460        let options = sys::EOS_Platform_Options {
461            ApiVersion: sys::EOS_PLATFORM_OPTIONS_API_LATEST as i32,
462            Reserved: null_mut(),
463            bIsServer: if opts.is_server { 1 } else { 0 },
464            EncryptionKey: encryption_key.as_ref().map(|s| s.as_ptr()).unwrap_or(std::ptr::null()),
465            OverrideCountryCode: override_country_code
466                .as_ref()
467                .map(|s| s.as_ptr())
468                .unwrap_or(std::ptr::null()),
469            OverrideLocaleCode: override_locale_code
470                .as_ref()
471                .map(|s| s.as_ptr())
472                .unwrap_or(std::ptr::null()),
473            ProductId: product_id.as_ptr(),
474            SandboxId: sandbox_id.as_ptr(),
475            DeploymentId: deployment_id.as_ptr(),
476            ClientCredentials: sys::EOS_Platform_ClientCredentials {
477                ClientId: client_id.as_ptr(),
478                ClientSecret: client_secret.as_ptr(),
479            },
480            Flags: 0,
481            CacheDirectory: std::ptr::null(),
482            TickBudgetInMilliseconds: 0,
483            RTCOptions: std::ptr::null(),
484            IntegratedPlatformOptionsContainerHandle: null_mut(),
485            SystemSpecificOptions: null(),
486            TaskNetworkTimeoutSeconds: null_mut(),
487        };
488
489        let handle = unsafe { sys::EOS_Platform_Create(&options) };
490        let nn = NonNull::new(handle as *mut sys::EOS_PlatformHandle).ok_or(Error::Null)?;
491        Ok(Self(nn))
492    }
493
494    #[inline]
495    fn as_handle(&self) -> sys::EOS_HPlatform {
496        self.0.as_ptr() as sys::EOS_HPlatform
497    }
498
499    pub fn tick(&self) {
500        unsafe { sys::EOS_Platform_Tick(self.as_handle()) };
501    }
502
503    pub fn raw_handle(&self) -> sys::EOS_HPlatform {
504        self.as_handle()
505    }
506
507    pub fn auth(&self) -> Auth {
508        let h = unsafe { sys::EOS_Platform_GetAuthInterface(self.as_handle()) };
509        Auth(NonNull::new(h as *mut sys::EOS_AuthHandle).expect("EOS auth interface null"))
510    }
511
512    pub fn connect(&self) -> Connect {
513        let h = unsafe { sys::EOS_Platform_GetConnectInterface(self.as_handle()) };
514        Connect(NonNull::new(h as *mut sys::EOS_ConnectHandle).expect("EOS connect interface null"))
515    }
516
517    pub fn achievements(&self) -> Achievements {
518        let h = unsafe { sys::EOS_Platform_GetAchievementsInterface(self.as_handle()) };
519        Achievements(
520            NonNull::new(h as *mut sys::EOS_AchievementsHandle)
521                .expect("EOS achievements interface null"),
522        )
523    }
524
525    pub fn anticheat_client(&self) -> AntiCheatClient {
526        let h = unsafe { sys::EOS_Platform_GetAntiCheatClientInterface(self.as_handle()) };
527        AntiCheatClient(
528            NonNull::new(h as *mut sys::EOS_AntiCheatClientHandle)
529                .expect("EOS anticheat client interface null"),
530        )
531    }
532
533    pub fn anticheat_server(&self) -> AntiCheatServer {
534        let h = unsafe { sys::EOS_Platform_GetAntiCheatServerInterface(self.as_handle()) };
535        AntiCheatServer(
536            NonNull::new(h as *mut sys::EOS_AntiCheatServerHandle)
537                .expect("EOS anticheat server interface null"),
538        )
539    }
540
541    pub fn custom_invites(&self) -> CustomInvites {
542        let h = unsafe { sys::EOS_Platform_GetCustomInvitesInterface(self.as_handle()) };
543        CustomInvites(
544            NonNull::new(h as *mut sys::EOS_CustomInvitesHandle)
545                .expect("EOS custom invites interface null"),
546        )
547    }
548
549    pub fn ecom(&self) -> Ecom {
550        let h = unsafe { sys::EOS_Platform_GetEcomInterface(self.as_handle()) };
551        Ecom(NonNull::new(h as *mut sys::EOS_EcomHandle).expect("EOS ecom interface null"))
552    }
553
554    pub fn friends(&self) -> Friends {
555        let h = unsafe { sys::EOS_Platform_GetFriendsInterface(self.as_handle()) };
556        Friends(NonNull::new(h as *mut sys::EOS_FriendsHandle).expect("EOS friends interface null"))
557    }
558
559    pub fn integrated_platform(&self) -> IntegratedPlatform {
560        let h = unsafe { sys::EOS_Platform_GetIntegratedPlatformInterface(self.as_handle()) };
561        IntegratedPlatform(
562            NonNull::new(h as *mut sys::EOS_IntegratedPlatformHandle)
563                .expect("EOS integrated platform interface null"),
564        )
565    }
566
567    pub fn kws(&self) -> Kws {
568        let h = unsafe { sys::EOS_Platform_GetKWSInterface(self.as_handle()) };
569        Kws(NonNull::new(h as *mut sys::EOS_KWSHandle).expect("EOS KWS interface null"))
570    }
571
572    pub fn leaderboards(&self) -> Leaderboards {
573        let h = unsafe { sys::EOS_Platform_GetLeaderboardsInterface(self.as_handle()) };
574        Leaderboards(
575            NonNull::new(h as *mut sys::EOS_LeaderboardsHandle)
576                .expect("EOS leaderboards interface null"),
577        )
578    }
579
580    pub fn lobby(&self) -> Lobby {
581        let h = unsafe { sys::EOS_Platform_GetLobbyInterface(self.as_handle()) };
582        Lobby(NonNull::new(h as *mut sys::EOS_LobbyHandle).expect("EOS lobby interface null"))
583    }
584
585    pub fn metrics(&self) -> Metrics {
586        let h = unsafe { sys::EOS_Platform_GetMetricsInterface(self.as_handle()) };
587        Metrics(NonNull::new(h as *mut sys::EOS_MetricsHandle).expect("EOS metrics interface null"))
588    }
589
590    pub fn mods(&self) -> Mods {
591        let h = unsafe { sys::EOS_Platform_GetModsInterface(self.as_handle()) };
592        Mods(NonNull::new(h as *mut sys::EOS_ModsHandle).expect("EOS mods interface null"))
593    }
594
595    pub fn p2p(&self) -> P2P {
596        let h = unsafe { sys::EOS_Platform_GetP2PInterface(self.as_handle()) };
597        P2P(NonNull::new(h as *mut sys::EOS_P2PHandle).expect("EOS p2p interface null"))
598    }
599
600    pub fn player_data_storage(&self) -> PlayerDataStorage {
601        let h = unsafe { sys::EOS_Platform_GetPlayerDataStorageInterface(self.as_handle()) };
602        PlayerDataStorage(
603            NonNull::new(h as *mut sys::EOS_PlayerDataStorageHandle)
604                .expect("EOS player data storage interface null"),
605        )
606    }
607
608    pub fn presence(&self) -> Presence {
609        let h = unsafe { sys::EOS_Platform_GetPresenceInterface(self.as_handle()) };
610        Presence(
611            NonNull::new(h as *mut sys::EOS_PresenceHandle).expect("EOS presence interface null"),
612        )
613    }
614
615    pub fn progressionsnapshot(&self) -> ProgressionSnapshot {
616        let h = unsafe { sys::EOS_Platform_GetProgressionSnapshotInterface(self.as_handle()) };
617        ProgressionSnapshot(
618            NonNull::new(h as *mut sys::EOS_ProgressionSnapshotHandle)
619                .expect("EOS progression snapshot interface null"),
620        )
621    }
622
623    pub fn reports(&self) -> Reports {
624        let h = unsafe { sys::EOS_Platform_GetReportsInterface(self.as_handle()) };
625        Reports(
626            NonNull::new(h as *mut sys::EOS_ReportsHandle).expect("EOS reports interface null"),
627        )
628    }
629
630    pub fn rtc(&self) -> Rtc {
631        let h = unsafe { sys::EOS_Platform_GetRTCInterface(self.as_handle()) };
632        Rtc(NonNull::new(h as *mut sys::EOS_RTCHandle).expect("EOS RTC interface null"))
633    }
634
635    pub fn rtc_admin(&self) -> RtcAdmin {
636        let h = unsafe { sys::EOS_Platform_GetRTCAdminInterface(self.as_handle()) };
637        RtcAdmin(
638            NonNull::new(h as *mut sys::EOS_RTCAdminHandle).expect("EOS RTC admin interface null"),
639        )
640    }
641
642    pub fn sanctions(&self) -> Sanctions {
643        let h = unsafe { sys::EOS_Platform_GetSanctionsInterface(self.as_handle()) };
644        Sanctions(
645            NonNull::new(h as *mut sys::EOS_SanctionsHandle).expect("EOS sanctions interface null"),
646        )
647    }
648
649    pub fn sessions(&self) -> Sessions {
650        let h = unsafe { sys::EOS_Platform_GetSessionsInterface(self.as_handle()) };
651        Sessions(
652            NonNull::new(h as *mut sys::EOS_SessionsHandle).expect("EOS sessions interface null"),
653        )
654    }
655
656    pub fn stats(&self) -> Stats {
657        let h = unsafe { sys::EOS_Platform_GetStatsInterface(self.as_handle()) };
658        Stats(NonNull::new(h as *mut sys::EOS_StatsHandle).expect("EOS stats interface null"))
659    }
660
661    pub fn title_storage(&self) -> TitleStorage {
662        let h = unsafe { sys::EOS_Platform_GetTitleStorageInterface(self.as_handle()) };
663        TitleStorage(
664            NonNull::new(h as *mut sys::EOS_TitleStorageHandle)
665                .expect("EOS title storage interface null"),
666        )
667    }
668
669    pub fn ui(&self) -> Ui {
670        let h = unsafe { sys::EOS_Platform_GetUIInterface(self.as_handle()) };
671        Ui(NonNull::new(h as *mut sys::EOS_UIHandle).expect("EOS UI interface null"))
672    }
673
674    pub fn userinfo(&self) -> UserInfo {
675        let h = unsafe { sys::EOS_Platform_GetUserInfoInterface(self.as_handle()) };
676        UserInfo(
677            NonNull::new(h as *mut sys::EOS_UserInfoHandle).expect("EOS userinfo interface null"),
678        )
679    }
680}
681
682impl Drop for Platform {
683    fn drop(&mut self) {
684        unsafe { sys::EOS_Platform_Release(self.as_handle()) };
685    }
686}
687
688pub struct Auth(NonNull<sys::EOS_AuthHandle>);
689pub struct Connect(NonNull<sys::EOS_ConnectHandle>);
690pub struct Achievements(NonNull<sys::EOS_AchievementsHandle>);
691pub struct AntiCheatClient(NonNull<sys::EOS_AntiCheatClientHandle>);
692pub struct AntiCheatServer(NonNull<sys::EOS_AntiCheatServerHandle>);
693pub struct CustomInvites(NonNull<sys::EOS_CustomInvitesHandle>);
694pub struct Ecom(NonNull<sys::EOS_EcomHandle>);
695pub struct Friends(NonNull<sys::EOS_FriendsHandle>);
696pub struct IntegratedPlatform(NonNull<sys::EOS_IntegratedPlatformHandle>);
697pub struct Kws(NonNull<sys::EOS_KWSHandle>);
698pub struct Leaderboards(NonNull<sys::EOS_LeaderboardsHandle>);
699pub struct Lobby(NonNull<sys::EOS_LobbyHandle>);
700pub struct Metrics(NonNull<sys::EOS_MetricsHandle>);
701pub struct Mods(NonNull<sys::EOS_ModsHandle>);
702pub struct P2P(NonNull<sys::EOS_P2PHandle>);
703pub struct PlayerDataStorage(NonNull<sys::EOS_PlayerDataStorageHandle>);
704pub struct Presence(NonNull<sys::EOS_PresenceHandle>);
705pub struct ProgressionSnapshot(NonNull<sys::EOS_ProgressionSnapshotHandle>);
706pub struct Reports(NonNull<sys::EOS_ReportsHandle>);
707pub struct Rtc(NonNull<sys::EOS_RTCHandle>);
708pub struct RtcAdmin(NonNull<sys::EOS_RTCAdminHandle>);
709pub struct Sanctions(NonNull<sys::EOS_SanctionsHandle>);
710pub struct Sessions(NonNull<sys::EOS_SessionsHandle>);
711pub struct Stats(NonNull<sys::EOS_StatsHandle>);
712pub struct TitleStorage(NonNull<sys::EOS_TitleStorageHandle>);
713pub struct Ui(NonNull<sys::EOS_UIHandle>);
714pub struct UserInfo(NonNull<sys::EOS_UserInfoHandle>);
715
716/// Owns a callback allocation until EOS triggers it once.
717struct CallbackOnce<T> {
718    ptr: *mut T,
719    _marker: PhantomData<T>,
720}
721
722impl<T> CallbackOnce<T> {
723    fn new(val: T) -> Self {
724        let b = Box::new(val);
725        Self {
726            ptr: Box::into_raw(b),
727            _marker: PhantomData,
728        }
729    }
730}
731
732impl Auth {
733    fn as_handle(&self) -> sys::EOS_HAuth {
734        self.0.as_ptr() as sys::EOS_HAuth
735    }
736
737    pub fn raw_handle(&self) -> sys::EOS_HAuth {
738        self.as_handle()
739    }
740
741    pub fn get_login_status(&self, local_user: EpicAccountId) -> LoginStatus {
742        let raw = unsafe { sys::EOS_Auth_GetLoginStatus(self.as_handle(), local_user.raw()) };
743        LoginStatus::from_raw(raw)
744    }
745
746    pub fn copy_user_auth_token(&self, local_user: EpicAccountId) -> Result<AuthToken> {
747        let options = sys::EOS_Auth_CopyUserAuthTokenOptions {
748            ApiVersion: sys::EOS_AUTH_COPYUSERAUTHTOKEN_API_LATEST as i32,
749        };
750        let mut token_ptr: *mut sys::EOS_Auth_Token = std::ptr::null_mut();
751        let res = unsafe {
752            sys::EOS_Auth_CopyUserAuthToken(
753                self.as_handle(),
754                &options,
755                local_user.raw(),
756                &mut token_ptr,
757            )
758        };
759        ok(res)?;
760        unsafe { AuthToken::from_raw(token_ptr) }
761    }
762
763    pub fn copy_id_token(&self, account: EpicAccountId) -> Result<AuthIdToken> {
764        let options = sys::EOS_Auth_CopyIdTokenOptions {
765            ApiVersion: sys::EOS_AUTH_COPYIDTOKEN_API_LATEST as i32,
766            AccountId: account.raw(),
767        };
768        let mut token_ptr: *mut sys::EOS_Auth_IdToken = std::ptr::null_mut();
769        let res = unsafe { sys::EOS_Auth_CopyIdToken(self.as_handle(), &options, &mut token_ptr) };
770        ok(res)?;
771        unsafe { AuthIdToken::from_raw(token_ptr) }
772    }
773
774    pub fn query_id_token(
775        &self,
776        local_user: EpicAccountId,
777        target_account: EpicAccountId,
778        cb: impl FnOnce(Result<sys::EOS_Auth_QueryIdTokenCallbackInfo>) + Send + 'static,
779    ) {
780        #[repr(C)]
781        struct Cb {
782            f: Option<Box<dyn FnOnce(Result<sys::EOS_Auth_QueryIdTokenCallbackInfo>) + Send>>,
783        }
784
785        unsafe extern "C" fn trampoline(data: *const sys::EOS_Auth_QueryIdTokenCallbackInfo) {
786            let client_data = (*data).ClientData as *mut Cb;
787            let mut boxed = Box::from_raw(client_data);
788            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
789                Ok(*data)
790            } else {
791                Err(Error::Eos((*data).ResultCode))
792            };
793            if let Some(f) = boxed.f.take() {
794                f(res);
795            }
796        }
797
798        let cb_box = CallbackOnce::new(Cb {
799            f: Some(Box::new(cb)),
800        });
801
802        let options = sys::EOS_Auth_QueryIdTokenOptions {
803            ApiVersion: sys::EOS_AUTH_QUERYIDTOKEN_API_LATEST as i32,
804            LocalUserId: local_user.raw(),
805            TargetAccountId: target_account.raw(),
806        };
807
808        unsafe {
809            sys::EOS_Auth_QueryIdToken(
810                self.as_handle(),
811                &options,
812                cb_box.ptr as *mut _,
813                Some(trampoline),
814            );
815        }
816    }
817
818    pub fn logout(
819        &self,
820        local_user: EpicAccountId,
821        cb: impl FnOnce(Result<sys::EOS_Auth_LogoutCallbackInfo>) + Send + 'static,
822    ) {
823        #[repr(C)]
824        struct Cb {
825            f: Option<Box<dyn FnOnce(Result<sys::EOS_Auth_LogoutCallbackInfo>) + Send>>,
826        }
827
828        unsafe extern "C" fn trampoline(data: *const sys::EOS_Auth_LogoutCallbackInfo) {
829            let client_data = (*data).ClientData as *mut Cb;
830            let mut boxed = Box::from_raw(client_data);
831            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
832                Ok(*data)
833            } else {
834                Err(Error::Eos((*data).ResultCode))
835            };
836            if let Some(f) = boxed.f.take() {
837                f(res);
838            }
839        }
840
841        let cb_box = CallbackOnce::new(Cb {
842            f: Some(Box::new(cb)),
843        });
844
845        let options = sys::EOS_Auth_LogoutOptions {
846            ApiVersion: sys::EOS_AUTH_LOGOUT_API_LATEST as i32,
847            LocalUserId: local_user.raw(),
848        };
849
850        unsafe {
851            sys::EOS_Auth_Logout(
852                self.as_handle(),
853                &options,
854                cb_box.ptr as *mut _,
855                Some(trampoline),
856            );
857        }
858    }
859
860    pub fn login_epic_exchange_code(
861        &self,
862        exchange_code: &str,
863        cb: impl FnOnce(Result<sys::EOS_Auth_LoginCallbackInfo>) + Send + 'static,
864    ) -> Result<()> {
865        let exchange_code = CString::new(exchange_code)?;
866
867        #[repr(C)]
868        struct Cb {
869            f: Option<Box<dyn FnOnce(Result<sys::EOS_Auth_LoginCallbackInfo>) + Send>>,
870        }
871
872        unsafe extern "C" fn trampoline(data: *const sys::EOS_Auth_LoginCallbackInfo) {
873            let client_data = (*data).ClientData as *mut Cb;
874            let mut boxed = Box::from_raw(client_data);
875            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
876                Ok(*data)
877            } else {
878                Err(Error::Eos((*data).ResultCode))
879            };
880            if let Some(f) = boxed.f.take() {
881                f(res);
882            }
883        }
884
885        let cb_box = CallbackOnce::new(Cb {
886            f: Some(Box::new(cb)),
887        });
888
889        let creds = sys::EOS_Auth_Credentials {
890            ApiVersion: sys::EOS_AUTH_CREDENTIALS_API_LATEST as i32,
891            Id: std::ptr::null(),
892            Token: exchange_code.as_ptr(),
893            Type: sys::EOS_ELoginCredentialType_EOS_LCT_ExchangeCode,
894            SystemAuthCredentialsOptions: null_mut(),
895            ExternalType: sys::EOS_EExternalCredentialType_EOS_ECT_EPIC,
896        };
897
898        let options = sys::EOS_Auth_LoginOptions {
899            ApiVersion: sys::EOS_AUTH_LOGIN_API_LATEST as i32,
900            Credentials: &creds,
901            ScopeFlags: 0,
902            LoginFlags: 0,
903        };
904
905        unsafe {
906            sys::EOS_Auth_Login(
907                self.as_handle(),
908                &options,
909                cb_box.ptr as *mut _,
910                Some(trampoline),
911            );
912        }
913        Ok(())
914    }
915}
916
917impl Connect {
918    pub fn raw_handle(&self) -> sys::EOS_HConnect {
919        self.0.as_ptr() as sys::EOS_HConnect
920    }
921
922    pub fn get_login_status(&self, local_user: ProductUserId) -> LoginStatus {
923        let raw = unsafe { sys::EOS_Connect_GetLoginStatus(self.raw_handle(), local_user.raw()) };
924        LoginStatus::from_raw(raw)
925    }
926
927    pub fn copy_id_token(&self, local_user: ProductUserId) -> Result<ConnectIdToken> {
928        let options = sys::EOS_Connect_CopyIdTokenOptions {
929            ApiVersion: sys::EOS_CONNECT_COPYIDTOKEN_API_LATEST as i32,
930            LocalUserId: local_user.raw(),
931        };
932        let mut token_ptr: *mut sys::EOS_Connect_IdToken = std::ptr::null_mut();
933        let res = unsafe { sys::EOS_Connect_CopyIdToken(self.raw_handle(), &options, &mut token_ptr) };
934        ok(res)?;
935        unsafe { ConnectIdToken::from_raw(token_ptr) }
936    }
937
938    pub fn login_openid_access_token(
939        &self,
940        token: &str,
941        display_name: Option<&str>,
942        cb: impl FnOnce(Result<sys::EOS_Connect_LoginCallbackInfo>) + Send + 'static,
943    ) -> Result<()> {
944        let token = CString::new(token)?;
945        let display_name_cstr = match display_name {
946            Some(v) => Some(CString::new(v)?),
947            None => None,
948        };
949
950        #[repr(C)]
951        struct Cb {
952            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_LoginCallbackInfo>) + Send>>,
953        }
954        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_LoginCallbackInfo) {
955            let client_data = (*data).ClientData as *mut Cb;
956            let mut boxed = Box::from_raw(client_data);
957            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
958                Ok(*data)
959            } else {
960                Err(Error::Eos((*data).ResultCode))
961            };
962            if let Some(f) = boxed.f.take() {
963                f(res);
964            }
965        }
966
967        let cb_box = CallbackOnce::new(Cb {
968            f: Some(Box::new(cb)),
969        });
970
971        let credentials = sys::EOS_Connect_Credentials {
972            ApiVersion: sys::EOS_CONNECT_CREDENTIALS_API_LATEST as i32,
973            Token: token.as_ptr(),
974            Type: sys::EOS_EExternalCredentialType_EOS_ECT_OPENID_ACCESS_TOKEN,
975        };
976
977        let user_login_info;
978        let user_login_info_ptr = if let Some(name) = display_name_cstr.as_ref() {
979            user_login_info = sys::EOS_Connect_UserLoginInfo {
980                ApiVersion: sys::EOS_CONNECT_USERLOGININFO_API_LATEST as i32,
981                DisplayName: name.as_ptr(),
982                NsaIdToken: std::ptr::null(),
983            };
984            &user_login_info as *const sys::EOS_Connect_UserLoginInfo
985        } else {
986            std::ptr::null()
987        };
988
989        let options = sys::EOS_Connect_LoginOptions {
990            ApiVersion: sys::EOS_CONNECT_LOGIN_API_LATEST as i32,
991            Credentials: &credentials,
992            UserLoginInfo: user_login_info_ptr,
993        };
994
995        unsafe {
996            sys::EOS_Connect_Login(
997                self.raw_handle(),
998                &options,
999                cb_box.ptr as *mut _,
1000                Some(trampoline),
1001            );
1002        }
1003        Ok(())
1004    }
1005
1006    pub fn create_user(
1007        &self,
1008        continuance_token: ContinuanceToken,
1009        cb: impl FnOnce(Result<sys::EOS_Connect_CreateUserCallbackInfo>) + Send + 'static,
1010    ) {
1011        #[repr(C)]
1012        struct Cb {
1013            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_CreateUserCallbackInfo>) + Send>>,
1014        }
1015        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_CreateUserCallbackInfo) {
1016            let client_data = (*data).ClientData as *mut Cb;
1017            let mut boxed = Box::from_raw(client_data);
1018            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1019                Ok(*data)
1020            } else {
1021                Err(Error::Eos((*data).ResultCode))
1022            };
1023            if let Some(f) = boxed.f.take() {
1024                f(res);
1025            }
1026        }
1027
1028        let cb_box = CallbackOnce::new(Cb {
1029            f: Some(Box::new(cb)),
1030        });
1031
1032        let options = sys::EOS_Connect_CreateUserOptions {
1033            ApiVersion: sys::EOS_CONNECT_CREATEUSER_API_LATEST as i32,
1034            ContinuanceToken: continuance_token.raw(),
1035        };
1036
1037        unsafe {
1038            sys::EOS_Connect_CreateUser(
1039                self.raw_handle(),
1040                &options,
1041                cb_box.ptr as *mut _,
1042                Some(trampoline),
1043            );
1044        }
1045    }
1046
1047    pub fn logout(
1048        &self,
1049        local_user: ProductUserId,
1050        cb: impl FnOnce(Result<sys::EOS_Connect_LogoutCallbackInfo>) + Send + 'static,
1051    ) {
1052        #[repr(C)]
1053        struct Cb {
1054            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_LogoutCallbackInfo>) + Send>>,
1055        }
1056        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_LogoutCallbackInfo) {
1057            let client_data = (*data).ClientData as *mut Cb;
1058            let mut boxed = Box::from_raw(client_data);
1059            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1060                Ok(*data)
1061            } else {
1062                Err(Error::Eos((*data).ResultCode))
1063            };
1064            if let Some(f) = boxed.f.take() {
1065                f(res);
1066            }
1067        }
1068        let cb_box = CallbackOnce::new(Cb {
1069            f: Some(Box::new(cb)),
1070        });
1071        let options = sys::EOS_Connect_LogoutOptions {
1072            ApiVersion: sys::EOS_CONNECT_LOGOUT_API_LATEST as i32,
1073            LocalUserId: local_user.raw(),
1074        };
1075        unsafe {
1076            sys::EOS_Connect_Logout(
1077                self.raw_handle(),
1078                &options,
1079                cb_box.ptr as *mut _,
1080                Some(trampoline),
1081            );
1082        }
1083    }
1084
1085    pub fn link_account(
1086        &self,
1087        local_user: ProductUserId,
1088        continuance_token: ContinuanceToken,
1089        cb: impl FnOnce(Result<sys::EOS_Connect_LinkAccountCallbackInfo>) + Send + 'static,
1090    ) {
1091        #[repr(C)]
1092        struct Cb {
1093            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_LinkAccountCallbackInfo>) + Send>>,
1094        }
1095        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_LinkAccountCallbackInfo) {
1096            let client_data = (*data).ClientData as *mut Cb;
1097            let mut boxed = Box::from_raw(client_data);
1098            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1099                Ok(*data)
1100            } else {
1101                Err(Error::Eos((*data).ResultCode))
1102            };
1103            if let Some(f) = boxed.f.take() {
1104                f(res);
1105            }
1106        }
1107        let cb_box = CallbackOnce::new(Cb {
1108            f: Some(Box::new(cb)),
1109        });
1110        let options = sys::EOS_Connect_LinkAccountOptions {
1111            ApiVersion: sys::EOS_CONNECT_LINKACCOUNT_API_LATEST as i32,
1112            LocalUserId: local_user.raw(),
1113            ContinuanceToken: continuance_token.raw(),
1114        };
1115        unsafe {
1116            sys::EOS_Connect_LinkAccount(
1117                self.raw_handle(),
1118                &options,
1119                cb_box.ptr as *mut _,
1120                Some(trampoline),
1121            );
1122        }
1123    }
1124
1125    pub fn unlink_account(
1126        &self,
1127        local_user: ProductUserId,
1128        cb: impl FnOnce(Result<sys::EOS_Connect_UnlinkAccountCallbackInfo>) + Send + 'static,
1129    ) {
1130        #[repr(C)]
1131        struct Cb {
1132            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_UnlinkAccountCallbackInfo>) + Send>>,
1133        }
1134        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_UnlinkAccountCallbackInfo) {
1135            let client_data = (*data).ClientData as *mut Cb;
1136            let mut boxed = Box::from_raw(client_data);
1137            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1138                Ok(*data)
1139            } else {
1140                Err(Error::Eos((*data).ResultCode))
1141            };
1142            if let Some(f) = boxed.f.take() {
1143                f(res);
1144            }
1145        }
1146        let cb_box = CallbackOnce::new(Cb {
1147            f: Some(Box::new(cb)),
1148        });
1149        let options = sys::EOS_Connect_UnlinkAccountOptions {
1150            ApiVersion: sys::EOS_CONNECT_UNLINKACCOUNT_API_LATEST as i32,
1151            LocalUserId: local_user.raw(),
1152        };
1153        unsafe {
1154            sys::EOS_Connect_UnlinkAccount(
1155                self.raw_handle(),
1156                &options,
1157                cb_box.ptr as *mut _,
1158                Some(trampoline),
1159            );
1160        }
1161    }
1162
1163    pub fn create_device_id(
1164        &self,
1165        device_model: &str,
1166        cb: impl FnOnce(Result<sys::EOS_Connect_CreateDeviceIdCallbackInfo>) + Send + 'static,
1167    ) -> Result<()> {
1168        #[repr(C)]
1169        struct Cb {
1170            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_CreateDeviceIdCallbackInfo>) + Send>>,
1171        }
1172        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_CreateDeviceIdCallbackInfo) {
1173            let client_data = (*data).ClientData as *mut Cb;
1174            let mut boxed = Box::from_raw(client_data);
1175            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1176                Ok(*data)
1177            } else {
1178                Err(Error::Eos((*data).ResultCode))
1179            };
1180            if let Some(f) = boxed.f.take() {
1181                f(res);
1182            }
1183        }
1184        let cb_box = CallbackOnce::new(Cb {
1185            f: Some(Box::new(cb)),
1186        });
1187        let model = CString::new(device_model)?;
1188        let options = sys::EOS_Connect_CreateDeviceIdOptions {
1189            ApiVersion: sys::EOS_CONNECT_CREATEDEVICEID_API_LATEST as i32,
1190            DeviceModel: model.as_ptr(),
1191        };
1192        unsafe {
1193            sys::EOS_Connect_CreateDeviceId(
1194                self.raw_handle(),
1195                &options,
1196                cb_box.ptr as *mut _,
1197                Some(trampoline),
1198            );
1199        }
1200        Ok(())
1201    }
1202
1203    pub fn delete_device_id(
1204        &self,
1205        cb: impl FnOnce(Result<sys::EOS_Connect_DeleteDeviceIdCallbackInfo>) + Send + 'static,
1206    ) {
1207        #[repr(C)]
1208        struct Cb {
1209            f: Option<Box<dyn FnOnce(Result<sys::EOS_Connect_DeleteDeviceIdCallbackInfo>) + Send>>,
1210        }
1211        unsafe extern "C" fn trampoline(data: *const sys::EOS_Connect_DeleteDeviceIdCallbackInfo) {
1212            let client_data = (*data).ClientData as *mut Cb;
1213            let mut boxed = Box::from_raw(client_data);
1214            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1215                Ok(*data)
1216            } else {
1217                Err(Error::Eos((*data).ResultCode))
1218            };
1219            if let Some(f) = boxed.f.take() {
1220                f(res);
1221            }
1222        }
1223        let cb_box = CallbackOnce::new(Cb {
1224            f: Some(Box::new(cb)),
1225        });
1226        let options = sys::EOS_Connect_DeleteDeviceIdOptions {
1227            ApiVersion: sys::EOS_CONNECT_DELETEDEVICEID_API_LATEST as i32,
1228        };
1229        unsafe {
1230            sys::EOS_Connect_DeleteDeviceId(
1231                self.raw_handle(),
1232                &options,
1233                cb_box.ptr as *mut _,
1234                Some(trampoline),
1235            );
1236        }
1237    }
1238
1239    pub fn transfer_device_id_account(
1240        &self,
1241        primary_local_user: ProductUserId,
1242        local_device_user: ProductUserId,
1243        product_user_to_preserve: ProductUserId,
1244        cb: impl FnOnce(Result<sys::EOS_Connect_TransferDeviceIdAccountCallbackInfo>) + Send + 'static,
1245    ) {
1246        #[repr(C)]
1247        struct Cb {
1248            f: Option<
1249                Box<dyn FnOnce(Result<sys::EOS_Connect_TransferDeviceIdAccountCallbackInfo>) + Send>,
1250            >,
1251        }
1252        unsafe extern "C" fn trampoline(
1253            data: *const sys::EOS_Connect_TransferDeviceIdAccountCallbackInfo,
1254        ) {
1255            let client_data = (*data).ClientData as *mut Cb;
1256            let mut boxed = Box::from_raw(client_data);
1257            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1258                Ok(*data)
1259            } else {
1260                Err(Error::Eos((*data).ResultCode))
1261            };
1262            if let Some(f) = boxed.f.take() {
1263                f(res);
1264            }
1265        }
1266        let cb_box = CallbackOnce::new(Cb {
1267            f: Some(Box::new(cb)),
1268        });
1269        let options = sys::EOS_Connect_TransferDeviceIdAccountOptions {
1270            ApiVersion: sys::EOS_CONNECT_TRANSFERDEVICEIDACCOUNT_API_LATEST as i32,
1271            PrimaryLocalUserId: primary_local_user.raw(),
1272            LocalDeviceUserId: local_device_user.raw(),
1273            ProductUserIdToPreserve: product_user_to_preserve.raw(),
1274        };
1275        unsafe {
1276            sys::EOS_Connect_TransferDeviceIdAccount(
1277                self.raw_handle(),
1278                &options,
1279                cb_box.ptr as *mut _,
1280                Some(trampoline),
1281            );
1282        }
1283    }
1284}
1285
1286macro_rules! impl_raw_handle {
1287    ($ty:ident, $raw:ty) => {
1288        impl $ty {
1289            pub fn raw_handle(&self) -> $raw {
1290                self.0.as_ptr() as $raw
1291            }
1292        }
1293    };
1294}
1295
1296impl_raw_handle!(Achievements, sys::EOS_HAchievements);
1297impl_raw_handle!(AntiCheatClient, sys::EOS_HAntiCheatClient);
1298impl_raw_handle!(AntiCheatServer, sys::EOS_HAntiCheatServer);
1299impl_raw_handle!(CustomInvites, sys::EOS_HCustomInvites);
1300impl_raw_handle!(Ecom, sys::EOS_HEcom);
1301impl_raw_handle!(Friends, sys::EOS_HFriends);
1302impl_raw_handle!(IntegratedPlatform, sys::EOS_HIntegratedPlatform);
1303impl_raw_handle!(Kws, sys::EOS_HKWS);
1304impl_raw_handle!(Leaderboards, sys::EOS_HLeaderboards);
1305impl_raw_handle!(Metrics, sys::EOS_HMetrics);
1306impl_raw_handle!(Mods, sys::EOS_HMods);
1307impl_raw_handle!(PlayerDataStorage, sys::EOS_HPlayerDataStorage);
1308impl_raw_handle!(Presence, sys::EOS_HPresence);
1309impl_raw_handle!(ProgressionSnapshot, sys::EOS_HProgressionSnapshot);
1310impl_raw_handle!(Reports, sys::EOS_HReports);
1311impl_raw_handle!(Rtc, sys::EOS_HRTC);
1312impl_raw_handle!(RtcAdmin, sys::EOS_HRTCAdmin);
1313impl_raw_handle!(Sanctions, sys::EOS_HSanctions);
1314impl_raw_handle!(Sessions, sys::EOS_HSessions);
1315impl_raw_handle!(Stats, sys::EOS_HStats);
1316impl_raw_handle!(TitleStorage, sys::EOS_HTitleStorage);
1317impl_raw_handle!(Ui, sys::EOS_HUI);
1318impl_raw_handle!(UserInfo, sys::EOS_HUserInfo);
1319
1320impl Lobby {
1321    pub fn raw_handle(&self) -> sys::EOS_HLobby {
1322        self.0.as_ptr() as sys::EOS_HLobby
1323    }
1324
1325    pub fn get_invite_count(&self, local_user: ProductUserId) -> u32 {
1326        let opts = sys::EOS_Lobby_GetInviteCountOptions {
1327            ApiVersion: sys::EOS_LOBBY_GETINVITECOUNT_API_LATEST as i32,
1328            LocalUserId: local_user.raw(),
1329        };
1330        unsafe { sys::EOS_Lobby_GetInviteCount(self.raw_handle(), &opts) }
1331    }
1332
1333    pub fn get_invite_id_by_index(&self, local_user: ProductUserId, index: u32) -> Result<String> {
1334        let opts = sys::EOS_Lobby_GetInviteIdByIndexOptions {
1335            ApiVersion: sys::EOS_LOBBY_GETINVITEIDBYINDEX_API_LATEST as i32,
1336            LocalUserId: local_user.raw(),
1337            Index: index,
1338        };
1339        let mut buf = vec![0i8; (sys::EOS_LOBBY_INVITEID_MAX_LENGTH + 1) as usize];
1340        let mut len = buf.len() as i32;
1341        let res =
1342            unsafe { sys::EOS_Lobby_GetInviteIdByIndex(self.raw_handle(), &opts, buf.as_mut_ptr(), &mut len) };
1343        ok(res)?;
1344        Ok(unsafe { CStr::from_ptr(buf.as_ptr()) }
1345            .to_string_lossy()
1346            .into_owned())
1347    }
1348
1349    pub fn create_lobby_search(&self, max_results: u32) -> Result<LobbySearch> {
1350        let opts = sys::EOS_Lobby_CreateLobbySearchOptions {
1351            ApiVersion: sys::EOS_LOBBY_CREATELOBBYSEARCH_API_LATEST as i32,
1352            MaxResults: max_results,
1353        };
1354        let mut out: sys::EOS_HLobbySearch = std::ptr::null_mut();
1355        let res = unsafe { sys::EOS_Lobby_CreateLobbySearch(self.raw_handle(), &opts, &mut out) };
1356        ok(res)?;
1357        unsafe { LobbySearch::from_raw(out) }
1358    }
1359
1360    pub fn copy_lobby_details_handle(
1361        &self,
1362        lobby_id: &str,
1363        local_user: ProductUserId,
1364    ) -> Result<LobbyDetails> {
1365        let lobby_id = CString::new(lobby_id)?;
1366        let opts = sys::EOS_Lobby_CopyLobbyDetailsHandleOptions {
1367            ApiVersion: sys::EOS_LOBBY_COPYLOBBYDETAILSHANDLE_API_LATEST as i32,
1368            LobbyId: lobby_id.as_ptr(),
1369            LocalUserId: local_user.raw(),
1370        };
1371        let mut out: sys::EOS_HLobbyDetails = std::ptr::null_mut();
1372        let res = unsafe { sys::EOS_Lobby_CopyLobbyDetailsHandle(self.raw_handle(), &opts, &mut out) };
1373        ok(res)?;
1374        unsafe { LobbyDetails::from_raw(out) }
1375    }
1376
1377    pub fn update_lobby_modification(
1378        &self,
1379        local_user: ProductUserId,
1380        lobby_id: &str,
1381    ) -> Result<LobbyModification> {
1382        let lobby_id = CString::new(lobby_id)?;
1383        let opts = sys::EOS_Lobby_UpdateLobbyModificationOptions {
1384            ApiVersion: sys::EOS_LOBBY_UPDATELOBBYMODIFICATION_API_LATEST as i32,
1385            LocalUserId: local_user.raw(),
1386            LobbyId: lobby_id.as_ptr(),
1387        };
1388        let mut out: sys::EOS_HLobbyModification = std::ptr::null_mut();
1389        let res = unsafe { sys::EOS_Lobby_UpdateLobbyModification(self.raw_handle(), &opts, &mut out) };
1390        ok(res)?;
1391        unsafe { LobbyModification::from_raw(out) }
1392    }
1393
1394    pub fn get_rtc_room_name(&self, lobby_id: &str, local_user: ProductUserId) -> Result<String> {
1395        let lobby_id = CString::new(lobby_id)?;
1396        let opts = sys::EOS_Lobby_GetRTCRoomNameOptions {
1397            ApiVersion: sys::EOS_LOBBY_GETRTCROOMNAME_API_LATEST as i32,
1398            LobbyId: lobby_id.as_ptr(),
1399            LocalUserId: local_user.raw(),
1400        };
1401        // EOS headers do not expose a public max-length constant for RTC room name.
1402        // Use a conservative fixed buffer; EOS returns EOS_LimitExceeded if insufficient.
1403        let mut buf = vec![0i8; 256];
1404        let mut len = buf.len() as u32;
1405        let res =
1406            unsafe { sys::EOS_Lobby_GetRTCRoomName(self.raw_handle(), &opts, buf.as_mut_ptr(), &mut len) };
1407        ok(res)?;
1408        Ok(unsafe { CStr::from_ptr(buf.as_ptr()) }
1409            .to_string_lossy()
1410            .into_owned())
1411    }
1412
1413    pub fn create_lobby(
1414        &self,
1415        local_user: ProductUserId,
1416        params: &CreateLobbyParams,
1417        cb: impl FnOnce(Result<sys::EOS_Lobby_CreateLobbyCallbackInfo>) + Send + 'static,
1418    ) -> Result<()> {
1419        #[repr(C)]
1420        struct Cb {
1421            f: Option<Box<dyn FnOnce(Result<sys::EOS_Lobby_CreateLobbyCallbackInfo>) + Send>>,
1422        }
1423        unsafe extern "C" fn trampoline(data: *const sys::EOS_Lobby_CreateLobbyCallbackInfo) {
1424            let client_data = (*data).ClientData as *mut Cb;
1425            let mut boxed = Box::from_raw(client_data);
1426            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1427                Ok(*data)
1428            } else {
1429                Err(Error::Eos((*data).ResultCode))
1430            };
1431            if let Some(f) = boxed.f.take() {
1432                f(res);
1433            }
1434        }
1435        let cb_box = CallbackOnce::new(Cb {
1436            f: Some(Box::new(cb)),
1437        });
1438
1439        let bucket_id = CString::new(params.bucket_id.clone())?;
1440        let options = sys::EOS_Lobby_CreateLobbyOptions {
1441            ApiVersion: sys::EOS_LOBBY_CREATELOBBY_API_LATEST as i32,
1442            LocalUserId: local_user.raw(),
1443            MaxLobbyMembers: params.max_lobby_members,
1444            PermissionLevel: params.permission_level,
1445            bPresenceEnabled: if params.presence_enabled { 1 } else { 0 },
1446            bAllowInvites: if params.allow_invites { 1 } else { 0 },
1447            BucketId: bucket_id.as_ptr(),
1448            bDisableHostMigration: if params.disable_host_migration { 1 } else { 0 },
1449            bEnableRTCRoom: if params.enable_rtc_room { 1 } else { 0 },
1450            LocalRTCOptions: std::ptr::null(),
1451            LobbyId: std::ptr::null(),
1452            bEnableJoinById: if params.enable_join_by_id { 1 } else { 0 },
1453            bRejoinAfterKickRequiresInvite: if params.rejoin_after_kick_requires_invite { 1 } else { 0 },
1454            AllowedPlatformIds: std::ptr::null(),
1455            AllowedPlatformIdsCount: 0,
1456            bCrossplayOptOut: 0,
1457            RTCRoomJoinActionType: sys::EOS_ELobbyRTCRoomJoinActionType_EOS_LRRJAT_AutomaticJoin,
1458        };
1459
1460        unsafe {
1461            sys::EOS_Lobby_CreateLobby(
1462                self.raw_handle(),
1463                &options,
1464                cb_box.ptr as *mut _,
1465                Some(trampoline),
1466            );
1467        }
1468        Ok(())
1469    }
1470
1471    pub fn join_lobby(
1472        &self,
1473        lobby_details: &LobbyDetails,
1474        local_user: ProductUserId,
1475        presence_enabled: bool,
1476        cb: impl FnOnce(Result<sys::EOS_Lobby_JoinLobbyCallbackInfo>) + Send + 'static,
1477    ) {
1478        #[repr(C)]
1479        struct Cb {
1480            f: Option<Box<dyn FnOnce(Result<sys::EOS_Lobby_JoinLobbyCallbackInfo>) + Send>>,
1481        }
1482        unsafe extern "C" fn trampoline(data: *const sys::EOS_Lobby_JoinLobbyCallbackInfo) {
1483            let client_data = (*data).ClientData as *mut Cb;
1484            let mut boxed = Box::from_raw(client_data);
1485            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1486                Ok(*data)
1487            } else {
1488                Err(Error::Eos((*data).ResultCode))
1489            };
1490            if let Some(f) = boxed.f.take() {
1491                f(res);
1492            }
1493        }
1494        let cb_box = CallbackOnce::new(Cb {
1495            f: Some(Box::new(cb)),
1496        });
1497        let options = sys::EOS_Lobby_JoinLobbyOptions {
1498            ApiVersion: sys::EOS_LOBBY_JOINLOBBY_API_LATEST as i32,
1499            LobbyDetailsHandle: lobby_details.raw_handle(),
1500            LocalUserId: local_user.raw(),
1501            bPresenceEnabled: if presence_enabled { 1 } else { 0 },
1502            LocalRTCOptions: std::ptr::null(),
1503            bCrossplayOptOut: 0,
1504            RTCRoomJoinActionType: sys::EOS_ELobbyRTCRoomJoinActionType_EOS_LRRJAT_AutomaticJoin,
1505        };
1506        unsafe {
1507            sys::EOS_Lobby_JoinLobby(
1508                self.raw_handle(),
1509                &options,
1510                cb_box.ptr as *mut _,
1511                Some(trampoline),
1512            );
1513        }
1514    }
1515
1516    pub fn leave_lobby(
1517        &self,
1518        local_user: ProductUserId,
1519        lobby_id: &str,
1520        cb: impl FnOnce(Result<sys::EOS_Lobby_LeaveLobbyCallbackInfo>) + Send + 'static,
1521    ) -> Result<()> {
1522        #[repr(C)]
1523        struct Cb {
1524            f: Option<Box<dyn FnOnce(Result<sys::EOS_Lobby_LeaveLobbyCallbackInfo>) + Send>>,
1525        }
1526        unsafe extern "C" fn trampoline(data: *const sys::EOS_Lobby_LeaveLobbyCallbackInfo) {
1527            let client_data = (*data).ClientData as *mut Cb;
1528            let mut boxed = Box::from_raw(client_data);
1529            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1530                Ok(*data)
1531            } else {
1532                Err(Error::Eos((*data).ResultCode))
1533            };
1534            if let Some(f) = boxed.f.take() {
1535                f(res);
1536            }
1537        }
1538        let cb_box = CallbackOnce::new(Cb {
1539            f: Some(Box::new(cb)),
1540        });
1541        let lobby_id = CString::new(lobby_id)?;
1542        let options = sys::EOS_Lobby_LeaveLobbyOptions {
1543            ApiVersion: sys::EOS_LOBBY_LEAVELOBBY_API_LATEST as i32,
1544            LobbyId: lobby_id.as_ptr(),
1545            LocalUserId: local_user.raw(),
1546        };
1547        unsafe {
1548            sys::EOS_Lobby_LeaveLobby(
1549                self.raw_handle(),
1550                &options,
1551                cb_box.ptr as *mut _,
1552                Some(trampoline),
1553            );
1554        }
1555        Ok(())
1556    }
1557
1558    pub fn destroy_lobby(
1559        &self,
1560        local_user: ProductUserId,
1561        lobby_id: &str,
1562        cb: impl FnOnce(Result<sys::EOS_Lobby_DestroyLobbyCallbackInfo>) + Send + 'static,
1563    ) -> Result<()> {
1564        #[repr(C)]
1565        struct Cb {
1566            f: Option<Box<dyn FnOnce(Result<sys::EOS_Lobby_DestroyLobbyCallbackInfo>) + Send>>,
1567        }
1568        unsafe extern "C" fn trampoline(data: *const sys::EOS_Lobby_DestroyLobbyCallbackInfo) {
1569            let client_data = (*data).ClientData as *mut Cb;
1570            let mut boxed = Box::from_raw(client_data);
1571            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1572                Ok(*data)
1573            } else {
1574                Err(Error::Eos((*data).ResultCode))
1575            };
1576            if let Some(f) = boxed.f.take() {
1577                f(res);
1578            }
1579        }
1580        let cb_box = CallbackOnce::new(Cb {
1581            f: Some(Box::new(cb)),
1582        });
1583        let lobby_id = CString::new(lobby_id)?;
1584        let options = sys::EOS_Lobby_DestroyLobbyOptions {
1585            ApiVersion: sys::EOS_LOBBY_DESTROYLOBBY_API_LATEST as i32,
1586            LocalUserId: local_user.raw(),
1587            LobbyId: lobby_id.as_ptr(),
1588        };
1589        unsafe {
1590            sys::EOS_Lobby_DestroyLobby(
1591                self.raw_handle(),
1592                &options,
1593                cb_box.ptr as *mut _,
1594                Some(trampoline),
1595            );
1596        }
1597        Ok(())
1598    }
1599}
1600
1601impl P2P {
1602    pub fn raw_handle(&self) -> sys::EOS_HP2P {
1603        self.0.as_ptr() as sys::EOS_HP2P
1604    }
1605
1606    pub fn query_nat_type(
1607        &self,
1608        cb: impl FnOnce(Result<NatType>) + Send + 'static,
1609    ) {
1610        #[repr(C)]
1611        struct Cb {
1612            f: Option<Box<dyn FnOnce(Result<NatType>) + Send>>,
1613        }
1614        unsafe extern "C" fn trampoline(data: *const sys::EOS_P2P_OnQueryNATTypeCompleteInfo) {
1615            let client_data = (*data).ClientData as *mut Cb;
1616            let mut boxed = Box::from_raw(client_data);
1617            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
1618                Ok(NatType::from_raw((*data).NATType))
1619            } else {
1620                Err(Error::Eos((*data).ResultCode))
1621            };
1622            if let Some(f) = boxed.f.take() {
1623                f(res);
1624            }
1625        }
1626
1627        let cb_box = CallbackOnce::new(Cb {
1628            f: Some(Box::new(cb)),
1629        });
1630        let opts = sys::EOS_P2P_QueryNATTypeOptions {
1631            ApiVersion: sys::EOS_P2P_QUERYNATTYPE_API_LATEST as i32,
1632        };
1633        unsafe {
1634            sys::EOS_P2P_QueryNATType(self.raw_handle(), &opts, cb_box.ptr as *mut _, Some(trampoline));
1635        }
1636    }
1637
1638    pub fn get_nat_type(&self) -> Result<NatType> {
1639        let opts = sys::EOS_P2P_GetNATTypeOptions {
1640            ApiVersion: sys::EOS_P2P_GETNATTYPE_API_LATEST as i32,
1641        };
1642        let mut out = sys::EOS_ENATType_EOS_NAT_Unknown;
1643        let res = unsafe { sys::EOS_P2P_GetNATType(self.raw_handle(), &opts, &mut out) };
1644        ok(res)?;
1645        Ok(NatType::from_raw(out))
1646    }
1647
1648    pub fn set_relay_control(&self, relay: RelayControl) -> Result<()> {
1649        let opts = sys::EOS_P2P_SetRelayControlOptions {
1650            ApiVersion: sys::EOS_P2P_SETRELAYCONTROL_API_LATEST as i32,
1651            RelayControl: relay.to_raw(),
1652        };
1653        let res = unsafe { sys::EOS_P2P_SetRelayControl(self.raw_handle(), &opts) };
1654        ok(res)
1655    }
1656
1657    pub fn get_relay_control(&self) -> Result<RelayControl> {
1658        let opts = sys::EOS_P2P_GetRelayControlOptions {
1659            ApiVersion: sys::EOS_P2P_GETRELAYCONTROL_API_LATEST as i32,
1660        };
1661        let mut out = sys::EOS_ERelayControl_EOS_RC_AllowRelays;
1662        let res = unsafe { sys::EOS_P2P_GetRelayControl(self.raw_handle(), &opts, &mut out) };
1663        ok(res)?;
1664        Ok(RelayControl::from_raw(out))
1665    }
1666
1667    pub fn set_port_range(&self, port: u16, max_additional_ports: u16) -> Result<()> {
1668        let opts = sys::EOS_P2P_SetPortRangeOptions {
1669            ApiVersion: sys::EOS_P2P_SETPORTRANGE_API_LATEST as i32,
1670            Port: port,
1671            MaxAdditionalPortsToTry: max_additional_ports,
1672        };
1673        let res = unsafe { sys::EOS_P2P_SetPortRange(self.raw_handle(), &opts) };
1674        ok(res)
1675    }
1676
1677    pub fn get_port_range(&self) -> Result<(u16, u16)> {
1678        let opts = sys::EOS_P2P_GetPortRangeOptions {
1679            ApiVersion: sys::EOS_P2P_GETPORTRANGE_API_LATEST as i32,
1680        };
1681        let mut port = 0u16;
1682        let mut extra = 0u16;
1683        let res = unsafe { sys::EOS_P2P_GetPortRange(self.raw_handle(), &opts, &mut port, &mut extra) };
1684        ok(res)?;
1685        Ok((port, extra))
1686    }
1687
1688    pub fn set_packet_queue_size(&self, incoming_max: u64, outgoing_max: u64) -> Result<()> {
1689        let opts = sys::EOS_P2P_SetPacketQueueSizeOptions {
1690            ApiVersion: sys::EOS_P2P_SETPACKETQUEUESIZE_API_LATEST as i32,
1691            IncomingPacketQueueMaxSizeBytes: incoming_max,
1692            OutgoingPacketQueueMaxSizeBytes: outgoing_max,
1693        };
1694        let res = unsafe { sys::EOS_P2P_SetPacketQueueSize(self.raw_handle(), &opts) };
1695        ok(res)
1696    }
1697
1698    pub fn get_packet_queue_info(&self) -> Result<PacketQueueInfo> {
1699        let opts = sys::EOS_P2P_GetPacketQueueInfoOptions {
1700            ApiVersion: sys::EOS_P2P_GETPACKETQUEUEINFO_API_LATEST as i32,
1701        };
1702        let mut out = sys::EOS_P2P_PacketQueueInfo::default();
1703        let res = unsafe { sys::EOS_P2P_GetPacketQueueInfo(self.raw_handle(), &opts, &mut out) };
1704        ok(res)?;
1705        Ok(PacketQueueInfo {
1706            incoming_max_size_bytes: out.IncomingPacketQueueMaxSizeBytes,
1707            incoming_current_size_bytes: out.IncomingPacketQueueCurrentSizeBytes,
1708            incoming_current_packet_count: out.IncomingPacketQueueCurrentPacketCount,
1709            outgoing_max_size_bytes: out.OutgoingPacketQueueMaxSizeBytes,
1710            outgoing_current_size_bytes: out.OutgoingPacketQueueCurrentSizeBytes,
1711            outgoing_current_packet_count: out.OutgoingPacketQueueCurrentPacketCount,
1712        })
1713    }
1714
1715    pub fn send_packet(
1716        &self,
1717        local_user: ProductUserId,
1718        remote_user: ProductUserId,
1719        socket_name: &str,
1720        channel: u8,
1721        data: &[u8],
1722        reliability: PacketReliability,
1723        allow_delayed_delivery: bool,
1724        disable_auto_accept_connection: bool,
1725    ) -> Result<()> {
1726        let socket = make_socket_id(socket_name)?;
1727        let opts = sys::EOS_P2P_SendPacketOptions {
1728            ApiVersion: sys::EOS_P2P_SENDPACKET_API_LATEST as i32,
1729            LocalUserId: local_user.raw(),
1730            RemoteUserId: remote_user.raw(),
1731            SocketId: &socket,
1732            Channel: channel,
1733            DataLengthBytes: data.len() as u32,
1734            Data: data.as_ptr() as *const _,
1735            bAllowDelayedDelivery: if allow_delayed_delivery { 1 } else { 0 },
1736            Reliability: reliability.to_raw(),
1737            bDisableAutoAcceptConnection: if disable_auto_accept_connection { 1 } else { 0 },
1738        };
1739        let res = unsafe { sys::EOS_P2P_SendPacket(self.raw_handle(), &opts) };
1740        ok(res)
1741    }
1742
1743    pub fn get_next_received_packet_size(
1744        &self,
1745        local_user: ProductUserId,
1746        requested_channel: Option<u8>,
1747    ) -> Result<u32> {
1748        let requested = requested_channel.unwrap_or_default();
1749        let requested_ptr = if requested_channel.is_some() {
1750            &requested as *const u8
1751        } else {
1752            std::ptr::null()
1753        };
1754        let opts = sys::EOS_P2P_GetNextReceivedPacketSizeOptions {
1755            ApiVersion: sys::EOS_P2P_GETNEXTRECEIVEDPACKETSIZE_API_LATEST as i32,
1756            LocalUserId: local_user.raw(),
1757            RequestedChannel: requested_ptr,
1758        };
1759        let mut size = 0u32;
1760        let res = unsafe { sys::EOS_P2P_GetNextReceivedPacketSize(self.raw_handle(), &opts, &mut size) };
1761        ok(res)?;
1762        Ok(size)
1763    }
1764
1765    pub fn receive_packet(
1766        &self,
1767        local_user: ProductUserId,
1768        max_data_size_bytes: u32,
1769        requested_channel: Option<u8>,
1770    ) -> Result<ReceivedPacket> {
1771        let requested = requested_channel.unwrap_or_default();
1772        let requested_ptr = if requested_channel.is_some() {
1773            &requested as *const u8
1774        } else {
1775            std::ptr::null()
1776        };
1777        let opts = sys::EOS_P2P_ReceivePacketOptions {
1778            ApiVersion: sys::EOS_P2P_RECEIVEPACKET_API_LATEST as i32,
1779            LocalUserId: local_user.raw(),
1780            MaxDataSizeBytes: max_data_size_bytes,
1781            RequestedChannel: requested_ptr,
1782        };
1783        let mut peer = std::ptr::null_mut();
1784        let mut socket = sys::EOS_P2P_SocketId::default();
1785        let mut channel = 0u8;
1786        let mut data = vec![0u8; max_data_size_bytes as usize];
1787        let mut bytes_written = 0u32;
1788        let res = unsafe {
1789            sys::EOS_P2P_ReceivePacket(
1790                self.raw_handle(),
1791                &opts,
1792                &mut peer,
1793                &mut socket,
1794                &mut channel,
1795                data.as_mut_ptr() as *mut _,
1796                &mut bytes_written,
1797            )
1798        };
1799        ok(res)?;
1800        data.truncate(bytes_written as usize);
1801        Ok(ReceivedPacket {
1802            peer_id: ProductUserId(peer),
1803            socket_name: socket_name_from_raw(&socket),
1804            channel,
1805            data,
1806        })
1807    }
1808
1809    pub fn accept_connection(
1810        &self,
1811        local_user: ProductUserId,
1812        remote_user: ProductUserId,
1813        socket_name: &str,
1814    ) -> Result<()> {
1815        let socket = make_socket_id(socket_name)?;
1816        let opts = sys::EOS_P2P_AcceptConnectionOptions {
1817            ApiVersion: sys::EOS_P2P_ACCEPTCONNECTION_API_LATEST as i32,
1818            LocalUserId: local_user.raw(),
1819            RemoteUserId: remote_user.raw(),
1820            SocketId: &socket,
1821        };
1822        ok(unsafe { sys::EOS_P2P_AcceptConnection(self.raw_handle(), &opts) })
1823    }
1824
1825    pub fn close_connection(
1826        &self,
1827        local_user: ProductUserId,
1828        remote_user: ProductUserId,
1829        socket_name: Option<&str>,
1830    ) -> Result<()> {
1831        let socket = match socket_name {
1832            Some(n) => Some(make_socket_id(n)?),
1833            None => None,
1834        };
1835        let opts = sys::EOS_P2P_CloseConnectionOptions {
1836            ApiVersion: sys::EOS_P2P_CLOSECONNECTION_API_LATEST as i32,
1837            LocalUserId: local_user.raw(),
1838            RemoteUserId: remote_user.raw(),
1839            SocketId: socket
1840                .as_ref()
1841                .map(|s| s as *const _)
1842                .unwrap_or(std::ptr::null()),
1843        };
1844        ok(unsafe { sys::EOS_P2P_CloseConnection(self.raw_handle(), &opts) })
1845    }
1846
1847    pub fn close_connections(&self, local_user: ProductUserId, socket_name: &str) -> Result<()> {
1848        let socket = make_socket_id(socket_name)?;
1849        let opts = sys::EOS_P2P_CloseConnectionsOptions {
1850            ApiVersion: sys::EOS_P2P_CLOSECONNECTIONS_API_LATEST as i32,
1851            LocalUserId: local_user.raw(),
1852            SocketId: &socket,
1853        };
1854        ok(unsafe { sys::EOS_P2P_CloseConnections(self.raw_handle(), &opts) })
1855    }
1856
1857    pub fn clear_packet_queue(
1858        &self,
1859        local_user: ProductUserId,
1860        remote_user: ProductUserId,
1861        socket_name: &str,
1862    ) -> Result<()> {
1863        let socket = make_socket_id(socket_name)?;
1864        let opts = sys::EOS_P2P_ClearPacketQueueOptions {
1865            ApiVersion: sys::EOS_P2P_CLEARPACKETQUEUE_API_LATEST as i32,
1866            LocalUserId: local_user.raw(),
1867            RemoteUserId: remote_user.raw(),
1868            SocketId: &socket,
1869        };
1870        ok(unsafe { sys::EOS_P2P_ClearPacketQueue(self.raw_handle(), &opts) })
1871    }
1872}
1873
1874// ---- Owned EOS objects with explicit Release() ----
1875
1876macro_rules! owned_ptr_release {
1877    ($name:ident, $inner:ty, $release:path) => {
1878        pub struct $name(NonNull<$inner>);
1879
1880        impl $name {
1881            pub unsafe fn from_raw(ptr: *mut $inner) -> Result<Self> {
1882                Ok(Self(NonNull::new(ptr).ok_or(Error::Null)?))
1883            }
1884
1885            pub fn as_ptr(&self) -> *mut $inner {
1886                self.0.as_ptr()
1887            }
1888
1889            pub fn into_raw(self) -> *mut $inner {
1890                let p = self.0.as_ptr();
1891                std::mem::forget(self);
1892                p
1893            }
1894        }
1895
1896        impl Drop for $name {
1897            fn drop(&mut self) {
1898                unsafe { $release(self.0.as_ptr()) }
1899            }
1900        }
1901    };
1902}
1903
1904macro_rules! owned_handle_release {
1905    ($name:ident, $handle:ty, $release:path) => {
1906        pub struct $name($handle);
1907
1908        impl $name {
1909            pub unsafe fn from_raw(h: $handle) -> Result<Self> {
1910                if (h as usize) == 0 {
1911                    return Err(Error::Null);
1912                }
1913                Ok(Self(h))
1914            }
1915
1916            pub fn raw_handle(&self) -> $handle {
1917                self.0
1918            }
1919
1920            pub fn into_raw(self) -> $handle {
1921                let h = self.0;
1922                std::mem::forget(self);
1923                h
1924            }
1925        }
1926
1927        impl Drop for $name {
1928            fn drop(&mut self) {
1929                unsafe { $release(self.0) }
1930            }
1931        }
1932    };
1933}
1934
1935owned_ptr_release!(AuthToken, sys::EOS_Auth_Token, sys::EOS_Auth_Token_Release);
1936owned_ptr_release!(AuthIdToken, sys::EOS_Auth_IdToken, sys::EOS_Auth_IdToken_Release);
1937owned_ptr_release!(
1938    ConnectExternalAccountInfo,
1939    sys::EOS_Connect_ExternalAccountInfo,
1940    sys::EOS_Connect_ExternalAccountInfo_Release
1941);
1942owned_ptr_release!(ConnectIdToken, sys::EOS_Connect_IdToken, sys::EOS_Connect_IdToken_Release);
1943owned_ptr_release!(
1944    EcomEntitlement,
1945    sys::EOS_Ecom_Entitlement,
1946    sys::EOS_Ecom_Entitlement_Release
1947);
1948owned_ptr_release!(
1949    EcomCatalogItem,
1950    sys::EOS_Ecom_CatalogItem,
1951    sys::EOS_Ecom_CatalogItem_Release
1952);
1953owned_ptr_release!(
1954    EcomCatalogOffer,
1955    sys::EOS_Ecom_CatalogOffer,
1956    sys::EOS_Ecom_CatalogOffer_Release
1957);
1958owned_ptr_release!(
1959    EcomKeyImageInfo,
1960    sys::EOS_Ecom_KeyImageInfo,
1961    sys::EOS_Ecom_KeyImageInfo_Release
1962);
1963owned_ptr_release!(
1964    EcomCatalogRelease,
1965    sys::EOS_Ecom_CatalogRelease,
1966    sys::EOS_Ecom_CatalogRelease_Release
1967);
1968owned_handle_release!(
1969    EcomTransaction,
1970    sys::EOS_Ecom_HTransaction,
1971    sys::EOS_Ecom_Transaction_Release
1972);
1973owned_ptr_release!(PresenceInfo, sys::EOS_Presence_Info, sys::EOS_Presence_Info_Release);
1974owned_handle_release!(
1975    PresenceModification,
1976    sys::EOS_HPresenceModification,
1977    sys::EOS_PresenceModification_Release
1978);
1979owned_handle_release!(
1980    SessionModification,
1981    sys::EOS_HSessionModification,
1982    sys::EOS_SessionModification_Release
1983);
1984owned_handle_release!(
1985    ActiveSession,
1986    sys::EOS_HActiveSession,
1987    sys::EOS_ActiveSession_Release
1988);
1989owned_handle_release!(
1990    SessionDetails,
1991    sys::EOS_HSessionDetails,
1992    sys::EOS_SessionDetails_Release
1993);
1994owned_handle_release!(
1995    SessionSearch,
1996    sys::EOS_HSessionSearch,
1997    sys::EOS_SessionSearch_Release
1998);
1999owned_ptr_release!(
2000    SessionDetailsAttribute,
2001    sys::EOS_SessionDetails_Attribute,
2002    sys::EOS_SessionDetails_Attribute_Release
2003);
2004owned_ptr_release!(
2005    SessionDetailsInfo,
2006    sys::EOS_SessionDetails_Info,
2007    sys::EOS_SessionDetails_Info_Release
2008);
2009owned_ptr_release!(
2010    ActiveSessionInfo,
2011    sys::EOS_ActiveSession_Info,
2012    sys::EOS_ActiveSession_Info_Release
2013);
2014owned_handle_release!(
2015    LobbyModification,
2016    sys::EOS_HLobbyModification,
2017    sys::EOS_LobbyModification_Release
2018);
2019owned_handle_release!(LobbyDetails, sys::EOS_HLobbyDetails, sys::EOS_LobbyDetails_Release);
2020owned_handle_release!(LobbySearch, sys::EOS_HLobbySearch, sys::EOS_LobbySearch_Release);
2021owned_ptr_release!(
2022    LobbyDetailsInfo,
2023    sys::EOS_LobbyDetails_Info,
2024    sys::EOS_LobbyDetails_Info_Release
2025);
2026owned_ptr_release!(
2027    LobbyAttribute,
2028    sys::EOS_Lobby_Attribute,
2029    sys::EOS_Lobby_Attribute_Release
2030);
2031owned_ptr_release!(
2032    LobbyMemberInfo,
2033    sys::EOS_LobbyDetails_MemberInfo,
2034    sys::EOS_LobbyDetails_MemberInfo_Release
2035);
2036owned_ptr_release!(UserInfoData, sys::EOS_UserInfo, sys::EOS_UserInfo_Release);
2037owned_ptr_release!(
2038    ExternalUserInfo,
2039    sys::EOS_UserInfo_ExternalUserInfo,
2040    sys::EOS_UserInfo_ExternalUserInfo_Release
2041);
2042owned_ptr_release!(
2043    BestDisplayName,
2044    sys::EOS_UserInfo_BestDisplayName,
2045    sys::EOS_UserInfo_BestDisplayName_Release
2046);
2047owned_ptr_release!(
2048    PlayerDataStorageFileMetadata,
2049    sys::EOS_PlayerDataStorage_FileMetadata,
2050    sys::EOS_PlayerDataStorage_FileMetadata_Release
2051);
2052owned_handle_release!(
2053    PlayerDataStorageFileTransferRequest,
2054    sys::EOS_HPlayerDataStorageFileTransferRequest,
2055    sys::EOS_PlayerDataStorageFileTransferRequest_Release
2056);
2057owned_ptr_release!(
2058    TitleStorageFileMetadata,
2059    sys::EOS_TitleStorage_FileMetadata,
2060    sys::EOS_TitleStorage_FileMetadata_Release
2061);
2062owned_handle_release!(
2063    TitleStorageFileTransferRequest,
2064    sys::EOS_HTitleStorageFileTransferRequest,
2065    sys::EOS_TitleStorageFileTransferRequest_Release
2066);
2067owned_ptr_release!(
2068    AchievementsDefinitionV2,
2069    sys::EOS_Achievements_DefinitionV2,
2070    sys::EOS_Achievements_DefinitionV2_Release
2071);
2072owned_ptr_release!(
2073    AchievementsPlayerAchievement,
2074    sys::EOS_Achievements_PlayerAchievement,
2075    sys::EOS_Achievements_PlayerAchievement_Release
2076);
2077owned_ptr_release!(
2078    AchievementsDefinition,
2079    sys::EOS_Achievements_Definition,
2080    sys::EOS_Achievements_Definition_Release
2081);
2082owned_ptr_release!(
2083    AchievementsUnlockedAchievement,
2084    sys::EOS_Achievements_UnlockedAchievement,
2085    sys::EOS_Achievements_UnlockedAchievement_Release
2086);
2087owned_ptr_release!(StatsStat, sys::EOS_Stats_Stat, sys::EOS_Stats_Stat_Release);
2088owned_ptr_release!(
2089    LeaderboardsDefinition,
2090    sys::EOS_Leaderboards_Definition,
2091    sys::EOS_Leaderboards_Definition_Release
2092);
2093owned_ptr_release!(
2094    LeaderboardsUserScore,
2095    sys::EOS_Leaderboards_LeaderboardUserScore,
2096    sys::EOS_Leaderboards_LeaderboardUserScore_Release
2097);
2098owned_ptr_release!(
2099    LeaderboardsRecord,
2100    sys::EOS_Leaderboards_LeaderboardRecord,
2101    sys::EOS_Leaderboards_LeaderboardRecord_Release
2102);
2103owned_ptr_release!(
2104    LeaderboardsLeaderboardDefinition,
2105    sys::EOS_Leaderboards_Definition,
2106    sys::EOS_Leaderboards_LeaderboardDefinition_Release
2107);
2108owned_ptr_release!(ModsModInfo, sys::EOS_Mods_ModInfo, sys::EOS_Mods_ModInfo_Release);
2109owned_ptr_release!(
2110    SanctionsPlayerSanction,
2111    sys::EOS_Sanctions_PlayerSanction,
2112    sys::EOS_Sanctions_PlayerSanction_Release
2113);
2114owned_ptr_release!(
2115    KwsPermissionStatus,
2116    sys::EOS_KWS_PermissionStatus,
2117    sys::EOS_KWS_PermissionStatus_Release
2118);
2119owned_ptr_release!(
2120    RtcAdminUserToken,
2121    sys::EOS_RTCAdmin_UserToken,
2122    sys::EOS_RTCAdmin_UserToken_Release
2123);
2124
2125impl LobbySearch {
2126    pub fn set_lobby_id(&self, lobby_id: &str) -> Result<()> {
2127        let lobby_id = CString::new(lobby_id)?;
2128        let opts = sys::EOS_LobbySearch_SetLobbyIdOptions {
2129            ApiVersion: sys::EOS_LOBBYSEARCH_SETLOBBYID_API_LATEST as i32,
2130            LobbyId: lobby_id.as_ptr(),
2131        };
2132        ok(unsafe { sys::EOS_LobbySearch_SetLobbyId(self.raw_handle(), &opts) })
2133    }
2134
2135    pub fn set_target_user_id(&self, target_user_id: ProductUserId) -> Result<()> {
2136        let opts = sys::EOS_LobbySearch_SetTargetUserIdOptions {
2137            ApiVersion: sys::EOS_LOBBYSEARCH_SETTARGETUSERID_API_LATEST as i32,
2138            TargetUserId: target_user_id.raw(),
2139        };
2140        ok(unsafe { sys::EOS_LobbySearch_SetTargetUserId(self.raw_handle(), &opts) })
2141    }
2142
2143    pub fn set_max_results(&self, max_results: u32) -> Result<()> {
2144        let opts = sys::EOS_LobbySearch_SetMaxResultsOptions {
2145            ApiVersion: sys::EOS_LOBBYSEARCH_SETMAXRESULTS_API_LATEST as i32,
2146            MaxResults: max_results,
2147        };
2148        ok(unsafe { sys::EOS_LobbySearch_SetMaxResults(self.raw_handle(), &opts) })
2149    }
2150
2151    pub fn set_parameter(
2152        &self,
2153        key: &str,
2154        value: &LobbySearchValue,
2155        comparison_op: sys::EOS_EComparisonOp,
2156    ) -> Result<()> {
2157        let key = CString::new(key)?;
2158        let string_storage;
2159        let (value_union, value_type) = match value {
2160            LobbySearchValue::Bool(v) => (
2161                sys::_tagEOS_Lobby_AttributeData__bindgen_ty_1 {
2162                    AsBool: if *v { 1 } else { 0 },
2163                },
2164                sys::EOS_EAttributeType_EOS_AT_BOOLEAN,
2165            ),
2166            LobbySearchValue::Int64(v) => (
2167                sys::_tagEOS_Lobby_AttributeData__bindgen_ty_1 { AsInt64: *v },
2168                sys::EOS_EAttributeType_EOS_AT_INT64,
2169            ),
2170            LobbySearchValue::Double(v) => (
2171                sys::_tagEOS_Lobby_AttributeData__bindgen_ty_1 { AsDouble: *v },
2172                sys::EOS_EAttributeType_EOS_AT_DOUBLE,
2173            ),
2174            LobbySearchValue::String(v) => {
2175                string_storage = CString::new(v.as_str())?;
2176                (
2177                    sys::_tagEOS_Lobby_AttributeData__bindgen_ty_1 {
2178                        AsUtf8: string_storage.as_ptr(),
2179                    },
2180                    sys::EOS_EAttributeType_EOS_AT_STRING,
2181                )
2182            }
2183        };
2184
2185        let attr = sys::EOS_Lobby_AttributeData {
2186            ApiVersion: sys::EOS_LOBBY_ATTRIBUTEDATA_API_LATEST as i32,
2187            Key: key.as_ptr(),
2188            Value: value_union,
2189            ValueType: value_type,
2190        };
2191
2192        let opts = sys::EOS_LobbySearch_SetParameterOptions {
2193            ApiVersion: sys::EOS_LOBBYSEARCH_SETPARAMETER_API_LATEST as i32,
2194            Parameter: &attr,
2195            ComparisonOp: comparison_op,
2196        };
2197        ok(unsafe { sys::EOS_LobbySearch_SetParameter(self.raw_handle(), &opts) })
2198    }
2199
2200    pub fn remove_parameter(&self, key: &str, comparison_op: sys::EOS_EComparisonOp) -> Result<()> {
2201        let key = CString::new(key)?;
2202        let opts = sys::EOS_LobbySearch_RemoveParameterOptions {
2203            ApiVersion: sys::EOS_LOBBYSEARCH_REMOVEPARAMETER_API_LATEST as i32,
2204            Key: key.as_ptr(),
2205            ComparisonOp: comparison_op,
2206        };
2207        ok(unsafe { sys::EOS_LobbySearch_RemoveParameter(self.raw_handle(), &opts) })
2208    }
2209
2210    pub fn find(
2211        &self,
2212        local_user: ProductUserId,
2213        cb: impl FnOnce(Result<sys::EOS_LobbySearch_FindCallbackInfo>) + Send + 'static,
2214    ) {
2215        #[repr(C)]
2216        struct Cb {
2217            f: Option<Box<dyn FnOnce(Result<sys::EOS_LobbySearch_FindCallbackInfo>) + Send>>,
2218        }
2219        unsafe extern "C" fn trampoline(data: *const sys::EOS_LobbySearch_FindCallbackInfo) {
2220            let client_data = (*data).ClientData as *mut Cb;
2221            let mut boxed = Box::from_raw(client_data);
2222            let res = if (*data).ResultCode == sys::EOS_EResult_EOS_Success {
2223                Ok(*data)
2224            } else {
2225                Err(Error::Eos((*data).ResultCode))
2226            };
2227            if let Some(f) = boxed.f.take() {
2228                f(res);
2229            }
2230        }
2231
2232        let cb_box = CallbackOnce::new(Cb {
2233            f: Some(Box::new(cb)),
2234        });
2235        let opts = sys::EOS_LobbySearch_FindOptions {
2236            ApiVersion: sys::EOS_LOBBYSEARCH_FIND_API_LATEST as i32,
2237            LocalUserId: local_user.raw(),
2238        };
2239        unsafe {
2240            sys::EOS_LobbySearch_Find(self.raw_handle(), &opts, cb_box.ptr as *mut _, Some(trampoline));
2241        }
2242    }
2243
2244    pub fn get_search_result_count(&self) -> u32 {
2245        let opts = sys::EOS_LobbySearch_GetSearchResultCountOptions {
2246            ApiVersion: sys::EOS_LOBBYSEARCH_GETSEARCHRESULTCOUNT_API_LATEST as i32,
2247        };
2248        unsafe { sys::EOS_LobbySearch_GetSearchResultCount(self.raw_handle(), &opts) }
2249    }
2250
2251    pub fn copy_search_result_by_index(&self, lobby_index: u32) -> Result<LobbyDetails> {
2252        let opts = sys::EOS_LobbySearch_CopySearchResultByIndexOptions {
2253            ApiVersion: sys::EOS_LOBBYSEARCH_COPYSEARCHRESULTBYINDEX_API_LATEST as i32,
2254            LobbyIndex: lobby_index,
2255        };
2256        let mut out: sys::EOS_HLobbyDetails = std::ptr::null_mut();
2257        let res = unsafe { sys::EOS_LobbySearch_CopySearchResultByIndex(self.raw_handle(), &opts, &mut out) };
2258        ok(res)?;
2259        unsafe { LobbyDetails::from_raw(out) }
2260    }
2261}
2262
2263impl AuthToken {
2264    pub fn account_id(&self) -> EpicAccountId {
2265        // SAFETY: pointer is owned by this RAII wrapper and valid for its lifetime.
2266        let token = unsafe { &*self.as_ptr() };
2267        EpicAccountId(token.AccountId)
2268    }
2269
2270    pub fn access_token(&self) -> Option<&str> {
2271        let token = unsafe { &*self.as_ptr() };
2272        if token.AccessToken.is_null() {
2273            return None;
2274        }
2275        Some(unsafe { CStr::from_ptr(token.AccessToken) }.to_str().ok()?)
2276    }
2277}
2278
2279impl AuthIdToken {
2280    pub fn account_id(&self) -> EpicAccountId {
2281        let token = unsafe { &*self.as_ptr() };
2282        EpicAccountId(token.AccountId)
2283    }
2284
2285    pub fn json_web_token(&self) -> Option<&str> {
2286        let token = unsafe { &*self.as_ptr() };
2287        if token.JsonWebToken.is_null() {
2288            return None;
2289        }
2290        Some(unsafe { CStr::from_ptr(token.JsonWebToken) }.to_str().ok()?)
2291    }
2292}
2293
2294impl ConnectIdToken {
2295    pub fn product_user_id(&self) -> ProductUserId {
2296        let token = unsafe { &*self.as_ptr() };
2297        ProductUserId(token.ProductUserId)
2298    }
2299
2300    pub fn json_web_token(&self) -> Option<&str> {
2301        let token = unsafe { &*self.as_ptr() };
2302        if token.JsonWebToken.is_null() {
2303            return None;
2304        }
2305        Some(unsafe { CStr::from_ptr(token.JsonWebToken) }.to_str().ok()?)
2306    }
2307}
2308
2309