using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; namespace XGame { class UpdateModule : MonoBehaviour { public static UpdateModule Instance; 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"; #else static string sPlatform = "pc"; #endif GameObject m_MainObj; //更新相关 string UpdateServer;// string UpdatePath; string ServerListName = "files"; string UpdateListName = "updatefiles"; string TempListName = "tempfiles"; private Dictionary m_gameResDict = new Dictionary(); private Dictionary m_localResDict = new Dictionary(); private Dictionary m_tempResDict = new Dictionary(); private PackWairDownList[] m_packWairDownList = new PackWairDownList[10]; private int m_wairDownCount = 0; private int m_downDownCount = 0; //private bool m_bHotfix = false; string m_sNewMD5; string m_sOldMD5; //bool m_bChange = false; //UI 相关 Slider m_sliderUpdate; TextMeshProUGUI m_textUpdate; XProgress m_Proc; private void Awake() { Instance = this; #if UNITY_WEBGL UpdatePath = $"{Application.streamingAssetsPath}/"; UpdateServer = $"{Application.streamingAssetsPath}/"; #else UpdatePath = Application.persistentDataPath + "/"; UpdateServer = $"{XGame.AppConst.UpdateServer}{sPlatform}/";//$"http://127.0.0.1/update/{sPlatform}/"; // #endif m_Proc = new XProgress(); Init(); } public void Init() { for (int i = 0; i < 10; ++i) { m_packWairDownList[i] = new PackWairDownList(); } /*/Test //默认Main预设会直接挂好,直接使用下面的Update组件 GameObject sliderObj = GameObject.Find("Main(Clone)/Update/Slider");//Base");// if (sliderObj != null) { m_sliderUpdate = sliderObj.transform.GetComponent(); } m_textUpdate = GameObject.Find("Main(Clone)/Update/Text").transform.GetComponent(); //*/ } public string GetResMD5() { return m_sNewMD5; } //设置资源管理器的文件列表hash 字典 public void SetAllResHash() { if (m_gameResDict.Count == 0) {//还未加载file.txt LoadServerResourceMap(); } string str; XResLoader.m_ResHash.Clear(); foreach (var dic in m_gameResDict) { //保存UpdatePath/res Hash128 hash = Hash128.Parse(dic.Value.md5); XResLoader.m_ResHash[$"{AppConst.UpdatePath}{dic.Key}"] = hash; str = hash.ToString(); } } public void AddResHash(Dictionary resList) { foreach (var dic in resList) { //保存UpdatePath/res Hash128 hash = Hash128.Parse(dic.Value.md5); XResLoader.m_ResHash[$"{AppConst.UpdatePath}{dic.Key}"] = hash; } } #if UNITY_WEBGL public IEnumerator UpdateResAllWebGL(System.Action onDownloadComplete) { Dictionary dicFilelist = new Dictionary(); string downloadServer = UpdateServer; UnityWebRequest uwr = UnityWebRequest.Get($"{downloadServer}updatever.txt"); yield return uwr.SendWebRequest(); if (uwr.downloadHandler.isDone && uwr.result == UnityWebRequest.Result.Success) { m_sNewMD5 = uwr.downloadHandler.text; int npos = m_sNewMD5.IndexOf(','); if (npos != -1)//兼容单双md5 m_sNewMD5 = m_sNewMD5.Substring(npos+1, m_sNewMD5.Length - npos -1); Debug.Log($"Download DataFile {m_sNewMD5} OK"); } else { Debug.LogErrorFormat($"{downloadServer}updatever.txt is not exists"); yield break; } } #endif public IEnumerator UpdateResAll(System.Action onDownloadComplete) { //检查更新 string sNewMD5File = $"{UpdatePath}updatever.txt"; string sOldMD5File = $"{UpdatePath}ver.txt"; if (File.Exists(sNewMD5File)) { m_sNewMD5 = File.ReadAllText(sNewMD5File); int npos = m_sNewMD5.IndexOf(','); if (npos != -1)//兼容单双md5 m_sNewMD5 = m_sNewMD5.Substring(npos + 1, m_sNewMD5.Length - npos - 1); } else { //直接结束 if(onDownloadComplete != null) onDownloadComplete(); yield break; } if (File.Exists(sOldMD5File)) { m_sOldMD5 = File.ReadAllText(sOldMD5File); } else {//无文件,说明是第一次需要都更新 //m_bChange = true; } if (m_sNewMD5 != m_sOldMD5) { //m_bChange = true; } else { //无差异直接结束 if (onDownloadComplete != null) onDownloadComplete(); yield break; } yield return DownLoadAssets(onDownloadComplete); } //进度变化的回调 void UpdateProcess(int curFinish, int totalNum) { if(totalNum > 0) m_Proc.Set(curFinish * 100 / totalNum); //if(m_sliderUpdate != null) // m_sliderUpdate.value = curFinish; } void OnFileDownloadBegin(string filename, int data) { } void WriteVerFile() { using (StreamWriter sw = new StreamWriter($"{UpdatePath}ver.txt")) { sw.WriteLine(m_sNewMD5); } } IEnumerator DownLoadAssets(Action onDownloadComplete) { bool isVerChange = true; string[] sNewMd5s = m_sNewMD5.Split(','); string[] sOldMd5s = { "SDFASD123SFDSerw","gagfewr21dsfa" }; if(!string.IsNullOrEmpty(m_sOldMD5)) sOldMd5s = m_sOldMD5.Split(','); if (sNewMd5s[0] == sOldMd5s[0]) {//资源md5相同,写ver更新结束 WriteVerFile(); onDownloadComplete(); yield break; } //更新流程 //获得版本号updatever.txt,对比本地ver.txt //yield return CheckPackage(UpdateServer, "updatever.txt", "ver.txt", (isChange, newmd5) => //{ // isVerChange = isChange; // m_packWairDownList[0].md5 = newmd5; // m_packWairDownList[0].isChange = isChange; //}); m_packWairDownList[0].md5 = sNewMd5s[0]; m_packWairDownList[0].isChange = true; //Debug.Log($"Update Res List Begin : {sNewMd5s[0]}"); if (isVerChange) { m_gameResDict.Clear(); m_localResDict.Clear(); m_tempResDict.Clear(); //有改变,需要下载并对比列表文件 yield return DownloadFile(UpdateServer, $"{ServerListName}.txt", result => { if (result > 0) { System.DateTime dt = System.DateTime.Now; LoadServerResourceMap(); Debug.LogFormat("LoadServerResourceMap {0} 耗时:{1}ms", ServerListName, (System.DateTime.Now - dt).TotalMilliseconds); } else { Debug.LogFormat("File not exist : {0}{1}", UpdatePath, ServerListName); isVerChange = false; } }); if (!isVerChange)//失败 { onDownloadComplete(); yield break; } yield return LoadPackageResourceMap(); 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];//暂时只用一份更新,无分包 packList.list.Add(gameRes.Key); packList.fileSize += gameRes.Value.size; } } //m_packSizeRecord[gameRes.Value.pack - 1] += gameRes.Value.size; UpdateProcess(count++, m_gameResDict.Count); } //m_sliderUpdate.maxValue = m_packWairDownList[0].list.Count; //m_sliderUpdate.minValue = 0; //m_sliderUpdate.value = 0; m_Proc.Set(0); //写入ver.txt文件(md5) 这样可以防止以后服务器没更新的时候还下载检查更新 if (m_packWairDownList[0].fileSize <= 0) { WriteVerFile(); 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); } XDownloadHandler dh = new XDownloadHandler(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; if (m_textUpdate) { int percent = 100 * m_downDownCount / count; m_textUpdate.text = $"{percent}%";//namePath; } 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); } } //m_sliderUpdate.value = m_packWairDownList[0].list.Count; m_packWairDownList[0].list.Clear(); m_packWairDownList[0].fileSize = 0; m_packWairDownList[0].isChange = false; //using (StreamWriter sw = new StreamWriter(string.Format("{0}ver.txt", UpdatePath))) //{ // sw.WriteLine(m_packWairDownList[0].md5); //} WriteVerFile(); //if (pack <= 1) { fs?.Close(); yield return DownloadFile(downloadServer, ServerListName + ".txt", result => { if (result > 0) { if (File.Exists(tempListFile)) { File.Delete(tempListFile); } } else { Debug.LogErrorFormat("{0} is not exists", downloadServer + ServerListName + ".txt"); } }); m_wairDownCount = 0; m_downDownCount = 0; //m_Proc.Set(100); //m_downEndFun(isDllUpdate ? 1 : 0); //yield break; } } Debug.Log("Download Finish!"); if(onDownloadComplete != null) onDownloadComplete(); } //协程下载文件 IEnumerator DownloadFile(string downloadServer, string fileName, System.Action onFinish) { UnityWebRequest uwr; //Logger.LogFormat("download ServerList {0}", downloadServer + fileName); 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); File.WriteAllBytes(outfile, uwr.downloadHandler.data); onFinish(uwr.downloadHandler.data.Length); } else { //Logger.LogFormat("Download Failed {0}", downloadServer + fileName); onFinish(0); //if (RequestErrorCallback != null) { RequestErrorCallback("DownloadFile", uwr.error); } } } IEnumerator CheckPackage(string downloadServer, string fileName, string verName, System.Action onFinish) { yield return DownloadFile(downloadServer, fileName, result => { bool ischange = true; string vermd51 = null, newmd51 = null; if (result > 0 && File.Exists(UpdatePath + fileName)) { using (StreamReader sr = new StreamReader(UpdatePath + fileName)) { string line; line = sr.ReadLine(); if (line != null || line != "") { newmd51 = line; } } if (File.Exists(UpdatePath + verName)) { 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, newmd51); }); } //加载包内文件 IEnumerator LoadPackageResourceMap() { string content; //在协程里,加载完才往下走 yield return XResLoader.OnLoadPackageFile($"{ServerListName}.txt", (data) => { string str; if (data != null) { str = System.Text.Encoding.UTF8.GetString(data); TextAsset textAsset = new TextAsset(str);//Resources.Load($"{ServerListName}.txt"); 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)) { content = File.ReadAllText(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(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]); resMap[strs[0]] = resInfo; } else { Debug.LogWarningFormat("invalid line: ", line); } } } void LoadServerResourceMap() { string listPath = $"{UpdatePath}{ServerListName}.txt"; if (!File.Exists(listPath)) {//本地还没有下载过资源清单(首次启动 / 未触发增量更新),无缓存可读,安全跳过 Debug.LogWarning($"LoadServerResourceMap skip, not found: {listPath}"); return; } string content;//= ResourceLoader.instance.LoadAssetString(ServerListName + ".txt");//Android上读的是内部的文件 byte[] data; long size; using (var stream = File.OpenRead(listPath))// _{ m_sNewMD5} { 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!"); } } private void Update() { //m_Proc.Update(); } public XProgress GetProgress() { return m_Proc; } } }