From 5620f48ea976adbb1361b9402dc0a6e9c35a9168 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 24 Mar 2017 22:45:43 +0400 Subject: [PATCH] (PX): additional formats. --- ArcFormats/Leaf/ArcA.cs | 50 ++++++- ArcFormats/Leaf/ImagePX.cs | 291 ++++++++++++++++++++++++++++++++++++- 2 files changed, 330 insertions(+), 11 deletions(-) diff --git a/ArcFormats/Leaf/ArcA.cs b/ArcFormats/Leaf/ArcA.cs index e626cf4c..086a21f8 100644 --- a/ArcFormats/Leaf/ArcA.cs +++ b/ArcFormats/Leaf/ArcA.cs @@ -30,6 +30,11 @@ using GameRes.Compression; namespace GameRes.Formats.Leaf { + internal class ALeafEntry : PackedEntry + { + public byte Key; + } + [Export(typeof(ArchiveFormat))] public class AOpener : ArchiveFormat { @@ -60,8 +65,9 @@ namespace GameRes.Formats.Leaf var name = file.View.ReadString (index_offset, 0x17); if (string.IsNullOrEmpty (name)) return null; - var entry = FormatCatalog.Instance.Create (name); - entry.IsPacked = 0 != file.View.ReadByte (index_offset+0x17); + var entry = FormatCatalog.Instance.Create (name); + entry.Key = file.View.ReadByte (index_offset+0x17); + entry.IsPacked = 0 != entry.Key; entry.Size = file.View.ReadUInt32 (index_offset+0x18); entry.Offset = base_offset + file.View.ReadUInt32 (index_offset+0x1C); if (!entry.CheckPlacement (file.MaxOffset)) @@ -74,13 +80,47 @@ namespace GameRes.Formats.Leaf public override Stream OpenEntry (ArcFile arc, Entry entry) { - var pent = entry as PackedEntry; + var pent = entry as ALeafEntry; if (null == pent || !pent.IsPacked) return base.OpenEntry (arc, entry); if (0 == pent.UnpackedSize) pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset); - var input = arc.File.CreateStream (entry.Offset+4, entry.Size-4); - return new LzssStream (input); + Stream input = arc.File.CreateStream (entry.Offset+4, entry.Size-4); + input = new LzssStream (input); + if (pent.Key >= 0x7F && pent.Key <= 0x89 && pent.UnpackedSize > 0x20) + { + using (input) + return Decrypt (input, pent.UnpackedSize, (byte)(pent.Key & 0xF)); + } + return input; + } + + Stream Decrypt (Stream input, uint length, byte key) + { + var data = new byte[length]; + input.Read (data, 0, data.Length); + uint width = data.ToUInt32 (0); + uint height = data.ToUInt32 (4); + uint image_size = width * height; + int type = data.ToUInt16 (0x10); + int bits = data.ToUInt16 (0x12); + if (1 == type && 0x20 == bits && image_size > 0 && (32 + image_size * 4) <= length) + { + byte r = 0, g = 0, b = 0; + int dst = 0x20; + for (uint i = 0; i < image_size; ++i) + { + byte a = data[dst+3]; + b += (byte)(data[dst ] + a - key); + g += (byte)(data[dst+1] + a - key); + r += (byte)(data[dst+2] + a - key); + data[dst++] = b; + data[dst++] = g; + data[dst++] = r; + data[dst++] = 0; + } + } + return new BinMemoryStream (data); } } } diff --git a/ArcFormats/Leaf/ImagePX.cs b/ArcFormats/Leaf/ImagePX.cs index 02b0709c..d5bbca8d 100644 --- a/ArcFormats/Leaf/ImagePX.cs +++ b/ArcFormats/Leaf/ImagePX.cs @@ -27,6 +27,7 @@ using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; +using System.Windows.Media.Imaging; using GameRes.Utility; namespace GameRes.Formats.Leaf @@ -78,7 +79,7 @@ namespace GameRes.Formats.Leaf BlocksHeight = header.ToUInt16 (0x1E), }; } - else if (0x80 == type || 0x90 == type) + else if (0x90 == type) { if (!header.AsciiEqual (0x14, "Leaf")) return null; @@ -99,6 +100,34 @@ namespace GameRes.Formats.Leaf FrameCount = count, }; } + else if (0x40 == type || 0x44 == type) + { + int count = header.ToInt32 (0); + if (!ArchiveFormat.IsSaneCount (count)) + return null; + return new PxMetaData + { + Width = header.ToUInt32 (0x14), + Height = header.ToUInt32 (0x18), + Type = 0x40, + BPP = 32, + FrameCount = count, + }; + } + else if (1 == type || 4 == type || 7 == type) + { + int bpp = header.ToUInt16 (0x12); + if (bpp != 32 && bpp != 8) + return null; + return new PxMetaData + { + Width = header.ToUInt32 (0x14), + Height = header.ToUInt32 (0x18), + Type = type, + BPP = bpp, + FrameCount = 1, + }; + } return null; } @@ -107,7 +136,7 @@ namespace GameRes.Formats.Leaf using (var reader = new PxReader (stream, (PxMetaData)info)) { var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, null, pixels, reader.Stride); + return ImageData.Create (info, reader.Format, reader.Palette, pixels, reader.Stride); } } @@ -125,10 +154,11 @@ namespace GameRes.Formats.Leaf int m_pixel_size; int m_stride; - public PixelFormat Format { get { return PixelFormats.Bgra32; } } + public PixelFormat Format { get; private set; } public byte[] Data { get { return m_output; } } public int Stride { get { return m_stride; } } public int FrameCount { get { return m_info.FrameCount; } } + public BitmapPalette Palette { get; private set; } public PxReader (IBinaryStream input, PxMetaData info) { @@ -137,6 +167,10 @@ namespace GameRes.Formats.Leaf m_pixel_size = m_info.BPP / 8; m_stride = (int)m_info.Width * m_pixel_size; m_output = new byte[m_stride * (int)m_info.Height]; + if (1 == m_pixel_size) + Format = PixelFormats.Gray8; + else + Format = PixelFormats.Bgra32; } public byte[] Unpack (int frame = 0) @@ -147,8 +181,8 @@ namespace GameRes.Formats.Leaf { case 0x0C: Unpack0C (frame); break; case 0x90: Unpack90 (frame); break; - case 0x80: Unpack80 (frame); break; - default: throw new NotImplementedException(); + case 0x40: Unpack40(); break; + default: ReadBlock (0); break; } return m_output; } @@ -195,11 +229,256 @@ namespace GameRes.Formats.Leaf throw new EndOfStreamException(); } - void Unpack80 (int frame) + void Unpack40 () + { + m_input.Position = 0x20; + uint data_offset = 0x20 + (uint)m_info.FrameCount * 4; + var offsets = new uint[m_info.FrameCount]; + for (int i = 0; i < offsets.Length; ++i) + offsets[i] = data_offset + m_input.ReadUInt32(); + + foreach (var offset in offsets) + ReadBlock (offset); + } + + internal class PxBlock + { + public int Width; + public int Height; + public int X; + public int Y; + public int Type; + public int Bits; + } + + void ReadBlock (uint offset) + { + m_input.Position = offset; + var px = new PxBlock(); + px.Width = m_input.ReadInt32(); + px.Height = m_input.ReadInt32(); + px.X = m_input.ReadInt32(); + px.Y = m_input.ReadInt32(); + px.Type = m_input.ReadUInt16(); + px.Bits = m_input.ReadUInt16(); + m_input.Position = offset + 0x20; + switch (px.Type) + { + case 0: + Palette = ImageFormat.ReadPalette (m_input.AsStream, (int)px.Width); + break; + + case 1: + switch (px.Bits) + { + case 8: UnpackBlock_1_8 (px); break; + case 0x20: UnpackBlock_1_20 (px); break; + } + break; + + case 4: + switch (px.Bits) + { + case 8: UnpackBlock_4_8 (px); break; + case 9: UnpackBlock_4_9 (px); break; + case 0x20: UnpackBlock_4_20 (px); break; + case 0x30: UnpackBlock_4_30 (px); break; + } + break; + + case 7: + UnpackBlock_7 (px); + break; + + default: + throw new InvalidFormatException(); + } + } + + void UnpackBlock_1_8 (PxBlock block) + { + m_input.Read (m_output, 0, (int)m_info.Width * (int)m_info.Height); + } + + void UnpackBlock_1_20 (PxBlock block) + { + int dst = 0; + for (int y = 0; y < block.Height; ++y) + for (int x = 0; x < block.Width; ++x) + { + m_input.Read (m_output, dst, 4); + if (0 != m_output[dst+3]) + { + byte alpha = (byte)((m_output[dst+3] << 1 | m_output[dst+2] >> 7) + 0xFF); + m_output[dst+3] = alpha; + } + else + { + m_output[dst+3] = 0xFF; + } + dst += 4; + } + } + + void UnpackBlock_4_8 (PxBlock block) { throw new NotImplementedException(); } + const int MaxBlockSize = 1024; + // lazily evaluated to avoid unnecessary allocation + Lazy m_block = new Lazy (() => new uint[MaxBlockSize * MaxBlockSize]); + + uint[] NewBlock (PxBlock block_info) + { + var block = m_block.Value; + Array.Clear (block, 0, MaxBlockSize * block_info.Height); + return block; + } + + void UnpackBlock_4_9 (PxBlock block_info) + { + var output = NewBlock (block_info); + int dst = 0; + bool has_alpha = true; + for (;;) + { + int code = m_input.ReadInt32(); + if (-1 == code) + break; + if (0 != (code & 0x180000)) + has_alpha = !has_alpha; + dst += (code & 0x1FF) * MaxBlockSize; + dst += code >> 21; + + int count = (code >> 9) & 0x3FF; + for (int i = 0; i < count; ++i) + { + byte alpha = (byte)(has_alpha ? (m_input.ReadUInt8() << 1) - 1 : 0xFF); + int color_idx = m_input.ReadByte(); + if (dst < output.Length) + { + var color = Palette.Colors[color_idx]; + output[dst] = (uint)(color.B | color.G << 8 | color.R << 16 | alpha << 24); + } + dst++; + } + } + PutBlock (block_info); + } + + void UnpackBlock_4_20 (PxBlock block_info) + { + var block = NewBlock (block_info); + int dst = 0; + int next; + while ((next = m_input.ReadInt32()) != -1) + { + if (next < 0 || next > 0xFFFFFF) + continue; + dst += next / 4; + m_input.ReadInt32(); + + int count = m_input.ReadInt32(); + while (count --> 0) + { + uint color = m_input.ReadUInt32(); + if (dst < block.Length) + { + if (0 != (color & 0xFF000000)) + { + uint alpha = ((color >> 23) + 0xFF) << 24; + block[dst] = (color & 0xFFFFFFu) | alpha; + } + else + { + block[dst] = color | 0xFF000000u; + } + } + ++dst; + } + } + PutBlock (block_info); + } + + void UnpackBlock_4_30 (PxBlock block_info) + { + var output = NewBlock (block_info); + int dst = 0; + for (;;) + { + int next = m_input.ReadInt32(); + if (-1 == next) + break; + if (0 != (next & 0xFF000000)) + continue; + dst += next / 4; + m_input.ReadInt32(); + + int count = m_input.ReadInt32(); + for (int i = 0; i < count; ++i) + { + uint color = m_input.ReadUInt32(); + m_input.ReadInt16(); + if (dst < output.Length) + { + if (0 != (color & 0xFF000000)) + { + uint alpha = ((color >> 23) + 0xFF) << 24; + output[dst] = (color & 0xFFFFFF) | alpha; + } + else + { + output[dst] = 0; + } + } + dst++; + } + } + PutBlock (block_info); + } + + void UnpackBlock_7 (PxBlock block) + { + m_stride = 4 * block.Width; + m_output = new byte[m_stride * block.Height]; + Format = PixelFormats.Bgra32; + m_info.OffsetX = block.X; + m_info.OffsetY = block.Y; + int dst = 0; + var color_map = ImageFormat.ReadColorMap (m_input.AsStream, 0x100, PaletteFormat.BgrA); + for (int y = 0; y < block.Height; ++y) + for (int x = 0; x < block.Width; ++x) + { + int idx = m_input.ReadUInt8(); + var c = color_map[idx]; + m_output[dst++] = c.B; + m_output[dst++] = c.G; + m_output[dst++] = c.R; + if (c.A != 0) + m_output[dst++] = (byte)((c.A << 1 | c.R >> 7) + 0xFF); + else + m_output[dst++] = 0xFF; + } + } + + void PutBlock (PxBlock block_info) + { + var block = m_block.Value; + int left = Math.Max (0, block_info.X); + int top = Math.Max (0, block_info.Y); + int right = Math.Min (block_info.X + block_info.Width, (int)m_info.Width); + int bottom = Math.Min (block_info.Y + block_info.Height, (int)m_info.Height); + int dst_row = top * m_stride + left * 4; + int row_size = (right - left) * 4; + for (int y = top; y < bottom; ++y) + { + int src = (left - block_info.X) + (y - block_info.Y) * MaxBlockSize; + Buffer.BlockCopy (block, src * 4, m_output, dst_row, row_size); + dst_row += m_stride; + } + } + #region IDisposable Members public void Dispose () {