using HybridCLR; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using TMPro; using UnityEngine; using UnityEngine.Networking; #if UNITY_WEBGL using WeChatWASM; #endif public class LoadDll : MonoBehaviour { public class ResInfo { //public string fileName; public string md5; public string md5Path; public int pack; public int priority; public int size; } class PackWairDownList { public List list = new List(); public int fileSize = 0; public string md5 = ""; public bool isChange = false; } #if UNITY_ANDROID static string sPlatform = "android"; #elif UNITY_IOS static string sPlatform = "ios"; #elif UNITY_WEBGL static string sPlatform = "webgl"; #else static string sPlatform = "pc"; #endif //static public string m_sLog; static public LoadDll Instance; //是否在微信环境 public static bool m_bInWX; GameObject m_UIObj; //更新相关 string UpdateServer; string UpdatePath; string ServerListName = "dllfiles"; string UpdateListName = "updatedllfiles"; string TempListName = "tempdllfiles"; private Dictionary m_gameResDict = new Dictionary(); private Dictionary m_localResDict = new Dictionary(); private Dictionary m_tempResDict = new Dictionary(); private PackWairDownList[] m_packWairDownList = new PackWairDownList[1]; private int m_wairDownCount = 0; private int m_downDownCount = 0; string ResMD5; string DllMD5; private int m_nPercent = 0; private string m_sPercent = ""; private TMP_Text m_tmpText; //游戏的更新程序集(c#) public static List GameAssemblyNames { get; } = new List() { //"Assembly-CSharp.dll", //"XMain.dll", //"res/Main", }; #if UNITY_EDITOR public static List HotfixList = new List() { //"Assembly-CSharp.dll", //"XMain.dll", }; #else public static List HotfixList = new List(); #endif public static List AOTMetaAssemblyNames { get; } = new List() { "mscorlib.dll", "System.dll", "System.Core.dll", }; private static Dictionary s_assetDatas = new Dictionary(); private static Dictionary s_AssetBundle = new Dictionary(); private static string s_pw = "2026091211325890";//test才写死这里,正式环境请改为服务器下发,长度必须16。 public static byte[] ChangeBytes(byte[] src, byte xor = 0xb4) { for (int i = 0; i < src.Length; i++) { src[i] = (byte)(src[i] ^ xor); } return src; } void Start() { Instance = this; m_bInWX = false; //StartCoroutine(coTest(null)); #if UNITY_WEBGL && !UNITY_EDITOR GetStorageInfoSyncOption option = WXSDKManagerHandler.Instance.GetStorageInfoSync(); m_bInWX = (option.limitSize > 0); #endif GameObject obj = GameObject.Find("UIBase/Percent");// if (obj != null) { m_tmpText = obj.GetComponent(); Debug.Log($"m_tmpText is {m_tmpText != null}"); } else { obj = new GameObject("UIBase"); Canvas canvas = obj.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; GameObject textObj = new GameObject("Percent"); textObj.transform.SetParent(obj.transform); m_tmpText = textObj.AddComponent(); m_tmpText.text = "%"; m_tmpText.fontSize = 24; m_tmpText.color = Color.white; m_tmpText.alignment = TextAlignmentOptions.Center; RectTransform rt = textObj.GetComponent(); rt.anchoredPosition = new Vector2(0, 50-Screen.height/2); // 居中显示 rt.sizeDelta = new Vector2(200, 50); // 文本框尺寸 } m_UIObj = obj; for (int i = 0; i < 1; ++i) { m_packWairDownList[i] = new PackWairDownList(); } bool bLocal = false; #if UNITY_WEBGL if (GlobalData.bWX) bLocal = WX.StorageHasKeySync("XWorldLocal"); else #endif //与热更层(GameLauncher)一致:XWorldLocal==1 才是内网测试。关闭内网时它写 0 而非删 key,只判 HasKey 会把"已关闭"误判成内网。 bLocal = PlayerPrefs.GetInt("XWorldLocal", 0) == 1; #if !UNITY_WEBGL //LoadDll 是底包代码,不能引用热更层(GlobalData),故写死生产 URL。 //生产:统一走 60S/XWorld//(与大厅/资源同一套,HTTPS 经 Caddy TLS)。 //内网(bLocal):CDN 跑在开发机,真机上 127.0.0.1 指手机自身连不到,先在协程里 UDP 广播发现开发机 LAN CDN(见 CoLocalDiscoverThenDownload),此处只留生产默认值兜底。 UpdateServer = $"https://www.xworld.ren/game/60S/XWorld/{sPlatform}/"; #else //WebGL 只能用CDN目录下载 UpdateServer = $"{Application.streamingAssetsPath}/";//$"http://192.168.9.231/G3/webglbase/StreamingAssets/"; #endif UpdatePath = Application.persistentDataPath + "/"; m_tmpText.text = "10%"; #if UNITY_EDITOR_X //编辑器不走热更流程,纯本地文件 m_tmpText.text = ""; //obj.SetActive(false); string assetPath = "Assets/Base/Prefab/Main.prefab"; GameObject asset = Instantiate(UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath)); asset.name = "Main";//需要改回原来的名字 GameObject.Destroy(m_UIObj); return; #endif #if !UNITY_WEBGL if (bLocal) StartCoroutine(CoLocalDiscoverThenDownload()); else StartCoroutine(DownLoadAssets(this.StartGame)); #else StartCoroutine(DownloadWebGL(this.StartGame)); #endif } #if !UNITY_WEBGL //内网热更:先 UDP 广播发现开发机 CDN(拿其 LAN IP),成功则改用它;失败弹输入框让开发者输入开发机 IP, //探测成功写入 persistentDataPath/dev_server.txt(下次启动第 1 轮直接命中不再弹);[跳过]回退 127.0.0.1(真机需 adb reverse tcp:15081)。 //token 须与热更层 GlobalData.LanDiscoveryToken 及服务端 Gateway.Runner --devToken 一致(均为 "xw-dev")。 IEnumerator CoLocalDiscoverThenDownload() { Debug.Log($"[LoadDll] CoLocalDiscoverThenDownload begin"); //第 1 轮:广播/loopback/定向广播 + dev_server.txt 已存 IP(若有)。 //Discover 会阻塞(UDP 收发+超时),放后台线程跑,协程逐帧等它结束,避免卡加载界面; //persistentDataPath 不能在后台线程访问,配置在主线程先读好传入。 string cdnRoot = null; string savedIp = DevServerConfig.Load(); { Thread th = new Thread(() => { cdnRoot = LocalCdnDiscovery.Discover("xw-dev", 1500, savedIp); }) { IsBackground = true }; th.Start(); while (th.IsAlive) yield return null; } //第 1 轮失败 → 弹输入框(预填旧值,开发机换 IP 时可直接改);输入 IP 验证探测成功才写配置。 if (string.IsNullOrEmpty(cdnRoot)) { DevServerPrompt prompt = gameObject.AddComponent(); prompt.InputIp = savedIp ?? ""; while (string.IsNullOrEmpty(cdnRoot)) { while (prompt.State == DevServerPrompt.PromptState.Waiting) yield return null; if (prompt.State == DevServerPrompt.PromptState.Skipped) break; //Confirmed:先主线程校验格式(只认 IPv4,与 DevServerConfig.Load 同语义),非法不发起探测直接回弹框。 string inputIp = (prompt.InputIp ?? "").Trim(); if (!System.Net.IPAddress.TryParse(inputIp, out System.Net.IPAddress inputAddr) || inputAddr.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) { prompt.ResetToWaiting($"IP 格式不对:{inputIp}"); continue; } Thread th = new Thread(() => { cdnRoot = LocalCdnDiscovery.Discover("xw-dev", 1500, inputIp); }) { IsBackground = true }; th.Start(); while (th.IsAlive) yield return null; if (string.IsNullOrEmpty(cdnRoot)) { prompt.ResetToWaiting($"探测失败:连不到 {inputIp}:48923(网关起了吗?防火墙放行 UDP 48923 了吗?)"); continue; } DevServerConfig.Save(inputIp); Debug.Log($"[LoadDll] dev server ip saved: {inputIp}"); } Destroy(prompt); } if (!string.IsNullOrEmpty(cdnRoot)) { if (!cdnRoot.EndsWith("/")) cdnRoot += "/"; //发现回复的 ResourceBaseUrl 是 CDN 根(http://:15081/),DLL 热更在其下的 XWorld//。 UpdateServer = $"{cdnRoot}XWorld/{sPlatform}/"; Debug.Log($"[LoadDll] LAN CDN discovered: {UpdateServer}"); } else { UpdateServer = $"http://127.0.0.1:15081/XWorld/{sPlatform}/"; Debug.LogWarning($"[LoadDll] LAN CDN discovery failed/skipped, fallback {UpdateServer} (真机需 adb reverse tcp:15081 tcp:15081,且开发机网关须 --lan --devToken xw-dev)"); } yield return DownLoadAssets(this.StartGame); } #endif public static byte[] GetEncryptAssetData(string dllName) { if (!s_assetDatas.TryGetValue(dllName, out byte[] encryptedData) || encryptedData == null) { Debug.LogError($"dll {dllName} not found in loaded asset data."); return null; } try { byte[] decryptdata = AESManager.Decrypt(encryptedData); Debug.Log($"dll {dllName} bytes {encryptedData.Length}, {decryptdata.Length}, head:{GetAssetDataHead(decryptdata)}"); return decryptdata; } catch (Exception e) { Debug.LogError($"dll {dllName} decrypt exception: {e}"); return null; } } public static byte[] GetAssetData(string dllName) { return s_assetDatas[dllName]; } private static AssetBundle LoadEncryptedAssetBundle(string assetBundleName) { byte[] assetData = GetEncryptAssetData(assetBundleName); if (assetData == null || assetData.Length == 0) return null; if (!IsUnityAssetBundleData(assetData)) { Debug.LogError($"{assetBundleName} decrypt data is not a Unity AssetBundle. " + $"head:{GetAssetDataHead(assetData)}, len:{assetData.Length}. " + "Please rebuild/encrypt this AssetBundle with the same AES key used by LoadDll."); return null; } AssetBundle ab = AssetBundle.LoadFromMemory(assetData); if (ab == null) { Debug.LogError($"{assetBundleName} LoadFromMemory failed."); } return ab; } private static bool IsUnityAssetBundleData(byte[] data) { if (data == null || data.Length < 5) return false; return data[0] == (byte)'U' && data[1] == (byte)'n' && data[2] == (byte)'i' && data[3] == (byte)'t' && data[4] == (byte)'y'; } private static string GetAssetDataHead(byte[] data) { if (data == null || data.Length == 0) return ""; int len = Math.Min(16, data.Length); StringBuilder sb = new StringBuilder(len * 3); for (int i = 0; i < len; i++) { byte b = data[i]; sb.Append(b >= 32 && b <= 126 ? (char)b : '.'); } sb.Append(" / "); for (int i = 0; i < len; i++) { if (i > 0) sb.Append(' '); sb.Append(data[i].ToString("X2")); } return sb.ToString(); } private string GetWebRequestPath(string asset) { //此处可以改为自己的http更新dll目录 #if UNITY_EDITOR var path = $"{Application.streamingAssetsPath}/{asset}";//dll为streamAssets里的 //if (!File.Exists(path)) //{ // path = $"{UpdatePath}/{asset}"; //} #else var path = $"{UpdatePath}{asset}"; //m_sLog = path; //if (!File.Exists(path)) //{ // Debug.Log($"File not exist : {path}"); // path = $"{Application.streamingAssetsPath}/{asset}"; //} #endif if (!path.Contains("://")) { path = "file://" + path; } return path; } void UpdateProcess(int curFinish, int totalNum) { } IEnumerator DownLoadAssets(Action onDownloadComplete) { //bool isVerChange = false; bool isDllChange = false; //更新流程 //获得版本号updatever.txt,对比本地ver.txt yield return CheckPackage(UpdateServer, "updatever.txt", "ver.txt", (isChange,oldmd5, newmd5) => { Debug.Log($"CheckVersion({UpdateServer}): {oldmd5} {newmd5} ({isChange})"); //总是记录当前版本 md5:无变化分支也要用它拼 dllfiles_.txt(原来只在 isChange 时设→无变化时空 md5→dllfiles_.txt 404) if (!string.IsNullOrEmpty(newmd5)) m_packWairDownList[0].md5 = newmd5; if (isChange) { isDllChange = true; m_packWairDownList[0].isChange = isChange; } }); if (isDllChange) { m_gameResDict.Clear(); m_localResDict.Clear(); m_tempResDict.Clear(); //有改变,需要下载并对比列表文件dllfiles string[]filemd5 = m_packWairDownList[0].md5.Split(','); string dlllistname = GetFileMd5Name($"{ServerListName}.txt", filemd5[0]);// $"{ServerListName}.txt_{filemd5[0]}"; yield return DownloadFile(UpdateServer, dlllistname, result => { if (result > 0) { System.DateTime dt = System.DateTime.Now; LoadServerResourceMap(UpdatePath + dlllistname);//UpdatePath + ServerListName + ".txt" Debug.LogFormat("LoadServerResourceMap {0} 耗时:{1}ms", ServerListName, (System.DateTime.Now - dt).TotalMilliseconds); } else { Debug.LogFormat("File not exist : {0}{1}", UpdatePath, ServerListName); } }); LoadLocalResourceMap(); int count = 1; foreach (var gameRes in m_gameResDict) { ResInfo localRes = null; if (m_localResDict.TryGetValue(gameRes.Key, out localRes) && localRes.md5 == gameRes.Value.md5) { //m_existFiles[gameRes.Key] = EExistLocation.Local; } else { if (m_tempResDict.TryGetValue(gameRes.Key, out localRes) && localRes.md5 == gameRes.Value.md5) { //m_existFiles[gameRes.Key] = EExistLocation.Local; } else { var packList = m_packWairDownList[0];//暂时只用一份更新,无分包 string finalName = GetFileMd5Name(gameRes.Key, gameRes.Value.md5); packList.list.Add(finalName); packList.fileSize += gameRes.Value.size; } } //m_packSizeRecord[gameRes.Value.pack - 1] += gameRes.Value.size; UpdateProcess(count++, m_gameResDict.Count); } //写入ver.txt文件(md5) 这样可以防止以后服务器没更新的时候还下载检查更新 if (m_packWairDownList[0].fileSize <= 0) { using (StreamWriter sw = new StreamWriter(string.Format("{0}ver.txt", UpdatePath))) { sw.WriteLine(m_packWairDownList[0].md5); } //isVerChange = false; m_packWairDownList[0].isChange = false; } //下载过程 UnityWebRequest uwr; string downloadServer = UpdateServer; Debug.LogFormat("downloadServer : {0} Total:{1}", downloadServer, m_packWairDownList[0].list.Count); int i = 0; count = m_packWairDownList[0].list.Count; string tempListFile = UpdatePath + TempListName + ".txt"; StreamWriter fs = null; if (count > 0) { fs = new StreamWriter(tempListFile, true); } while (i < count) { string namePath = m_packWairDownList[0].list[i]; var path = downloadServer + namePath; string outfile = UpdatePath + namePath; uwr = UnityWebRequest.Get(path); #region 下列代码修改下载的方式 用于处理断点下载和堆内存峰值 // by xl string tempFile = outfile + ".temp"; if (File.Exists(tempFile)) { File.Delete(tempFile); } GYDownloadHandler dh = new GYDownloadHandler(outfile); dh.OnFinish = (int size) => { if (size > 0) { if (m_gameResDict.TryGetValue(namePath, out ResInfo resInfo)) { //没关闭流 可能写不完整 fs?.WriteLine(string.Format("{{'{0}','{1}','{2}'}}", namePath, resInfo.md5, 1, resInfo.size)); } ++m_downDownCount; UpdateProcess(m_downDownCount, m_wairDownCount); ++i; } else { Debug.LogError("File Miss : " + path); } }; uwr.downloadHandler = dh; #endregion yield return uwr.SendWebRequest(); if (uwr.result != UnityWebRequest.Result.Success) { Debug.LogError("File Download Error : " + path); } else Debug.Log("File Download Success : " + path); } m_packWairDownList[0].list.Clear(); m_packWairDownList[0].fileSize = 0; m_packWairDownList[0].isChange = false; //此处只复制了dll,所以未算彻底完成 /*using (StreamWriter sw = new StreamWriter(string.Format("{0}ver.txt", UpdatePath))) { sw.WriteLine(m_packWairDownList[0].md5); }//*/ //if (pack <= 1) { fs?.Close(); string[] fmd5 = m_packWairDownList[0].md5.Split(','); string dlllist = GetFileMd5Name($"{ServerListName}.txt", fmd5[0]); yield return DownloadFile(downloadServer, dlllist, result => { if (result > 0) { Debug.Log("Download End "); if (File.Exists(tempListFile)) { File.Delete(tempListFile); } } else { Debug.LogErrorFormat("{0} is not exists", downloadServer + dlllist); } }); m_wairDownCount = 0; m_downDownCount = 0; //m_downEndFun(isDllUpdate ? 1 : 0); //yield break; } } else { Debug.Log("Update : Dll No Change !"); //加载列表md5 string[] filemd5 = m_packWairDownList[0].md5.Split(','); string dlllistname = GetFileMd5Name($"{ServerListName}.txt", filemd5[0]);// $"{ServerListName}.txt_{filemd5[0]}"; yield return DownloadFile(UpdateServer, dlllistname, result => { if (result > 0) { System.DateTime dt = System.DateTime.Now; LoadServerResourceMap(UpdatePath + dlllistname);//UpdatePath + ServerListName + ".txt" Debug.LogFormat("LoadServerResourceMap {0} 耗时:{1}ms", ServerListName, (System.DateTime.Now - dt).TotalMilliseconds); } else { Debug.LogFormat("File not exist : {0}{1}", UpdatePath, ServerListName); } }); } if(m_gameResDict.Count == 0) Debug.LogError("Download Dll m_gameResDict Error!"); //set byte[] w = System.Text.Encoding.UTF8.GetBytes($"#@pOrhgf{s_pw}vciu%c-X"); AESManager.Set(ChangeBytes(w)); string[] baseABName = { "a", "h", "xhotfix/base" }; foreach (var file in baseABName) { string name = $"{file}.unity3d"; string dllPath; if (!m_gameResDict.ContainsKey(name)) {//更新中没有此文件 dllPath = $"{Application.streamingAssetsPath}/{name}"; } else { dllPath = GetFileMd5Name($"{UpdatePath}{name}", m_gameResDict[name].md5); if (!File.Exists(dllPath)) { dllPath = $"{Application.streamingAssetsPath}/{name}"; } else { if (!dllPath.Contains("://")) { dllPath = "file://" + dllPath; } } } UnityWebRequest www = UnityWebRequest.Get(dllPath); yield return www.SendWebRequest(); if (www.result != UnityWebRequest.Result.Success) { Debug.Log($"{www.error} ({dllPath})"); } else { // Or retrieve results as binary data byte[] assetData = www.downloadHandler.data; Debug.Log($"dll:{dllPath} size:{assetData.Length}"); s_assetDatas[name] = assetData; } } onDownloadComplete(); } IEnumerator DownloadWebGL(Action onDownloadComplete) { Dictionary dicFilelist = new Dictionary(); string filelistmd5; string downloadServer = UpdateServer; DateTime now = DateTime.UtcNow; #if !UNITY_EDITOR UnityWebRequest uwr = UnityWebRequest.Get($"{downloadServer}update.txt?{now.Ticks}"); #else UnityWebRequest uwr = UnityWebRequest.Get($"{downloadServer}updatever.txt"); #endif yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { filelistmd5 = uwr.downloadHandler.text; int npos = filelistmd5.IndexOf(','); if (npos != -1) { ResMD5 = filelistmd5.Substring(npos + 1); filelistmd5 = filelistmd5.Substring(0, npos); } DllMD5 = filelistmd5; Debug.Log($"Download {downloadServer}update.txt {filelistmd5} OK"); } else { Debug.LogErrorFormat($"{downloadServer}update.txt is not exists"); yield break; } m_tmpText.text = "20%"; //读取filelist //string url = GetFileMd5Name("dllfiles.txt", filelistmd5);//"dllfiles.txt" //uwr = UnityWebRequest.Get($"{downloadServer}{url}"); string url = GetFileMd5Name("dllfiles.txt", filelistmd5); uwr = UnityWebRequest.Get(url);//$"{downloadServer}dllfiles.txt_{filelistmd5}" yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { string txt = uwr.downloadHandler.text; string[] lines = txt.Split('\n'); int lineCount = lines.Length; for (int i = 0; i < lineCount; ++i) { if (lines[i].Length < 3) continue; string line = lines[i].Substring(1, lines[i].Length - 2); string[] datas = line.Split(','); dicFilelist[datas[0]] = datas[1]; //Debug.Log($"dll : {datas[0]}"); } //Debug.Log($"Download {url} : count = {lineCount}"); } else { Debug.LogErrorFormat($"{url} is not exists"); yield break; } m_tmpText.text = "30%"; int per = 30; AOTMetaAssemblyNames.Clear(); HotfixList.Clear(); foreach (var dll in GameAssemblyNames) {//dll需要确定加载顺序 HotfixList.Add(dll); } UnityWebRequest www; string[] baseABName = { "a", "h", "xhotfix/base" }; foreach (var file in baseABName) { string name = $"{file}.unity3d"; if (!dicFilelist.ContainsKey(name)) continue; //www = UnityWebRequest.Get($"{downloadServer}{GetFileMd5Name(name, dicFilelist[name])}"); string urlfinal = $"{downloadServer}{GetFileMd5Name(name, dicFilelist[name])}";// $"{downloadServer}{name}_{dicFilelist[name]}"; www = UnityWebRequest.Get(urlfinal); // if (!m_bInWX) // { // } // else // { //#if UNITY_WEBGL // //微信小游戏 WXAssetBundle 不能支持加密 // www = WXAssetBundle.GetAssetBundle(urlfinal); //#else // www = UnityWebRequest.Get(urlfinal); //#endif // } yield return www.SendWebRequest(); per += 10; if(per <= 90) m_tmpText.text = $"{per}%"; if (www.result != UnityWebRequest.Result.Success) { Debug.LogError(www.error); } else { byte[] assetData = www.downloadHandler.data; //Debug.Log(GetFileMd5Name("dllfiles.txt", filelistmd5)); //Debug.Log($"dll {name} Load:{name}_{dicFilelist[name]} size:{assetData.Length}"); s_assetDatas[name] = assetData; // if (!m_bInWX) // { // } // else // {//微信小游戏 //#if UNITY_WEBGL // AssetBundle bundle = (www.downloadHandler as DownloadHandlerWXAssetBundle).assetBundle; // s_AssetBundle[name] = bundle; //#else // byte[] assetData = www.downloadHandler.data; // //Debug.Log($"dll {name} Load:{name}_{dicFilelist[name]} size:{assetData.Length}"); // s_assetDatas[name] = assetData; //#endif // } } } m_tmpText.text = "90%"; Debug.Log($"onDownloadComplete"); onDownloadComplete(); } static string GetFileMd5Name(string filename, string md5) { int pos = filename.LastIndexOf('.'); if (pos != -1) { return $"{filename.Substring(0, pos)}_{md5}{filename.Substring(pos)}"; } else return $"{filename}_{md5}"; } void StartGame() { //Assembly assMain = null; //Debug.Log($"LoadMetadataForAOTAssembly: begin"); LoadMetadataForAOTAssemblies(); m_tmpText.text = "95%"; #if !UNITY_EDITOR AssetBundle ab; ab = LoadEncryptedAssetBundle("h.unity3d"); if (ab == null) return; //if (!m_bInWX) //{ //}else //{ // ab = s_AssetBundle["h.unity3d"]; //} TextAsset[] dlls = ab.LoadAllAssets(); //热更dll需要排序,目前先提取出y开头的优先排序(yooAsset) foreach (var dll in dlls) { if (dll.name[0] == 'y' || dll.name[0] == 'Y' ) { Debug.Log($"load dll {dll.name}"); System.Reflection.Assembly.Load(dll.bytes); } } //共享协议层 XWorld.Framework.Shared 现为热更程序集,被 XWorld.Link 引用, //须先于 XWorld.Link 加载(依赖在前),否则 Link 解析其类型会失败。 const string SharedAsmName = "XWorld.Framework.Shared"; foreach (var dll in dlls) { if (dll.name == SharedAsmName) { Debug.Log($"load dll {dll.name} {dll.bytes.Length}"); try { System.Reflection.Assembly.Load(dll.bytes); } catch (Exception e) { Debug.LogError($"DLL Load Error : {e.ToString()}"); } } } foreach (var dll in dlls) { if (dll.name[0] != 'y' && dll.name[0] != 'Y' && dll.name != SharedAsmName) { Debug.Log($"load dll {dll.name} {dll.bytes.Length}"); try { System.Reflection.Assembly.Load(dll.bytes); } catch (Exception e) { Debug.LogError($"DLL Load Error : {e.ToString()}"); } } } //if (m_bInWX) //{ // dlls = null; // ab.WXUnload(true); //} #else foreach (var dll in HotfixList) { string name; if (dll.EndsWith(".dll")) name = dll.Substring(0, dll.Length - 4); else { continue; } //m_sLog = $"Load Assembly : {name}"; try { var gameAss = AppDomain.CurrentDomain.GetAssemblies().First(assembly => assembly.GetName().Name == name); } catch (Exception e) { Debug.LogError($"DLL Load Error : {e.ToString()}"); } } #endif //editor 加载main.prefab AssetBundle prefabAb; prefabAb = LoadEncryptedAssetBundle("xhotfix/base.unity3d"); if (prefabAb == null) return; //if (!m_bInWX) //{ // byte[] prefab = GetAssetData("xhotfix/Main.unity3d"); // if (prefab == null) // Debug.Log($"Start Load prefab null"); // prefabAb = AssetBundle.LoadFromMemory(prefab); //} //else //{ // prefabAb = s_AssetBundle["xhotfix/Main.unity3d"]; //} //string assetPath = "Assets/res/base/Main.prefab"; //GameObject asset = Instantiate(UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath)); m_tmpText.text = "100%"; GameObject asset = Instantiate(prefabAb.LoadAsset("Main.prefab")); asset.name = "Main";//需要改回原来的名字 GameObject.Destroy(m_UIObj); } /// /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。 /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行 /// private static void LoadMetadataForAOTAssemblies() { //加载aot ab Debug.Log($"LoadMetadataForAOTAssemblies aot"); if (!s_assetDatas.ContainsKey("a.unity3d")) { //a.unity3d(AOT 元数据包)按设计不热更(BuildDllsFileIndex 在 bUpdateDll 时跳过),须打进底包 streamingAssets。 //当前底包缺失且服务器 dllfiles 未列它 → 跳过 AOT 元数据补充(泛型走解释器,可能较慢/个别报错),避免 KeyNotFoundException 卡死。 Debug.LogError("AOT 元数据 a.unity3d 缺失:未在底包 streamingAssets(assets/a.unity3d 404),dllfiles 也未列出。" + "请在打 APK 前确保 Assets/StreamingAssets/a.unity3d 存在(生成 hotfix 时产出)。暂跳过 AOT 补充。"); return; } AssetBundle ab = LoadEncryptedAssetBundle("a.unity3d"); if (ab == null) { Debug.LogError("a.unity3d Decrypt Failed!"); return; } TextAsset[] datas = ab.LoadAllAssets(); //#if !UNITY_WEBGL // 可以加载任意aot assembly的对应的dll。但要求dll必须与unity build过程中生成的裁剪后的dll一致,而不能直接使用原始dll。 // 我们在BuildProcessors里添加了处理代码,这些裁剪后的dll在打包时自动被复制到 {项目目录}/HybridCLRData/AssembliesPostIl2CppStrip/{Target} 目录。 /// 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。 /// 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误 /// foreach (var t in datas) { byte[] dllBytes = t.bytes;// GetAssetData(aotDllName); // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码 LoadImageErrorCode err = RuntimeApi.LoadMetadataForAOTAssembly(dllBytes, HomologousImageMode.SuperSet); //Debug.Log($"LoadMetadataForAOTAssembly:{t.name} size{dllBytes.Length}"); } //#endif } //携程下载文件 public IEnumerator DownloadFile(string downloadServer, string fileName, System.Action onFinish) { UnityWebRequest uwr; //Debug.Log($"download ServerList {downloadServer + fileName}"); using (uwr = UnityWebRequest.Get(downloadServer + fileName)) { // uwr.timeout = m_httpRequestTimeout; yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { string outfile = UpdatePath + fileName; string dir = Path.GetDirectoryName(outfile); //if (!Directory.Exists(dir)) //{ // Directory.CreateDirectory(dir); //} if (File.Exists(outfile)) File.Delete(outfile); if (uwr.downloadHandler.data == null) { Debug.LogError($"Data = null ({fileName})"); } else File.WriteAllBytes(outfile, uwr.downloadHandler.data); onFinish(uwr.downloadHandler.data.Length); } else { Debug.LogError($"Download Failed {downloadServer + fileName}"); onFinish(0); //if (RequestErrorCallback != null) { RequestErrorCallback("DownloadFile", uwr.error); } } } } //void OnGUI() //{ // GUI.Label(new Rect(50, Screen.height - 48, 300, 300), $"{m_wairDownCount}/{m_wairDownCount}", m_Style); // GUI.Label(new Rect(50, Screen.height - 68, 300, 300), m_sLog, m_Style); //} IEnumerator CheckPackage(string downloadServer, string fileName, string verName, System.Action onFinish) { int rs = 0; yield return DownloadFile(downloadServer, fileName, result => { rs = result; }); { bool ischange = true; string vermd51 = null, newmd51 = null; if (rs > 0 && File.Exists(UpdatePath + fileName)) { using (StreamReader sr = new StreamReader(UpdatePath + fileName)) { string line; line = sr.ReadLine(); if (line != null || line != "") { newmd51 = line; } } bool bExist = File.Exists(UpdatePath + verName); if (!bExist) { //第一次运行没有版本文件,尝试从包内复制出来 string localVerFile = $"{Application.streamingAssetsPath}/{fileName}"; UnityWebRequest request = UnityWebRequest.Get(localVerFile); yield return request.SendWebRequest(); if (request.result == UnityWebRequest.Result.Success) { File.WriteAllBytes(UpdatePath + verName, request.downloadHandler.data); bExist = true; } else { Debug.LogError($"包内无版本文件: {localVerFile}"); } } if (bExist) { using (StreamReader sr = new StreamReader(UpdatePath + verName)) { string line; line = sr.ReadLine(); if (line != null || line != "") { vermd51 = line; } } } if (newmd51 != null) { if (vermd51 != null && newmd51 == vermd51) { ischange = false; } } else { ischange = false; } } else { ischange = false; } onFinish(ischange, vermd51, newmd51); } } void LoadPackageResourceMap() { string content; TextAsset textAsset = Resources.Load(ServerListName); if (textAsset != null) { content = textAsset.text; LoadResourceMap(content, m_localResDict); } Debug.LogFormat("{0} file(s) exist in game.", m_localResDict.Count); } void LoadLocalResourceMap() { string localListFile = UpdatePath + UpdateListName + ".txt"; string content = ""; if (File.Exists(localListFile)) { LoadResourceMap(content, m_localResDict); } } void LoadTempResourceMap() { string tempListFile = UpdatePath + TempListName + ".txt"; string content = ""; if (File.Exists(tempListFile)) { content = File.ReadAllText(tempListFile); //此处是为了修复 文件写入不完整的问题 if (!string.IsNullOrEmpty(content) && !content.EndsWith("}")) { int lastIdx = content.LastIndexOf("}"); content = content.Substring(0, lastIdx + 2);//包含换行 File.WriteAllText(tempListFile, content); } LoadResourceMap(content, m_tempResDict); } } void LoadResourceMap(string content, Dictionary resMap) { if (string.IsNullOrEmpty(content)) { Debug.LogWarningFormat("Can't load file_list"); return; } string[] lines = content.Split('\n'); if (lines == null) { Debug.LogWarningFormat("Can't load res_list, it is required!"); return; } foreach (string line in lines) { if (line.Length < 2) { continue; } string s = line.Substring(2, line.Length - 2 - 2); //去掉开头的{'和结尾的}, s = s.Replace("\'", ""); //把lua的字符串包围符去掉. string[] strs = s.Split(','); if (strs.Length > 2) { ResInfo resInfo = new ResInfo(); resInfo.md5Path = strs[0]; resInfo.md5 = strs[1]; resInfo.size = int.Parse(strs[2]); resMap[strs[0]] = resInfo; } else { Debug.LogWarningFormat("invalid line: ", line); } } } void LoadServerResourceMap(string path) { string content;//= ResourceLoader.instance.LoadAssetString(ServerListName + ".txt");//Android上读的是内部的文件 byte[] data; long size; using (var stream = File.OpenRead(path))//UpdatePath + ServerListName + ".txt" { size = stream.Seek(0, SeekOrigin.End); data = new byte[size]; stream.Seek(0, SeekOrigin.Begin); stream.Read(data, 0, data.Length); content = System.Text.Encoding.UTF8.GetString(data); } if (string.IsNullOrEmpty(content)) { Debug.LogWarningFormat("LoadServerResourceMap Can't load file_list"); } string[] lines = content.Split('\n'); //Debug.LogFormat("{0} lines in ServerListName", lines.Length); if (lines != null) { foreach (string line in lines) { if (line.Length < 2) { continue; } //fileName, md5, size //{'ui/copy_fui.ab','2ca8dd785bf3e99decaab1f9e3e4cd22',1257}, string s = line.Substring(1, line.Length - 1 - 2); //去掉开头的{'和结尾的}, s = s.Replace("\'", ""); //把lua的字符串包围符去掉. string[] strs = s.Split(','); if (strs.Length > 2) { ResInfo resInfo = new ResInfo(); resInfo.md5Path = strs[0]; resInfo.md5 = strs[1]; resInfo.size = int.Parse(strs[2]); m_gameResDict[strs[0]] = resInfo; } else { //Logger.LogWarningFormat("invalid line: ", line); } } //Logger.LogFormat("{0} file(s) exist in game.", m_gameResDict.Count); } else {//失败 //throw new InvalidResListException("Can't load server_res_list, it is required!"); } } public string GetResMD5() { return ResMD5; } public string GetDllMD5() { return DllMD5; } }