Files
AIC-Project/Client/Assets/Script/xmain/Update/Update.cs
T
2026-07-10 10:24:29 +08:00

638 lines
24 KiB
C#

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<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";
#else
static string sPlatform = "pc";
#endif
GameObject m_MainObj;
//更新相关
string UpdateServer;//
string UpdatePath;
string ServerListName = "files";
string UpdateListName = "updatefiles";
string TempListName = "tempfiles";
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[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<Slider>();
}
m_textUpdate = GameObject.Find("Main(Clone)/Update/Text").transform.GetComponent<TextMeshProUGUI>();
//*/
}
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<string, ResInfo> 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<string, string> dicFilelist = new Dictionary<string, string>();
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<int> 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<bool, string> 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<TextAsset>($"{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<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(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;
}
}
}