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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Compression;
using System.Numerics;
using System.Security.Cryptography;
using System.Text.Json;

namespace XFEEDToolBox.Core.Rendering;

/// <summary>The authored RouteInstances and RouteHotspotInstances textures and named FGM values.</summary>
public sealed class GalaxyOriginalTradeAssets
{
    public static GalaxyOriginalTradeAssets Reference { get; } = Read();
    public GalaxyOriginalTradeTexture Route { get; }
    public GalaxyOriginalTradeTexture Trail { get; }
    public GalaxyOriginalTradeTexture Hotspot { get; }
    public GalaxyOriginalTradeMaterial RouteMaterial { get; }
    public GalaxyOriginalTradeMaterial HotspotMaterial { get; }

    private GalaxyOriginalTradeAssets(JsonElement metadata)
    {
        if (metadata.GetProperty("instanceBytes").GetInt32() != 44 ||
            metadata.GetProperty("fullOriginalTradeRestored").GetBoolean())
            throw new InvalidDataException("原贸易流资源版本无效。");
        var textures = metadata.GetProperty("textures").EnumerateArray().ToArray();
        if (textures.Length != 3) throw new InvalidDataException("原贸易流纹理数量无效。");
        Route = Texture(textures.Single(x => x.GetProperty("key").GetString() == "route"), 512, 512, 1024, 131072,
            "418f4065055df6e1aa32b545b48cd8d3ccf0ab584547486149f6f7157563c78e");
        Trail = Texture(textures.Single(x => x.GetProperty("key").GetString() == "trail"), 64, 4, 256, 512,
            "95fc33b4232c4eb41708974765f03a7160633a83746457efb4ed7e3f35a85816");
        Hotspot = Texture(textures.Single(x => x.GetProperty("key").GetString() == "hotspot"), 512, 512, 1024, 131072,
            "a1832a90773542d6fd6d200830e13638d3dea9f88fc4135328fa539590c1ed6f");
        var materials = metadata.GetProperty("materials").EnumerateArray().ToArray();
        RouteMaterial = Material(materials.Single(x => x.GetProperty("name").GetString() == "traderoute"), false);
        HotspotMaterial = Material(materials.Single(x => x.GetProperty("name").GetString() == "traderoutehotspot"), true);
    }

    private static GalaxyOriginalTradeMaterial Material(JsonElement value, bool hotspot)
    {
        if (value.GetProperty("shaderClass").GetString() != (hotspot ? "RouteHotspotInstances" : "RouteInstances"))
            throw new InvalidDataException("原贸易流材质无效。");
        var parameters = value.GetProperty("parameters").EnumerateArray()
            .ToDictionary(x => x.GetProperty("name").GetString()!, StringComparer.Ordinal);
        float Scalar(string name) => parameters[name].GetProperty("floatValues")[0].GetSingle();
        var scales = parameters["gStandardMapScales"].GetProperty("floatValues");
        var material = new GalaxyOriginalTradeMaterial(new(scales[0].GetSingle(), scales[1].GetSingle(), scales[2].GetSingle()),
            Scalar("pRouteCurvature"), Scalar("pRouteEaseInOut"), Scalar("pRouteHotspotTileLength"),
            Scalar("pRouteHotspotTimeScale"), Scalar("pRoutePinch"), hotspot ? null : Scalar("pRouteTileLength"),
            Scalar("pRouteWidthScale"));
        if (material.StandardMapScales != new Vector3(hotspot ? 2 : 5, 1, 1) || material.Curvature != .25f ||
            material.EaseInOut != .75f || material.HotspotTileLength != 3 || material.HotspotTimeScale != 2 ||
            material.Pinch != 1 || material.TileLength != (hotspot ? null : 4f) || material.WidthScale != (hotspot ? .65f : 1))
            throw new InvalidDataException("原贸易流材质参数校验失败。");
        return material;
    }

    private static GalaxyOriginalTradeTexture Texture(JsonElement item, int width, int height, int pitch, int size, string sha)
    {
        var source = item.GetProperty("source");
        var layout = source.GetProperty("mipLayout");
        if (item.GetProperty("nativeFormat").GetInt32() != 72 || item.GetProperty("format").GetString() != "BC1_UNorm_SRgb" ||
            source.GetProperty("sourceFormat").GetInt32() != 96 || source.GetProperty("width").GetInt32() != width ||
            source.GetProperty("height").GetInt32() != height || source.GetProperty("depth").GetInt32() != 1 ||
            source.GetProperty("array").GetInt32() != 1 || source.GetProperty("mips").GetInt32() != 1 || layout.GetArrayLength() != 1 ||
            layout[0].GetProperty("offset").GetInt32() != 0 || layout[0].GetProperty("rowPitch").GetInt32() != pitch ||
            layout[0].GetProperty("sliceBytes").GetInt32() != size || item.GetProperty("payloadBytes").GetInt32() != size ||
            item.GetProperty("payloadSha256").GetString() != sha)
            throw new InvalidDataException("原贸易流纹理布局无效。");
        var resource = item.GetProperty("resource").GetString()!;
        using var stream = typeof(GalaxyOriginalTradeAssets).Assembly.GetManifestResourceStream("XFEEDToolBox.Core.Rendering." + resource)
            ?? throw new InvalidDataException("原贸易流纹理缺失。");
        using var gzip = new GZipStream(stream, CompressionMode.Decompress);
        var payload = new byte[size]; gzip.ReadExactly(payload);
        if (gzip.ReadByte() != -1 || Convert.ToHexStringLower(SHA256.HashData(payload)) != sha)
            throw new InvalidDataException("原贸易流纹理校验失败。");
        // Retain authored padding. In particular the 64x4 trail has a 256-byte row pitch, not its 128-byte tight pitch.
        return new(width, height, pitch, payload);
    }

    private static GalaxyOriginalTradeAssets Read()
    {
        using var source = typeof(GalaxyOriginalTradeAssets).Assembly.GetManifestResourceStream("XFEEDToolBox.Core.Rendering.original-current-trade.json")!;
        using var json = JsonDocument.Parse(source);
        return new(json.RootElement);
    }
}

public sealed record GalaxyOriginalTradeTexture(int Width, int Height, int RowPitch, ReadOnlyMemory<byte> Payload);
public sealed record GalaxyOriginalTradeMaterial(Vector3 StandardMapScales, float Curvature, float EaseInOut,
    float HotspotTileLength, float HotspotTimeScale, float Pinch, float? TileLength, float WidthScale);