using System.Globalization; using System.Text.Json; namespace XFEEDToolBox.Core.Galaxy; public sealed record OriginalTradeRoute(long FromSystemAddress,long ToSystemAddress,string Commodity,double NpcAmount,double PlayerAmount); /// Original account trade routes at their recorded timestamp. Missing files or routes never imply no data is available. public sealed record OriginalTradeDataSnapshot(ulong CommanderId,long SystemAddress,DateTimeOffset Timestamp,IReadOnlyList Routes,string SourcePath) { public bool HasRecordedTradeData=>true; } public static class OriginalTradeDataCache { public const int MaximumBytes=8*1024*1024,MaximumRoutes=100_000; public static OriginalTradeDataSnapshot ReadForCommander(string gameCacheDirectory,ulong commanderId,long systemAddress) { if(commanderId==0||systemAddress<=0)throw new ArgumentException("原游戏贸易缓存账号或星系地址无效。"); return Read(Path.Combine(gameCacheDirectory,commanderId.ToString(CultureInfo.InvariantCulture),systemAddress.ToString(CultureInfo.InvariantCulture)+".trd"),commanderId,systemAddress); } public static OriginalTradeDataSnapshot Read(string path,ulong expectedCommanderId,long expectedSystemAddress) { using var file=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.ReadWrite|FileShare.Delete); if(file.Length>MaximumBytes)throw new InvalidDataException("原游戏贸易路线缓存超过长度限制。"); var bytes=new byte[checked((int)file.Length)];file.ReadExactly(bytes); return Parse(bytes,expectedCommanderId,expectedSystemAddress) with{SourcePath=Path.GetFullPath(path)}; } public static OriginalTradeDataSnapshot Parse(ReadOnlyMemory bytes,ulong expectedCommanderId,long expectedSystemAddress) { if(expectedCommanderId==0||expectedSystemAddress<=0||bytes.Length>MaximumBytes)throw new InvalidDataException("原游戏贸易缓存标识或长度无效。"); using var document=JsonDocument.Parse(bytes,new JsonDocumentOptions{MaxDepth=16});var root=document.RootElement; if(root.GetProperty("version").GetInt32()!=4||root.GetProperty("systemaddress").GetInt64()!=expectedSystemAddress)throw new InvalidDataException("原游戏贸易缓存版本或星系地址不符。"); var rows=root.GetProperty("traderoutes");if(rows.ValueKind!=JsonValueKind.Array||rows.GetArrayLength()>MaximumRoutes)throw new InvalidDataException("原游戏贸易路线记录无效。"); static double Amount(JsonElement row,string key) { if(!row.TryGetProperty(key,out var value))return 0; var valid=value.ValueKind==JsonValueKind.Number?value.TryGetDouble(out var number)?number:double.NaN: value.ValueKind==JsonValueKind.String&&double.TryParse(value.GetString(),NumberStyles.Float,CultureInfo.InvariantCulture,out number)?number:double.NaN; if(!double.IsFinite(valid)||valid<0)throw new InvalidDataException("原游戏贸易路线流量无效。");return valid; } var routes=new List(rows.GetArrayLength()); foreach(var row in rows.EnumerateArray()) { var from=row.GetProperty("FromStarID").GetInt64();var to=row.GetProperty("ToStarID").GetInt64();var commodity=row.GetProperty("name").GetString(); if(from<=0||to<=0||string.IsNullOrWhiteSpace(commodity)||commodity.Length>128||from!=expectedSystemAddress&&to!=expectedSystemAddress)throw new InvalidDataException("原游戏贸易路线标识无效。"); routes.Add(new(from,to,commodity,Amount(row,"npcamount"),Amount(row,"playeramount"))); } var timestamp=DateTimeOffset.FromUnixTimeSeconds(root.GetProperty("timestamp").GetInt64()); return new(expectedCommanderId,expectedSystemAddress,timestamp,routes.AsReadOnly(),""); } }