mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-24 06:05:35 +08:00
298 lines
11 KiB
C#
298 lines
11 KiB
C#
|
//! \file ImagePSB.cs
|
||
|
//! \date Thu Jun 23 20:16:31 2016
|
||
|
//! \brief PVNS 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;
|
||
|
using GameRes.Utility;
|
||
|
|
||
|
namespace GameRes.Formats.Pvns
|
||
|
{
|
||
|
internal class PsbMetaData : ImageMetaData
|
||
|
{
|
||
|
public int Method;
|
||
|
public int TableOffset;
|
||
|
public int DataOffset;
|
||
|
}
|
||
|
|
||
|
[Export(typeof(ImageFormat))]
|
||
|
public class PsbFormat : ImageFormat
|
||
|
{
|
||
|
public override string Tag { get { return "PSB"; } }
|
||
|
public override string Description { get { return "PVNS engine image format"; } }
|
||
|
public override uint Signature { get { return 0x50425350; } } // 'PSBP'
|
||
|
|
||
|
public override ImageMetaData ReadMetaData (Stream stream)
|
||
|
{
|
||
|
var header = new byte[0x14];
|
||
|
if (header.Length != stream.Read (header, 0, header.Length))
|
||
|
return null;
|
||
|
stream.Seek (-0x13, SeekOrigin.End);
|
||
|
var tail = new byte[0x13];
|
||
|
stream.Read (tail, 0, tail.Length);
|
||
|
for (int i = 4; i < 0x14; ++i)
|
||
|
{
|
||
|
header[i] ^= tail[tail.Length - 3 + (i & 1)];
|
||
|
header[i] -= tail[i-4];
|
||
|
}
|
||
|
return new PsbMetaData
|
||
|
{
|
||
|
Width = LittleEndian.ToUInt16 (header, 0x0E),
|
||
|
Height = LittleEndian.ToUInt16 (header, 0x10),
|
||
|
BPP = LittleEndian.ToUInt16 (header, 0x12),
|
||
|
Method = LittleEndian.ToUInt16 (header, 0x0C),
|
||
|
TableOffset = LittleEndian.ToInt32 (header, 4),
|
||
|
DataOffset = LittleEndian.ToInt32 (header, 8),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public override ImageData Read (Stream stream, ImageMetaData info)
|
||
|
{
|
||
|
var reader = new PsbReader (stream, (PsbMetaData)info);
|
||
|
reader.Unpack();
|
||
|
return ImageData.Create (info, reader.Format, null, reader.Data);
|
||
|
}
|
||
|
|
||
|
public override void Write (Stream file, ImageData image)
|
||
|
{
|
||
|
throw new System.NotImplementedException ("PsbFormat.Write not implemented");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal sealed class PsbReader
|
||
|
{
|
||
|
byte[] m_input;
|
||
|
byte[] m_output;
|
||
|
PsbMetaData m_info;
|
||
|
int m_width;
|
||
|
int m_height;
|
||
|
int m_channels;
|
||
|
byte[] m_lzss_frame;
|
||
|
|
||
|
public byte[] Data { get { return m_output; } }
|
||
|
public PixelFormat Format { get; private set; }
|
||
|
|
||
|
public PsbReader (Stream input, PsbMetaData info)
|
||
|
{
|
||
|
m_info = info;
|
||
|
m_width = (int)m_info.Width;
|
||
|
m_height = (int)m_info.Height;
|
||
|
m_channels = m_info.BPP / 8;
|
||
|
if (4 == m_channels)
|
||
|
Format = PixelFormats.Bgra32;
|
||
|
else if (3 == m_channels)
|
||
|
Format = PixelFormats.Bgr24;
|
||
|
else
|
||
|
throw new InvalidFormatException();
|
||
|
m_input = new byte[input.Length];
|
||
|
input.Read (m_input, 0, m_input.Length);
|
||
|
m_output = new byte[info.Width * info.Height * m_channels];
|
||
|
m_lzss_frame = new byte[0x1000];
|
||
|
}
|
||
|
|
||
|
public void Unpack ()
|
||
|
{
|
||
|
switch (m_info.Method)
|
||
|
{
|
||
|
case 2: UnpackV2(); break;
|
||
|
case 3: UnpackV3(); break;
|
||
|
default:
|
||
|
throw new NotImplementedException (string.Format ("PSB images type {0} not implemented", m_info.Method));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UnpackV2 ()
|
||
|
{
|
||
|
int plane_size = m_width * m_height;
|
||
|
var plane = new byte[plane_size];
|
||
|
|
||
|
int src = m_info.TableOffset;
|
||
|
var bits_table = new int[m_channels];
|
||
|
int offset = m_info.TableOffset + 4 * m_channels;
|
||
|
for (int i = 0; i < m_channels; ++i)
|
||
|
{
|
||
|
bits_table[i] = offset;
|
||
|
offset += LittleEndian.ToInt32 (m_input, src);
|
||
|
src += 4;
|
||
|
}
|
||
|
src = offset;
|
||
|
offset += 4 * m_channels;
|
||
|
var data_table = new int[m_channels];
|
||
|
for (int i = 0; i < m_channels; ++i)
|
||
|
{
|
||
|
data_table[i] = offset;
|
||
|
offset += LittleEndian.ToInt32 (m_input, src);
|
||
|
src += 4;
|
||
|
}
|
||
|
for (int channel = 0; channel < m_channels; ++channel)
|
||
|
{
|
||
|
LzssUnpack (bits_table[channel], data_table[channel], plane, plane.Length);
|
||
|
int dst = channel;
|
||
|
byte pixel = 0;
|
||
|
for (int i = 0; i < plane_size; ++i)
|
||
|
{
|
||
|
pixel += plane[i];
|
||
|
m_output[dst] = pixel;
|
||
|
dst += m_channels;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UnpackV3 ()
|
||
|
{
|
||
|
int stride = m_width * m_channels;
|
||
|
var plane = new byte[m_width * m_height];
|
||
|
int y_blocks = m_height >> 3;
|
||
|
int x_blocks = m_width >> 3;
|
||
|
if (0 != (m_width & 7))
|
||
|
++x_blocks;
|
||
|
if (0 != (m_height & 7))
|
||
|
++y_blocks;
|
||
|
|
||
|
int src = m_info.TableOffset;
|
||
|
var bits_table = new int[m_channels];
|
||
|
int offset = m_info.TableOffset + 4 * m_channels;
|
||
|
for (int i = 0; i < m_channels; ++i)
|
||
|
{
|
||
|
bits_table[i] = offset;
|
||
|
offset += LittleEndian.ToInt32 (m_input, src);
|
||
|
src += 4;
|
||
|
}
|
||
|
src = m_info.DataOffset;
|
||
|
var data_offsets = new int[m_channels];
|
||
|
offset = m_info.DataOffset + 4 * m_channels;
|
||
|
for (int i = 0; i < m_channels; ++i)
|
||
|
{
|
||
|
data_offsets[i] = offset;
|
||
|
offset += LittleEndian.ToInt32 (m_input, src);
|
||
|
src += 4;
|
||
|
}
|
||
|
|
||
|
for (int channel = 0; channel < m_channels; ++channel)
|
||
|
{
|
||
|
int dst = channel;
|
||
|
|
||
|
src = bits_table[channel];
|
||
|
int bit_length = LittleEndian.ToInt32 (m_input, src);
|
||
|
int bit_src = src + 12 + bit_length + LittleEndian.ToInt32 (m_input, src+4);
|
||
|
int plane_size = LittleEndian.ToInt32 (m_input, src+8);
|
||
|
|
||
|
LzssUnpack (bit_src, data_offsets[channel], plane, plane_size);
|
||
|
|
||
|
int plane_src = 0;
|
||
|
bit_src = src + 12;
|
||
|
src = bit_src + bit_length;
|
||
|
int bit_mask = 128;
|
||
|
int y_pos = 0;
|
||
|
for (int y = 0; y < y_blocks; ++y)
|
||
|
{
|
||
|
int block_height = Math.Min (8, m_height - y_pos);
|
||
|
y_pos += 8;
|
||
|
int x_pos = 0;
|
||
|
int dst_origin = dst;
|
||
|
for (int x = 0; x < x_blocks; ++x)
|
||
|
{
|
||
|
int block_width = Math.Min (8, m_width - x_pos);
|
||
|
x_pos += 8;
|
||
|
if (0 == bit_mask)
|
||
|
{
|
||
|
++bit_src;
|
||
|
bit_mask = 128;
|
||
|
}
|
||
|
if (0 != (bit_mask & m_input[bit_src]))
|
||
|
{
|
||
|
byte b = m_input[src++];
|
||
|
for (int j = 0; j < block_height; ++j)
|
||
|
{
|
||
|
int d = dst + stride * j;
|
||
|
for (int i = 0; i < block_width; ++i)
|
||
|
{
|
||
|
m_output[d] = b;
|
||
|
d += m_channels;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int j = 0; j < block_height; ++j)
|
||
|
{
|
||
|
int d = dst + stride * j;
|
||
|
for (int i = 0; i < block_width; ++i)
|
||
|
{
|
||
|
m_output[d] = plane[plane_src++];
|
||
|
d += m_channels;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
bit_mask >>= 1;
|
||
|
dst += 8 * m_channels;
|
||
|
}
|
||
|
dst = dst_origin + 8 * stride;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void LzssUnpack (int bit_src, int data_src, byte[] output, int output_size)
|
||
|
{
|
||
|
for (int i = 0; i < m_lzss_frame.Length; ++i)
|
||
|
m_lzss_frame[i] = 0;
|
||
|
int dst = 0;
|
||
|
int bit_mask = 0x80;
|
||
|
int frame_offset = 0xFEE;
|
||
|
while (dst < output_size)
|
||
|
{
|
||
|
if (0 == bit_mask)
|
||
|
{
|
||
|
bit_mask = 0x80;
|
||
|
++bit_src;
|
||
|
}
|
||
|
if (0 != (bit_mask & m_input[bit_src]))
|
||
|
{
|
||
|
int v = LittleEndian.ToUInt16 (m_input, data_src);
|
||
|
data_src += 2;
|
||
|
int count = (v & 0xF) + 3;
|
||
|
int offset = v >> 4;
|
||
|
for (int i = 0; i < count; ++i)
|
||
|
{
|
||
|
byte b = m_lzss_frame[(i + offset) & 0xFFF];
|
||
|
output[dst++] = b;
|
||
|
m_lzss_frame[frame_offset++] = b;
|
||
|
frame_offset &= 0xFFF;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
byte b = m_input[data_src++];
|
||
|
output[dst++] = b;
|
||
|
m_lzss_frame[frame_offset++] = b;
|
||
|
frame_offset &= 0xFFF;
|
||
|
}
|
||
|
bit_mask >>= 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|