From 6596df030ebbc611919a0184b9fafa00ee3345a5 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 3 Dec 2016 07:37:06 +0400 Subject: [PATCH] implemented CPZ2 archives and PB2 images. --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/Cmvs/ArcCPZ2.cs | 127 ++++++++++++++++++++ ArcFormats/Cmvs/ImageMSK.cs | 67 +++++++++++ ArcFormats/Cmvs/ImagePB2.cs | 224 +++++++++++++++++++++++++++++++++++ supported.html | 8 +- 5 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/Cmvs/ArcCPZ2.cs create mode 100644 ArcFormats/Cmvs/ImageMSK.cs create mode 100644 ArcFormats/Cmvs/ImagePB2.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 22e9b33f..9fbed517 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -91,6 +91,9 @@ + + + diff --git a/ArcFormats/Cmvs/ArcCPZ2.cs b/ArcFormats/Cmvs/ArcCPZ2.cs new file mode 100644 index 00000000..4ab982a7 --- /dev/null +++ b/ArcFormats/Cmvs/ArcCPZ2.cs @@ -0,0 +1,127 @@ +//! \file ArcCPZ2.cs +//! \date Fri Dec 02 22:24:17 2016 +//! \brief Purple Software 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Utility; + +namespace GameRes.Formats.Purple +{ + [Export(typeof(ArchiveFormat))] + public class Cpz2Opener : ArchiveFormat + { + public override string Tag { get { return "CPZ2"; } } + public override string Description { get { return "CVNS engine resource archive"; } } + public override uint Signature { get { return 0x325A5043; } } // 'CPZ2' + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public Cpz2Opener () + { + Extensions = new string[] { "cpz" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = (int)(file.View.ReadUInt32 (4) ^ 0xE47C59F3); + if (!IsSaneCount (count)) + return null; + uint index_size = file.View.ReadUInt32 (8) ^ 0x3F71DE2Au; + uint key = file.View.ReadUInt32 (0x10) ^ 0x40DE832Cu; + var index = file.View.ReadBytes (0x14, index_size); + DecryptData (index, key); + long base_offset = 0x14 + index_size; + int index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + int entry_size = LittleEndian.ToInt32 (index, index_offset); + if (entry_size <= 0 || entry_size > index.Length - index_offset) + return null; + var name = Binary.GetCString (index, index_offset+0x18); + var entry = FormatCatalog.Instance.Create (name); + entry.Size = LittleEndian.ToUInt32 (index, index_offset+4); + entry.Offset = LittleEndian.ToUInt32 (index, index_offset+8) + base_offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.Key = LittleEndian.ToUInt32 (index, index_offset+0x14) ^ 0x796C3AFDu; + dir.Add (entry); + index_offset += entry_size; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var cent = entry as CpzEntry; + if (null == cent) + return base.OpenEntry (arc, entry); + var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); + DecryptData (data, cent.Key); + if (Binary.AsciiEqual (data, "PSS0")) + data = CpzOpener.UnpackLzss (data); + return new BinMemoryStream (data, entry.Name); + } + + void DecryptData (byte[] data, uint key) + { + int shift = 5; + int k = (int)key; + for (int i = 0; i < 8; ++i) + { + shift ^= k & 0xF; + k >>= 4; + } + shift += 8; + unsafe + { + fixed (byte* data_fixed = data) + { + uint* data32 = (uint*)data_fixed; + int table_ptr = 0; + for (int count = data.Length >> 2; count > 0; --count) + { + uint t = (*data32 ^ (EncryptionTable[table_ptr++ & 0xF] + key)) - 0x15C3E7u; + *data32++ = Binary.RotR (t, shift); + } + byte* data8 = (byte*)data32; + shift = 0; + for (int count = data.Length & 3; count > 0; --count) + { + *data8 = (byte)((*data8 ^ ((EncryptionTable[table_ptr++ & 0xF] + key) >> shift)) + 0x37); + shift += 4; + ++data8; + } + } + } + } + + static readonly uint[] EncryptionTable = { + 0x3A68CDBF, 0xD3C3A711, 0x8414876E, 0x657BEFDB, 0xCDD7C125, 0x09328580, 0x288FFEDD, 0x99EBF13A, + 0x5A471F95, 0x1EA3F4F1, 0xF4FF524E, 0xD358E8A9, 0xC5B71015, 0xA913046F, 0x2D6FD2BD, 0x68C8BE19 + }; + } +} diff --git a/ArcFormats/Cmvs/ImageMSK.cs b/ArcFormats/Cmvs/ImageMSK.cs new file mode 100644 index 00000000..ab662ad5 --- /dev/null +++ b/ArcFormats/Cmvs/ImageMSK.cs @@ -0,0 +1,67 @@ +//! \file ImageMSK.cs +//! \date Sat Dec 03 01:35:46 2016 +//! \brief Cvns engine grayscale 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; + +namespace GameRes.Formats.Purple +{ + [Export(typeof(ImageFormat))] + public class MskFormat : ImageFormat + { + public override string Tag { get { return "MSK0"; } } + public override string Description { get { return "Cvns engine grayscale image format"; } } + public override uint Signature { get { return 0x304B534D; } } // 'MSK0' + + public MskFormat () + { + Extensions = new string[] { "msk" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + return new ImageMetaData + { + Width = header.ToUInt32 (8), + Height = header.ToUInt32 (0xC), + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x10; + var pixels = file.ReadBytes ((int)info.Width * (int)info.Height); + return ImageData.Create (info, PixelFormats.Gray8, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("MskFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/Cmvs/ImagePB2.cs b/ArcFormats/Cmvs/ImagePB2.cs new file mode 100644 index 00000000..901aeaef --- /dev/null +++ b/ArcFormats/Cmvs/ImagePB2.cs @@ -0,0 +1,224 @@ +//! \file ImagePB2.cs +//! \date Fri Dec 02 23:35:44 2016 +//! \brief Cvns engine 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; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +namespace GameRes.Formats.Purple +{ + internal class Pb2MetaData : PbMetaData + { + public int Offset1; + public int Offset2; + public int FrameCount; + } + + [Export(typeof(ImageFormat))] + public class Pb2Format : ImageFormat + { + public override string Tag { get { return "PB2"; } } + public override string Description { get { return "CVNS engine image format"; } } + public override uint Signature { get { return 0x41324250; } } // 'PB2A' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x20); + file.Position = file.Length-27; + var key = file.ReadBytes (27); + for (int i = 8; i < 0x20; i += 2) + { + header[i] ^= key[24]; + header[i] -= key[i-8]; + header[i+1] ^= key[25]; + header[i+1] -= key[i-7]; + } + return new Pb2MetaData + { + InputSize = header.ToInt32 (4), + FrameCount = header.ToInt32 (8), + Type = header.ToUInt16 (0x10), + Width = header.ToUInt16 (0x12), + Height = header.ToUInt16 (0x14), + BPP = header.ToUInt16 (0x16), + Offset1 = header.ToInt32 (0x18), + Offset2 = header.ToInt32 (0x1C), + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Pb2Reader (file, (Pb2MetaData)info); + reader.Unpack(); + return ImageData.Create (info, reader.Format, null, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Pb2Format.Write not implemented"); + } + } + + internal sealed class Pb2Reader : PbReaderBase + { + int m_offset1; + int m_offset2; + int m_frame_count; + + public Pb2Reader (IBinaryStream input, Pb2MetaData info) : base (info) + { + if (32 == info.BPP || 4 == info.Type || 6 == info.Type) + Format = PixelFormats.Bgra32; + else + Format = PixelFormats.Bgr24; + m_input = new byte[input.Length]; + input.Read (m_input, 0, m_input.Length); + if (4 == info.Type || 6 == info.Type) + m_stride = (int)info.Width * 4; + else + m_stride = (int)m_info.Width * info.BPP / 8; + m_offset1 = info.Offset1; + m_offset2 = info.Offset2; + m_frame_count = info.FrameCount; + } + + public void Unpack () + { + switch (m_info.Type) + { + case 2: UnpackV2(); break; + case 4: UnpackJbp (0x20, m_offset1); break; + case 6: UnpackV6(); break; + case 1: + case 3: + case 5: + case 7: + default: throw new NotSupportedException(string.Format ("PB2 v{0} images not supported", m_info.Type)); + } + } + + void UnpackV2 () + { + m_output = new byte[m_stride * (int)m_info.Height]; + const int block_size = 8; + byte[] block_data = null; + int pixel_size = m_info.BPP / 8; + int width = (int)m_info.Width; + int height = (int)m_info.Height; + int w_block_count = (width + block_size - 1) / block_size; + int h_block_count = (height + block_size - 1) / block_size; + int ctl_offset = m_offset1 + pixel_size * 4; + int data_offset = m_offset2 + pixel_size * 4; + for (int c = 0; c < pixel_size; ++c) + { + int bit_src = ctl_offset + m_input.ToInt32 (ctl_offset) + m_input.ToInt32 (ctl_offset+4) + 12; + int unpacked_size = m_input.ToInt32 (ctl_offset+8); + if (null == block_data || block_data.Length < unpacked_size) + block_data = new byte[unpacked_size]; + LzssResetFrame(); + LzssUnpack (bit_src, data_offset, block_data, unpacked_size); + + byte bit_mask = 0x80; + int block_src = 0; + bit_src = ctl_offset + 12; + int src = ctl_offset + m_input.ToInt32 (ctl_offset) + 12; + int dst = c; + for (int y = 0, lBlockY = 0; lBlockY < h_block_count; y += block_size, lBlockY++) + { + int dst_origin = dst; + int max_height = Math.Min (height - y, block_size); + for (int x = 0, lBlockX = 0; lBlockX < w_block_count; x += block_size, lBlockX++) + { + if (0 == bit_mask) + { + bit_src++; + bit_mask = 0x80; + } + int dst3 = dst_origin; + int max_width = Math.Min (width - x, block_size); + if (0 != (bit_mask & m_input[bit_src])) + { + byte b = m_input[src++]; + for (int i = 0 ; i < max_height; i++) + { + for (int j = 0 ; j < max_width; j++) + m_output[dst3 + j * pixel_size] = b; + dst3 += m_stride; + } + } + else + { + for (int i = 0 ; i < max_height; i++) + { + for (int j = 0 ; j < max_width; j++) + m_output[dst3 + j * pixel_size] = block_data[block_src++]; + dst3 += m_stride; + } + } + dst_origin += block_size * pixel_size; + bit_mask >>= 1; + } + dst += m_stride * block_size; + } + ctl_offset += m_input.ToInt32 (m_offset1 + c * 4); + data_offset += m_input.ToInt32 (m_offset2 + c * 4); + } + } + + void UnpackV6 () + { + int channel_size = (int)m_info.Width * (int)m_info.Height; + m_output = new byte[4 * channel_size]; + int src = Array.IndexOf (m_input, 0, 0x24); + src = (src + 3) & ~3; + byte[][] channels = new byte[4][]; + + for (int i = 0 ; i < 4; ++i) + { + channels[i] = new byte[channel_size]; + int bit_src = src + 0x20 + m_input.ToInt32 (src + i * 8); + int data_src = src + 0x20 + m_input.ToInt32 (src + i * 8 + 4); + LzssResetFrame(); + LzssUnpack (bit_src, data_src, channels[i], channel_size); + } + int dst = 0; + for (int i = 0; i < channel_size; ++i) + { + m_output[dst ] = channels[0][i]; + m_output[dst+1] = channels[1][i]; + m_output[dst+2] = channels[2][i]; + m_output[dst+3] = channels[3][i]; + + byte d1 = (byte)(m_output[dst+2] ^ m_output[dst+3]); + byte d2 = (byte)(m_output[dst+1] ^ d1); + m_output[dst+2] = d1; + m_output[dst+1] = d2; + m_output[dst ] ^= d2; + dst += 4; + } + } + } +} diff --git a/supported.html b/supported.html index 95d33fda..7578f72a 100644 --- a/supported.html +++ b/supported.html @@ -904,16 +904,20 @@ Otto no Mae de Okasarete...
Gakuen Saimin Reido
*.akbAKBNo -*.cpzCPZ5
CPZ6NoCMVS +*.cpzCPZ2
CPZ5
CPZ6NoCMVS
CVNS +Alto
Amatsutsumi
Ashita no Kimi to Au Tame ni
Chrono Clock
Hapymaher
Haruiro Ouse
Memoria
+Miharu - Alto Another Story
Natsu ni Kanaderu Bokura no Uta
-*.pb3PB3BNo +*.pb2PB2ANo +*.pb3PB3BNo +*.mskMSK0No *.g2
*.stx-NoGLib2 Hikikomori Muke Hitozuma-sensei
Hitozuma Net Auction