using UnityEngine;
|
using System;
|
using System.IO;
|
using System.Collections.Generic;
|
using LitJsonForAot;
|
using System.Collections;
|
using UnityEngine.Networking;
|
using StartAotSDK;
|
using UnityEngine.U2D;
|
using System.Linq;
|
|
|
namespace StartAot
|
{
|
public class ResourcesModel : SingletonMonobehaviour<ResourcesModel>
|
{
|
//不下载时本地安卓测试路径
|
public string assetBundlesPath { get; private set; }
|
public static string bytesFolderName = "logicbytes/";
|
public string StreamingAssetPath { get; private set; }
|
public string ExternalStorePath { get; private set; }
|
|
//用于editor 下的资源路径
|
public string ResourcesOutPath { get; private set; }
|
public string CONFIG_FODLER { get; private set; }
|
public const string ResourcesOutAssetPath = "Assets/ResourcesOut/";
|
|
public bool isPCTestDownLoad = false;
|
|
public static int downLoadCount = 0;
|
|
|
//安卓线上用
|
public static readonly string[] VERSION_URL = new string[] {
|
"http://hyvncenter.yuxiangshcn.com:11000/center/appversion_new.php/?",
|
"http://129.226.95.201:11000/center/appversion_new.php/?",
|
};
|
|
readonly static List<string> excludePngs = new List<string>() { "Launch_1.png", "Launch_2.png", "Launch_3.png", "LoginBackGround.png", "TB_DL_Logo.png" };
|
|
public int debugBranch { get; private set; }
|
|
public class DebugBranch
|
{
|
public int branch = -1;
|
}
|
|
private int urlIndex = 0;
|
private string versionUrl;
|
public VersionInfo versionInfo { get; private set; }
|
|
public enum LoadDllStep
|
{
|
None,
|
Wait, //每个阶段的等待时间 比如在下载中就处于等待状态,完成后再进入下一个阶段
|
RequestVersion,
|
PrepareDownLoad,
|
DownLoad,
|
ReadBytes,
|
Completed,
|
}
|
|
static LoadDllStep m_step;
|
public static LoadDllStep step
|
{
|
get { return m_step; }
|
set
|
{
|
m_step = value;
|
Debug.Log("LoadDllStep:" + m_step);
|
}
|
}
|
|
string assetBytesUrl;
|
//从网络获取的资源版本信息
|
public Dictionary<string, AssetVersion> assetVersions = new Dictionary<string, AssetVersion>();
|
//本地LogicBytes文件和 assetVersions 比较是否需要下载
|
public Dictionary<string, AssetVersion> localAssetVersions = new Dictionary<string, AssetVersion>();
|
public static string versionUrlResult { get; private set; }
|
|
/// <summary>
|
/// 语言的ID,用于区分下载资源
|
/// </summary>
|
/// <value></value>
|
public static string Id
|
{
|
get
|
{
|
if (languageShowDict == null || languageShowDict.Count == 0)
|
return "";
|
return LocalSave.GetString("LANGUAGE_ID1");
|
}
|
set
|
{
|
LocalSave.SetString("LANGUAGE_ID1", value);
|
}
|
}
|
|
|
//默认路径不附加地址,Id不为空时附加Id
|
public static string fixPath
|
{
|
get
|
{
|
if (string.IsNullOrEmpty(Id))
|
return "";
|
return string.Format("/{0}", Id);
|
}
|
}
|
|
|
AssetBundle spriteBundle = null; //需要卸载
|
AssetBundle prefabBundle = null; //需要卸载
|
SpriteAtlas spriteAtlas = null;
|
|
public void Init()
|
{
|
//if (Application.isMobilePlatform)
|
{
|
if (LocalSave.GetString("#@#BrancH") != string.Empty)
|
{
|
int tmpbranch;
|
int.TryParse(LocalSave.GetString("#@#BrancH").Substring(1), out tmpbranch);
|
if (tmpbranch != 0)
|
debugBranch = tmpbranch;
|
}
|
|
var parentDirectory = Directory.GetParent(Application.persistentDataPath);
|
if (File.Exists(parentDirectory + "/Debug"))
|
{
|
var content = File.ReadAllText(parentDirectory + "/Debug");
|
if (!string.IsNullOrEmpty(content))
|
{
|
var json = JsonMapper.ToObject<DebugBranch>(File.ReadAllText(parentDirectory + "/Debug"));
|
debugBranch = json.branch;
|
}
|
}
|
}
|
Clock.Init();
|
Debug.Log("ResourcesModel.Init");
|
}
|
|
void Awake()
|
{
|
#if UNITY_ANDROID
|
StreamingAssetPath = Application.streamingAssetsPath + "/android/";
|
#elif UNITY_IOS
|
StreamingAssetPath = Application.streamingAssetsPath + "/ios/";
|
#else
|
StreamingAssetPath = Application.streamingAssetsPath + "/standalone/";
|
#endif
|
|
if (!Application.isEditor)
|
{
|
switch (Application.platform)
|
{
|
case RuntimePlatform.WindowsPlayer:
|
ExternalStorePath = Path.GetDirectoryName(Application.streamingAssetsPath) + "/ExternalAssets/";
|
break;
|
default:
|
ExternalStorePath = Application.persistentDataPath + "/";
|
break;
|
}
|
}
|
else
|
{
|
ExternalStorePath = Application.persistentDataPath + "/";
|
}
|
assetBundlesPath = Application.dataPath + "/../AssetBundles/Android/";
|
ResourcesOutPath = Application.dataPath + "/ResourcesOut/";
|
CONFIG_FODLER = ResourcesOutPath + "refdata/Config";
|
Debug.Log("ResourcesModel 路径初始化");
|
}
|
|
//显示调用卸载资源
|
public void Destroy()
|
{
|
spriteBundle?.Unload(true); //true完全卸载,更新后重新加载
|
prefabBundle?.Unload(true);
|
assetVersions = null;
|
localAssetVersions = null;
|
spriteAtlas = null;
|
Debug.Log("提前ResourcesModel.Destroy资源");
|
}
|
|
|
public void RequestVersionCheck()
|
{
|
var versionConfig = VersionConfigEx.Get();
|
var tables = new Dictionary<string, string>();
|
tables["channel"] = versionConfig.appId;
|
tables["versioncode"] = versionConfig.version;
|
if (versionConfig.branch != 0)
|
{
|
tables["branch"] = versionConfig.branch.ToString();
|
}
|
|
tables["game"] = versionConfig.gameId;
|
|
var url = string.Concat(VERSION_URL[urlIndex % VERSION_URL.Length], HttpRequest.HashtablaToString(tables));
|
|
urlIndex++;
|
versionUrl = url;
|
Debug.Log("http地址:versionUrl " + url);
|
HttpRequest.Instance.RequestHttpGet(url, HttpRequest.defaultHttpContentType, 1, OnVersionCheckResult);
|
}
|
|
|
|
private void OnVersionCheckResult(bool _ok, string _result)
|
{
|
if (_ok)
|
{
|
versionUrlResult = _result.Replace("{}", "null");
|
versionInfo = JsonMapper.ToObject<VersionInfo>(versionUrlResult);
|
|
if (VersionConfigEx.Get().assetAccess == VersionConfigEx.InstalledAsset.IngoreDownLoad)
|
{
|
assetVersions = localAssetVersions;
|
step = LoadDllStep.ReadBytes;
|
return;
|
}
|
step = LoadDllStep.PrepareDownLoad;
|
}
|
else
|
{
|
Debug.Log("http 数据通讯: VersionUtility:" + versionUrl + " result:" + versionUrlResult);
|
Clock.AlarmAt(DateTime.Now + new TimeSpan(TimeSpan.TicksPerSecond), RequestVersionCheck);
|
}
|
}
|
|
public class VersionInfo
|
{
|
public JsonData notice_flag;
|
public JsonData resource_url; //这里只用到这个取下载地址
|
public string ResourceAward;
|
public int downAsset = 1;
|
public string downUrl;
|
public int VersionCount;
|
|
|
public string GetResourcesURL(int _branch)
|
{
|
if (resource_url != null)
|
{
|
return resource_url[_branch.ToString()].ToString();
|
}
|
else
|
{
|
return string.Empty;
|
}
|
}
|
|
}
|
|
|
//加密的修改涉及
|
//1.所有文件名的修改 EncodeFileName 函数
|
//2.表第一行加字符串 和 地图数据表增加一个int大小的字段 修改TableTool的CopyTxtBuild函数
|
//3.代码dll.bytes改名和加开头加字节 修改 AssetBundleBuildTab的ChangeBytes函数
|
//4.其他资源的修改因为资源名变了,所以打包的资源MD5一定会变,所以只要处理改名即可 EncodeFileName 函数
|
//是否开启自定义的代码混淆, 需同步修改AssetVersionUtility.cs
|
public bool IsOpenDMHunxiao()
|
{
|
//不用宏定义的原因是还需要运维感知切换操作,不新增字段的原因是线上版本不新增
|
//需要开关需通知程序修改
|
var versionConfig = VersionConfigEx.Get();
|
if (versionConfig.sdkFileName == "hyenglish_ios")
|
{
|
return true;
|
}
|
return false;
|
}
|
|
const string fixFileName = "_dip";
|
//将文件名倒序,加上后缀 fixFileName
|
public string EncodeFileName(string name)
|
{
|
if (!IsOpenDMHunxiao())
|
return name;
|
|
name = name.Replace("\\", "/");
|
int index = name.LastIndexOf("/");
|
string headString;
|
if (index >= 0)
|
{
|
int dotLastIndex = name.LastIndexOf(".");
|
if (dotLastIndex == -1)
|
{
|
headString = name.Substring(0, index);
|
name = name.Substring(index + 1);
|
return StringUtility.Contact(headString, "/", new string(name.Reverse().ToArray()), fixFileName);
|
}
|
else
|
{
|
headString = name.Substring(0, index);
|
var ext = name.Substring(dotLastIndex);
|
name = name.Substring(index + 1, dotLastIndex - index - 1);
|
return StringUtility.Contact(headString, "/", new string(name.Reverse().ToArray()), fixFileName, ext);
|
}
|
}
|
else
|
{
|
int dotLastIndex = name.LastIndexOf(".");
|
if (dotLastIndex == -1)
|
return StringUtility.Contact(new string(name.Reverse().ToArray()), fixFileName);
|
else
|
{
|
var ext = name.Substring(dotLastIndex);
|
name = name.Substring(0, dotLastIndex);
|
return StringUtility.Contact(new string(name.Reverse().ToArray()), fixFileName, ext);
|
}
|
}
|
|
}
|
|
public string GetAssetFilePath(string _assetKey, bool reverse = true)
|
{
|
if (reverse)
|
_assetKey = EncodeFileName(_assetKey);
|
var path = Path.Combine(ExternalStorePath, _assetKey);
|
if (!File.Exists(path))
|
{
|
path = Path.Combine(StreamingAssetPath, _assetKey);
|
}
|
|
return path;
|
}
|
|
public Sprite LoadSprite(string name)
|
{
|
Sprite sprite = null;
|
#if UNITY_EDITOR
|
if (excludePngs.Contains(StringUtility.Contact(name, ".png")))
|
{
|
var path = StringUtility.Contact("Assets/ResourcesOut/BuiltIn/Sprites/", name, ".png");
|
sprite = UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>(path);
|
}
|
else
|
{
|
var spriteAtlas = UnityEditor.AssetDatabase.LoadAssetAtPath<SpriteAtlas>("Assets/ResourcesOut/BuiltIn/Sprites/sprites.spriteatlasv2");
|
sprite = spriteAtlas.GetSprite(name);
|
}
|
|
#else
|
if (spriteBundle == null)
|
{
|
string _path = GetAssetFilePath("builtin/sprites");
|
spriteBundle = AssetBundle.LoadFromFile(_path);
|
}
|
//if (spriteAtlas == null)
|
//{
|
// spriteAtlas = spriteBundle.LoadAsset("sprites", typeof(SpriteAtlas)) as SpriteAtlas;
|
//}
|
//sprite = spriteAtlas?.GetSprite(name);
|
//if (sprite == null)
|
{
|
sprite = spriteBundle.LoadAsset(name, typeof(Sprite)) as Sprite;
|
}
|
#endif
|
if (sprite == null)
|
{
|
DebugEx.LogErrorFormat("LoadSprite() => 加载不到资源: {0}.", name);
|
}
|
|
return sprite;
|
}
|
|
|
public GameObject LoadBuiltInPrefab(string name)
|
{
|
GameObject prefab = null;
|
#if UNITY_EDITOR
|
var path = string.Concat("Assets/ResourcesOut/BuiltIn/Prefabs/", name, ".prefab");
|
prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
#else
|
if (prefabBundle == null)
|
{
|
string _path = GetAssetFilePath("builtin/prefabs");
|
prefabBundle = AssetBundle.LoadFromFile(_path);
|
}
|
prefab = prefabBundle.LoadAsset(name) as GameObject;
|
#endif
|
if (prefab == null)
|
{
|
DebugEx.LogErrorFormat("LoadBuiltInPrefab() => 加载不到资源: {0}.", name);
|
}
|
|
return prefab;
|
}
|
|
public void OpenWindow(string windowName, Transform parent)
|
{
|
GameObject window = GameObject.Instantiate(LoadBuiltInPrefab(windowName));
|
window.transform.SetParent(parent);
|
window.transform.localScale = Vector3.one;
|
(window.transform as RectTransform).sizeDelta = Vector3.zero;
|
}
|
|
|
static Dictionary<string, string> languageShowDict = new Dictionary<string, string>(); //设置中显示多语言版本选择
|
Dictionary<string, string> languageDict = new Dictionary<string, string>(); //unity语言配置对应的约定字符
|
string defaultLanguage = "";
|
public void InitDefaultLanguage()
|
{
|
if (languageShowDict == null || languageShowDict.Count == 0)
|
{
|
var config = InitialFunctionConfig.Get("Language");
|
Debug.LogFormat("系统语言:{0} 配置: {1}", Application.systemLanguage, config.Numerical1);
|
if (string.IsNullOrEmpty(config.Numerical1))
|
return;
|
languageShowDict = JsonMapper.ToObject<Dictionary<string, string>>(config.Numerical1);
|
languageDict = JsonMapper.ToObject<Dictionary<string, string>>(config.Numerical5);
|
defaultLanguage = config.Numerical2;
|
if (!languageShowDict.ContainsKey(defaultLanguage))
|
defaultLanguage = "";
|
}
|
|
var id = LocalSave.GetString("LANGUAGE_ID1");
|
Debug.LogFormat("系统语言:{0} 当前语言: {1}", Application.systemLanguage, id);
|
if (!string.IsNullOrEmpty(id))
|
return;
|
|
string languageMark = ((int)Application.systemLanguage).ToString();
|
if (languageDict.ContainsKey(languageMark))
|
{
|
id = languageDict[languageMark];
|
}
|
|
Id = languageShowDict.ContainsKey(id) ? id : defaultLanguage;
|
Debug.LogFormat("系统语言:{0} 设置为: {1}", Application.systemLanguage, Id);
|
}
|
|
|
|
//LogicBytes文件的MD5信息
|
public void RequestLogicBytes()
|
{
|
var remoteURL = string.Concat(versionInfo.GetResourcesURL(VersionConfigEx.Get().branch), fixPath, "/logicbytes.txt");
|
//var localURL = string.Concat(.ExternalStorePath, "/logicbytes.txt");
|
assetBytesUrl = remoteURL;
|
Debug.Log("http地址:logicbytesUrl " + assetBytesUrl);
|
//HttpRequest.Instance.RequestHttpGet(assetBytesUrl, HttpRequest.defaultHttpContentType, 3, OnGetAssetVersionFile);
|
HttpRequest.Instance.UnityWebRequestGet(assetBytesUrl, 5, OnGetAssetVersionFile);
|
}
|
|
private void OnGetAssetVersionFile(bool _ok, string _result)
|
{
|
Debug.LogFormat("RequestLogicBytes文件结果:时间 {0},结果 {1}, 文件大小 {2}", DateTime.Now, _ok, _result.Length);
|
if (_ok)
|
{
|
UpdateAssetVersions(_result);
|
step = LoadDllStep.DownLoad;
|
}
|
else
|
{
|
Debug.Log("http 数据通讯: RequestLogicBytes:" + assetBytesUrl + " result:" + _result);
|
Clock.AlarmAt(DateTime.Now + new TimeSpan(TimeSpan.TicksPerSecond * 3), RequestLogicBytes);
|
}
|
}
|
|
public Dictionary<string, AssetVersion> UpdateAssetVersions(string _assetVersionFile)
|
{
|
var lines = _assetVersionFile.Split(new string[] { FileExtersion.lineSplit }, StringSplitOptions.RemoveEmptyEntries);
|
assetVersions.Clear();
|
for (int i = 0; i < lines.Length; i++)
|
{
|
var assetVersion = new AssetVersion(lines[i]);
|
assetVersions[assetVersion.relativePath] = assetVersion;
|
}
|
return assetVersions;
|
}
|
|
//读表 使用UnityWebRequest 考虑安卓环境
|
IEnumerator ReadText(string table, Action<bool, string> OnComplete = null, string filePath = "")
|
{
|
var path = string.Empty;
|
string content = string.Empty;
|
bool result = false;
|
if (filePath != "")
|
{
|
path = Path.Combine(filePath + $"{table}.txt");
|
}
|
else
|
{
|
#if UNITY_EDITOR
|
path = CONFIG_FODLER + $"/{table}.txt";
|
#else
|
path = GetAssetFilePath($"config/{table}.txt");
|
#endif
|
}
|
if (!path.Contains("file:"))
|
{
|
//ExternalStorePath 路径需要添加
|
path = "file://" + path;
|
}
|
Debug.LogFormat("ReadText() => path: {0}", path);
|
UnityWebRequest www = UnityWebRequest.Get(path);
|
yield return www.SendWebRequest();
|
|
#if UNITY_2020_1_OR_NEWER
|
if (www.result != UnityWebRequest.Result.Success)
|
{
|
Debug.Log(www.error);
|
OnComplete(result, content);
|
}
|
#else
|
if (www.isHttpError || www.isNetworkError)
|
{
|
Debug.Log(www.error);
|
OnComplete(result, content);
|
}
|
#endif
|
else
|
{
|
content = www.downloadHandler.text;
|
Debug.Log($"table:{table} size:{content.Length}");
|
result = true;
|
OnComplete(result, content);
|
}
|
|
}
|
|
public void InitTable(Action OnComplete = null)
|
{
|
StartCoroutine(ReadText("InitialFunction", (isOK, value) =>
|
{
|
if (isOK)
|
{
|
InitialFunctionConfig.Init(value);
|
OnComplete();
|
}
|
else
|
{
|
Debug.LogError("InitTable InitialFunctionConfig error");
|
}
|
}));
|
if (isPCTestDownLoad || Application.isMobilePlatform)
|
{
|
//读取的一定是StreamingAssetPath路径
|
StartCoroutine(ReadText("logicbytes", (isOK, value) =>
|
{
|
if (isOK)
|
InitLocalLogicbytes(value);
|
else
|
Debug.LogWarning("InitTable logicbytes error");
|
}, StreamingAssetPath));
|
}
|
}
|
|
//随包安装的资源不同平台不一定可以获取FileInfo,所以需要下载一个文件来获取资源的MD5信息
|
//该文件在打包时生成 记录随包的所有资源的信息
|
//该文件记录的资源在包中肯定存在,所以不用去取指定的资源文件获得FileInfo 计算MD5
|
//随包LogicBytes文件的MD5信息 用于下载对比
|
void InitLocalLogicbytes(string content)
|
{
|
using (StringReader reader = new StringReader(content))
|
{
|
string line;
|
while ((line = reader.ReadLine()) != null)
|
{
|
try
|
{
|
var assetVersion = new AssetVersion(line);
|
assetVersion.localValid = true;
|
localAssetVersions[assetVersion.relativePath] = assetVersion;
|
}
|
catch (System.Exception ex)
|
{
|
Debug.LogError(ex);
|
}
|
}
|
}
|
}
|
|
}
|
}
|