//! \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; } } } }