diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 8defd977..8c9ce15a 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -125,7 +125,8 @@ - + + @@ -216,7 +217,6 @@ - diff --git a/Legacy/StudioJikkenshitsu/ImageGRD.cs b/Legacy/StudioJikkenshitsu/ImageGRD.cs index 3e79f1c3..28545222 100644 --- a/Legacy/StudioJikkenshitsu/ImageGRD.cs +++ b/Legacy/StudioJikkenshitsu/ImageGRD.cs @@ -23,9 +23,13 @@ // IN THE SOFTWARE. // +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; +using System.Windows.Media.Imaging; +using GameRes.Compression; +using GameRes.Utility; // [030411][Studio Jikkenshitsu] Giin Oyako @@ -33,9 +37,10 @@ namespace GameRes.Formats.Jikkenshitsu { internal class GrdMetaData : ImageMetaData { - public int PackedLength; - public int AlphaLength; - public bool IsEncrypted; + public int PackedLength; + public int AlphaLength; + public bool IsEncrypted; + public byte[] Key; } [Export(typeof(ImageFormat))] @@ -45,6 +50,9 @@ namespace GameRes.Formats.Jikkenshitsu public override string Description { get { return "Studio Jikkenshitsu image format"; } } public override uint Signature { get { return 0x20445247; } } // 'GRD ' + // Giin Oyako + static readonly byte[] DefaultKey = { 0xF, 0, 1, 2, 8, 5, 0xA, 0xB, 5, 9, 0xE, 0xD, 1, 8, 0, 6 }; + public override ImageMetaData ReadMetaData (IBinaryStream file) { var header = file.ReadHeader (0x18); @@ -53,16 +61,16 @@ namespace GameRes.Formats.Jikkenshitsu Height = header.ToUInt16 (8), BPP = header[4], PackedLength = header.ToInt32 (0xC), - AlphaLength = header.ToInt32 (0x10), + AlphaLength = header.ToInt32 (0x14), IsEncrypted = (header[5] & 0x80) != 0, + Key = DefaultKey, }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { var reader = new GrdReader (file, (GrdMetaData)info); - var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, null, pixels); + return reader.Unpack(); } public override void Write (Stream file, ImageData image) @@ -77,54 +85,102 @@ namespace GameRes.Formats.Jikkenshitsu GrdMetaData m_info; byte[] m_output; - public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + public PixelFormat Format { get; private set; } + public int Stride { get; private set; } public GrdReader (IBinaryStream input, GrdMetaData info) { m_input = input; m_info = info; if (8 == m_info.BPP) - Format = PixelFormats.Gray8; + Format = PixelFormats.Indexed8; else if (24 == m_info.BPP) Format = PixelFormats.Bgr24; else throw new InvalidFormatException(); - int image_size = (int)m_info.Width * (int)m_info.Height; - if (m_info.BPP >= 8) - image_size *= (m_info.BPP + 1) / 8; - else - image_size = image_size * m_info.BPP / 8; - m_output = new byte[image_size]; + int width = (int)m_info.Width; + if (m_info.BPP <= 8) + width = (width + 3) & ~3; + Stride = width * m_info.BPP / 8; + m_output = new byte[Stride * (int)m_info.Height]; } - public byte[] Unpack () + public ImageData Unpack () { - m_input.Position = 0x18; - var input = m_input.ReadBytes (m_info.PackedLength); + Stream input = new StreamRegion (m_input.AsStream, 0x18, m_info.PackedLength, true); if (m_info.IsEncrypted) - DecryptData (input, 0, input.Length & -8); - using (var mem = new MemoryStream (input)) - using (var lzss = new LzssStream (mem)) - lzss.Read (m_output, 0, m_output.Length); - if (m_info.AlphaLength > 0) + input = new InputCryptoStream (input, new SjTransform (m_info.Key)); + using (input = new LzssStream (input)) { + var header = new byte[0x28]; + input.Read (header, 0, header.Length); + if (8 == m_info.BPP) + Palette = ImageFormat.ReadPalette (input); + input.Read (m_output, 0, m_output.Length); + } + if (m_info.AlphaLength > 0 && m_info.BPP == 8) + { + m_input.Position = 0x18 + m_info.PackedLength; var alpha = new byte[m_info.AlphaLength]; using (var lzss = new LzssStream (m_input.AsStream, LzssMode.Decompress, true)) lzss.Read (alpha, 0, alpha.Length); + return ApplyAlpha (alpha); } - return m_output; + return ImageData.CreateFlipped (m_info, Format, Palette, m_output, Stride); } - void DecryptData (byte[] data, int pos, int length) + ImageData ApplyAlpha (byte[] alpha) { - for (int i = 0; i < length; i += 8) + int width = (int)m_info.Width; + int height = (int)m_info.Height; + int dst_stride = width * 4; + var pixels = new byte[dst_stride * height]; + var colors = Palette.Colors; + + var row_table = new ushort[height]; + int row_table_size = row_table.Length * sizeof(ushort); + Buffer.BlockCopy (alpha, 0, row_table, 0, row_table_size); + var lines = new int[height]; + int lines_size = lines.Length * sizeof(int); + Buffer.BlockCopy (alpha, row_table_size, lines, 0, lines_size); + + int alpha_pos = row_table_size + lines_size; + int src = m_output.Length - Stride; + int dst_row = 0; + for (int y = 0; y < height; ++y) { - DecryptBlock (data, pos + i); + int dst = dst_row; + for (int x = 0; x < width; ++x) + { + byte code = m_output[src+x]; + var color = colors[code]; + pixels[dst++] = color.B; + pixels[dst++] = color.G; + pixels[dst++] = color.R; + pixels[dst++] = (byte)(code != 0 ? 0xFF : 0); + } + int asrc = alpha_pos + 4 * lines[y]; + for (int i = 0; i < row_table[y]; ++i) + { + byte a = Math.Min (alpha[asrc+3], (byte)0xF); + dst = dst_row + LittleEndian.ToUInt16 (alpha, asrc) * 4; + if (a > 0) + { + var color = colors[alpha[asrc+2]]; + pixels[dst ] = color.B; + pixels[dst+1] = color.G; + pixels[dst+2] = color.R; + pixels[dst+3] = (byte)(a * 0x11); + } + else + pixels[dst+3] = 0; + asrc += 4; + } + dst_row += dst_stride; + src -= Stride; } - } - - void DecryptBlock (byte[] data, int pos) - { + return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, dst_stride); } } }