//! \file ArcWARC.cs //! \date Fri Apr 10 03:10:42 2015 //! \brief ShiinaRio engine archive format. // // Copyright (C) 2015-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.Collections.Generic; using System.ComponentModel.Composition; using System.IO; using System.Linq; using GameRes.Compression; using GameRes.Formats.Properties; using GameRes.Formats.Strings; using GameRes.Utility; namespace GameRes.Formats.ShiinaRio // 椎名里緒 { internal class WarOptions : ResourceOptions { public EncryptionScheme Scheme { get; set; } } internal class WarcEntry : PackedEntry { public long FileTime; public uint Flags; } internal class WarcFile : ArcFile { public readonly Decoder Decoder; public WarcFile (ArcView arc, ArchiveFormat impl, ICollection dir, Decoder decoder) : base (arc, impl, dir) { this.Decoder = decoder; } } [Serializable] public class WarcScheme : ResourceScheme { public EncryptionScheme[] KnownSchemes; } [Export(typeof(ArchiveFormat))] public class WarOpener : ArchiveFormat { public override string Tag { get { return "WAR"; } } public override string Description { get { return "ShiinaRio engine resource archive"; } } public override uint Signature { get { return 0x43524157; } } // 'WARC' public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } public override ResourceScheme Scheme { get { return new WarcScheme { KnownSchemes = Decoder.KnownSchemes }; } set { Decoder.KnownSchemes = ((WarcScheme)value).KnownSchemes; } } public override ArcFile TryOpen (ArcView file) { if (!file.View.AsciiEqual (4, " 1.")) return null; int version = file.View.ReadByte (7) - 0x30; version = 100 + version * 10; if (170 != version && 150 != version && 140 != version && 130 != version && 120 != version) throw new NotSupportedException ("Not supported WARC version"); uint index_offset = 0xf182ad82u ^ file.View.ReadUInt32 (8); if (index_offset >= file.MaxOffset) return null; var scheme = QueryEncryption (file.Name); if (null == scheme) return null; var decoder = new Decoder (version, scheme); uint max_index_len = decoder.MaxIndexLength; uint index_length = (uint)Math.Min (max_index_len, file.MaxOffset - index_offset); if (index_length < 8) return null; var enc_index = new byte[max_index_len]; if (index_length != file.View.Read (index_offset, enc_index, 0, index_length)) return null; decoder.DecryptIndex (index_offset, enc_index); Stream index; if (version >= 170) { if (0x78 != enc_index[8]) // check if it looks like ZLib stream return null; var zindex = new MemoryStream (enc_index, 8, (int)index_length-8); index = new ZLibStream (zindex, CompressionMode.Decompress); } else { var unpacked = new byte[max_index_len]; index_length = UnpackRNG (enc_index, 0, index_length, unpacked); if (0 == index_length) return null; index = new MemoryStream (unpacked, 0, (int)index_length); } using (var header = new BinaryReader (index)) { byte[] name_buf = new byte[decoder.EntryNameSize]; var dir = new List (); var unique_names = new HashSet(); while (name_buf.Length == header.Read (name_buf, 0, name_buf.Length)) { var name = Binary.GetCString (name_buf, 0, name_buf.Length); var entry = FormatCatalog.Instance.Create (name); entry.Offset = header.ReadUInt32(); entry.Size = header.ReadUInt32(); if (!entry.CheckPlacement (file.MaxOffset)) return null; entry.UnpackedSize = header.ReadUInt32(); entry.IsPacked = entry.Size != entry.UnpackedSize; entry.FileTime = header.ReadInt64(); entry.Flags = header.ReadUInt32(); if (0 != name.Length && unique_names.Add (name)) dir.Add (entry); } if (0 == dir.Count) return null; return new WarcFile (file, this, dir, decoder); } } public override Stream OpenEntry (ArcFile arc, Entry entry) { var warc = arc as WarcFile; var wentry = entry as WarcEntry; if (null == warc || null == wentry || entry.Size < 8) return arc.File.CreateStream (entry.Offset, entry.Size); var enc_data = new byte[entry.Size]; if (entry.Size != arc.File.View.Read (entry.Offset, enc_data, 0, entry.Size)) return Stream.Null; uint sig = LittleEndian.ToUInt32 (enc_data, 0); uint unpacked_size = LittleEndian.ToUInt32 (enc_data, 4); sig ^= (unpacked_size ^ 0x82AD82) & 0xffffff; if (entry.Size > 8) { if (0 != (wentry.Flags & 0x80000000u)) // encrypted entry warc.Decoder.Decrypt (enc_data, 8, entry.Size-8); if (warc.Decoder.ExtraCrypt != null) warc.Decoder.ExtraCrypt.Decrypt (enc_data, 8, entry.Size-8, 0x202); if (0 != (wentry.Flags & 0x20000000u)) warc.Decoder.Decrypt2 (enc_data, 8, entry.Size-8); } byte[] unpacked = enc_data; UnpackMethod unpack = null; switch (sig & 0xffffff) { case 0x314859: // 'YH1' unpack = UnpackYH1; break; case 0x4b5059: // 'YPK' unpack = UnpackYPK; break; case 0x5a4c59: // 'YLZ' unpack = UnpackYLZ; break; } if (null != unpack) { unpacked = new byte[unpacked_size]; unpack (enc_data, unpacked); if (0 != (wentry.Flags & 0x40000000)) warc.Decoder.Decrypt2 (unpacked, 0, (uint)unpacked.Length); if (warc.Decoder.ExtraCrypt != null) warc.Decoder.ExtraCrypt.Decrypt (unpacked, 0, (uint)unpacked.Length, 0x204); } return new BinMemoryStream (unpacked, entry.Name); } delegate void UnpackMethod (byte[] input, byte[] output); void UnpackYH1 (byte[] input, byte[] output) { if (0 != input[3]) { uint key = 0x6393528e^0x4b4du; // 'KM' unsafe { fixed (byte* buf_raw = input) { uint* encoded = (uint*)buf_raw; int i; for (i = 2; i < input.Length/4; ++i) encoded[i] ^= key; } } } var decoder = new HuffmanReader (input, 8, input.Length-8, output); decoder.Unpack(); } void UnpackYPK (byte[] input, byte[] output) { if (0 != input[3]) { uint key = ~0x4b4d4b4du; // 'KMKM' unsafe { fixed (byte* buf_raw = input) { uint* encoded = (uint*)buf_raw; int i; for (i = 2; i < input.Length/4; ++i) encoded[i] ^= key; for (i *= 4; i < input.Length; ++i) buf_raw[i] ^= (byte)key; } } } if (0x78 != input[8]) throw new ApplicationException ("Invalid decryption scheme"); var src = new MemoryStream (input, 8, input.Length-8); using (var zlib = new ZLibStream (src, CompressionMode.Decompress)) zlib.Read (output, 0, output.Length); } void UnpackYLZ (byte[] input, byte[] output) { if (0 != input[3]) { uint key = 0x4b4d4b4du; // 'KMKM' unsafe { fixed (byte* buf_raw = input) { uint* encoded = (uint*)buf_raw; int i; for (i = 2; i < input.Length/4; ++i) encoded[i] ^= key; for (i *= 4; i < input.Length; ++i) { buf_raw[i] ^= (byte)key; key >>= 8; } } } } var decoder = new YlzReader (input, 8, output); decoder.Unpack(); } uint UnpackRNG (byte[] input, int in_start, uint input_size, byte[] output) { var coder = new Kogado.CRangeCoder(); coder.InitQSModel (257, 12, 2000, null, false); return coder.Decode (output, 0, (uint)output.Length, input, (uint)in_start, input_size); } public override ResourceOptions GetDefaultOptions () { return new WarOptions { Scheme = GetScheme (Settings.Default.WARCScheme), }; } public override object GetAccessWidget () { return new GUI.WidgetWARC(); } EncryptionScheme QueryEncryption (string arc_name) { EncryptionScheme scheme = null; var title = FormatCatalog.Instance.LookupGame (arc_name); if (!string.IsNullOrEmpty (title)) scheme = GetScheme (title); if (null == scheme) { var options = Query (arcStrings.ArcEncryptedNotice); scheme = options.Scheme; } return scheme; } static EncryptionScheme GetScheme (string scheme) { return Decoder.KnownSchemes.FirstOrDefault (s => s.Name == scheme); } } internal class YlzReader { byte[] m_input; byte[] m_output; int m_src; uint m_ctl = 0; uint m_mask = 0; public YlzReader (byte[] input, int src_offset, byte[] output) { m_input = input; m_src = src_offset; m_output = output; } bool GetCtlBit () { bool bit = 0 != (m_ctl & m_mask); m_mask >>= 1; if (0 == m_mask) { m_ctl = LittleEndian.ToUInt32 (m_input, m_src); m_src += 4; m_mask = 0x80000000; } return bit; } int GetBits (int n) { int v = 0; for (int i = 0; i < n; ++i) { v <<= 1; if (GetCtlBit()) v |= 1; } return v; } public void Unpack () { GetCtlBit(); int dst = 0; while (dst < m_output.Length) { if (GetCtlBit()) { m_output[dst++] = m_input[m_src++]; continue; } bool next_bit = GetCtlBit(); int offset = m_input[m_src++] | ~0xffff; int ah = 0xff; int count = 0; if (next_bit) // 5e { if (GetCtlBit()) // 10d { ah = (ah << 1) | GetBits (1); } else if (GetCtlBit()) // 13e { ah = (ah << 1) | GetBits (1); offset -= 0x200; } else if (GetCtlBit()) // 174 { ah = (ah << 2) | GetBits (2); offset -= 0x400; } else if (GetCtlBit()) // 1c0 { ah = (ah << 3) | GetBits (3); offset -= 0x800; } else { ah = (ah << 4) | GetBits (4); offset -= 0x1000; } if (GetCtlBit()) // 296 { count = 3; } else if (GetCtlBit()) // 2a2 { count = 4; } else if (GetCtlBit()) // 2c2 { count = 5 + GetBits (1); } else if (GetCtlBit()) // 2f8 { count = 7 + GetBits (2); } else if (GetCtlBit()) // 33f { count = 0x0b + GetBits (3); } else { count = 0x13 + m_input[m_src++]; } } else if (GetCtlBit()) // 94 { ah <<= 3; // b2 ah |= GetBits (3); ah = (ah - 1) & 0xff; count = 2; } else if (0xff == (offset & 0xff)) { return; } else { count = 2; } offset += (ah & 0xff) << 8; // 280 Binary.CopyOverlapped (m_output, dst + offset, dst, count); dst += count; } } } internal class HuffmanReader { byte[] m_src; byte[] m_dst; ushort[,] m_tree = new ushort[2,511]; int m_origin; int m_total; int m_input_pos; int m_remaining; int m_curbits; uint m_cache; ushort m_curindex; public HuffmanReader (byte[] src, int index, int length, byte[] dst) { m_src = src; m_dst = dst; m_origin = index; m_total = length; } public HuffmanReader (byte[] src, byte[] dst) : this (src, 0, src.Length, dst) { } public byte[] Unpack () { m_input_pos = m_origin; m_remaining = m_total; m_curbits = 0; m_curindex = 256; ushort root = CreateTree(); for (int i = 0; i < m_dst.Length; ++i) { ushort symbol = root; while (symbol >= 256) { symbol = m_tree[GetBits(1), symbol]; } m_dst[i] = (byte)symbol; } return m_dst; } uint ReadUInt32 () { uint v; if (m_remaining >= 4) { v = LittleEndian.ToUInt32 (m_src, m_input_pos); m_input_pos += 4; m_remaining -= 4; } else if (m_remaining > 0) { v = m_src[m_input_pos++]; int shift = 8; while (--m_remaining != 0) { v |= (uint)(m_src[m_input_pos++] << shift); shift += 8; } } else throw new InvalidFormatException ("Unexpected end of file"); return v; } uint GetBits (int req_bits) { uint ret_val = 0; if (req_bits > m_curbits) { req_bits -= m_curbits; ret_val |= (m_cache & ((1u << m_curbits) - 1u)) << req_bits; m_cache = ReadUInt32(); m_curbits = 32; } m_curbits -= req_bits; return ret_val | ((1u << req_bits) - 1u) & (m_cache >> m_curbits); } ushort CreateTree () { ushort i; if (0 != GetBits (1)) { i = m_curindex++; m_tree[0,i] = CreateTree(); m_tree[1,i] = CreateTree(); } else i = (ushort)GetBits (8); return i; } } }