581 lines
22 KiB
C#
581 lines
22 KiB
C#
using asap.core;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using Unity.SharpZipLib.Zip;
|
|
using System.Net;
|
|
using UnityEngine.Networking;
|
|
using System.Threading;
|
|
|
|
namespace cfg
|
|
{
|
|
public sealed partial class Tables
|
|
{
|
|
#if UNITY_EDITOR
|
|
public static async System.Threading.Tasks.Task<Tables> InitEditor(Action<float> progress)
|
|
{
|
|
var cwd = Directory.GetParent(Directory.GetCurrentDirectory());
|
|
var dir = Path.Combine(cwd.FullName, "cfgData");
|
|
|
|
var tables = new cfg.Tables(
|
|
tableName => SimpleJSON.JSON.Parse(File.ReadAllText(Path.Combine(dir, $"{tableName}.json")))
|
|
);
|
|
await Task.Yield();
|
|
progress.Invoke(1f);
|
|
await Task.Yield();
|
|
return tables;
|
|
}
|
|
#endif
|
|
|
|
public static async System.Threading.Tasks.Task<Tables> Init(Action<float> progress)
|
|
{
|
|
|
|
var dir = Path.Combine(Application.persistentDataPath, "cfgData");
|
|
var verFilePath = Path.Combine(dir, "conf_ver.txt");
|
|
var ver = File.ReadAllText(verFilePath).Trim();
|
|
Debug.Log($"cfg init ver {ver}");
|
|
var zipFilePath = Path.Combine(dir, $"{ver}.zip");
|
|
#if UNITY_EDITOR
|
|
UnityEngine.Assertions.Assert.IsTrue(File.Exists(zipFilePath), $"File {zipFilePath} not found");
|
|
#endif
|
|
progress.Invoke(0f);
|
|
|
|
var p = (VersionTool.PackVer.Replace("_", "") + ver).Substring(0, 8);
|
|
var map = await Task.Run<Dictionary<string, string>>(() => GetEntryContent(zipFilePath, p));
|
|
|
|
|
|
progress.Invoke(0.5f);
|
|
await Task.Yield();
|
|
|
|
var tables = new cfg.Tables(
|
|
tableName => SimpleJSON.JSON.Parse(map[tableName])
|
|
);
|
|
|
|
progress.Invoke(1f);
|
|
|
|
return tables;
|
|
}
|
|
|
|
private static Dictionary<string, string> GetEntryContent(string zipPath, string p)
|
|
{
|
|
var map = new Dictionary<string, string>();
|
|
var memoryStream = new MemoryStream();
|
|
using (var zip = new ZipFile(zipPath))
|
|
{
|
|
zip.Password = p;
|
|
foreach (ZipEntry entry in zip)
|
|
{
|
|
if (entry.Name.EndsWith(".json"))
|
|
{
|
|
using (var stream = zip.GetInputStream(entry))
|
|
{
|
|
memoryStream.SetLength(0);
|
|
memoryStream.Position = 0;
|
|
stream.CopyTo(memoryStream);
|
|
var content = Encoding.UTF8.GetString(memoryStream.ToArray());
|
|
map.Add(Path.GetFileNameWithoutExtension(entry.Name), content);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
memoryStream.Close();
|
|
memoryStream.Dispose();
|
|
return map;
|
|
}
|
|
|
|
//public static async System.Threading.Tasks.Task<Tables> Init(Action<float> progress)
|
|
//{
|
|
//var rawDataMap = new Dictionary<string, string>();
|
|
//var opMap = new Dictionary<string, AsyncOperationHandle<TextAsset>>();
|
|
|
|
//var props = typeof(Tables).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
|
//var addrs = props.Select(_ => _.Name.ToLower()).ToList();
|
|
|
|
//foreach (var addr in addrs)
|
|
//{
|
|
//var op = Addressables.LoadAssetAsync<TextAsset>(addr);
|
|
//opMap.Add(addr, op);
|
|
//}
|
|
|
|
//var tasks = opMap.Values.Select(_ => _.Task);
|
|
//var total = (float)tasks.Count();
|
|
//while (true)
|
|
//{
|
|
//var completed = tasks.Count(_ => _.IsCompleted);
|
|
//if (completed == total)
|
|
//{
|
|
//progress.Invoke(1f);
|
|
//break;
|
|
//}
|
|
//else
|
|
//{
|
|
//progress.Invoke(completed / total);
|
|
//}
|
|
//await Awaiters.NextFrame;
|
|
//}
|
|
|
|
//var tables = new cfg.Tables(
|
|
//tableName => SimpleJSON.JSON.Parse(opMap[tableName].Result.text)
|
|
//);
|
|
|
|
//foreach (var kv in opMap)
|
|
//{
|
|
//Addressables.Release(kv.Value);
|
|
//}
|
|
|
|
//progress.Invoke(1f);
|
|
|
|
//return tables;
|
|
//}
|
|
//
|
|
|
|
private static void EnsureDefaultCfgFileExists(out string dir, out string persistCfgFile)
|
|
{
|
|
//Copy build-in cfg to persistentDataPath is not exists
|
|
dir = Path.Combine(Application.persistentDataPath, "cfgData");
|
|
persistCfgFile = Path.Combine(dir, "conf_ver.txt");
|
|
|
|
if (!Directory.Exists(dir))
|
|
{
|
|
Debug.Log($"create dir {dir}");
|
|
Directory.CreateDirectory(dir);
|
|
var files = BetterStreamingAssets.GetFiles("cfgData");
|
|
foreach (var file in files)
|
|
{
|
|
var savePath = Path.Combine(dir, Path.GetFileName(file));
|
|
if (File.Exists(savePath)) File.Delete(savePath);
|
|
Debug.Log($"copy {file} to {savePath}");
|
|
File.WriteAllBytes(savePath, BetterStreamingAssets.ReadAllBytes(file));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static readonly List<(float vtimeout, float ztimeout)> timeoutValues = new() { (1, 3), (2, 5), (2, 5) };
|
|
public static async Task<bool> UpdateCfg(IConfig config, bool isSuccessLoadRemoteConfigFile)
|
|
{
|
|
EnsureDefaultCfgFileExists(out var dir, out var persistCfgFile);
|
|
|
|
if (!isSuccessLoadRemoteConfigFile)
|
|
return true;
|
|
|
|
var maxRetryTimes = timeoutValues.Count;
|
|
|
|
for (int retryTime = 0; retryTime < maxRetryTimes; retryTime++)
|
|
{
|
|
(var vtimeout, var ztimeout) = timeoutValues[retryTime];
|
|
|
|
if (await DoUpdateCfgUWR(config, dir, persistCfgFile, (int)MathF.Ceiling(vtimeout), (int)MathF.Ceiling(ztimeout), retryTime, maxRetryTimes))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static async Task<bool> DoUpdateCfgUWR(
|
|
IConfig config,
|
|
string dir, string persistCfgFile,
|
|
int verFileTimeoutInSec, int cfgDataTimeoutInSec,
|
|
int retryTimes, int maxRetryTimes)
|
|
{
|
|
var stopwatch = new System.Diagnostics.Stopwatch();
|
|
string localVer = File.ReadAllText(persistCfgFile).Trim();
|
|
string remoteVer = localVer;
|
|
var staticResUrl = config.Get<string>(GConstant.K_Static_Res_URL, GConstant.V_Static_Res_URL);
|
|
var remoteVerUrl = $"{staticResUrl}/cfgData/conf_ver.txt";
|
|
using (var verReq = UnityWebRequest.Get(remoteVerUrl))
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
cts.CancelAfter(TimeSpan.FromSeconds(verFileTimeoutInSec));
|
|
var ct = cts.Token;
|
|
verReq.timeout = verFileTimeoutInSec;
|
|
verReq.SetRequestHeader("Accept", "application/json");
|
|
stopwatch.Start();
|
|
var op = verReq.SendWebRequest();
|
|
try
|
|
{
|
|
while (!op.isDone && !ct.IsCancellationRequested)
|
|
{
|
|
await Awaiters.NextFrame;
|
|
}
|
|
ct.ThrowIfCancellationRequested();
|
|
stopwatch.Stop();
|
|
if (verReq.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.LogWarning($"Load cfg Ver file failed {verReq.result.ToString()} {verReq.error} {retryTimes + 1}/{maxRetryTimes}");
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfgver");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", verReq.result.ToString());
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", verReq.error);
|
|
}
|
|
return false;
|
|
}
|
|
remoteVer = verReq.downloadHandler.text.Trim();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
stopwatch.Stop();
|
|
verReq.Abort();
|
|
Debug.LogWarning($"Load cfg Ver file cancel {verFileTimeoutInSec}s {retryTimes + 1}/{maxRetryTimes}");
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfgver");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", "cancel");
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", verReq.error ?? "cancel");
|
|
}
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
cts.Dispose();
|
|
}
|
|
}
|
|
|
|
if (remoteVer == localVer) return true;
|
|
|
|
var remoteCfgUrl = $"{staticResUrl}/cfgData/{remoteVer}.zip";
|
|
var savePath = Path.Combine(dir, $"{remoteVer}.zip");
|
|
using (var cfgReq = UnityWebRequest.Get(remoteCfgUrl))
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
cts.CancelAfter(TimeSpan.FromSeconds(cfgDataTimeoutInSec));
|
|
var ct = cts.Token;
|
|
cfgReq.timeout = cfgDataTimeoutInSec;
|
|
cfgReq.downloadHandler = new DownloadHandlerFile(savePath, false);
|
|
stopwatch.Reset();
|
|
stopwatch.Restart();
|
|
var op = cfgReq.SendWebRequest();
|
|
try
|
|
{
|
|
while (!op.isDone && !ct.IsCancellationRequested)
|
|
{
|
|
await Awaiters.NextFrame;
|
|
}
|
|
ct.ThrowIfCancellationRequested();
|
|
stopwatch.Stop();
|
|
if (cfgReq.result != UnityWebRequest.Result.Success)
|
|
{
|
|
if (File.Exists(savePath)) File.Delete(savePath);
|
|
|
|
Debug.LogWarning($"Load cfg file failed {cfgReq.result.ToString()} {cfgReq.error} {retryTimes + 1}/{maxRetryTimes}");
|
|
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", cfgReq.result.ToString());
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", cfgReq.error);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (File.Exists(persistCfgFile)) File.Delete(persistCfgFile);
|
|
File.WriteAllText(persistCfgFile, remoteVer);
|
|
Debug.Log($"cfg is updated from {localVer} to {remoteVer}");
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
cfgReq.Abort();
|
|
stopwatch.Stop();
|
|
Debug.LogWarning($"Load cfg file cancel {cfgDataTimeoutInSec}s {retryTimes + 1}/{maxRetryTimes}");
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
e.AddContent("reason", "cancel");
|
|
e.AddContent("skip", retryTimes >= maxRetryTimes - 1);
|
|
e.AddContent("error", cfgReq.error ?? "cancel");
|
|
}
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
cts.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
private static async Task<bool> DoUpdateCfg(
|
|
IConfig config,
|
|
string dir, string persistCfgFile,
|
|
int verFileTimeoutInSec, int cfgDataTimeoutInSec,
|
|
int retryTimes)
|
|
{
|
|
|
|
var stopwatch = new System.Diagnostics.Stopwatch();
|
|
var content = string.Empty;
|
|
|
|
try
|
|
{
|
|
string localVer = File.ReadAllText(persistCfgFile).Trim();
|
|
var staticResUrl = config.Get<string>(GConstant.K_Static_Res_URL, GConstant.V_Static_Res_URL);
|
|
if (staticResUrl.Equals(GConstant.V_Static_Res_URL))
|
|
{
|
|
staticResUrl = string.Format(GConstant.V_Static_Res_URL, VersionTool.PackVer);
|
|
}
|
|
var remoteVerUrl = $"{staticResUrl}/cfgData/conf_ver.txt";
|
|
content = "conf_ver";
|
|
|
|
var handler = new System.Net.Http.HttpClientHandler()
|
|
{
|
|
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls11 | System.Security.Authentication.SslProtocols.Tls,
|
|
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
|
|
};
|
|
|
|
using (var httpClient = new HttpClient(handler))
|
|
{
|
|
httpClient.Timeout = TimeSpan.FromSeconds(verFileTimeoutInSec);
|
|
stopwatch.Start();
|
|
var verFileRespon = await httpClient.GetAsync(remoteVerUrl);
|
|
stopwatch.Stop();
|
|
|
|
if (verFileRespon.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
Debug.LogWarning($"Load cfg Ver file failed {verFileRespon.StatusCode.ToString()}");
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", verFileRespon.StatusCode.ToString());
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
}
|
|
return false;
|
|
}
|
|
var remoteVer = (await verFileRespon.Content.ReadAsStringAsync()).Trim();
|
|
|
|
if (remoteVer != localVer)
|
|
{
|
|
var remoteCfgUrl = $"{staticResUrl}/cfgData/{remoteVer}.zip";
|
|
content = "cfgzip";
|
|
httpClient.Timeout = TimeSpan.FromSeconds(cfgDataTimeoutInSec);
|
|
stopwatch.Reset();
|
|
stopwatch.Start();
|
|
var remoteCfgRespon = await httpClient.GetAsync(remoteCfgUrl);
|
|
stopwatch.Stop();
|
|
|
|
if (remoteCfgRespon.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
Debug.LogWarning($"Load cfg zip file failed {remoteCfgRespon.StatusCode.ToString()}");
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", remoteCfgRespon.StatusCode.ToString());
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
e.AddContent("content", content);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
var savePath = Path.Combine(dir, $"{remoteVer}.zip");
|
|
var remoteCfg = await remoteCfgRespon.Content.ReadAsByteArrayAsync();
|
|
|
|
if (File.Exists(savePath)) File.Delete(savePath);
|
|
await File.WriteAllBytesAsync(savePath, remoteCfg);
|
|
|
|
if (File.Exists(persistCfgFile)) File.Delete(persistCfgFile);
|
|
File.WriteAllText(persistCfgFile, remoteVer);
|
|
Debug.Log($"cfg is updated from {localVer} to {remoteVer}");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (System.Net.Http.HttpRequestException httpEx)
|
|
{
|
|
if (stopwatch.IsRunning)
|
|
stopwatch.Stop();
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
|
|
var errorMessage = $"HTTP request failed: {httpEx.Message}";
|
|
if (httpEx.InnerException != null)
|
|
{
|
|
errorMessage += $" Inner: {httpEx.InnerException.Message}";
|
|
if (httpEx.InnerException.InnerException != null)
|
|
{
|
|
errorMessage += $" InnerInner: {httpEx.InnerException.InnerException.Message}";
|
|
}
|
|
}
|
|
Debug.LogWarning(errorMessage);
|
|
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", "httpEx");
|
|
e.AddContent("error", httpEx.Message);
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
e.AddContent("content", content);
|
|
}
|
|
return false;
|
|
}
|
|
catch (System.Threading.Tasks.TaskCanceledException timeoutEx)
|
|
{
|
|
if (stopwatch.IsRunning)
|
|
stopwatch.Stop();
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
|
|
Debug.LogWarning($"Request timeout after {verFileTimeoutInSec}s: {timeoutEx.Message}");
|
|
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", "timeout");
|
|
e.AddContent("error", timeoutEx.Message);
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
e.AddContent("content", content);
|
|
}
|
|
return false;
|
|
}
|
|
catch (System.IO.IOException ioEx)
|
|
{
|
|
if (stopwatch.IsRunning)
|
|
stopwatch.Stop();
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
|
|
Debug.LogWarning($"IO operation failed: {ioEx.Message}");
|
|
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", "io_exception");
|
|
e.AddContent("error", ioEx.Message);
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
e.AddContent("content", content);
|
|
}
|
|
return false;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if (stopwatch.IsRunning) stopwatch.Stop();
|
|
var elapsed = stopwatch.ElapsedMilliseconds;
|
|
|
|
var errorMessage = $"Unexpected error loading cfg: {exception.Message}";
|
|
if (exception.InnerException != null)
|
|
{
|
|
errorMessage += $" Inner: {exception.InnerException.Message}";
|
|
}
|
|
Debug.LogError(errorMessage);
|
|
|
|
using (var e = GEvent.TackEvent("retry"))
|
|
{
|
|
e.AddContent("type", "cfg");
|
|
e.AddContent("duration_ms", elapsed);
|
|
e.AddContent("reason", "unexpected_error");
|
|
e.AddContent("error", exception.Message);
|
|
e.AddContent("skip", retryTimes >= 2);
|
|
e.AddContent("content", content);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
**/
|
|
|
|
//private async Task ExtractCfg(string ver)
|
|
//{
|
|
//var zipFilePath = Path.Combine(Application.persistentDataPath, $"{ver}.zip");
|
|
//var saveDir = Path.Combine(Application.persistentDataPath, "cfgData");
|
|
//if (Directory.Exists(saveDir))
|
|
//{
|
|
//Directory.Delete(saveDir, true);
|
|
//}
|
|
//await Task.Run(() => ZipFile.ExtractToDirectory(zipFilePath, Application.persistentDataPath));
|
|
//File.Delete(zipFilePath);
|
|
//}
|
|
}
|
|
|
|
public static class TablesExtensions
|
|
{
|
|
public static cfg.MapData GetMapData(this Tables tables, int id)
|
|
{
|
|
return tables.TbMapData[id];
|
|
}
|
|
|
|
public static cfg.FishData GetFishData(this Tables tables, int id)
|
|
{
|
|
var fishData = tables.TbFishData;
|
|
|
|
if (!fishData.DataMap.ContainsKey(id))
|
|
{
|
|
return fishData.DataList[0];
|
|
}
|
|
|
|
return fishData[id];
|
|
}
|
|
|
|
public static cfg.RodData GetRodData(this Tables tables, int id)
|
|
{
|
|
var fishRodData = tables.TbRodData;
|
|
if (!fishRodData.DataMap.ContainsKey(id))
|
|
{
|
|
return fishRodData.DataList[0];
|
|
}
|
|
|
|
return fishRodData[id];
|
|
}
|
|
|
|
public static cfg.AlbumExchange GetAlbumExchange(this Tables tables, int id)
|
|
{
|
|
return tables.TbAlbumExchange.GetOrDefault(id);
|
|
}
|
|
|
|
public static cfg.Item GetItemData(this Tables tables, int id)
|
|
{
|
|
return tables.TbItem[id];
|
|
}
|
|
|
|
public static string GetItemIconName(this Tables tables, int id)
|
|
{
|
|
var item = tables.TbItem;
|
|
if (!item.DataMap.ContainsKey(id))
|
|
{
|
|
return "";
|
|
}
|
|
|
|
return item[id].Icon;
|
|
}
|
|
|
|
}
|
|
|
|
public sealed partial class Text
|
|
{
|
|
public string GetText(string code)
|
|
{
|
|
string text = json["text_" + code];
|
|
return string.IsNullOrEmpty(text) ? TextEn : text;
|
|
}
|
|
}
|
|
public sealed partial class ShopPackManager
|
|
{
|
|
public int MaxPurchaseNew
|
|
{
|
|
get
|
|
{
|
|
if (MaxPurchase == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
return MaxPurchase;
|
|
}
|
|
}
|
|
}
|
|
}
|