diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 13aaa5d5..5f6247b2 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -327,6 +327,8 @@ + + Code diff --git a/ArcFormats/VnEngine/ArcAXR.cs b/ArcFormats/VnEngine/ArcAXR.cs new file mode 100644 index 00000000..a808b86b --- /dev/null +++ b/ArcFormats/VnEngine/ArcAXR.cs @@ -0,0 +1,179 @@ +//! \file ArcAXR.cs +//! \date Fri Jul 01 13:43:07 2016 +//! \brief GEM/vnengine resource archive. +// +// Copyright (C) 2016 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.IO; +using GameRes.Utility; + +namespace GameRes.Formats.VnEngine +{ + internal class AxrArchive : ArcFile + { + public readonly byte[] KeyTable = new byte[0x400]; + + public AxrArchive (ArcView arc, ArchiveFormat impl, ICollection dir, uint key) + : base (arc, impl, dir) + { + AxrOpener.Decrypt (KeyTable, key, 0x400); + } + } + + [Export(typeof(ArchiveFormat))] + public class AxrOpener : ArchiveFormat + { + public override string Tag { get { return "AXR"; } } + public override string Description { get { return "GEM/vnengine resource archive"; } } + public override uint Signature { get { return 0x65525841; } } // 'AXRe' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + var header = file.View.ReadBytes (0, 0x10); + byte checksum = header[4]; + for (int i = 1; i < 8; ++i) + { + checksum ^= Binary.RotByteR (header[4+i], i); + } + uint t = MutateKey (MutateKey (LittleEndian.ToUInt32 (header, 8) ^ LittleEndian.ToUInt32 (header, 0))); + uint key = LittleEndian.ToUInt32 (header, 4); + uint index_size = t ^ key; + uint stored_checksum = LittleEndian.ToUInt32 (header, 12) ^ MutateKey (t); + if (index_size < 8 || checksum != stored_checksum) + return null; + var index = new byte[(index_size + 4) & ~3u]; + if (index_size != file.View.Read (0x10, index, 0, index_size)) + return null; + Decrypt (index, index_size, index.Length); + index[index_size] = 0; + var dir = new List(); + int current_offset = 0; + while (current_offset+8 < (int)index_size) + { + uint offset = LittleEndian.ToUInt32 (index, current_offset); + uint size = LittleEndian.ToUInt32 (index, current_offset+4); + current_offset += 8; + int name_end = Array.IndexOf (index, 0, current_offset); + int name_length = name_end - current_offset; + if (0 == name_length) + break; + var name = Encodings.cp932.GetString (index, current_offset, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = offset; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + current_offset += (name_length + 4) & -4; + } + if (0 == dir.Count) + return null; + return new AxrArchive (file, this, dir, key); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var axr = arc as AxrArchive; + if (null == axr) + return input; + return new AxrEncryptedStream (input, axr.KeyTable); + } + + static uint MutateKey (uint key) + { + key ^= (key & 0xFFF) << 17; + return ~(key ^ (key << 18 | key >> 15)); + } + + internal static void Decrypt (byte[] data, uint key, int count) + { + if (count > data.Length) + throw new ArgumentException ("count"); + count /= 4; + if (0 == count) + return; + unsafe + { + fixed (byte* data8 = data) + { + uint* data32 = (uint*)data8; + for (int i = 0; i < count; ++i) + { + key = MutateKey (key); + uint v = *data32 ^ key; + *data32++ = v; + key += v; + } + } + } + } + } + + internal class AxrEncryptedStream : ProxyStream + { + byte[] m_key; + + public AxrEncryptedStream (Stream input, byte[] key, bool leave_open = false) + : base (input, leave_open) + { + m_key = key; + } + + public override bool CanWrite { get { return false; } } + + public override int Read (byte[] buffer, int offset, int count) + { + int start = (int)Position; + int read = BaseStream.Read (buffer, offset, count); + for (int i = 0; i < read; ++i) + { + buffer[offset+i] ^= m_key[start++ & 0x3FF]; + } + return read; + } + + public override int ReadByte () + { + int start = (int)Position & 0x3FF; + int b = BaseStream.ReadByte(); + if (-1 != b) + b ^= m_key[start]; + return b; + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException ("AxrEncryptedStream.Write method is not supported"); + } + + public override void WriteByte (byte value) + { + throw new NotSupportedException ("AxrEncryptedStream.WriteByte method is not supported"); + } + } +} diff --git a/ArcFormats/VnEngine/ImageZAW.cs b/ArcFormats/VnEngine/ImageZAW.cs new file mode 100644 index 00000000..79bfe215 --- /dev/null +++ b/ArcFormats/VnEngine/ImageZAW.cs @@ -0,0 +1,105 @@ +//! \file ImageZAW.cs +//! \date Fri Jul 01 18:22:28 2016 +//! \brief GEM/vnengine image format. +// +// Copyright (C) 2016 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 GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.VnEngine +{ + [Export(typeof(ImageFormat))] + public class ZawFormat : ImageFormat + { + public override string Tag { get { return "ZAW"; } } + public override string Description { get { return "GEM/vnengine image format"; } } + public override uint Signature { get { return 0x57415A; } } // 'ZAW' + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x40]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + uint crc = LittleEndian.ToUInt32 (header, 4); + LittleEndian.Pack (0u, header, 4); + if (crc != Crc32.Compute (header, 0, header.Length)) + return null; + int bpp = 3 == header[12] ? 32 + : 2 == header[12] ? 16 + : 1 == header[12] ? 24 : 8; + return new ImageMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x10), + Height = LittleEndian.ToUInt32 (header, 0x14), + OffsetX = LittleEndian.ToInt32 (header, 0x18), + OffsetY = LittleEndian.ToInt32 (header, 0x1C), + BPP = bpp, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + int pixel_size = info.BPP / 8; + int stride = (int)info.Width * pixel_size; + var pixels = new byte[stride * (int)info.Height]; + stream.Position = 0x40; + using (var input = new ZLibStream (stream, CompressionMode.Decompress, true)) + input.Read (pixels, 0, pixels.Length); + if (24 == info.BPP) + return ImageData.Create (info, PixelFormats.Rgb24, null, pixels, stride); + if (8 == info.BPP) + return ImageData.Create (info, PixelFormats.Gray8, null, pixels, stride); + if (16 == info.BPP) + return GrayWithAlpha (info, pixels); + for (int i = 0; i < pixels.Length; i += pixel_size) + { + byte t = pixels[i]; + pixels[i] = pixels[i+2]; + pixels[i+2] = t; + pixels[i+3] = (byte)System.Math.Min (pixels[i+3] * 0xFF / 0x80, 0xFF); + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride); + } + + ImageData GrayWithAlpha (ImageMetaData info, byte[] input) + { + int stride = (int)info.Width * 4; + byte[] pixels = new byte[stride * info.Height]; + int src = 0; + for (int dst = 0; dst < pixels.Length; dst += 4) + { + pixels[dst] = pixels[dst+1] = pixels[dst+2] = input[src++]; + pixels[dst+3] = (byte)System.Math.Min (input[src++] * 0xFF / 0x80, 0xFF); + } + return ImageData.Create (info, PixelFormats.Bgra32, null, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("ZawFormat.Write not implemented"); + } + } +} diff --git a/supported.html b/supported.html index c1543cbc..6801656d 100644 --- a/supported.html +++ b/supported.html @@ -922,6 +922,10 @@ Peropero Saimin 3 Raouden ~Yousei no Sakuryaku~
*.gxpGXPNoAstronauts Demonion 2 ~Maou to Sannin no Joou~
+*.axrAXReNoGEM/vnengine +Natsunone -Ring-
+ +*.zawZAWNo

1 Non-encrypted only