PNG spritesheet support, along with PaletteFromPng.

Cursor palette loader can now be specified via yaml.
This commit is contained in:
Andre Mohren
2018-07-24 15:56:04 +02:00
committed by reaperrr
parent 48248266a8
commit 693b5a54af
9 changed files with 549 additions and 226 deletions

View File

@@ -1,206 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
namespace OpenRA.FileFormats
{
public static class PngLoader
{
public static Bitmap Load(string filename)
{
using (var s = File.OpenRead(filename))
return Load(s);
}
public static Bitmap Load(Stream s)
{
using (var br = new BinaryReader(s))
{
var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
foreach (var b in signature)
if (br.ReadByte() != b)
throw new InvalidDataException("PNG Signature is bogus");
Bitmap bitmap = null;
Color[] palette = null;
var data = new List<byte>();
try
{
for (;;)
{
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
var content = br.ReadBytes(length);
/*var crc = */br.ReadInt32();
if (bitmap == null && type != "IHDR")
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
using (var ms = new MemoryStream(content))
using (var cr = new BinaryReader(ms))
switch (type)
{
case "IHDR":
{
if (bitmap != null)
throw new InvalidDataException("Invalid PNG file - duplicate header.");
var width = IPAddress.NetworkToHostOrder(cr.ReadInt32());
var height = IPAddress.NetworkToHostOrder(cr.ReadInt32());
var bitDepth = cr.ReadByte();
var colorType = (PngColorType)cr.ReadByte();
var compression = cr.ReadByte();
/*var filter = */cr.ReadByte();
var interlace = cr.ReadByte();
if (compression != 0) throw new InvalidDataException("Compression method not supported");
if (interlace != 0) throw new InvalidDataException("Interlacing not supported");
bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType));
}
break;
case "PLTE":
{
palette = new Color[256];
for (var i = 0; i < length / 3; i++)
{
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
palette[i] = Color.FromArgb(r, g, b);
}
}
break;
case "tRNS":
{
if (palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
for (var i = 0; i < length; i++)
palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]);
}
break;
case "IDAT":
{
data.AddRange(content);
}
break;
case "IEND":
{
var bits = bitmap.LockBits(bitmap.Bounds(),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
using (var ns = new MemoryStream(data.ToArray()))
{
// 'zlib' flags bytes; confuses the DeflateStream.
/*var flags = (byte)*/ns.ReadByte();
/*var moreFlags = (byte)*/ns.ReadByte();
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
using (var dr = new BinaryReader(ds))
{
var prevLine = new byte[bitmap.Width]; // all zero
for (var y = 0; y < bitmap.Height; y++)
{
var filter = (PngFilter)dr.ReadByte();
var line = dr.ReadBytes(bitmap.Width);
for (var i = 0; i < bitmap.Width; i++)
line[i] = i > 0
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length);
prevLine = line;
}
}
}
bitmap.UnlockBits(bits);
if (palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
{
var cp = temp.Palette;
for (var i = 0; i < 256; i++)
cp.Entries[i] = palette[i]; // finalize the palette.
bitmap.Palette = cp;
return bitmap;
}
}
}
}
}
catch
{
if (bitmap != null)
bitmap.Dispose();
throw;
}
}
}
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
{
switch (f)
{
case PngFilter.None: return x;
case PngFilter.Sub: return (byte)(x + a);
case PngFilter.Up: return (byte)(x + b);
case PngFilter.Average: return (byte)(x + (a + b) / 2);
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
default:
throw new InvalidOperationException("Unsupported Filter");
}
}
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
[Flags]
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter { None, Sub, Up, Average, Paeth }
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
{
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
return PixelFormat.Format8bppIndexed;
throw new InvalidDataException("Unknown pixel format");
}
}
}

View File

@@ -273,7 +273,6 @@
<Compile Include="FieldSaver.cs" />
<Compile Include="Manifest.cs" />
<Compile Include="Graphics\Vertex.cs" />
<Compile Include="FileFormats\PngLoader.cs" />
<Compile Include="Primitives\ActionQueue.cs" />
<Compile Include="Primitives\BitSet.cs" />
<Compile Include="Primitives\Cache.cs" />

View File

@@ -0,0 +1,205 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Text;
namespace OpenRA.FileFormats
{
public class Png
{
public int Width { get; set; }
public int Height { get; set; }
public PixelFormat PixelFormat { get; set; }
public Color[] Palette { get; set; }
public byte[] Data { get; set; }
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
public Png(Stream s)
{
if (!Verify(s))
throw new InvalidDataException("PNG Signature is bogus");
s.Position += 8;
var headerParsed = false;
var data = new List<byte>();
for (;;)
{
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
var content = s.ReadBytes(length);
/*var crc = */s.ReadInt32();
if (!headerParsed && type != "IHDR")
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
using (var ms = new MemoryStream(content))
{
switch (type)
{
case "IHDR":
{
if (headerParsed)
throw new InvalidDataException("Invalid PNG file - duplicate header.");
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
Data = new byte[Width * Height];
var bitDepth = ms.ReadUInt8();
var colorType = (PngColorType)ms.ReadByte();
PixelFormat = MakePixelFormat(bitDepth, colorType);
var compression = ms.ReadByte();
/*var filter = */ms.ReadByte();
var interlace = ms.ReadByte();
if (compression != 0)
throw new InvalidDataException("Compression method not supported");
if (interlace != 0)
throw new InvalidDataException("Interlacing not supported");
headerParsed = true;
break;
}
case "PLTE":
{
Palette = new Color[256];
for (var i = 0; i < length / 3; i++)
{
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
Palette[i] = Color.FromArgb(r, g, b);
}
break;
}
case "tRNS":
{
if (Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
for (var i = 0; i < length; i++)
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
break;
}
case "IDAT":
{
data.AddRange(content);
break;
}
case "tEXt":
{
var key = ms.ReadASCIIZ();
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
break;
}
case "IEND":
{
using (var ns = new MemoryStream(data.ToArray()))
{
// 'zlib' flags bytes; confuses the DeflateStream.
/*var flags = (byte)*/ns.ReadByte();
/*var moreFlags = (byte)*/ns.ReadByte();
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
{
var prevLine = new byte[Width]; // all zero
for (var y = 0; y < Height; y++)
{
var filter = (PngFilter)ds.ReadByte();
var line = ds.ReadBytes(Width);
for (var i = 0; i < Width; i++)
line[i] = i > 0
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
Array.Copy(line, 0, Data, y * Width, line.Length);
prevLine = line;
}
}
}
if (Palette == null)
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
return;
}
}
}
}
}
public static bool Verify(Stream s)
{
var pos = s.Position;
var signature = new[] { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
var isPng = signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
s.Position = pos;
return isPng;
}
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
{
switch (f)
{
case PngFilter.None: return x;
case PngFilter.Sub: return (byte)(x + a);
case PngFilter.Up: return (byte)(x + b);
case PngFilter.Average: return (byte)(x + (a + b) / 2);
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
default:
throw new InvalidOperationException("Unsupported Filter");
}
}
static byte Paeth(byte a, byte b, byte c)
{
var p = a + b - c;
var pa = Math.Abs(p - a);
var pb = Math.Abs(p - b);
var pc = Math.Abs(p - c);
return (pa <= pb && pa <= pc) ? a :
(pb <= pc) ? b : c;
}
[Flags]
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
enum PngFilter { None, Sub, Up, Average, Paeth }
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
{
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
return PixelFormat.Format8bppIndexed;
throw new InvalidDataException("Unknown pixel format");
}
}
}

View File

@@ -146,6 +146,7 @@
<Compile Include="Effects\MapNotificationEffect.cs" />
<Compile Include="Effects\RallyPointIndicator.cs" />
<Compile Include="Effects\SpriteEffect.cs" />
<Compile Include="FileFormats\Png.cs" />
<Compile Include="Graphics\RailgunRenderable.cs" />
<Compile Include="Effects\LaunchEffect.cs" />
<Compile Include="Lint\CheckNotifications.cs" />
@@ -559,6 +560,7 @@
<Compile Include="Traits\World\MPStartUnits.cs" />
<Compile Include="Traits\World\PaletteFromFile.cs" />
<Compile Include="Traits\World\PaletteFromGimpOrJascFile.cs" />
<Compile Include="Traits\World\PaletteFromPng.cs" />
<Compile Include="Traits\World\PaletteFromRGBA.cs" />
<Compile Include="Traits\World\ValidateOrder.cs" />
<Compile Include="Pathfinder\CellInfoLayerPool.cs" />
@@ -596,6 +598,8 @@
<Compile Include="UtilityCommands\ExtractTraitDocsCommand.cs" />
<Compile Include="UtilityCommands\ExtractWeaponDocsCommand.cs" />
<Compile Include="Warheads\ChangeOwnerWarhead.cs" />
<Compile Include="UtilityCommands\PngSheetExportMetadataCommand.cs" />
<Compile Include="UtilityCommands\PngSheetImportMetadataCommand.cs" />
<Compile Include="Widgets\Logic\MusicHotkeyLogic.cs" />
<Compile Include="WorldExtensions.cs" />
<Compile Include="UtilityCommands\GetMapHashCommand.cs" />
@@ -697,6 +701,7 @@
<Compile Include="SpriteLoaders\TmpRALoader.cs" />
<Compile Include="SpriteLoaders\TmpTDLoader.cs" />
<Compile Include="SpriteLoaders\ShpD2Loader.cs" />
<Compile Include="SpriteLoaders\PngSheetLoader.cs" />
<Compile Include="LoadScreens\LogoStripeLoadScreen.cs" />
<Compile Include="LoadScreens\BlankLoadScreen.cs" />
<Compile Include="Widgets\Logic\ReplayUtils.cs" />

View File

@@ -0,0 +1,134 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
namespace OpenRA.Mods.Common.SpriteLoaders
{
public class PngSheetLoader : ISpriteLoader
{
class PngSheetFrame : ISpriteFrame
{
public Size Size { get; set; }
public Size FrameSize { get; set; }
public float2 Offset { get; set; }
public byte[] Data { get; set; }
public bool DisableExportPadding { get { return false; } }
}
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames)
{
if (!Png.Verify(s))
{
frames = null;
return false;
}
var png = new Png(s);
List<Rectangle> frameRegions;
List<float2> frameOffsets;
// Prefer manual defined regions over auto sliced regions.
if (png.EmbeddedData.Any(meta => meta.Key.StartsWith("Frame[")))
RegionsFromFrames(png, out frameRegions, out frameOffsets);
else
RegionsFromSlices(png, out frameRegions, out frameOffsets);
frames = new ISpriteFrame[frameRegions.Count];
for (var i = 0; i < frames.Length; i++)
{
var frameStart = frameRegions[i].X + frameRegions[i].Y * png.Width;
var frameSize = new Size(frameRegions[i].Width, frameRegions[i].Height);
frames[i] = new PngSheetFrame()
{
Size = frameSize,
FrameSize = frameSize,
Offset = frameOffsets[i],
Data = new byte[frameRegions[i].Width * frameRegions[i].Height]
};
for (var y = 0; y < frames[i].Size.Height; y++)
Array.Copy(png.Data, frameStart + y * png.Width, frames[i].Data, y * frames[i].Size.Width, frames[i].Size.Width);
}
return true;
}
void RegionsFromFrames(Png png, out List<Rectangle> regions, out List<float2> offsets)
{
regions = new List<Rectangle>();
offsets = new List<float2>();
string frame;
for (var i = 0; png.EmbeddedData.TryGetValue("Frame[" + i + "]", out frame); i++)
{
// Format: x,y,width,height;offsetX,offsetY
var coords = frame.Split(';');
regions.Add(FieldLoader.GetValue<Rectangle>("Region", coords[0]));
offsets.Add(FieldLoader.GetValue<float2>("Offset", coords[1]));
}
}
void RegionsFromSlices(Png png, out List<Rectangle> regions, out List<float2> offsets)
{
// Default: whole image is 1 frame.
var frameSize = new Size(png.Width, png.Height);
var frameAmount = 1;
if (png.EmbeddedData.ContainsKey("FrameSize"))
{
// If FrameSize exist, use it and...
frameSize = FieldLoader.GetValue<Size>("FrameSize", png.EmbeddedData["FrameSize"]);
// ... either use FrameAmount or calculate how many times FrameSize fits into the image.
if (png.EmbeddedData.ContainsKey("FrameAmount"))
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
else
frameAmount = png.Width / frameSize.Width * png.Height / frameSize.Height;
}
else if (png.EmbeddedData.ContainsKey("FrameAmount"))
{
// Otherwise, calculate the number of frames by splitting the image horizontaly by FrameAmount.
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
frameSize = new Size(png.Width / frameAmount, png.Height);
}
float2 offset;
// If Offset property exists, use its value. Otherwise assume the frame is centered.
if (png.EmbeddedData.ContainsKey("Offset"))
offset = FieldLoader.GetValue<float2>("Offset", png.EmbeddedData["Offset"]);
else
offset = float2.Zero;
var framesPerRow = png.Width / frameSize.Width;
regions = new List<Rectangle>();
offsets = new List<float2>();
for (var i = 0; i < frameAmount; i++)
{
var x = i % framesPerRow * frameSize.Width;
var y = i / framesPerRow * frameSize.Height;
regions.Add(new Rectangle(x, y, frameSize.Width, frameSize.Height));
offsets.Add(offset);
}
}
}
}

View File

@@ -0,0 +1,75 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Load a PNG and use its embedded palette.")]
class PaletteFromPngInfo : ITraitInfo
{
[FieldLoader.Require, PaletteDefinition]
[Desc("Internal palette name")]
public readonly string Name = null;
[Desc("If defined, load the palette only for this tileset.")]
public readonly string Tileset = null;
[FieldLoader.Require]
[Desc("Filename to load")]
public readonly string Filename = null;
[Desc("Map listed indices to shadow. Ignores previous color.")]
public readonly int[] ShadowIndex = { };
public readonly bool AllowModifiers = true;
public object Create(ActorInitializer init) { return new PaletteFromPng(init.World, this); }
}
class PaletteFromPng : ILoadsPalettes, IProvidesAssetBrowserPalettes
{
readonly World world;
readonly PaletteFromPngInfo info;
public PaletteFromPng(World world, PaletteFromPngInfo info)
{
this.world = world;
this.info = info;
}
public void LoadPalettes(WorldRenderer wr)
{
if (info.Tileset != null && info.Tileset.ToLowerInvariant() != world.Map.Tileset.ToLowerInvariant())
return;
var png = new Png(world.Map.Open(info.Filename));
var colors = new uint[Palette.Size];
for (var i = 0; i < png.Palette.Length; i++)
colors[i] = (uint)png.Palette[i].ToArgb();
wr.AddPalette(info.Name, new ImmutablePalette(colors), info.AllowModifiers);
}
public IEnumerable<string> PaletteNames
{
get
{
// Only expose the palette if it is available for the shellmap's tileset (which is a requirement for its use).
if (info.Tileset == null || info.Tileset == world.Map.Rules.TileSet.Id)
yield return info.Name;
}
}
}
}

View File

@@ -35,14 +35,14 @@ namespace OpenRA.Mods.Common.UtilityCommands
{
var inputFiles = GlobArgs(args).OrderBy(a => a).ToList();
var dest = inputFiles[0].Split('-').First() + ".shp";
var frames = inputFiles.Select(a => PngLoader.Load(a));
var size = frames.First().Size;
if (frames.Any(f => f.Size != size))
var frames = inputFiles.Select(a => new Png(File.OpenRead(a))).ToList();
var size = new Size(frames[0].Width, frames[0].Height);
if (frames.Any(f => f.Width != size.Width || f.Height != size.Height))
throw new InvalidOperationException("All frames must be the same size");
using (var destStream = File.Create(dest))
ShpTDSprite.Write(destStream, size, frames.Select(f => ToBytes(f)));
ShpTDSprite.Write(destStream, size, frames.Select(f => f.Data));
Console.WriteLine(dest + " saved.");
}
@@ -53,20 +53,5 @@ namespace OpenRA.Mods.Common.UtilityCommands
foreach (var path in Glob.Expand(args[i]))
yield return path;
}
static byte[] ToBytes(Bitmap bitmap)
{
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
var bytes = new byte[bitmap.Width * bitmap.Height];
for (var i = 0; i < bitmap.Height; i++)
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
bytes, i * bitmap.Width, bitmap.Width);
bitmap.UnlockBits(data);
return bytes;
}
}
}

View File

@@ -0,0 +1,39 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
namespace OpenRA.Mods.Common.UtilityCommands
{
public class PngSheetExportMetadataCommand : IUtilityCommand
{
string IUtilityCommand.Name { get { return "--png-sheet-export"; } }
bool IUtilityCommand.ValidateArguments(string[] args)
{
return args.Length == 2;
}
[Desc("PNGFILE", "Export png metadata to yaml")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
using (var s = File.OpenRead(args[1]))
{
var png = new Png(s);
png.EmbeddedData.Select(m => new MiniYamlNode(m.Key, m.Value))
.ToList()
.WriteToFile(Path.ChangeExtension(args[1], "yaml"));
}
}
}
}

View File

@@ -0,0 +1,87 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.IO;
using System.Net;
using System.Text;
using ICSharpCode.SharpZipLib.Checksums;
namespace OpenRA.Mods.Common.UtilityCommands
{
public class PngSheetImportMetadataCommand : IUtilityCommand
{
string IUtilityCommand.Name { get { return "--png-sheet-import"; } }
bool IUtilityCommand.ValidateArguments(string[] args)
{
return args.Length == 2;
}
[Desc("PNGFILE", "Import yaml metadata to png")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
// HACK: This could eventually be merged into the Png class (add a Write method), however this requires complex png writing algorythms.
var rs = File.OpenRead(args[1]);
var ws = new MemoryStream();
using (var bw = new BinaryWriter(ws))
{
bw.Write(rs.ReadBytes(8));
var crc32 = new Crc32();
for (;;)
{
var length = IPAddress.NetworkToHostOrder(rs.ReadInt32());
var type = Encoding.UTF8.GetString(rs.ReadBytes(4));
var content = rs.ReadBytes(length);
var crc = rs.ReadUInt32();
switch (type)
{
case "tEXt":
break;
case "IEND":
rs.Close();
foreach (var node in MiniYaml.FromFile(Path.ChangeExtension(args[1], "yaml")))
{
bw.Write(IPAddress.NetworkToHostOrder(node.Key.Length + 1 + node.Value.Value.Length));
bw.Write("tEXt".ToCharArray());
bw.Write(node.Key.ToCharArray());
bw.Write((byte)0x00);
bw.Write(node.Value.Value.ToCharArray());
crc32.Reset();
crc32.Update(Encoding.ASCII.GetBytes("tEXt"));
crc32.Update(Encoding.ASCII.GetBytes(node.Key + (char)0x00 + node.Value.Value));
bw.Write((uint)IPAddress.NetworkToHostOrder((int)crc32.Value));
}
bw.Write(0);
bw.Write(type.ToCharArray());
bw.Write(crc);
File.WriteAllBytes(args[1], ws.ToArray());
ws.Close();
return;
default:
bw.Write(IPAddress.NetworkToHostOrder(length));
bw.Write(type.ToCharArray());
bw.Write(content);
bw.Write(crc);
break;
}
}
}
}
}
}