224 lines
8.2 KiB
C#
224 lines
8.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
|
|
public static class SaveIdentityUtility
|
|
{
|
|
public const string StableCompanyName = "FloatGamingStudio";
|
|
public const string StableProductName = "Bansonic";
|
|
public const string StableApplicationIdentifier = "com.FloatGamingStudio.Bansonic";
|
|
|
|
private static readonly string[] LegacyProductAliases =
|
|
{
|
|
"Bansonic",
|
|
"ban_total",
|
|
"EaseOut_Main_FreshNew"
|
|
};
|
|
|
|
private static readonly string[] LegacyCompanyAliases =
|
|
{
|
|
"FloatGamingStudio",
|
|
"DefaultCompany"
|
|
};
|
|
|
|
private static readonly string[] LegacyIdentifierAliases =
|
|
{
|
|
"com.DefaultCompany.Bansonic",
|
|
"com.FloatGamingStudio.ban_total",
|
|
"com.DefaultCompany.ban_total",
|
|
"com.FloatGamingStudio.EaseOut_Main_FreshNew",
|
|
"com.DefaultCompany.EaseOut_Main_FreshNew"
|
|
};
|
|
|
|
// 设备无关绑定令牌:新版存档的加密/签名统一改用它,取代 SystemInfo.deviceUniqueIdentifier。
|
|
// 根因修复——deviceUniqueIdentifier 在安卓更新/换签名/换渠道后会变化,导致旧密钥永远解不出存档。
|
|
// 用固定令牌后,更新或换机都不再影响解密。读取时仍会尝试真实设备 ID 变体以兼容本机旧存档。
|
|
public const string DeviceIndependentBindingToken = "device_independent_binding_v1";
|
|
|
|
// 【性能】这些值在进程生命周期内恒定:SystemInfo.deviceUniqueIdentifier、Application.identifier/
|
|
// companyName/productName 在安卓上开销较大(尤其 deviceUniqueIdentifier 涉及系统调用与哈希)。
|
|
// 存档每次读取/验签都会取用它们,若每次重算并分配 List,会在 UI 大量刷新(如 idols 角色切换、
|
|
// 场景切换批量重建)时造成明显卡顿。这里缓存一次,之后直接复用,行为完全不变。
|
|
private static IReadOnlyList<string> s_cachedApplicationIdentifierVariants;
|
|
private static IReadOnlyList<string> s_cachedDeviceBindingVariants;
|
|
private static string s_cachedDeviceUniqueIdentifier;
|
|
private static bool s_deviceUniqueIdentifierResolved;
|
|
|
|
public static string GetPrimaryApplicationIdentifier()
|
|
{
|
|
return StableApplicationIdentifier;
|
|
}
|
|
|
|
// 写入存档时使用的设备绑定:固定为设备无关令牌。
|
|
public static string GetPrimaryDeviceBinding()
|
|
{
|
|
return DeviceIndependentBindingToken;
|
|
}
|
|
|
|
// 读取/验签时尝试的设备绑定变体,按命中概率排序:
|
|
// 1) 设备无关令牌(新版写入所用,最常命中)
|
|
// 2) 当前设备真实 deviceUniqueIdentifier(兼容本机旧版写入的设备绑定存档,首次读到后会被重写为设备无关,实现无缝迁移)
|
|
// 结果缓存,避免每次读取都触发一次昂贵的 deviceUniqueIdentifier 查询与 List 分配。
|
|
public static IReadOnlyList<string> GetDeviceBindingVariants()
|
|
{
|
|
if (s_cachedDeviceBindingVariants != null)
|
|
{
|
|
return s_cachedDeviceBindingVariants;
|
|
}
|
|
|
|
var result = new List<string>();
|
|
AddDistinct(result, DeviceIndependentBindingToken);
|
|
AddDistinct(result, SafeDeviceUniqueIdentifier());
|
|
s_cachedDeviceBindingVariants = result;
|
|
return s_cachedDeviceBindingVariants;
|
|
}
|
|
|
|
private static string SafeDeviceUniqueIdentifier()
|
|
{
|
|
if (s_deviceUniqueIdentifierResolved)
|
|
{
|
|
return s_cachedDeviceUniqueIdentifier;
|
|
}
|
|
|
|
try
|
|
{
|
|
s_cachedDeviceUniqueIdentifier = SystemInfo.deviceUniqueIdentifier;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
s_cachedDeviceUniqueIdentifier = null;
|
|
}
|
|
|
|
s_deviceUniqueIdentifierResolved = true;
|
|
return s_cachedDeviceUniqueIdentifier;
|
|
}
|
|
|
|
public static string GetCanonicalPersistentRoot()
|
|
{
|
|
if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer)
|
|
{
|
|
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
string localLow = Path.GetFullPath(Path.Combine(localAppData, "..", "LocalLow"));
|
|
return Path.Combine(localLow, StableCompanyName, StableProductName);
|
|
}
|
|
|
|
return Application.persistentDataPath;
|
|
}
|
|
|
|
public static IReadOnlyList<string> GetApplicationIdentifierVariants()
|
|
{
|
|
if (s_cachedApplicationIdentifierVariants != null)
|
|
{
|
|
return s_cachedApplicationIdentifierVariants;
|
|
}
|
|
|
|
var result = new List<string>();
|
|
AddDistinct(result, StableApplicationIdentifier);
|
|
AddDistinct(result, Application.identifier);
|
|
|
|
string currentCompany = SafeTrim(Application.companyName);
|
|
string currentProduct = SafeTrim(Application.productName);
|
|
if (!string.IsNullOrEmpty(currentCompany) && !string.IsNullOrEmpty(currentProduct))
|
|
{
|
|
AddDistinct(result, $"com.{currentCompany}.{currentProduct}");
|
|
}
|
|
|
|
for (int i = 0; i < LegacyIdentifierAliases.Length; i++)
|
|
{
|
|
AddDistinct(result, LegacyIdentifierAliases[i]);
|
|
}
|
|
|
|
for (int companyIndex = 0; companyIndex < LegacyCompanyAliases.Length; companyIndex++)
|
|
{
|
|
for (int productIndex = 0; productIndex < LegacyProductAliases.Length; productIndex++)
|
|
{
|
|
AddDistinct(result, $"com.{LegacyCompanyAliases[companyIndex]}.{LegacyProductAliases[productIndex]}");
|
|
}
|
|
}
|
|
|
|
s_cachedApplicationIdentifierVariants = result;
|
|
return s_cachedApplicationIdentifierVariants;
|
|
}
|
|
|
|
public static IReadOnlyList<string> GetPersistentRootVariants(string extraLegacyPath = null)
|
|
{
|
|
var result = new List<string>();
|
|
|
|
AddDistinct(result, GetCanonicalPersistentRoot());
|
|
AddDistinct(result, Application.persistentDataPath);
|
|
|
|
string extraDirectory = string.IsNullOrEmpty(extraLegacyPath) ? null : Path.GetDirectoryName(extraLegacyPath);
|
|
AddDistinct(result, extraDirectory);
|
|
|
|
if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer)
|
|
{
|
|
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
string localLow = Path.GetFullPath(Path.Combine(localAppData, "..", "LocalLow"));
|
|
|
|
string currentCompany = SafeTrim(Application.companyName);
|
|
string currentProduct = SafeTrim(Application.productName);
|
|
if (!string.IsNullOrEmpty(currentCompany) && !string.IsNullOrEmpty(currentProduct))
|
|
{
|
|
AddDistinct(result, Path.Combine(localLow, currentCompany, currentProduct));
|
|
}
|
|
|
|
for (int companyIndex = 0; companyIndex < LegacyCompanyAliases.Length; companyIndex++)
|
|
{
|
|
for (int productIndex = 0; productIndex < LegacyProductAliases.Length; productIndex++)
|
|
{
|
|
AddDistinct(result, Path.Combine(localLow, LegacyCompanyAliases[companyIndex], LegacyProductAliases[productIndex]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static IReadOnlyList<string> GetVaultFilePathVariants(string fileName)
|
|
{
|
|
var result = new List<string>();
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
IReadOnlyList<string> roots = GetPersistentRootVariants();
|
|
for (int i = 0; i < roots.Count; i++)
|
|
{
|
|
string root = roots[i];
|
|
if (string.IsNullOrWhiteSpace(root))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AddDistinct(result, Path.Combine(root, ".cache_bridge", fileName));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void AddDistinct(List<string> target, string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < target.Count; i++)
|
|
{
|
|
if (string.Equals(target[i], value, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
target.Add(value);
|
|
}
|
|
|
|
private static string SafeTrim(string value)
|
|
{
|
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
|
}
|
|
}
|