TapCloudSaveStandalone.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Newtonsoft.Json;
  7. using TapSDK.CloudSave.Internal;
  8. using TapSDK.Core;
  9. using TapSDK.Core.Internal.Log;
  10. using TapSDK.Core.Internal.Utils;
  11. using TapSDK.Core.Standalone;
  12. using TapSDK.Core.Standalone.Internal;
  13. using TapSDK.Core.Standalone.Internal.Http;
  14. using TapSDK.Login;
  15. using UnityEngine;
  16. namespace TapSDK.CloudSave.Standalone
  17. {
  18. public class TapCloudSaveStandalone : ITapCloudSaveBridge
  19. {
  20. private List<ITapCloudSaveCallback> currentSaveCallback = null;
  21. private static readonly bool isRND = false;
  22. private bool _hasInitNative = false;
  23. private object _lockObj = new object();
  24. private TapLog cloudSaveLog;
  25. public void Init(TapTapSdkOptions options)
  26. {
  27. Log("TapCloudSave start init");
  28. TapCloudSaveTracker.Instance.TrackInit();
  29. string cacheDir = Path.Combine(
  30. Application.persistentDataPath,
  31. "cloudsave_" + options.clientId
  32. );
  33. string deviceID = SystemInfo.deviceUniqueIdentifier;
  34. Task.Run(async () =>
  35. {
  36. TapTapAccount tapAccount = await TapTapLogin.Instance.GetCurrentTapAccount();
  37. string loginKid = "";
  38. string loginKey = "";
  39. if (tapAccount != null && !string.IsNullOrEmpty(tapAccount.openId))
  40. {
  41. loginKey = tapAccount.accessToken.macKey;
  42. loginKid = tapAccount.accessToken.kid;
  43. }
  44. Dictionary<string, object> loginData = new Dictionary<string, object>
  45. {
  46. { "kid", loginKid },
  47. { "key", loginKey },
  48. };
  49. int region = isRND ? 2 : 0;
  50. try
  51. {
  52. Dictionary<string, object> initConfig = new Dictionary<string, object>()
  53. {
  54. { "region", region },
  55. { "log_to_console", 1 },
  56. { "log_level", 3 },
  57. { "data_dir", cacheDir },
  58. { "client_id", options.clientId },
  59. { "client_token", options.clientToken },
  60. { "ua", TapHttpUtils.GenerateUserAgent() },
  61. { "lang", Tracker.getServerLanguage() },
  62. { "platform", "PC" },
  63. { "device_id", deviceID },
  64. { "sdk_artifact", "Unity" },
  65. { "sdk_module_ver", TapTapCloudSave.Version },
  66. { "sdk_token", loginData },
  67. };
  68. Log(" start invoke TapSdkCloudSaveInitialize result ");
  69. string config = JsonConvert.SerializeObject(initConfig);
  70. int initResult = TapCloudSaveWrapper.TapSdkCloudSaveInitialize(config);
  71. Log("TapSdkCloudSaveInitialize result = " + initResult);
  72. if (initResult < 0)
  73. {
  74. RunOnMainThread(() =>
  75. {
  76. if (currentSaveCallback != null && currentSaveCallback.Count > 0)
  77. {
  78. foreach (var callback in currentSaveCallback)
  79. {
  80. callback?.OnResult(TapCloudSaveResultCode.INIT_FAIL);
  81. }
  82. }
  83. });
  84. }
  85. else
  86. {
  87. lock (_lockObj)
  88. {
  89. _hasInitNative = true;
  90. }
  91. }
  92. }
  93. catch (Exception e)
  94. {
  95. Log("TapSdkCloudSaveInitialize error " + e.Message);
  96. }
  97. });
  98. EventManager.AddListener(EventManager.OnTapUserChanged, OnLoginInfoChanged);
  99. }
  100. public Task<ArchiveData> CreateArchive(
  101. ArchiveMetadata metadata,
  102. string archiveFilePath,
  103. string archiveCoverPath
  104. )
  105. {
  106. var taskSource = new TaskCompletionSource<ArchiveData>();
  107. CheckPCLaunchState();
  108. string seesionId = Guid.NewGuid().ToString();
  109. const string method = "createArchive";
  110. Task.Run(async () =>
  111. {
  112. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  113. if (!hasInit)
  114. {
  115. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  116. return;
  117. }
  118. try
  119. {
  120. byte[] fileBytes = File.ReadAllBytes(archiveFilePath);
  121. byte[] coverData = null;
  122. if (!string.IsNullOrEmpty(archiveCoverPath))
  123. {
  124. coverData = File.ReadAllBytes(archiveCoverPath);
  125. }
  126. string metaValue = JsonConvert.SerializeObject(metadata);
  127. string result = TapCloudSaveWrapper.CreateArchive(
  128. metaValue,
  129. fileBytes,
  130. fileBytes.Length,
  131. coverData,
  132. coverData?.Length ?? 0
  133. );
  134. TapCloudSaveBaseResponse response =
  135. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(result);
  136. if (response.success)
  137. {
  138. ArchiveData data = response.data.ToObject<ArchiveData>();
  139. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  140. var archiveData = new ArchiveData()
  141. {
  142. FileId = data.FileId,
  143. Uuid = data.Uuid,
  144. Name = metadata.Name,
  145. Summary = metadata.Summary,
  146. Extra = metadata.Extra,
  147. Playtime = metadata.Playtime,
  148. };
  149. taskSource.TrySetResult(archiveData);
  150. }
  151. else
  152. {
  153. try
  154. {
  155. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  156. Log(
  157. "createArchive failed error = " + JsonConvert.SerializeObject(error)
  158. );
  159. TapCloudSaveTracker.Instance.TrackFailure(
  160. method,
  161. seesionId,
  162. error.code,
  163. error.msg ?? ""
  164. );
  165. taskSource.TrySetException(new TapException(error.code, $"创建存档失败: {error.msg}"));
  166. }
  167. catch (Exception e)
  168. {
  169. TapCloudSaveTracker.Instance.TrackFailure(
  170. method,
  171. seesionId,
  172. -1,
  173. "创建存档失败: 数据解析异常"
  174. );
  175. taskSource.TrySetException(new TapException(-1, "创建存档失败: 数据解析异常"));
  176. }
  177. }
  178. }
  179. catch (Exception e)
  180. {
  181. string msg = $"创建存档失败: {e.Message}";
  182. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  183. taskSource.TrySetException(new TapException(-1, msg));
  184. }
  185. });
  186. return taskSource.Task;
  187. }
  188. public Task<ArchiveData> DeleteArchive(string archiveUuid)
  189. {
  190. var taskSource = new TaskCompletionSource<ArchiveData>();
  191. CheckPCLaunchState();
  192. string seesionId = Guid.NewGuid().ToString();
  193. const string method = "deleteArchive";
  194. Task.Run(async () =>
  195. {
  196. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  197. if (!hasInit)
  198. {
  199. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  200. return;
  201. }
  202. try
  203. {
  204. string result = TapCloudSaveWrapper.DeleteArchive(archiveUuid);
  205. TapCloudSaveBaseResponse response =
  206. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(result);
  207. if (response.success)
  208. {
  209. ArchiveData archiveData = response.data.ToObject<ArchiveData>();
  210. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  211. taskSource.TrySetResult(archiveData);
  212. }
  213. else
  214. {
  215. try
  216. {
  217. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  218. Log(
  219. "deleteArchive failed error = " + JsonConvert.SerializeObject(error)
  220. );
  221. TapCloudSaveTracker.Instance.TrackFailure(
  222. method,
  223. seesionId,
  224. error.code,
  225. error.msg ?? ""
  226. );
  227. taskSource.TrySetException(new TapException(error.code, $"删除存档失败: {error.msg}"));
  228. }
  229. catch (Exception e)
  230. {
  231. TapCloudSaveTracker.Instance.TrackFailure(
  232. method,
  233. seesionId,
  234. -1,
  235. "删除存档失败: 数据解析异常"
  236. );
  237. taskSource.TrySetException(new TapException(-1, "删除存档失败: 数据解析异常"));
  238. }
  239. }
  240. }
  241. catch (Exception e)
  242. {
  243. string msg = $"删除失败: {e.Message}";
  244. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  245. taskSource.TrySetException(new TapException(-1, msg));
  246. }
  247. });
  248. return taskSource.Task;
  249. }
  250. public Task<byte[]> GetArchiveCover(
  251. string archiveUuid,
  252. string archiveFileId
  253. )
  254. {
  255. var taskSource = new TaskCompletionSource<byte[]>();
  256. CheckPCLaunchState();
  257. string seesionId = Guid.NewGuid().ToString();
  258. const string method = "getArchiveCover";
  259. Task.Run(async () =>
  260. {
  261. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  262. if (!hasInit)
  263. {
  264. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  265. return;
  266. }
  267. try
  268. {
  269. byte[] result = TapCloudSaveWrapper.GetArchiveCover(
  270. archiveUuid,
  271. archiveFileId,
  272. out int coverSize
  273. );
  274. if (coverSize >= 0)
  275. {
  276. taskSource.TrySetResult(result);
  277. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  278. }
  279. else
  280. {
  281. try
  282. {
  283. TapCloudSaveBaseResponse response =
  284. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(
  285. Encoding.UTF8.GetString(result)
  286. );
  287. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  288. Log(
  289. "getArchiveCover failed error = " + JsonConvert.SerializeObject(error)
  290. );
  291. TapCloudSaveTracker.Instance.TrackFailure(
  292. method,
  293. seesionId,
  294. error.code,
  295. error.msg ?? ""
  296. );
  297. taskSource.TrySetException(new TapException(error.code, $"获取封面失败: {error.msg}"));
  298. }
  299. catch (Exception e)
  300. {
  301. TapCloudSaveTracker.Instance.TrackFailure(
  302. method,
  303. seesionId,
  304. -1,
  305. "获取封面失败: 数据解析异常"
  306. );
  307. taskSource.TrySetException(new TapException(-1, "获取封面失败: 数据解析异常"));
  308. }
  309. }
  310. }
  311. catch (Exception e)
  312. {
  313. string msg = $"获取封面失败: {e.Message}";
  314. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  315. taskSource.TrySetException(new TapException(-1, msg));
  316. }
  317. });
  318. return taskSource.Task;
  319. }
  320. public Task<byte[]> GetArchiveData(
  321. string archiveUuid,
  322. string archiveFileId
  323. )
  324. {
  325. var taskSource = new TaskCompletionSource<byte[]>();
  326. CheckPCLaunchState();
  327. string seesionId = Guid.NewGuid().ToString();
  328. const string method = "getArchiveData";
  329. Task.Run(async () =>
  330. {
  331. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  332. if (!hasInit)
  333. {
  334. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  335. return;
  336. }
  337. try
  338. {
  339. byte[] result = TapCloudSaveWrapper.GetArchiveData(
  340. archiveUuid,
  341. archiveFileId,
  342. out int fileSize
  343. );
  344. if (fileSize >= 0)
  345. {
  346. taskSource.TrySetResult(result);
  347. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  348. }
  349. else
  350. {
  351. try
  352. {
  353. TapCloudSaveBaseResponse response =
  354. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(
  355. Encoding.UTF8.GetString(result)
  356. );
  357. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  358. Log(
  359. "getArchiveData failed error = " + JsonConvert.SerializeObject(error)
  360. );
  361. TapCloudSaveTracker.Instance.TrackFailure(
  362. method,
  363. seesionId,
  364. error.code,
  365. error.msg ?? ""
  366. );
  367. taskSource.TrySetException(new TapException(error.code, $"获取存档失败: {error.msg}"));
  368. }
  369. catch (Exception e)
  370. {
  371. TapCloudSaveTracker.Instance.TrackFailure(
  372. method,
  373. seesionId,
  374. -1,
  375. "获取存档失败: 数据解析异常"
  376. );
  377. taskSource.TrySetException(new TapException(-1, "获取存档失败: 数据解析异常"));
  378. }
  379. }
  380. }
  381. catch (Exception e)
  382. {
  383. string msg = $"获取存档失败: {e.Message}";
  384. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  385. taskSource.TrySetException(new TapException(-1, msg));
  386. }
  387. });
  388. return taskSource.Task;
  389. }
  390. public Task<List<ArchiveData>> GetArchiveList()
  391. {
  392. var taskSource = new TaskCompletionSource<List<ArchiveData>>();
  393. CheckPCLaunchState();
  394. string seesionId = Guid.NewGuid().ToString();
  395. const string method = "getArchiveList";
  396. Task.Run(async () =>
  397. {
  398. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  399. if (!hasInit)
  400. {
  401. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  402. return;
  403. }
  404. try
  405. {
  406. string result = TapCloudSaveWrapper.GetArchiveList();
  407. TapCloudSaveBaseResponse response =
  408. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(result);
  409. if (response.success)
  410. {
  411. TapCloudSaveArchiveListResponse archiveDatas =
  412. response.data.ToObject<TapCloudSaveArchiveListResponse>();
  413. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  414. taskSource.TrySetResult(archiveDatas.saves);
  415. }
  416. else
  417. {
  418. try
  419. {
  420. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  421. Log(
  422. "getArchiveList failed error = " + JsonConvert.SerializeObject(error)
  423. );
  424. TapCloudSaveTracker.Instance.TrackFailure(
  425. method,
  426. seesionId,
  427. error.code,
  428. error.msg ?? ""
  429. );
  430. taskSource.TrySetException(new TapException(error.code, $"获取存档列表失败: {error.msg}"));
  431. }
  432. catch (Exception e)
  433. {
  434. TapCloudSaveTracker.Instance.TrackFailure(
  435. method,
  436. seesionId,
  437. -1,
  438. "获取存档列表失败: 数据解析异常"
  439. );
  440. taskSource.TrySetException(new TapException(-1, "获取存档列表失败: 数据解析异常"));
  441. }
  442. }
  443. }
  444. catch (Exception e)
  445. {
  446. string msg = $"获取存档列表失败: {e.Message}";
  447. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  448. taskSource.TrySetException(new TapException(-1, msg));
  449. }
  450. });
  451. return taskSource.Task;
  452. }
  453. public void RegisterCloudSaveCallback(ITapCloudSaveCallback callback)
  454. {
  455. currentSaveCallback?.Clear();
  456. string seesionId = Guid.NewGuid().ToString();
  457. const string method = "registerCloudSaveCallback";
  458. TapCloudSaveTracker.Instance.TrackStart(method, seesionId);
  459. if (currentSaveCallback == null)
  460. {
  461. currentSaveCallback = new List<ITapCloudSaveCallback>();
  462. }
  463. if (!currentSaveCallback.Contains(callback))
  464. {
  465. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  466. currentSaveCallback.Add(callback);
  467. }
  468. else
  469. {
  470. TapCloudSaveTracker.Instance.TrackFailure(
  471. method,
  472. seesionId,
  473. errorMessage: "callback has already registered"
  474. );
  475. }
  476. }
  477. public Task<ArchiveData> UpdateArchive(
  478. string archiveUuid,
  479. ArchiveMetadata metadata,
  480. string archiveFilePath,
  481. string archiveCoverPath
  482. )
  483. {
  484. var taskSource = new TaskCompletionSource<ArchiveData>();
  485. CheckPCLaunchState();
  486. string seesionId = Guid.NewGuid().ToString();
  487. const string method = "updateArchive";
  488. Task.Run(async () =>
  489. {
  490. bool hasInit = await CheckInitAndLoginState(method, seesionId);
  491. if (!hasInit)
  492. {
  493. taskSource.TrySetException(new TapException(-1, "Init or login state check failed"));
  494. return;
  495. }
  496. try
  497. {
  498. byte[] fileBytes = File.ReadAllBytes(archiveFilePath);
  499. byte[] coverData = null;
  500. if (!string.IsNullOrEmpty(archiveCoverPath))
  501. {
  502. coverData = File.ReadAllBytes(archiveCoverPath);
  503. }
  504. string metaValue = JsonConvert.SerializeObject(metadata);
  505. string result = TapCloudSaveWrapper.UpdateArchive(
  506. archiveUuid,
  507. metaValue,
  508. fileBytes,
  509. fileBytes.Length,
  510. coverData,
  511. coverData?.Length ?? 0
  512. );
  513. TapCloudSaveBaseResponse response =
  514. JsonConvert.DeserializeObject<TapCloudSaveBaseResponse>(result);
  515. if (response.success)
  516. {
  517. ArchiveData data = response.data.ToObject<ArchiveData>();
  518. TapCloudSaveTracker.Instance.TrackSuccess(method, seesionId);
  519. taskSource.TrySetResult(data);
  520. }
  521. else
  522. {
  523. try
  524. {
  525. TapCloudSaveError error = response.data.ToObject<TapCloudSaveError>();
  526. Log(
  527. "updateArchive failed error = " + JsonConvert.SerializeObject(error)
  528. );
  529. TapCloudSaveTracker.Instance.TrackFailure(
  530. method,
  531. seesionId,
  532. error.code,
  533. error.msg ?? ""
  534. );
  535. taskSource.TrySetException(new TapException(error.code, $"更新存档失败: {error.msg}"));
  536. }
  537. catch (Exception e)
  538. {
  539. TapCloudSaveTracker.Instance.TrackFailure(
  540. method,
  541. seesionId,
  542. -1,
  543. "更新存档失败: 数据解析异常"
  544. );
  545. taskSource.TrySetException(new TapException(-1, "更新存档失败: 数据解析异常"));
  546. }
  547. }
  548. }
  549. catch (Exception e)
  550. {
  551. string msg = $"更新存档失败: {e.Message}";
  552. TapCloudSaveTracker.Instance.TrackFailure(method, seesionId, -1, msg);
  553. taskSource.TrySetException(new TapException(-1, msg));
  554. }
  555. });
  556. return taskSource.Task;
  557. }
  558. private async Task<bool> CheckInitAndLoginState(string method, string sessionId)
  559. {
  560. if (TapCoreStandalone.CheckInitState())
  561. {
  562. lock (_lockObj)
  563. {
  564. if (!_hasInitNative)
  565. {
  566. Log("not init success, so return", true);
  567. return false;
  568. }
  569. }
  570. TapCloudSaveTracker.Instance.TrackStart(method, sessionId);
  571. TapTapAccount tapAccount = await TapTapLogin.Instance.GetCurrentTapAccount();
  572. if (tapAccount != null && !string.IsNullOrEmpty(tapAccount.openId))
  573. {
  574. return true;
  575. }
  576. else
  577. {
  578. if (currentSaveCallback != null && currentSaveCallback.Count > 0)
  579. {
  580. foreach (var callback in currentSaveCallback)
  581. {
  582. callback?.OnResult(TapCloudSaveResultCode.NEED_LOGIN);
  583. }
  584. }
  585. TapCloudSaveTracker.Instance.TrackFailure(method, sessionId, -1, "not login");
  586. return false;
  587. }
  588. }
  589. return false;
  590. }
  591. /// <summary>
  592. /// 检查是否通过 PC 启动校验
  593. /// </summary>
  594. private void CheckPCLaunchState()
  595. {
  596. #if UNITY_STANDALONE_WIN
  597. if (!TapClientStandalone.isPassedInLaunchedFromTapTapPCCheck())
  598. {
  599. throw new Exception("TapCloudSave method must be invoked after isLaunchedFromTapTapPC succeed");
  600. }
  601. #endif
  602. }
  603. internal void OnLoginInfoChanged(object data)
  604. {
  605. lock (_lockObj)
  606. {
  607. if (_hasInitNative)
  608. {
  609. Task.Run(async () =>
  610. {
  611. TapTapAccount tapAccount =
  612. await TapTapLogin.Instance.GetCurrentTapAccount();
  613. string loginKid = "";
  614. string loginKey = "";
  615. if (tapAccount != null && !string.IsNullOrEmpty(tapAccount.openId))
  616. {
  617. loginKey = tapAccount.accessToken.macKey;
  618. loginKid = tapAccount.accessToken.kid;
  619. }
  620. Dictionary<string, object> loginData = new Dictionary<string, object>
  621. {
  622. { "kid", loginKid },
  623. { "key", loginKey },
  624. };
  625. int result = TapCloudSaveWrapper.TapSdkCloudSaveUpdateAccessToken(
  626. JsonConvert.SerializeObject(loginData)
  627. );
  628. Log("update login msg result = " + result);
  629. });
  630. }
  631. }
  632. }
  633. private void RunOnMainThread(Action action)
  634. {
  635. TapLoom.QueueOnMainThread(action);
  636. }
  637. private void Log(string msg, bool isError = false)
  638. {
  639. if (cloudSaveLog == null)
  640. {
  641. cloudSaveLog = new TapLog("TapCloudSave");
  642. }
  643. if (!string.IsNullOrEmpty(msg))
  644. {
  645. if (isError)
  646. {
  647. cloudSaveLog.Error(msg);
  648. }
  649. else
  650. {
  651. cloudSaveLog.Log(msg);
  652. }
  653. }
  654. }
  655. }
  656. }