From b03d2fdeccc3c33d9df703201eef8da38d4d3a48 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 25 Feb 2017 05:42:15 +0400 Subject: [PATCH] implemented MG2 and MAL images. --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/Valkyria/ArcDAT.cs | 74 ++++++++++++++++ ArcFormats/Valkyria/ImageMAL.cs | 82 ++++++++++++++++++ ArcFormats/Valkyria/ImageMG2.cs | 145 ++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 ArcFormats/Valkyria/ArcDAT.cs create mode 100644 ArcFormats/Valkyria/ImageMAL.cs create mode 100644 ArcFormats/Valkyria/ImageMG2.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 19593e5d..c29496b2 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -500,7 +500,10 @@ + + + diff --git a/ArcFormats/Valkyria/ArcDAT.cs b/ArcFormats/Valkyria/ArcDAT.cs new file mode 100644 index 00000000..13c0b5a7 --- /dev/null +++ b/ArcFormats/Valkyria/ArcDAT.cs @@ -0,0 +1,74 @@ +//! \file ArcDAT.cs +//! \date Sat Feb 25 02:30:54 2017 +//! \brief Valkyria resource archive. +// +// Copyright (C) 2017 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.Valkyria +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/VALKYRIA"; } } + public override string Description { get { return "Valkyria resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + uint index_size = file.View.ReadUInt32 (0); + if (0 == index_size || index_size >= file.MaxOffset) + return null; + int count = (int)index_size / 0x10C; + if (index_size != (uint)count * 0x10Cu || !IsSaneCount (count)) + return null; + uint index_offset = 4; + long base_offset = index_offset + index_size; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 0x104); + index_offset += 0x104; + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = base_offset + file.View.ReadUInt32 (index_offset); + entry.Size = file.View.ReadUInt32 (index_offset+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + index_offset += 8; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Valkyria/ImageMAL.cs b/ArcFormats/Valkyria/ImageMAL.cs new file mode 100644 index 00000000..cf24c6f4 --- /dev/null +++ b/ArcFormats/Valkyria/ImageMAL.cs @@ -0,0 +1,82 @@ +//! \file ImageMAL.cs +//! \date Sat Feb 25 05:13:05 2017 +//! \brief Valkyria mask image format. +// +// Copyright (C) 2017 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +namespace GameRes.Formats.Valkyria +{ + [Export(typeof(ImageFormat))] + public class MalFormat : ImageFormat + { + public override string Tag { get { return "MAL"; } } + public override string Description { get { return "Valkyria mask image format"; } } + public override uint Signature { get { return 0x4F43494D; } } // 'MICO' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0xE); + if (!header.AsciiEqual (4, "MSK00")) + return null; + return new ImageMetaData + { + Width = header.ToUInt16 (0xA), + Height = header.ToUInt16 (0xC), + BPP = 8 + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0xE; + int total = (int)info.Width * (int)info.Height; + var pixels = new byte[total + 15]; + int dst = 0; + while (dst < total) + { + int count = file.ReadUInt16(); + if (count > 0x7FFF) + { + count &= 0x7FFF; + file.Read (pixels, dst, count); + dst += count; + } + else + { + byte c = file.ReadUInt8(); + while (count --> 0) + pixels[dst++] = c; + } + } + return ImageData.Create (info, PixelFormats.Gray8, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("MalFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/Valkyria/ImageMG2.cs b/ArcFormats/Valkyria/ImageMG2.cs new file mode 100644 index 00000000..b08a07dd --- /dev/null +++ b/ArcFormats/Valkyria/ImageMG2.cs @@ -0,0 +1,145 @@ +//! \file ImageMG2.cs +//! \date Sat Feb 25 03:08:01 2017 +//! \brief Valkyria image format. +// +// Copyright (C) 2017 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Valkyria +{ + internal class Mg2MetaData : ImageMetaData + { + public int ImageLength; + public int AlphaLength; + } + + [Export(typeof(ImageFormat))] + public class Mg2Format : ImageFormat + { + public override string Tag { get { return "MG2"; } } + public override string Description { get { return "Valkyria image format"; } } + public override uint Signature { get { return 0x4F43494D; } } // 'MICO' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (4, "CG01")) + return null; + int length = header.ToInt32 (8); + using (var input = new Mg2EncryptedStream (file.AsStream, 0x10, length)) + using (var png = new BinaryStream (input, file.Name)) + { + var info = Png.ReadMetaData (png); + if (null == info) + return null; + return new Mg2MetaData + { + Width = info.Width, + Height = info.Height, + OffsetX = info.OffsetX, + OffsetY = info.OffsetY, + BPP = info.BPP, + ImageLength = length, + AlphaLength = header.ToInt32 (12) + }; + } + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (Mg2MetaData)info; + BitmapSource frame; + using (var input = new Mg2EncryptedStream (file.AsStream, 0x10, meta.ImageLength)) + { + var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + frame = decoder.Frames[0]; + if (0 == meta.AlphaLength) + { + frame.Freeze(); + return new ImageData (frame, info); + } + } + if (frame.Format.BitsPerPixel != 32) + frame = new FormatConvertedBitmap (frame, PixelFormats.Bgr32, null, 0); + int stride = frame.PixelWidth * 4; + var pixels = new byte[stride * (int)meta.Height]; + frame.CopyPixels (pixels, stride, 0); + + using (var input = new Mg2EncryptedStream (file.AsStream, 0x10+meta.ImageLength, meta.AlphaLength)) + { + var decoder = new PngBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource alpha_frame = decoder.Frames[0]; + if (alpha_frame.PixelWidth != frame.PixelWidth || alpha_frame.PixelHeight != frame.PixelHeight) + return ImageData.Create (info, PixelFormats.Bgr32, null, pixels, stride); + + alpha_frame = new FormatConvertedBitmap (alpha_frame, PixelFormats.Gray8, null, 0); + var alpha = new byte[alpha_frame.PixelWidth * alpha_frame.PixelHeight]; + alpha_frame.CopyPixels (alpha, alpha_frame.PixelWidth, 0); + + int src = 0; + for (int dst = 3; dst < pixels.Length; dst += 4) + { + pixels[dst] = alpha[src++]; + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Mg2Format.Write not implemented"); + } + } + + internal class Mg2EncryptedStream : StreamRegion + { + readonly int m_threshold; + + public Mg2EncryptedStream (Stream main, int offset, int length) + : base (main, offset, length, true) + { + m_threshold = length / 5; + } + + public override int Read (byte[] buffer, int offset, int count) + { + int pos = (int)Position; + int read = base.Read (buffer, offset, count); + for (int i = 0; i < read && pos < m_threshold; ++i) + buffer[offset+i] ^= (byte)pos++; + return read; + } + + public override int ReadByte () + { + long pos = Position; + int b = base.ReadByte(); + if (b != -1 && pos < m_threshold) + b ^= (byte)pos; + return b; + } + } +}