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);
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);