diff --git a/ArcFormats/Kaas/ArcKAAS.cs b/ArcFormats/Kaas/ArcKAAS.cs index 25ba01fe..60c7b73b 100644 --- a/ArcFormats/Kaas/ArcKAAS.cs +++ b/ArcFormats/Kaas/ArcKAAS.cs @@ -2,7 +2,7 @@ //! \date Fri Apr 10 15:21:30 2015 //! \brief KAAS engine archive format implementation. // -// Copyright (C) 2015 by morkt +// Copyright (C) 2015-2017 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 @@ -27,10 +27,21 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Windows.Media; using GameRes.Utility; namespace GameRes.Formats.KAAS { + internal interface IIndexDecryptor + { + void Decrypt (byte[] data, byte key); + } + + internal class PdImageEntry : Entry + { + public int Number; + } + [Export(typeof(ArchiveFormat))] public class PdOpener : ArchiveFormat { @@ -50,48 +61,155 @@ namespace GameRes.Formats.KAAS int index_offset = file.View.ReadByte (0); if (index_offset <= 2 || index_offset >= file.MaxOffset) return null; - int key = file.View.ReadByte (1); + byte key = file.View.ReadByte (1); int count = 0xfff & file.View.ReadUInt16 (index_offset); if (0 == count) return null; index_offset += 16; - - byte[] index = new byte[count*8]; - if (index.Length != file.View.Read (index_offset, index, 0, (uint)(index.Length))) - return null; - DecryptIndex (index, key); - + var index = new byte[count*8]; + var base_name = Path.GetFileNameWithoutExtension (file.Name); var dir = new List (count); int data_offset = index_offset + index.Length; - index_offset = 0; + + foreach (var decryptor in KnownDecryptors) + { + if (index.Length != file.View.Read (index_offset, index, 0, (uint)(index.Length))) + return null; + decryptor.Decrypt (index, key); + try + { + if (ReadIndex (index, dir, base_name, data_offset, file.MaxOffset) + && dir.Count > 0) + return new ArcFile (file, this, dir); + } + catch { /* ignore errors caused by wrong decrpytor */ } + dir.Clear(); + } + return null; + } + + bool ReadIndex (byte[] index, List dir, string base_name, long data_offset, long max_offset) + { + int index_offset = 0; + int count = index.Length / 8; for (int i = 0; i < count; ++i) { uint offset = LittleEndian.ToUInt32 (index, index_offset); uint size = LittleEndian.ToUInt32 (index, index_offset+4); - if (offset < data_offset || offset >= file.MaxOffset) - return null; - var entry = new Entry { - Name = string.Format ("{0:D4}.pic", i), - Type = "image", - Offset = offset, - Size = size, - }; - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - dir.Add (entry); + if (offset < data_offset || offset >= max_offset) + return false; + if (size > 0) + { + var entry = new PdImageEntry { + Name = string.Format ("{0}#{1:D4}", base_name, i), + Type = "image", + Offset = offset, + Size = size, + Number = dir.Count + }; + if (!entry.CheckPlacement (max_offset)) + return false; + dir.Add (entry); + } index_offset += 8; } - return new ArcFile (file, this, dir); + return true; } - private void DecryptIndex (byte[] index, int key) + static readonly IEnumerable KnownDecryptors = new IIndexDecryptor[] { + new DiscoveryDecryptor(), + new OldDecryptor(), + }; + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) { - for (int i = 0; i != index.Length; ++i) + byte pic_method = arc.File.View.ReadByte (entry.Offset); + if (pic_method != 1 && pic_method != 2) + return base.OpenImage (arc, entry); + var pent = (PdImageEntry)entry; + byte[] baseline = null; + var dir = arc.Dir as List; + for (int i = pent.Number-1; i >= 0; --i) { - int k = i + 14; - int r = 9 - (k & 7) * (k + 5) * key * 0x77; -// int r = ((k * 0x6b) % (k / 2 + 1)) + key * 0x3b * (k + 11) * (k % (k + 17)); - index[i] -= (byte)r; + var base_entry = dir[i]; + byte base_method = arc.File.View.ReadByte (base_entry.Offset); + if (base_method != 1 && base_method != 2) + { + PicMetaData base_info; + using (var base_input = arc.File.CreateStream (base_entry.Offset, base_entry.Size)) + { + base_info = s_picFormat.Value.ReadMetaData (base_input) as PicMetaData; + if (null == base_info) + throw new InvalidFormatException(); + using (var reader = new PicFormat.Reader (base_input, base_info)) + { + reader.Unpack(); + baseline = reader.Data; + } + } + var overlay_info = new PdOverlayMetaData { + Width = base_info.Width, + Height = base_info.Height, + BPP = 24, + Method = pic_method, + ChunkCount = arc.File.View.ReadInt32 (pent.Offset+4), + }; + var input = arc.File.CreateStream (pent.Offset, pent.Size); + return new PdOverlayImage (input, overlay_info, baseline); + } + } + return base.OpenImage (arc, entry); + } + + static readonly Lazy s_picFormat = new Lazy (() => ImageFormat.FindByTag ("PIC/KAAS")); + } + + internal class PdOverlayMetaData : ImageMetaData + { + public int Method; + public int ChunkCount; + } + + internal class PdOverlayImage : BinaryImageDecoder + { + int m_method; + int m_chunk_count; + byte[] m_baseline; + + public PdOverlayImage (IBinaryStream input, PdOverlayMetaData info, byte[] baseline) : base (input, info) + { + m_method = info.Method; + m_chunk_count = info.ChunkCount; + m_baseline = baseline; + } + + protected override ImageData GetImageData () + { + m_input.Position = 8; + if (1 == m_method) + ReadOverlayV1(); + else + ReadOverlayV2(); + return ImageData.Create (Info, PixelFormats.Bgr24, null, m_baseline); + } + + void ReadOverlayV1 () + { + for (int i = 0; i < m_chunk_count; ++i) + { + int dst = m_input.ReadInt24() * 3; + m_input.Read (m_baseline, dst, 3); + } + } + + void ReadOverlayV2 () + { + for (int i = 0; i < m_chunk_count; ++i) + { + int code = m_input.ReadInt24(); + int dst = (code & 0x7FFFF) * 3; + int count = ((code >> 19) + 1) * 3; + m_input.Read (m_baseline, dst, count); } } } @@ -100,7 +218,7 @@ namespace GameRes.Formats.KAAS public class PbOpener : ArchiveFormat { public override string Tag { get { return "PB"; } } - public override string Description { get { return "KAAS engine PB resource archive"; } } + public override string Description { get { return "KAAS engine audio archive"; } } public override uint Signature { get { return 0; } } public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } @@ -121,7 +239,7 @@ namespace GameRes.Formats.KAAS uint offset = file.View.ReadUInt32 (index_offset); Entry entry; if (!is_voice) - entry = AutoEntry.Create (file, offset, i.ToString ("D4")); + entry = new Entry { Name = i.ToString ("D4"), Type = "audio", Offset = offset }; else entry = new Entry { Name = string.Format ("{0:D4}.pb", i), Type = "archive", Offset = offset }; entry.Size = file.View.ReadUInt32 (index_offset + 4); @@ -133,4 +251,30 @@ namespace GameRes.Formats.KAAS return new ArcFile (file, this, dir); } } + + internal sealed class OldDecryptor : IIndexDecryptor + { + public void Decrypt (byte[] data, byte key) + { + for (int i = 0; i != data.Length; ++i) + { + int k = i + 14; + int r = 9 - (k & 7) * (k + 5) * key * 0x77; + data[i] -= (byte)r; + } + } + } + + internal sealed class DiscoveryDecryptor : IIndexDecryptor + { + public void Decrypt (byte[] data, byte key) + { + for (int i = 0; i != data.Length; ++i) + { + int k = i + 14; + int r = ((k * 0x6b) % (k / 2 + 1)) + key * 0x3b * (k + 11) * (k % (k + 17)); + data[i] -= (byte)r; + } + } + } } diff --git a/ArcFormats/Kaas/ImageKAAS.cs b/ArcFormats/Kaas/ImageKAAS.cs index b86a0a0f..1f9268ae 100644 --- a/ArcFormats/Kaas/ImageKAAS.cs +++ b/ArcFormats/Kaas/ImageKAAS.cs @@ -2,7 +2,7 @@ //! \date Sat Apr 11 03:09:41 2015 //! \brief KAAS engine image format implementation. // -// Copyright (C) 2015 by morkt +// Copyright (C) 2015-2017 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 @@ -46,10 +46,15 @@ namespace GameRes.Formats.KAAS [Export(typeof(ImageFormat))] public class PicFormat : ImageFormat { - public override string Tag { get { return "PIC"; } } + public override string Tag { get { return "PIC/KAAS"; } } public override string Description { get { return "KAAS engine image format"; } } public override uint Signature { get { return 0; } } + public PicFormat () + { + Extensions = new string[] { "" }; + } + public override void Write (Stream file, ImageData image) { throw new NotImplementedException ("PicFormat.Write not implemented"); @@ -57,26 +62,24 @@ namespace GameRes.Formats.KAAS public override ImageMetaData ReadMetaData (IBinaryStream stream) { - int mode = stream.ReadByte(); + var header = stream.ReadHeader (0x12); + int mode = header[0]; switch (mode) { - case 5: case 6: + case 5: case 6: case 8: case 9: break; default: return null; } - int key = stream.ReadByte(); - var header = stream.ReadBytes (0x10); - if (header.Length != 0x10) - return null; - uint width = LittleEndian.ToUInt16 (header, 0); - uint height = LittleEndian.ToUInt16 (header, 2); + int key = header[1]; + uint width = LittleEndian.ToUInt16 (header, 2); + uint height = LittleEndian.ToUInt16 (header, 4); if (0 == width || width > 4096 || 0 == height || height > 4096) return null; var file_len = stream.Length; - uint comp_size1 = LittleEndian.ToUInt32 (header, 6); - uint comp_size2 = LittleEndian.ToUInt32 (header, 10); - uint comp_size3 = LittleEndian.ToUInt16 (header, 14); + uint comp_size1 = LittleEndian.ToUInt32 (header, 8); + uint comp_size2 = LittleEndian.ToUInt32 (header, 12); + uint comp_size3 = LittleEndian.ToUInt16 (header, 16); if (comp_size1 >= file_len || comp_size2 >= file_len || comp_size3 >= file_len) return null; return new PicMetaData @@ -94,15 +97,14 @@ namespace GameRes.Formats.KAAS public override ImageData Read (IBinaryStream stream, ImageMetaData info) { - stream.Position = 0x12; - using (var reader = new Reader (stream.AsStream, (PicMetaData)info)) + using (var reader = new Reader (stream, (PicMetaData)info)) { reader.Unpack(); - return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data, (int)info.Width*3); + return ImageData.Create (info, reader.Format, null, reader.Data, reader.Stride); } } - internal class Reader : IDisposable + internal sealed class Reader : IDisposable { byte[] m_comp0; byte[] m_comp1; @@ -111,12 +113,16 @@ namespace GameRes.Formats.KAAS int m_mode; int m_key; - public byte[] Data { get { return m_output; } } + public PixelFormat Format { get { return PixelFormats.Bgr24; } } + public byte[] Data { get { return m_output; } } + public int Stride { get; private set; } - public Reader (Stream file, PicMetaData info) + public Reader (IBinaryStream file, PicMetaData info) { m_mode = info.Mode; m_key = info.Key; + Stride = (int)info.Width * 3; + file.Position = 0x12; if (6 == info.Mode) { uint out_len = info.Width * info.Height * 3; @@ -149,6 +155,8 @@ namespace GameRes.Formats.KAAS { case 5: Unpack5(); break; case 6: Unpack6(); break; + case 8: Unpack8(); break; + case 9: Unpack9(); break; default: throw new NotSupportedException ("[KAAS] Not supported image compression"); } @@ -156,54 +164,52 @@ namespace GameRes.Formats.KAAS private void Unpack5 () { - int i = 0; - int src = 0; + int src0 = 0; + int src1 = 0; + int src2 = 0; int dst = 0; int ctl = 1; - int x = 0; - for (;;) { -// int type = (m_comp0[x/4] >> (x & 3)*2) & 3; if (1 == ctl) { - if (x == m_comp0.Length) + if (src0 == m_comp0.Length) break; - ctl = m_comp0[x++] | 0x100; + ctl = m_comp0[src0++] | 0x100; } int type = ctl & 3; ctl >>= 2; - int count, off; + int count, offset; switch (type) { case 0: - m_output[dst++] = m_comp1[src++]; + m_output[dst++] = m_comp1[src1++]; break; case 1: - count = ((m_comp2[i / 2] >> (4 * (i & 1))) & 0xf) + 2; - off = m_comp1[src++] + 2; - Binary.CopyOverlapped (m_output, dst-off, dst, count); + count = ((m_comp2[src2 / 2] >> (4 * (src2 & 1))) & 0xf) + 2; + ++src2; + offset = m_comp1[src1++] + 2; + Binary.CopyOverlapped (m_output, dst-offset, dst, count); dst += count; - ++i; break; case 2: - count = LittleEndian.ToUInt16 (m_comp1, src); + count = LittleEndian.ToUInt16 (m_comp1, src1); if (0 == count) return; - off = (count & 0xfff) + 2; + src1 += 2; + offset = (count & 0xfff) + 2; count = (count >> 12) + 2; - Binary.CopyOverlapped (m_output, dst-off, dst, count); + Binary.CopyOverlapped (m_output, dst-offset, dst, count); dst += count; - src += 2; break; default: - off = (((m_comp2[i / 2] << (4 * (2 - (i & 1)))) & 0xf00) | m_comp1[src]) + 2; - count = m_comp1[src+1] + 18; - Binary.CopyOverlapped (m_output, dst-off, dst, count); + offset = (((m_comp2[src2 / 2] << (4 * (2 - (src2 & 1)))) & 0xf00) | m_comp1[src1]) + 2; + count = m_comp1[src1+1] + 18; + ++src2; + src1 += 2; + Binary.CopyOverlapped (m_output, dst-offset, dst, count); dst += count; - src += 2; - ++i; break; } } @@ -216,6 +222,108 @@ namespace GameRes.Formats.KAAS m_output[i] -= ScrambleTable[conv_base + (i&0xff)]; } + private void Unpack8 () + { + int src0 = 0; + int src1 = 0; + int src2 = 0; + int dst = 0; + for (;;) + { + int count, offset; + int type = (m_comp0[src0 / 8] >> (src0 & 6)) & 3; + src0 += 2; + if (type == 0) + { + m_output[dst++] = m_comp1[src1++]; + m_output[dst++] = m_comp1[src1++]; + m_output[dst++] = m_comp1[src1++]; + } + else if (1 == type) + { + int code = m_comp1[src1++] + ((m_comp2[src2 / 2] << (4 * (2 - (src2 & 1)))) & 0xf00); + ++src2; + count = ((code >> 10) + 1) * 3; + offset = ((code & 0x3FF) + 1) * 3; + Binary.CopyOverlapped (m_output, dst-offset, dst, count); + dst += count; + } + else if (2 == type) + { + int code = LittleEndian.ToUInt16 (m_comp1, src1); + src1 += 2; + count = ((code >> 14) + 1) * 3; + offset = ((code & 0x3FFF) + 1) * 3; + Binary.CopyOverlapped (m_output, dst-offset, dst, count); + dst += count; + } + else + { + int code = ((m_comp2[src2 / 2] << (4 * (4 - (src2 & 1)))) & 0xF0000) | LittleEndian.ToUInt16 (m_comp1, src1); + if (0 == code) + break; + src1 += 2; + ++src2; + count = ((code >> 14) + 5) * 3; + offset = ((code & 0x3FFF) + 1) * 3; + Binary.CopyOverlapped (m_output, dst-offset, dst, count); + dst += count; + } + } + } + + private void Unpack9 () + { + int src0 = 0; + int src1 = 0; + int src2 = 0; + int dst = 0; + for (;;) + { + int count, offset; + int type = (m_comp0[src0 / 8] >> (src0 & 6)) & 3; + src0 += 2; + if (0 == type) + { + count = (m_comp0[src0 / 8] >> (src0 & 6)) & 3; + src0 += 2; + count = (count + 1) * 3; + Buffer.BlockCopy (m_comp1, src1, m_output, dst, count); + dst += count; + src1 += count; + } + else + { + if (1 == type) + { + int code = m_comp1[src1++] + ((m_comp2[src2 / 2] << (4 * (2 - (src2 & 1)))) & 0xF00); + ++src2; + count = ((code >> 10) + 1) * 3; + offset = ((code & 0x3FF) + 1) * 3; + } + else if (2 == type) + { + int code = LittleEndian.ToUInt16 (m_comp1, src1); + src1 += 2; + count = ((code >> 14) + 1) * 3; + offset = ((code & 0x3FFF) + 1) * 3; + } + else + { + int code = ((m_comp2[src2 / 2] << (4 * (4 - (src2 & 1)))) & 0xf0000) | LittleEndian.ToUInt16 (m_comp1, src1); + if (0 == code) + break; + src1 += 2; + ++src2; + count = ((code >> 14) + 5) * 3; + offset = ((code & 0x3FFF) + 1) * 3; + } + Binary.CopyOverlapped (m_output, dst-offset, dst, count); + dst += count; + } + } + } + static readonly byte[] ScrambleTable = new byte[] { 0x29, 0x23, 0xbe, 0x84, 0xe1, 0x6c, 0xd6, 0xae, 0x52, 0x90, 0x49, 0xf1, 0xf1, 0xbb, 0xe9, 0xeb, 0xb3, 0xa6, 0xdb, 0x3c, 0x87, 0x0c, 0x3e, 0x99, 0x24, 0x5e, 0x0d, 0x1c, 0x06, 0xb7, 0x47, 0xde, @@ -248,5 +356,3 @@ namespace GameRes.Formats.KAAS } } } - -