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 { //不下载时本地安卓测试路径 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 excludePngs = new List() { "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 assetVersions = new Dictionary(); //本地LogicBytes文件和 assetVersions 比较是否需要下载 public Dictionary localAssetVersions = new Dictionary(); public static string versionUrlResult { get; private set; } /// /// 语言的ID,用于区分下载资源 /// /// 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(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(); 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(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(path); } else { var spriteAtlas = UnityEditor.AssetDatabase.LoadAssetAtPath("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(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 languageShowDict = new Dictionary(); //设置中显示多语言版本选择 Dictionary languageDict = new Dictionary(); //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>(config.Numerical1); languageDict = JsonMapper.ToObject>(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 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 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); } } } } } }