1use std::collections::VecDeque;
4use std::ffi::CStr;
5use std::sync::Mutex;
6
7use crate::error::SystemState;
8
9pub 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#[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#[derive(Debug, Clone)]
36pub struct AuthorizeFinishedData {
37 pub is_cancel: bool,
38 pub error: Option<String>,
39 pub token: Option<AuthToken>,
40}
41
42#[derive(Debug, Clone)]
44pub struct SystemStateChangedData {
45 pub state: SystemState,
46}
47
48#[derive(Debug, Clone)]
50pub struct GamePlayableStatusChangedData {
51 pub is_playable: bool,
52}
53
54#[derive(Debug, Clone)]
56pub struct DlcPlayableStatusChangedData {
57 pub dlc_id: String,
58 pub is_playable: bool,
59}
60
61#[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#[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#[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#[derive(Debug, Clone)]
94pub struct CloudSaveDeleteData {
95 pub request_id: i64,
96 pub error: Option<(i64, String)>,
97 pub uuid: String,
98}
99
100#[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#[derive(Debug, Clone)]
110pub enum TapEvent {
111 SystemStateChanged(SystemStateChangedData),
113 AuthorizeFinished(AuthorizeFinishedData),
115 GamePlayableStatusChanged(GamePlayableStatusChangedData),
117 DlcPlayableStatusChanged(DlcPlayableStatusChangedData),
119 CloudSaveList(CloudSaveListData),
121 CloudSaveCreate(CloudSaveCreateData),
123 CloudSaveUpdate(CloudSaveCreateData),
125 CloudSaveDelete(CloudSaveDeleteData),
127 CloudSaveGetData(CloudSaveGetFileData),
129 CloudSaveGetCover(CloudSaveGetFileData),
131 Unknown { event_id: u32 },
133}
134
135static EVENT_QUEUE: Mutex<VecDeque<TapEvent>> = Mutex::new(VecDeque::new());
137
138pub fn register_callbacks() {
140 unsafe {
141 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
170pub 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
213pub fn poll_events() -> Vec<TapEvent> {
218 unsafe {
220 tapsdk_pc_sys::TapSDK_RunCallbacks();
221 }
222
223 let mut queue = EVENT_QUEUE.lock().unwrap();
225 queue.drain(..).collect()
226}
227
228unsafe 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
240unsafe 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
426unsafe 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
442unsafe 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
458unsafe 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
467unsafe 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}