Skip to main content

tapsdk_pc/
callback.rs

1//! Callback registry and event handling for TapTap PC SDK
2
3use std::collections::VecDeque;
4use std::ffi::CStr;
5use std::sync::Mutex;
6
7use crate::error::SystemState;
8
9/// Event IDs matching the C SDK
10pub mod event_id {
11    pub const UNKNOWN: u32 = 0;
12    pub const SYSTEM_STATE_CHANGED: u32 = 1;
13    pub const AUTHORIZE_FINISHED: u32 = 2002;
14    pub const GAME_PLAYABLE_STATUS_CHANGED: u32 = 4001;
15    pub const DLC_PLAYABLE_STATUS_CHANGED: u32 = 4002;
16    pub const CLOUD_SAVE_LIST: u32 = 6001;
17    pub const CLOUD_SAVE_CREATE: u32 = 6002;
18    pub const CLOUD_SAVE_UPDATE: u32 = 6003;
19    pub const CLOUD_SAVE_DELETE: u32 = 6004;
20    pub const CLOUD_SAVE_GET_DATA: u32 = 6005;
21    pub const CLOUD_SAVE_GET_COVER: u32 = 6006;
22}
23
24/// Authorization token returned after successful authorization
25#[derive(Debug, Clone, Default)]
26pub struct AuthToken {
27    pub token_type: String,
28    pub kid: String,
29    pub mac_key: String,
30    pub mac_algorithm: String,
31    pub scope: String,
32}
33
34/// Authorization finished event data
35#[derive(Debug, Clone)]
36pub struct AuthorizeFinishedData {
37    pub is_cancel: bool,
38    pub error: Option<String>,
39    pub token: Option<AuthToken>,
40}
41
42/// System state changed event data
43#[derive(Debug, Clone)]
44pub struct SystemStateChangedData {
45    pub state: SystemState,
46}
47
48/// Game playable status changed event data
49#[derive(Debug, Clone)]
50pub struct GamePlayableStatusChangedData {
51    pub is_playable: bool,
52}
53
54/// DLC playable status changed event data
55#[derive(Debug, Clone)]
56pub struct DlcPlayableStatusChangedData {
57    pub dlc_id: String,
58    pub is_playable: bool,
59}
60
61/// Cloud save info
62#[derive(Debug, Clone)]
63pub struct CloudSaveInfo {
64    pub uuid: String,
65    pub file_id: String,
66    pub name: String,
67    pub save_size: u32,
68    pub cover_size: u32,
69    pub summary: Option<String>,
70    pub extra: Option<String>,
71    pub playtime: u32,
72    pub created_time: u32,
73    pub modified_time: u32,
74}
75
76/// Cloud save list response
77#[derive(Debug, Clone)]
78pub struct CloudSaveListData {
79    pub request_id: i64,
80    pub error: Option<(i64, String)>,
81    pub saves: Vec<CloudSaveInfo>,
82}
83
84/// Cloud save create/update response
85#[derive(Debug, Clone)]
86pub struct CloudSaveCreateData {
87    pub request_id: i64,
88    pub error: Option<(i64, String)>,
89    pub save: Option<CloudSaveInfo>,
90}
91
92/// Cloud save delete response
93#[derive(Debug, Clone)]
94pub struct CloudSaveDeleteData {
95    pub request_id: i64,
96    pub error: Option<(i64, String)>,
97    pub uuid: String,
98}
99
100/// Cloud save get file response
101#[derive(Debug, Clone)]
102pub struct CloudSaveGetFileData {
103    pub request_id: i64,
104    pub error: Option<(i64, String)>,
105    pub data: Vec<u8>,
106}
107
108/// Events that can be received from the SDK
109#[derive(Debug, Clone)]
110pub enum TapEvent {
111    /// System state changed
112    SystemStateChanged(SystemStateChangedData),
113    /// Authorization finished
114    AuthorizeFinished(AuthorizeFinishedData),
115    /// Game playable status changed
116    GamePlayableStatusChanged(GamePlayableStatusChangedData),
117    /// DLC playable status changed
118    DlcPlayableStatusChanged(DlcPlayableStatusChangedData),
119    /// Cloud save list response
120    CloudSaveList(CloudSaveListData),
121    /// Cloud save create response
122    CloudSaveCreate(CloudSaveCreateData),
123    /// Cloud save update response
124    CloudSaveUpdate(CloudSaveCreateData),
125    /// Cloud save delete response
126    CloudSaveDelete(CloudSaveDeleteData),
127    /// Cloud save get data response
128    CloudSaveGetData(CloudSaveGetFileData),
129    /// Cloud save get cover response
130    CloudSaveGetCover(CloudSaveGetFileData),
131    /// Unknown event
132    Unknown { event_id: u32 },
133}
134
135/// Global event queue
136static EVENT_QUEUE: Mutex<VecDeque<TapEvent>> = Mutex::new(VecDeque::new());
137
138/// Register the global callback handler with the SDK
139pub fn register_callbacks() {
140    unsafe {
141        // Register for all event types we care about
142        tapsdk_pc_sys::TapSDK_RegisterCallback(
143            event_id::SYSTEM_STATE_CHANGED,
144            Some(global_callback),
145        );
146        tapsdk_pc_sys::TapSDK_RegisterCallback(event_id::AUTHORIZE_FINISHED, Some(global_callback));
147        tapsdk_pc_sys::TapSDK_RegisterCallback(
148            event_id::GAME_PLAYABLE_STATUS_CHANGED,
149            Some(global_callback),
150        );
151        tapsdk_pc_sys::TapSDK_RegisterCallback(
152            event_id::DLC_PLAYABLE_STATUS_CHANGED,
153            Some(global_callback),
154        );
155        tapsdk_pc_sys::TapSDK_RegisterCallback(event_id::CLOUD_SAVE_LIST, Some(global_callback));
156        tapsdk_pc_sys::TapSDK_RegisterCallback(event_id::CLOUD_SAVE_CREATE, Some(global_callback));
157        tapsdk_pc_sys::TapSDK_RegisterCallback(event_id::CLOUD_SAVE_UPDATE, Some(global_callback));
158        tapsdk_pc_sys::TapSDK_RegisterCallback(event_id::CLOUD_SAVE_DELETE, Some(global_callback));
159        tapsdk_pc_sys::TapSDK_RegisterCallback(
160            event_id::CLOUD_SAVE_GET_DATA,
161            Some(global_callback),
162        );
163        tapsdk_pc_sys::TapSDK_RegisterCallback(
164            event_id::CLOUD_SAVE_GET_COVER,
165            Some(global_callback),
166        );
167    }
168}
169
170/// Unregister the global callback handler
171pub fn unregister_callbacks() {
172    unsafe {
173        tapsdk_pc_sys::TapSDK_UnregisterCallback(
174            event_id::SYSTEM_STATE_CHANGED,
175            Some(global_callback),
176        );
177        tapsdk_pc_sys::TapSDK_UnregisterCallback(
178            event_id::AUTHORIZE_FINISHED,
179            Some(global_callback),
180        );
181        tapsdk_pc_sys::TapSDK_UnregisterCallback(
182            event_id::GAME_PLAYABLE_STATUS_CHANGED,
183            Some(global_callback),
184        );
185        tapsdk_pc_sys::TapSDK_UnregisterCallback(
186            event_id::DLC_PLAYABLE_STATUS_CHANGED,
187            Some(global_callback),
188        );
189        tapsdk_pc_sys::TapSDK_UnregisterCallback(event_id::CLOUD_SAVE_LIST, Some(global_callback));
190        tapsdk_pc_sys::TapSDK_UnregisterCallback(
191            event_id::CLOUD_SAVE_CREATE,
192            Some(global_callback),
193        );
194        tapsdk_pc_sys::TapSDK_UnregisterCallback(
195            event_id::CLOUD_SAVE_UPDATE,
196            Some(global_callback),
197        );
198        tapsdk_pc_sys::TapSDK_UnregisterCallback(
199            event_id::CLOUD_SAVE_DELETE,
200            Some(global_callback),
201        );
202        tapsdk_pc_sys::TapSDK_UnregisterCallback(
203            event_id::CLOUD_SAVE_GET_DATA,
204            Some(global_callback),
205        );
206        tapsdk_pc_sys::TapSDK_UnregisterCallback(
207            event_id::CLOUD_SAVE_GET_COVER,
208            Some(global_callback),
209        );
210    }
211}
212
213/// Poll for events from the SDK
214///
215/// This calls `TapSDK_RunCallbacks()` to process pending callbacks,
216/// then returns all events that were queued.
217pub fn poll_events() -> Vec<TapEvent> {
218    // First, run the SDK callbacks to trigger our callback handler
219    unsafe {
220        tapsdk_pc_sys::TapSDK_RunCallbacks();
221    }
222
223    // Then drain the event queue
224    let mut queue = EVENT_QUEUE.lock().unwrap();
225    queue.drain(..).collect()
226}
227
228/// Global callback handler called by the SDK
229///
230/// # Safety
231/// This function is called from C code with raw pointers
232unsafe extern "C" fn global_callback(event_id: u32, data: *mut std::ffi::c_void) {
233    let event = parse_event(event_id, data);
234
235    if let Ok(mut queue) = EVENT_QUEUE.lock() {
236        queue.push_back(event);
237    }
238}
239
240/// Parse an event from raw SDK data
241unsafe fn parse_event(event_id: u32, data: *mut std::ffi::c_void) -> TapEvent {
242    match event_id {
243        event_id::SYSTEM_STATE_CHANGED => {
244            if data.is_null() {
245                return TapEvent::Unknown { event_id };
246            }
247            let notification = &*(data as *const tapsdk_pc_sys::TapSystemStateNotification);
248            TapEvent::SystemStateChanged(SystemStateChangedData {
249                state: SystemState::from(notification.state),
250            })
251        }
252
253        event_id::AUTHORIZE_FINISHED => {
254            if data.is_null() {
255                return TapEvent::Unknown { event_id };
256            }
257            let response = &*(data as *const tapsdk_pc_sys::AuthorizeFinishedResponse);
258
259            let error = {
260                let error_str = CStr::from_ptr(response.error.as_ptr())
261                    .to_string_lossy()
262                    .into_owned();
263                if error_str.is_empty() {
264                    None
265                } else {
266                    Some(error_str)
267                }
268            };
269
270            let token = if !response.is_cancel && error.is_none() {
271                Some(AuthToken {
272                    token_type: CStr::from_ptr(response.token_type.as_ptr())
273                        .to_string_lossy()
274                        .into_owned(),
275                    kid: CStr::from_ptr(response.kid.as_ptr())
276                        .to_string_lossy()
277                        .into_owned(),
278                    mac_key: CStr::from_ptr(response.mac_key.as_ptr())
279                        .to_string_lossy()
280                        .into_owned(),
281                    mac_algorithm: CStr::from_ptr(response.mac_algorithm.as_ptr())
282                        .to_string_lossy()
283                        .into_owned(),
284                    scope: CStr::from_ptr(response.scope.as_ptr())
285                        .to_string_lossy()
286                        .into_owned(),
287                })
288            } else {
289                None
290            };
291
292            TapEvent::AuthorizeFinished(AuthorizeFinishedData {
293                is_cancel: response.is_cancel,
294                error,
295                token,
296            })
297        }
298
299        event_id::GAME_PLAYABLE_STATUS_CHANGED => {
300            if data.is_null() {
301                return TapEvent::Unknown { event_id };
302            }
303            let response = &*(data as *const tapsdk_pc_sys::GamePlayableStatusChangedResponse);
304            TapEvent::GamePlayableStatusChanged(GamePlayableStatusChangedData {
305                is_playable: response.is_playable,
306            })
307        }
308
309        event_id::DLC_PLAYABLE_STATUS_CHANGED => {
310            if data.is_null() {
311                return TapEvent::Unknown { event_id };
312            }
313            let response = &*(data as *const tapsdk_pc_sys::DLCPlayableStatusChangedResponse);
314            TapEvent::DlcPlayableStatusChanged(DlcPlayableStatusChangedData {
315                dlc_id: CStr::from_ptr(response.dlc_id.as_ptr())
316                    .to_string_lossy()
317                    .into_owned(),
318                is_playable: response.is_playable,
319            })
320        }
321
322        event_id::CLOUD_SAVE_LIST => {
323            if data.is_null() {
324                return TapEvent::Unknown { event_id };
325            }
326            let response = &*(data as *const tapsdk_pc_sys::TapCloudSaveListResponse);
327
328            let error = parse_sdk_error(response.error);
329
330            let saves = if response.saves.is_null() || response.save_count <= 0 {
331                Vec::new()
332            } else {
333                let slice =
334                    std::slice::from_raw_parts(response.saves, response.save_count as usize);
335                slice.iter().map(|s| parse_cloud_save_info(s)).collect()
336            };
337
338            TapEvent::CloudSaveList(CloudSaveListData {
339                request_id: response.request_id,
340                error,
341                saves,
342            })
343        }
344
345        event_id::CLOUD_SAVE_CREATE | event_id::CLOUD_SAVE_UPDATE => {
346            if data.is_null() {
347                return TapEvent::Unknown { event_id };
348            }
349            let response = &*(data as *const tapsdk_pc_sys::TapCloudSaveCreateResponse);
350
351            let error = parse_sdk_error(response.error);
352
353            let save = if response.save.is_null() {
354                None
355            } else {
356                Some(parse_cloud_save_info(&*response.save))
357            };
358
359            let event_data = CloudSaveCreateData {
360                request_id: response.request_id,
361                error,
362                save,
363            };
364
365            if event_id == event_id::CLOUD_SAVE_CREATE {
366                TapEvent::CloudSaveCreate(event_data)
367            } else {
368                TapEvent::CloudSaveUpdate(event_data)
369            }
370        }
371
372        event_id::CLOUD_SAVE_DELETE => {
373            if data.is_null() {
374                return TapEvent::Unknown { event_id };
375            }
376            let response = &*(data as *const tapsdk_pc_sys::TapCloudSaveDeleteResponse);
377
378            let error = parse_sdk_error(response.error);
379
380            let uuid = if response.uuid.is_null() {
381                String::new()
382            } else {
383                CStr::from_ptr(response.uuid).to_string_lossy().into_owned()
384            };
385
386            TapEvent::CloudSaveDelete(CloudSaveDeleteData {
387                request_id: response.request_id,
388                error,
389                uuid,
390            })
391        }
392
393        event_id::CLOUD_SAVE_GET_DATA | event_id::CLOUD_SAVE_GET_COVER => {
394            if data.is_null() {
395                return TapEvent::Unknown { event_id };
396            }
397            let response = &*(data as *const tapsdk_pc_sys::TapCloudSaveGetFileResponse);
398
399            let error = parse_sdk_error(response.error);
400
401            let file_data = if response.data.is_null() || response.size == 0 {
402                Vec::new()
403            } else {
404                let slice =
405                    std::slice::from_raw_parts(response.data as *const u8, response.size as usize);
406                slice.to_vec()
407            };
408
409            let event_data = CloudSaveGetFileData {
410                request_id: response.request_id,
411                error,
412                data: file_data,
413            };
414
415            if event_id == event_id::CLOUD_SAVE_GET_DATA {
416                TapEvent::CloudSaveGetData(event_data)
417            } else {
418                TapEvent::CloudSaveGetCover(event_data)
419            }
420        }
421
422        _ => TapEvent::Unknown { event_id },
423    }
424}
425
426/// Parse SDK error from raw pointer
427unsafe fn parse_sdk_error(error: *const tapsdk_pc_sys::TapSDK_Error) -> Option<(i64, String)> {
428    if error.is_null() {
429        return None;
430    }
431
432    let err = &*error;
433    let message = if err.message.is_null() {
434        String::new()
435    } else {
436        CStr::from_ptr(err.message).to_string_lossy().into_owned()
437    };
438
439    Some((err.code, message))
440}
441
442/// Parse cloud save info from raw struct
443unsafe fn parse_cloud_save_info(info: &tapsdk_pc_sys::TapCloudSaveInfo) -> CloudSaveInfo {
444    CloudSaveInfo {
445        uuid: ptr_to_string(info.uuid),
446        file_id: ptr_to_string(info.file_id),
447        name: ptr_to_string(info.name),
448        save_size: info.save_size,
449        cover_size: info.cover_size,
450        summary: ptr_to_optional_string(info.summary),
451        extra: ptr_to_optional_string(info.extra),
452        playtime: info.playtime,
453        created_time: info.created_time,
454        modified_time: info.modified_time,
455    }
456}
457
458/// Convert a C string pointer to a Rust String
459unsafe fn ptr_to_string(ptr: *const std::os::raw::c_char) -> String {
460    if ptr.is_null() {
461        String::new()
462    } else {
463        CStr::from_ptr(ptr).to_string_lossy().into_owned()
464    }
465}
466
467/// Convert a C string pointer to an optional Rust String
468unsafe fn ptr_to_optional_string(ptr: *const std::os::raw::c_char) -> Option<String> {
469    if ptr.is_null() {
470        None
471    } else {
472        let s = CStr::from_ptr(ptr).to_string_lossy().into_owned();
473        if s.is_empty() {
474            None
475        } else {
476            Some(s)
477        }
478    }
479}