1163 lines
43 KiB
C#
1163 lines
43 KiB
C#
|
||
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;
|
||
}
|
||
}
|