Files
AIC-Project/Client/Assets/Base/LoadDll.cs
T
2026-07-10 10:24:29 +08:00

1163 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string> list = new List<string>();
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<string, ResInfo> m_gameResDict = new Dictionary<string, ResInfo>();
private Dictionary<string, ResInfo> m_localResDict = new Dictionary<string, ResInfo>();
private Dictionary<string, ResInfo> m_tempResDict = new Dictionary<string, ResInfo>();
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<string> GameAssemblyNames { get; } = new List<string>()
{
//"Assembly-CSharp.dll",
//"XMain.dll",
//"res/Main",
};
#if UNITY_EDITOR
public static List<string> HotfixList = new List<string>()
{
//"Assembly-CSharp.dll",
//"XMain.dll",
};
#else
public static List<string> HotfixList = new List<string>();
#endif
public static List<string> AOTMetaAssemblyNames { get; } = new List<string>()
{
"mscorlib.dll",
"System.dll",
"System.Core.dll",
};
private static Dictionary<string, byte[]> s_assetDatas = new Dictionary<string, byte[]>();
private static Dictionary<string, AssetBundle> s_AssetBundle = new Dictionary<string, AssetBundle>();
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<TMP_Text>();
Debug.Log($"m_tmpText is {m_tmpText != null}");
}
else {
obj = new GameObject("UIBase");
Canvas canvas = obj.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
GameObject textObj = new GameObject("Percent");
textObj.transform.SetParent(obj.transform);
m_tmpText = textObj.AddComponent<TextMeshProUGUI>();
m_tmpText.text = "%";
m_tmpText.fontSize = 24;
m_tmpText.color = Color.white;
m_tmpText.alignment = TextAlignmentOptions.Center;
RectTransform rt = textObj.GetComponent<RectTransform>();
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/<platform>/(与大厅/资源同一套,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<GameObject>(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<DevServerPrompt>();
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://<LAN-IP>:15081/)DLL 热更在其下的 XWorld/<platform>/。
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 "<empty>";
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_<md5>.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<string, string> dicFilelist = new Dictionary<string, string>();
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<TextAsset>();
//热更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<GameObject>(assetPath));
m_tmpText.text = "100%";
GameObject asset = Instantiate(prefabAb.LoadAsset<GameObject>("Main.prefab"));
asset.name = "Main";//需要改回原来的名字
GameObject.Destroy(m_UIObj);
}
/// <summary>
/// 为aot assembly加载原始metadata 这个代码放aot或者热更新都行。
/// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
/// </summary>
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<TextAsset>();
//#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<int> 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<bool, string,string> 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<TextAsset>(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<string, ResInfo> 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;
}
}