Move ShpTDLoader, LZO and XORDelta formats to Mods.Cnc

They're pretty much RA/TD-specific formats.
This commit is contained in:
reaperrr
2019-12-01 17:17:36 +01:00
committed by Paul Chote
parent cd123830c3
commit 4751b1a176
6 changed files with 9 additions and 5 deletions

View File

@@ -0,0 +1,289 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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
#region Additional Copyright & License Information
/*
* C# port of the crude minilzo source version 2.06 by Frank Razenberg
* The full LZO package can be found at http://www.oberhumer.com/opensource/lzo/
*
* Beware, you should never want to see C# code like this. You were warned.
* I simply ran the MSVC preprocessor on the original source, changed the datatypes
* to their C# counterpart and fixed changed some control flow stuff to amend for
* the different goto semantics between C and C#.
*
* Original copyright notice is included below.
*/
/*
* minilzo.c -- mini subset of the LZO real-time data compression library
*
* This file is part of the LZO real-time data compression library.
*
* Copyright (C) 2011 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2010 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2009 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2008 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2007 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2006 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2005 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2004 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer
* Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer
* All Rights Reserved.
*
* The LZO library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* The LZO library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the LZO library; see the file COPYING.
* If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Markus F.X.J. Oberhumer
* <markus@oberhumer.com>
* http://www.oberhumer.com/opensource/lzo/
*/
#endregion
using System;
namespace OpenRA.Mods.Cnc.FileFormats
{
public static class LZOCompression
{
static unsafe int LZO1xDecompress(byte* @in, uint inLen, byte* @out, ref uint outLen, void* wrkmem)
{
byte* op;
byte* ip;
uint t;
byte* mPos;
byte* ipEnd = @in + inLen;
outLen = 0;
op = @out;
ip = @in;
bool gtFirstLiteralRun = false;
bool gtMatchDone = false;
if (*ip > 17)
{
t = (uint)(*ip++ - 17);
if (t < 4)
MatchNext(ref op, ref ip, ref t);
else
{
do { *op++ = *ip++; } while (--t > 0);
gtFirstLiteralRun = true;
}
}
while (true)
{
if (gtFirstLiteralRun)
{
gtFirstLiteralRun = false;
goto first_literal_run;
}
t = *ip++;
if (t >= 16)
goto match;
if (t == 0)
{
while (*ip == 0)
{
t += 255;
ip++;
}
t += (uint)(15 + *ip++);
}
*(uint*)op = *(uint*)ip;
op += 4; ip += 4;
if (--t > 0)
{
if (t >= 4)
{
do
{
*(uint*)op = *(uint*)ip;
op += 4; ip += 4; t -= 4;
}
while (t >= 4);
if (t > 0)
do { *op++ = *ip++; } while (--t > 0);
}
else
do { *op++ = *ip++; } while (--t > 0);
}
first_literal_run:
t = *ip++;
if (t >= 16)
goto match;
mPos = op - (1 + 0x0800);
mPos -= t >> 2;
mPos -= *ip++ << 2;
*op++ = *mPos++; *op++ = *mPos++; *op++ = *mPos;
gtMatchDone = true;
match:
do
{
if (gtMatchDone)
{
gtMatchDone = false;
goto match_done;
}
if (t >= 64)
{
mPos = op - 1;
mPos -= (t >> 2) & 7;
mPos -= *ip++ << 3;
t = (t >> 5) - 1;
CopyMatch(ref op, ref mPos, ref t);
goto match_done;
}
else if (t >= 32)
{
t &= 31;
if (t == 0)
{
while (*ip == 0)
{
t += 255;
ip++;
}
t += (uint)(31 + *ip++);
}
mPos = op - 1;
mPos -= (*(ushort*)(void*)ip) >> 2;
ip += 2;
}
else if (t >= 16)
{
mPos = op;
mPos -= (t & 8) << 11;
t &= 7;
if (t == 0)
{
while (*ip == 0)
{
t += 255;
ip++;
}
t += (uint)(7 + *ip++);
}
mPos -= (*(ushort*)ip) >> 2;
ip += 2;
if (mPos == op)
goto eof_found;
mPos -= 0x4000;
}
else
{
mPos = op - 1;
mPos -= t >> 2;
mPos -= *ip++ << 2;
*op++ = *mPos++; *op++ = *mPos;
goto match_done;
}
if (t >= 2 * 4 - (3 - 1) && (op - mPos) >= 4)
{
*(uint*)op = *(uint*)mPos;
op += 4; mPos += 4; t -= 4 - (3 - 1);
do
{
*(uint*)op = *(uint*)mPos;
op += 4; mPos += 4; t -= 4;
}
while (t >= 4);
if (t > 0)
do { *op++ = *mPos++; } while (--t > 0);
}
else
{
// copy_match:
*op++ = *mPos++; *op++ = *mPos++;
do { *op++ = *mPos++; } while (--t > 0);
}
match_done:
t = (uint)(ip[-2] & 3);
if (t == 0)
break;
// match_next:
*op++ = *ip++;
if (t > 1)
{
*op++ = *ip++;
if (t > 2)
(*op++) = *ip++;
}
t = *ip++;
}
while (true);
}
eof_found:
outLen = (uint)(op - @out);
return ip == ipEnd ? 0 : (ip < ipEnd ? (-8) : (-4));
}
static unsafe void MatchNext(ref byte* op, ref byte* ip, ref uint t)
{
do { *op++ = *ip++; } while (--t > 0);
t = *ip++;
}
static unsafe void CopyMatch(ref byte* op, ref byte* mPos, ref uint t)
{
*op++ = *mPos++; *op++ = *mPos++;
do { *op++ = *mPos++; } while (--t > 0);
}
public static void DecodeInto(byte[] src, uint srcOffset, uint srcLength, byte[] dest, uint destOffset, ref uint destLength)
{
unsafe
{
fixed (byte* r = src, w = dest, wrkmem = new byte[IntPtr.Size * 16384])
{
LZO1xDecompress(r + srcOffset, srcLength, w + destOffset, ref destLength, wrkmem);
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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 OpenRA.Mods.Common.FileFormats;
namespace OpenRA.Mods.Cnc.FileFormats
{
// Data that is to be XORed against another set of data (aka Format40)
public static class XORDeltaCompression
{
public static int DecodeInto(byte[] src, byte[] dest, int srcOffset)
{
var ctx = new FastByteReader(src, srcOffset);
var destIndex = 0;
while (true)
{
var i = ctx.ReadByte();
if ((i & 0x80) == 0)
{
var count = i & 0x7F;
if (count == 0)
{
// case 6
count = ctx.ReadByte();
var value = ctx.ReadByte();
for (var end = destIndex + count; destIndex < end; destIndex++)
dest[destIndex] ^= value;
}
else
{
// case 5
for (var end = destIndex + count; destIndex < end; destIndex++)
dest[destIndex] ^= ctx.ReadByte();
}
}
else
{
var count = i & 0x7F;
if (count == 0)
{
count = ctx.ReadWord();
if (count == 0)
return destIndex;
if ((count & 0x8000) == 0)
{
// case 2
destIndex += count & 0x7FFF;
}
else if ((count & 0x4000) == 0)
{
// case 3
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
dest[destIndex] ^= ctx.ReadByte();
}
else
{
// case 4
var value = ctx.ReadByte();
for (var end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++)
dest[destIndex] ^= value;
}
}
else
{
// case 1
destIndex += count;
}
}
}
}
}
}

View File

@@ -0,0 +1,242 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Cnc.FileFormats;
using OpenRA.Mods.Common.FileFormats;
using OpenRA.Primitives;
namespace OpenRA.Mods.Cnc.SpriteLoaders
{
public class ShpTDLoader : ISpriteLoader
{
static bool IsShpTD(Stream s)
{
var start = s.Position;
// First word is the image count
var imageCount = s.ReadUInt16();
if (imageCount == 0)
{
s.Position = start;
return false;
}
// Last offset should point to the end of file
var finalOffset = start + 14 + 8 * imageCount;
if (finalOffset > s.Length)
{
s.Position = start;
return false;
}
s.Position = finalOffset;
var eof = s.ReadUInt32();
if (eof != s.Length)
{
s.Position = start;
return false;
}
// Check the format flag on the first frame
s.Position = start + 17;
var b = s.ReadUInt8();
s.Position = start;
return b == 0x20 || b == 0x40 || b == 0x80;
}
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames, out TypeDictionary metadata)
{
metadata = null;
if (!IsShpTD(s))
{
frames = null;
return false;
}
frames = new ShpTDSprite(s).Frames.ToArray();
return true;
}
}
public class ShpTDSprite
{
enum Format { XORPrev = 0x20, XORLCW = 0x40, LCW = 0x80 }
class ImageHeader : ISpriteFrame
{
public Size Size { get { return reader.Size; } }
public Size FrameSize { get { return reader.Size; } }
public float2 Offset { get { return float2.Zero; } }
public byte[] Data { get; set; }
public bool DisableExportPadding { get { return false; } }
public uint FileOffset;
public Format Format;
public uint RefOffset;
public Format RefFormat;
public ImageHeader RefImage;
ShpTDSprite reader;
// Used by ShpWriter
public ImageHeader() { }
public ImageHeader(Stream stream, ShpTDSprite reader)
{
this.reader = reader;
var data = stream.ReadUInt32();
FileOffset = data & 0xffffff;
Format = (Format)(data >> 24);
RefOffset = stream.ReadUInt16();
RefFormat = (Format)stream.ReadUInt16();
}
public void WriteTo(BinaryWriter writer)
{
writer.Write(FileOffset | ((uint)Format << 24));
writer.Write((ushort)RefOffset);
writer.Write((ushort)RefFormat);
}
}
public IReadOnlyList<ISpriteFrame> Frames { get; private set; }
public readonly Size Size;
int recurseDepth = 0;
readonly int imageCount;
readonly long shpBytesFileOffset;
readonly byte[] shpBytes;
public ShpTDSprite(Stream stream)
{
imageCount = stream.ReadUInt16();
stream.Position += 4;
var width = stream.ReadUInt16();
var height = stream.ReadUInt16();
Size = new Size(width, height);
stream.Position += 4;
var headers = new ImageHeader[imageCount];
Frames = headers.AsReadOnly();
for (var i = 0; i < headers.Length; i++)
headers[i] = new ImageHeader(stream, this);
// Skip eof and zero headers
stream.Position += 16;
var offsets = headers.ToDictionary(h => h.FileOffset, h => h);
for (var i = 0; i < imageCount; i++)
{
var h = headers[i];
if (h.Format == Format.XORPrev)
h.RefImage = headers[i - 1];
else if (h.Format == Format.XORLCW && !offsets.TryGetValue(h.RefOffset, out h.RefImage))
throw new InvalidDataException("Reference doesn't point to image data {0}->{1}".F(h.FileOffset, h.RefOffset));
}
shpBytesFileOffset = stream.Position;
shpBytes = stream.ReadBytes((int)(stream.Length - stream.Position));
foreach (var h in headers)
Decompress(h);
}
void Decompress(ImageHeader h)
{
// No extra work is required for empty frames
if (h.Size.Width == 0 || h.Size.Height == 0)
return;
if (recurseDepth > imageCount)
throw new InvalidDataException("Format20/40 headers contain infinite loop");
switch (h.Format)
{
case Format.XORPrev:
case Format.XORLCW:
{
if (h.RefImage.Data == null)
{
++recurseDepth;
Decompress(h.RefImage);
--recurseDepth;
}
h.Data = CopyImageData(h.RefImage.Data);
XORDeltaCompression.DecodeInto(shpBytes, h.Data, (int)(h.FileOffset - shpBytesFileOffset));
break;
}
case Format.LCW:
{
var imageBytes = new byte[Size.Width * Size.Height];
LCWCompression.DecodeInto(shpBytes, imageBytes, (int)(h.FileOffset - shpBytesFileOffset));
h.Data = imageBytes;
break;
}
default:
throw new InvalidDataException();
}
}
byte[] CopyImageData(byte[] baseImage)
{
var imageData = new byte[Size.Width * Size.Height];
Array.Copy(baseImage, imageData, imageData.Length);
return imageData;
}
public static void Write(Stream s, Size size, IEnumerable<byte[]> frames)
{
var compressedFrames = frames.Select(f => LCWCompression.Encode(f)).ToList();
// note: end-of-file and all-zeroes headers
var dataOffset = 14 + (compressedFrames.Count + 2) * 8;
using (var bw = new BinaryWriter(s))
{
bw.Write((ushort)compressedFrames.Count);
bw.Write((ushort)0);
bw.Write((ushort)0);
bw.Write((ushort)size.Width);
bw.Write((ushort)size.Height);
bw.Write(0U);
foreach (var f in compressedFrames)
{
var ih = new ImageHeader { Format = Format.LCW, FileOffset = (uint)dataOffset };
dataOffset += f.Length;
ih.WriteTo(bw);
}
var eof = new ImageHeader { FileOffset = (uint)dataOffset };
eof.WriteTo(bw);
var allZeroes = new ImageHeader { };
allZeroes.WriteTo(bw);
foreach (var f in compressedFrames)
bw.Write(f);
}
}
}
}

View File

@@ -14,7 +14,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Mods.Common.SpriteLoaders;
using OpenRA.Mods.Cnc.SpriteLoaders;
using OpenRA.Primitives;
namespace OpenRA.Mods.Cnc.UtilityCommands

View File

@@ -14,6 +14,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.Mods.Cnc.FileFormats;
using OpenRA.Mods.Common;
using OpenRA.Mods.Common.FileFormats;
using OpenRA.Mods.Common.Traits;

View File

@@ -14,7 +14,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.SpriteLoaders;
using OpenRA.Mods.Cnc.SpriteLoaders;
using OpenRA.Primitives;
using OpenRA.Traits;