Don't parse UTF

This commit is contained in:
Gustas
2026-01-15 16:56:03 +02:00
committed by Gustas Kažukauskas
parent 9528f19845
commit bea698b2f1

View File

@@ -10,6 +10,8 @@
#endregion #endregion
using System; using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@@ -23,6 +25,13 @@ namespace OpenRA.FileFormats
{ {
public class Png public class Png
{ {
public const uint ChunkIHDR = 0x49484452;
public const uint ChunkPLTE = 0x504C5445;
public const uint ChunkIDAT = 0x49444154;
public const uint ChunkIEND = 0x49454E44;
public const uint ChunkTRNS = 0x74524E53;
public const uint ChunkTEXT = 0x74455874;
static readonly byte[] Signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; static readonly byte[] Signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
public int Width { get; } public int Width { get; }
@@ -44,22 +53,27 @@ namespace OpenRA.FileFormats
var data = new List<byte>(); var data = new List<byte>();
Type = SpriteFrameType.Rgba32; Type = SpriteFrameType.Rgba32;
// Use a reusable 8-byte buffer for Length + Type.
Span<byte> chunkHeader = stackalloc byte[8];
byte bitDepth = 8; byte bitDepth = 8;
while (true) while (true)
{ {
var length = IPAddress.NetworkToHostOrder(s.ReadInt32()); s.ReadBytes(chunkHeader);
var type = s.ReadASCII(4); var length = BinaryPrimitives.ReadInt32BigEndian(chunkHeader[..4]);
var type = BinaryPrimitives.ReadUInt32BigEndian(chunkHeader[4..]);
var content = s.ReadBytes(length); var content = s.ReadBytes(length);
s.ReadInt32(); // crc s.ReadInt32(); // crc
if (!headerParsed && type != "IHDR") if (!headerParsed && type != ChunkIHDR)
throw new InvalidDataException("Invalid PNG file - header does not appear first."); throw new InvalidDataException("Invalid PNG file - header does not appear first.");
using (var ms = new MemoryStream(content)) using (var ms = new MemoryStream(content))
{ {
switch (type) switch (type)
{ {
case "IHDR": case ChunkIHDR:
{ {
if (headerParsed) if (headerParsed)
throw new InvalidDataException("Invalid PNG file - duplicate header."); throw new InvalidDataException("Invalid PNG file - duplicate header.");
@@ -90,7 +104,7 @@ namespace OpenRA.FileFormats
break; break;
} }
case "PLTE": case ChunkPLTE:
{ {
Palette = new Color[length / 3]; Palette = new Color[length / 3];
for (var i = 0; i < Palette.Length; i++) for (var i = 0; i < Palette.Length; i++)
@@ -102,7 +116,7 @@ namespace OpenRA.FileFormats
break; break;
} }
case "tRNS": case ChunkTRNS:
{ {
if (Palette == null) if (Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported."); throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
@@ -113,14 +127,14 @@ namespace OpenRA.FileFormats
break; break;
} }
case "IDAT": case ChunkIDAT:
{ {
data.AddRange(content); data.AddRange(content);
break; break;
} }
case "tEXt": case ChunkTEXT:
{ {
var key = ms.ReadASCIIZ(); var key = ms.ReadASCIIZ();
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1)); EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
@@ -128,7 +142,7 @@ namespace OpenRA.FileFormats
break; break;
} }
case "IEND": case ChunkIEND:
{ {
using (var ns = new MemoryStream(data.ToArray())) using (var ns = new MemoryStream(data.ToArray()))
{ {
@@ -314,19 +328,22 @@ namespace OpenRA.FileFormats
throw new InvalidDataException("Unknown pixel format"); throw new InvalidDataException("Unknown pixel format");
} }
static void WritePngChunk(MemoryStream output, string type, MemoryStream input) static void WritePngChunk(MemoryStream output, uint type, MemoryStream input)
{ {
input.Position = 0; Span<byte> header = stackalloc byte[8];
BinaryPrimitives.WriteInt32BigEndian(header[..4], (int)input.Length);
BinaryPrimitives.WriteUInt32BigEndian(header[4..], type);
var typeBytes = Encoding.ASCII.GetBytes(type); if (!input.TryGetBuffer(out var dataSegment))
output.Write(IPAddress.HostToNetworkOrder((int)input.Length)); dataSegment = new ArraySegment<byte>(input.ToArray());
output.Write(typeBytes);
var data = input.ReadAllBytes(); ReadOnlySpan<byte> data = dataSegment.AsSpan(0, (int)input.Length);
output.Write(header);
output.Write(data); output.Write(data);
var crc = 0xFFFFFFFF; var crc = 0xFFFFFFFF;
crc = CRC32.Update(crc, typeBytes); crc = CRC32.Update(crc, header[4..]);
crc = CRC32.Update(crc, data); crc = CRC32.Update(crc, data);
var finalCrc = CRC32.Finish(crc); var finalCrc = CRC32.Finish(crc);
@@ -352,7 +369,7 @@ namespace OpenRA.FileFormats
header.WriteByte(0); // Filter header.WriteByte(0); // Filter
header.WriteByte(0); // Interlacing header.WriteByte(0); // Interlacing
WritePngChunk(output, "IHDR", header); WritePngChunk(output, ChunkIHDR, header);
} }
var alphaPalette = false; var alphaPalette = false;
@@ -368,7 +385,7 @@ namespace OpenRA.FileFormats
alphaPalette |= c.A > 0; alphaPalette |= c.A > 0;
} }
WritePngChunk(output, "PLTE", palette); WritePngChunk(output, ChunkPLTE, palette);
} }
} }
@@ -379,7 +396,7 @@ namespace OpenRA.FileFormats
foreach (var c in Palette) foreach (var c in Palette)
alpha.WriteByte(c.A); alpha.WriteByte(c.A);
WritePngChunk(output, "tRNS", alpha); WritePngChunk(output, ChunkTRNS, alpha);
} }
} }
@@ -397,7 +414,7 @@ namespace OpenRA.FileFormats
} }
} }
WritePngChunk(output, "IDAT", data); WritePngChunk(output, ChunkIDAT, data);
} }
foreach (var kv in EmbeddedData) foreach (var kv in EmbeddedData)
@@ -405,11 +422,11 @@ namespace OpenRA.FileFormats
using (var text = new MemoryStream()) using (var text = new MemoryStream())
{ {
text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value)); text.Write(Encoding.ASCII.GetBytes(kv.Key + (char)0 + kv.Value));
WritePngChunk(output, "tEXt", text); WritePngChunk(output, ChunkTEXT, text);
} }
} }
WritePngChunk(output, "IEND", new MemoryStream()); WritePngChunk(output, ChunkIEND, new MemoryStream());
return output.ToArray(); return output.ToArray();
} }
} }