Helper.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { sys } from 'cc';
  2. import { ANDROID, IOS } from 'cc/env';
  3. const STORAGE_ENCRYPTION_HEAD = 'a6072637';
  4. const STORAGE_ENCRYPTION_PASSWORD = 'xbd4jl';
  5. const STORAGE_ENCRYPTION_ENABLED = true;
  6. /**
  7. * 读取本地数据,并兼容 game_web_us2 写入的加密格式。
  8. */
  9. export function localStorageGet<T = string>(key: string, defaultValue: T = '' as T): string | T {
  10. let value = sys.localStorage.getItem(key);
  11. if (value === null) return defaultValue;
  12. if (value.startsWith(STORAGE_ENCRYPTION_HEAD)) {
  13. try {
  14. value = decrypt(value.substring(STORAGE_ENCRYPTION_HEAD.length));
  15. } catch {
  16. // 与原项目保持一致:解密失败时返回空字符串。
  17. value = '';
  18. }
  19. }
  20. return value;
  21. }
  22. /**
  23. * 加密并保存本地数据。空字符串不加密,行为与 game_web_us2 保持一致。
  24. */
  25. export function localStorageSet(key: string, value: unknown): void {
  26. let storageValue = value;
  27. if (STORAGE_ENCRYPTION_ENABLED && storageValue !== null && storageValue !== '') {
  28. const plainValue = typeof storageValue === 'string' ? storageValue : String(storageValue);
  29. storageValue = plainValue;
  30. try {
  31. storageValue = STORAGE_ENCRYPTION_HEAD + encrypt(plainValue);
  32. } catch {
  33. // 加密失败时沿用原值保存,兼容原项目行为。
  34. }
  35. }
  36. sys.localStorage.setItem(key, String(storageValue));
  37. }
  38. export function localStorageRemove(key: string): void {
  39. sys.localStorage.removeItem(key);
  40. }
  41. function encrypt(value: string, password = STORAGE_ENCRYPTION_PASSWORD): string {
  42. if (value === '') return '';
  43. const source = encodeURIComponent(value);
  44. const encodedPassword = encodeURIComponent(password || STORAGE_ENCRYPTION_PASSWORD);
  45. if (encodedPassword.length === 0) return '';
  46. let randomSeed = '';
  47. for (let index = 0; index < encodedPassword.length; index += 1) {
  48. randomSeed += encodedPassword.charCodeAt(index).toString();
  49. }
  50. const seedPosition = Math.floor(randomSeed.length / 5);
  51. const multiplier = Number.parseInt(
  52. randomSeed.charAt(seedPosition)
  53. + randomSeed.charAt(seedPosition * 2)
  54. + randomSeed.charAt(seedPosition * 3)
  55. + randomSeed.charAt(seedPosition * 4)
  56. + randomSeed.charAt(seedPosition * 5),
  57. 10,
  58. );
  59. const increment = Math.ceil(encodedPassword.length / 2);
  60. const modulus = Math.pow(2, 31) - 1;
  61. if (multiplier < 2) return '';
  62. let salt = Math.round(Math.random() * 1_000_000_000) % 100_000_000;
  63. randomSeed += salt;
  64. randomSeed = reduceSeed(randomSeed);
  65. let pseudoRandom = (multiplier * Number(randomSeed) + increment) % modulus;
  66. let encrypted = '';
  67. for (let index = 0; index < source.length; index += 1) {
  68. const encryptedChar = source.charCodeAt(index) ^ Math.floor((pseudoRandom / modulus) * 255);
  69. encrypted += encryptedChar < 16
  70. ? `0${encryptedChar.toString(16)}`
  71. : encryptedChar.toString(16);
  72. pseudoRandom = (multiplier * pseudoRandom + increment) % modulus;
  73. }
  74. let saltHex = salt.toString(16);
  75. while (saltHex.length < 8) saltHex = `0${saltHex}`;
  76. return encrypted + saltHex;
  77. }
  78. function decrypt(value: string, password = STORAGE_ENCRYPTION_PASSWORD): string {
  79. if (value === '') return '';
  80. const encodedPassword = encodeURIComponent(password || STORAGE_ENCRYPTION_PASSWORD);
  81. if (value.length < 8 || encodedPassword.length === 0) return '';
  82. let randomSeed = '';
  83. for (let index = 0; index < encodedPassword.length; index += 1) {
  84. randomSeed += encodedPassword.charCodeAt(index).toString();
  85. }
  86. const seedPosition = Math.floor(randomSeed.length / 5);
  87. const multiplier = Number.parseInt(
  88. randomSeed.charAt(seedPosition)
  89. + randomSeed.charAt(seedPosition * 2)
  90. + randomSeed.charAt(seedPosition * 3)
  91. + randomSeed.charAt(seedPosition * 4)
  92. + randomSeed.charAt(seedPosition * 5),
  93. 10,
  94. );
  95. const increment = Math.round(encodedPassword.length / 2);
  96. const modulus = Math.pow(2, 31) - 1;
  97. const salt = Number.parseInt(value.substring(value.length - 8), 16);
  98. const encrypted = value.substring(0, value.length - 8);
  99. randomSeed += salt;
  100. randomSeed = reduceSeed(randomSeed);
  101. let pseudoRandom = (multiplier * Number(randomSeed) + increment) % modulus;
  102. let decrypted = '';
  103. for (let index = 0; index < encrypted.length; index += 2) {
  104. const decryptedChar = Number.parseInt(encrypted.substring(index, index + 2), 16)
  105. ^ Math.floor((pseudoRandom / modulus) * 255);
  106. decrypted += String.fromCharCode(decryptedChar);
  107. pseudoRandom = (multiplier * pseudoRandom + increment) % modulus;
  108. }
  109. return decodeURIComponent(decrypted);
  110. }
  111. function reduceSeed(seed: string): string {
  112. let result = seed;
  113. while (result.length > 10) {
  114. let remaining = result;
  115. let total = 0;
  116. while (remaining.length > 0) {
  117. total += Number.parseInt(remaining.substring(0, 10), 10);
  118. remaining = remaining.substring(10);
  119. }
  120. result = total.toString();
  121. }
  122. return result;
  123. }
  124. /**
  125. * 进入平台判定:0=普通浏览器 1=iOS壳包 2=Android壳包 3=PWA(桌面图标/TWA 进入)
  126. * 用于按来源差异化展示(如提现页官网引流提示:桌面图标进入的玩家不显示)。
  127. * 壳包判定优先于 PWA;TWA 判定内联实现,避免 helpers→UAUtils 循环依赖。
  128. */
  129. export function getEnterPlatform(): 0 | 1 | 2 | 3 {
  130. if (ANDROID) return 2;
  131. if (IOS) return 1;
  132. return 0;
  133. }
  134. export function createUUID() {
  135. const chars: string[] = [];
  136. const hexDigits = '0123456789abcdef';
  137. for (let index = 0; index < 36; index += 1) {
  138. chars[index] = hexDigits.charAt(
  139. Math.floor(Math.random() * 16),
  140. );
  141. }
  142. // UUID version 4:0100
  143. chars[14] = '4';
  144. // RFC variant:10xx
  145. const variant = (Number.parseInt(chars[19], 16) & 0x3) | 0x8;
  146. chars[19] = hexDigits.charAt(variant);
  147. chars[8] = '-';
  148. chars[13] = '-';
  149. chars[18] = '-';
  150. chars[23] = '-';
  151. return chars.join('');
  152. }
  153. export function getUrlParams(url: string) {
  154. const queryIndex = url.indexOf('?');
  155. const params = {};
  156. if (queryIndex >= 0) {
  157. url.slice(queryIndex + 1).split('&').forEach((entry) => {
  158. if (!entry) return;
  159. const separatorIndex = entry.indexOf('=');
  160. const rawKey = separatorIndex >= 0 ? entry.slice(0, separatorIndex) : entry;
  161. const rawValue = separatorIndex >= 0 ? entry.slice(separatorIndex + 1) : '';
  162. try {
  163. const key = decodeURIComponent(rawKey.replace(/\+/g, ' '));
  164. params[key] = decodeURIComponent(rawValue.replace(/\+/g, ' '));
  165. } catch {
  166. // Preserve malformed values instead of crashing the native callback.
  167. params[rawKey] = rawValue;
  168. }
  169. });
  170. }
  171. return params;
  172. }