XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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);
/// <summary>Original account trade routes at their recorded timestamp. Missing files or routes never imply no data is available.</summary>
public sealed record OriginalTradeDataSnapshot(ulong CommanderId,long SystemAddress,DateTimeOffset Timestamp,IReadOnlyList<OriginalTradeRoute> 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<byte> 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<OriginalTradeRoute>(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(),"");
    }
}