import { sys } from 'cc'; import { ANDROID, IOS } from 'cc/env'; const STORAGE_ENCRYPTION_HEAD = 'a6072637'; const STORAGE_ENCRYPTION_PASSWORD = 'xbd4jl'; const STORAGE_ENCRYPTION_ENABLED = true; /** * 将数字格式化为财务展示格式:使用千位分隔符并保留两位小数。 * 例如:1234567.8 -> 1,234,567.80。 */ export function formatCurrency(value: number, decimal = 0): string { if (!Number.isFinite(value)) value = 0; return value.toLocaleString('en-US', { minimumFractionDigits: decimal, maximumFractionDigits: decimal, useGrouping: true, }); } export function formatSCNum(value: number) { return formatCurrency(value, 2); } export function formatGCNum(value: number) { return formatCurrency(value, 0); } /** * 读取本地数据,并兼容 game_web_us2 写入的加密格式。 */ export function localStorageGet(key: string, defaultValue: T = '' as T): string | T { let value = sys.localStorage.getItem(key); if (value === null) return defaultValue; if (value.startsWith(STORAGE_ENCRYPTION_HEAD)) { try { value = decrypt(value.substring(STORAGE_ENCRYPTION_HEAD.length)); } catch { // 与原项目保持一致:解密失败时返回空字符串。 value = ''; } } return value; } /** * 加密并保存本地数据。空字符串不加密,行为与 game_web_us2 保持一致。 */ export function localStorageSet(key: string, value: unknown): void { let storageValue = value; if (STORAGE_ENCRYPTION_ENABLED && storageValue !== null && storageValue !== '') { const plainValue = typeof storageValue === 'string' ? storageValue : String(storageValue); storageValue = plainValue; try { storageValue = STORAGE_ENCRYPTION_HEAD + encrypt(plainValue); } catch { // 加密失败时沿用原值保存,兼容原项目行为。 } } sys.localStorage.setItem(key, String(storageValue)); } export function localStorageRemove(key: string): void { sys.localStorage.removeItem(key); } function encrypt(value: string, password = STORAGE_ENCRYPTION_PASSWORD): string { if (value === '') return ''; const source = encodeURIComponent(value); const encodedPassword = encodeURIComponent(password || STORAGE_ENCRYPTION_PASSWORD); if (encodedPassword.length === 0) return ''; let randomSeed = ''; for (let index = 0; index < encodedPassword.length; index += 1) { randomSeed += encodedPassword.charCodeAt(index).toString(); } const seedPosition = Math.floor(randomSeed.length / 5); const multiplier = Number.parseInt( randomSeed.charAt(seedPosition) + randomSeed.charAt(seedPosition * 2) + randomSeed.charAt(seedPosition * 3) + randomSeed.charAt(seedPosition * 4) + randomSeed.charAt(seedPosition * 5), 10, ); const increment = Math.ceil(encodedPassword.length / 2); const modulus = Math.pow(2, 31) - 1; if (multiplier < 2) return ''; let salt = Math.round(Math.random() * 1_000_000_000) % 100_000_000; randomSeed += salt; randomSeed = reduceSeed(randomSeed); let pseudoRandom = (multiplier * Number(randomSeed) + increment) % modulus; let encrypted = ''; for (let index = 0; index < source.length; index += 1) { const encryptedChar = source.charCodeAt(index) ^ Math.floor((pseudoRandom / modulus) * 255); encrypted += encryptedChar < 16 ? `0${encryptedChar.toString(16)}` : encryptedChar.toString(16); pseudoRandom = (multiplier * pseudoRandom + increment) % modulus; } let saltHex = salt.toString(16); while (saltHex.length < 8) saltHex = `0${saltHex}`; return encrypted + saltHex; } function decrypt(value: string, password = STORAGE_ENCRYPTION_PASSWORD): string { if (value === '') return ''; const encodedPassword = encodeURIComponent(password || STORAGE_ENCRYPTION_PASSWORD); if (value.length < 8 || encodedPassword.length === 0) return ''; let randomSeed = ''; for (let index = 0; index < encodedPassword.length; index += 1) { randomSeed += encodedPassword.charCodeAt(index).toString(); } const seedPosition = Math.floor(randomSeed.length / 5); const multiplier = Number.parseInt( randomSeed.charAt(seedPosition) + randomSeed.charAt(seedPosition * 2) + randomSeed.charAt(seedPosition * 3) + randomSeed.charAt(seedPosition * 4) + randomSeed.charAt(seedPosition * 5), 10, ); const increment = Math.round(encodedPassword.length / 2); const modulus = Math.pow(2, 31) - 1; const salt = Number.parseInt(value.substring(value.length - 8), 16); const encrypted = value.substring(0, value.length - 8); randomSeed += salt; randomSeed = reduceSeed(randomSeed); let pseudoRandom = (multiplier * Number(randomSeed) + increment) % modulus; let decrypted = ''; for (let index = 0; index < encrypted.length; index += 2) { const decryptedChar = Number.parseInt(encrypted.substring(index, index + 2), 16) ^ Math.floor((pseudoRandom / modulus) * 255); decrypted += String.fromCharCode(decryptedChar); pseudoRandom = (multiplier * pseudoRandom + increment) % modulus; } return decodeURIComponent(decrypted); } function reduceSeed(seed: string): string { let result = seed; while (result.length > 10) { let remaining = result; let total = 0; while (remaining.length > 0) { total += Number.parseInt(remaining.substring(0, 10), 10); remaining = remaining.substring(10); } result = total.toString(); } return result; } /** * 进入平台判定:0=普通浏览器 1=iOS壳包 2=Android壳包 3=PWA(桌面图标/TWA 进入) * 用于按来源差异化展示(如提现页官网引流提示:桌面图标进入的玩家不显示)。 * 壳包判定优先于 PWA;TWA 判定内联实现,避免 helpers→UAUtils 循环依赖。 */ export function getEnterPlatform(): 0 | 1 | 2 | 3 { if (ANDROID) return 2; if (IOS) return 1; return 0; } export function createUUID() { const chars: string[] = []; const hexDigits = '0123456789abcdef'; for (let index = 0; index < 36; index += 1) { chars[index] = hexDigits.charAt( Math.floor(Math.random() * 16), ); } // UUID version 4:0100 chars[14] = '4'; // RFC variant:10xx const variant = (Number.parseInt(chars[19], 16) & 0x3) | 0x8; chars[19] = hexDigits.charAt(variant); chars[8] = '-'; chars[13] = '-'; chars[18] = '-'; chars[23] = '-'; return chars.join(''); } export function getUrlParams(url: string) { const queryIndex = url.indexOf('?'); const params = {}; if (queryIndex >= 0) { url.slice(queryIndex + 1).split('&').forEach((entry) => { if (!entry) return; const separatorIndex = entry.indexOf('='); const rawKey = separatorIndex >= 0 ? entry.slice(0, separatorIndex) : entry; const rawValue = separatorIndex >= 0 ? entry.slice(separatorIndex + 1) : ''; try { const key = decodeURIComponent(rawKey.replace(/\+/g, ' ')); params[key] = decodeURIComponent(rawValue.replace(/\+/g, ' ')); } catch { // Preserve malformed values instead of crashing the native callback. params[rawKey] = rawValue; } }); } return params; } export function waitForSeconds(duration: number) { return new Promise((resolve, reject) => { setTimeout(resolve, duration * 1000) }) }