From fa4b8b6bceab0584aae208899d30b4dbb0ee5037 Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 25 Aug 2014 15:11:34 +0400 Subject: [PATCH] implemented Kogado resource archive. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/ArcKogado.cs | 291 ++++++++ ArcFormats/KogadoCocotte.cs | 833 ++++++++++++++++++++++ ArcFormats/Strings/arcStrings.Designer.cs | 9 + ArcFormats/Strings/arcStrings.resx | 3 + 5 files changed, 1138 insertions(+) create mode 100644 ArcFormats/ArcKogado.cs create mode 100644 ArcFormats/KogadoCocotte.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 07fc84d9..72cad74a 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -59,6 +59,7 @@ + @@ -101,6 +102,7 @@ + True diff --git a/ArcFormats/ArcKogado.cs b/ArcFormats/ArcKogado.cs new file mode 100644 index 00000000..c6c70d38 --- /dev/null +++ b/ArcFormats/ArcKogado.cs @@ -0,0 +1,291 @@ +//! \file ArcKogado.cs +//! \date Sun Aug 24 22:01:05 2014 +//! \brief Kogado game engine archive implementation. +// +// Copyright (C) 2014 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.IO; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Security.Cryptography; +using System.Diagnostics; +using System.Text; +using GameRes.Formats.Strings; +using GameRes.Utility; + +namespace GameRes.Formats.Kogado +{ + public class KogadoEntry : PackedEntry + { + // 0 : Not compressed + // 1 : Mariel compression + // 2 : Cocotte compression + // 3 : Xor 0xff encryption + public byte CompressionType; + public bool HasCheckSum; + public ushort CheckSum; + public long FileTime; + } + + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag { get { return "PAK"; } } + public override string Description { get { return arcStrings.KogadoDescription; } } + public override uint Signature { get { return 0x61507948; } } // 'HyPa' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "HyPack\x00")) + return null; + int version = file.View.ReadByte (7); + if (2 != version && 3 != version) + return null; + uint index_offset = 0x10 + file.View.ReadUInt32 (8); + if (index_offset >= file.MaxOffset) + return null; + uint entry_count = file.View.ReadUInt32 (12); + if (entry_count > 0xfffff) + return null; + uint index_size = entry_count * 48; + if (index_size > file.View.Reserve (index_offset, index_size)) + return null; + long data_offset = 0x10; + + var dir = new List ((int)entry_count); + for (uint i = 0; i < entry_count; ++i) + { + string name = file.View.ReadString (index_offset, 0x15); + string ext = file.View.ReadString (index_offset+0x15, 3); + var entry = new KogadoEntry { Name = name+'.'+ext }; + entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); + entry.Offset = data_offset + file.View.ReadUInt32 (index_offset + 0x18); + entry.UnpackedSize = file.View.ReadUInt32 (index_offset + 0x1c); + entry.Size = file.View.ReadUInt32 (index_offset + 0x20); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.CompressionType = file.View.ReadByte (index_offset + 0x24); + entry.HasCheckSum = 0 != file.View.ReadByte (index_offset + 0x25); + entry.CheckSum = file.View.ReadUInt16 (index_offset + 0x26); + entry.FileTime = file.View.ReadInt64 (index_offset + 0x28); + entry.IsPacked = 0 != entry.CompressionType; + dir.Add (entry); + index_offset += 48; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var packed_entry = entry as KogadoEntry; + if (null == packed_entry || !packed_entry.IsPacked) + return input; + if (packed_entry.CompressionType > 3) + { + Trace.WriteLine (string.Format ("{1}: Unknown compression type {0}", + packed_entry.CompressionType, packed_entry.Name), + "Kogado.PakOpener.OpenEntry"); + return input; + } + if (3 == packed_entry.CompressionType) + return new CryptoStream (input, new NotTransform(), CryptoStreamMode.Read); + try + { + if (2 == packed_entry.CompressionType) + { + var decoded = new MemoryStream ((int)packed_entry.UnpackedSize); + try + { + var cocotte = new CocotteEncoder(); + if (!cocotte.Decode (input, decoded)) + throw new InvalidFormatException ("Invalid Cocotte-encoded stream"); + decoded.Position = 0; + return decoded; + } + catch + { + decoded.Dispose(); + throw; + } + } + // if (1 == packed_entry.CompressionType) + var unpacked = new byte[packed_entry.UnpackedSize]; + var mariel = new MarielEncoder(); + mariel.Unpack (input, unpacked, unpacked.Length); + return new MemoryStream (unpacked, false); + } + finally + { + input.Dispose(); + } + } + + // files inside archive are aligned to 0x10 boundary. + // to convert DateTime structure into entry time: + // entry.FileTime = file_info.CreationTimeUtc.Ticks; + // + // last two bytes of archive is CRC16 of the whole file + } + + public sealed class Crc16 : ICheckSum + { + private ushort m_value = 0xffff; + + public uint Value { get { return m_value; } } + + public void Update (byte[] buf, int pos, int len) + { + for (int i = 0; i < len; ++i) + { + m_value = (ushort)(Crc16Table[(m_value^buf[pos+i]) & 0xff] ^ (m_value >> 8)); + } + } + + private static readonly ushort[] Crc16Table = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78, + }; + } + + internal class MarielEncoder + { + public void Unpack (Stream input, byte[] dest, int dest_size) + { + int out_pos = 0; + using (var reader = new BinaryReader (input, Encoding.ASCII, true)) + { + uint bits = 0; + while (dest_size > 0) + { + bool carry = 0 != (bits & 0x80000000); + bits <<= 1; + if (0 == bits) + { + bits = reader.ReadUInt32(); + carry = 0 != (bits & 0x80000000); + bits = (bits << 1) | 1u; + } + int b = input.ReadByte(); + if (-1 == b) + break; + if (!carry) + { + dest[out_pos++] = (byte)b; + dest_size--; + continue; + } + int offset = (b & 0x0f) + 1; + int count = ((b >> 4) & 0x0f) + 1; + if (0x0f == count) + { + b = input.ReadByte(); + if (-1 == b) + break; + count = (byte)b; + } + else if (count > 0x0f) + { + count = reader.ReadUInt16(); + } + if (offset >= 0x0b) + { + offset -= 0x0b; + offset <<= 8; + offset |= reader.ReadByte(); + } + if (count > dest_size) + count = dest_size; + int src = out_pos - offset; + if (src < 0 || src >= out_pos) + break; + Binary.CopyOverlapped (dest, src, out_pos, count); + out_pos += count; + dest_size -= count; + } + } + } + } + + public sealed class NotTransform : ICryptoTransform + { + public bool CanReuseTransform { get { return true; } } + public bool CanTransformMultipleBlocks { get { return true; } } + public int InputBlockSize { get { return 256; } } + public int OutputBlockSize { get { return 256; } } + + public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, + byte[] outputBuffer, int outputOffset) + { + for (int i = 0; i < inputCount; ++i) + { + outputBuffer[outputOffset++] = (byte)~inputBuffer[inputOffset+i]; + } + return inputCount; + } + + public byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) + { + byte[] outputBuffer = new byte[inputCount]; + TransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, 0); + return outputBuffer; + } + + public void Dispose () + { + System.GC.SuppressFinalize (this); + } + } +} diff --git a/ArcFormats/KogadoCocotte.cs b/ArcFormats/KogadoCocotte.cs new file mode 100644 index 00000000..0336c9d6 --- /dev/null +++ b/ArcFormats/KogadoCocotte.cs @@ -0,0 +1,833 @@ +//! \file KogadoCocotte.cs +//! \date Mon Aug 25 13:35:37 2014 +//! \brief Kogado engine Cocotte compression/encryption implementation. +// +// 作者について: +// あんたいとるどどきゅめんと < http://juicy.s53.xrea.com/program/ > +// Written by juicy.gt < juicy[atm@rk]s53.xrea.com > +// +// Original code licensed under GNU GPL Version 2 +// +// C# port by mørkt +// 2014/08/25 +// + +using System; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Kogado +{ + public class CocotteEncoder + { + BWTEncode m_cBWTEncode = new BWTEncode(); + MTFEncode m_cMTFEncode = new MTFEncode(); + CRangeCoder m_cRangeCoder = new CRangeCoder(); + + const int RANGECODER_BLOCKSIZE = 0x2000; + + /* + // Encode + BOOL Encode( DWORD dwCompressionLevel, BYTE *pDestBuffer, const BYTE *pSrcBuffer, DWORD dwDestLength, DWORD dwSrcLength, DWORD *pdwWritten, HPA_Callback callback, LPVOID pCallbackArg ) + { + // BWTEncode でサイズが 2 増えるので + 2 する + BYTE buffer[ RANGECODER_BLOCKSIZE + 2 ]; + DWORD dwSrcCursor = 0, dwDestCursor = 0; + DWORD written; + DWORD write_src_size; + + m_cMTFEncode.InitMTFOrder(); + m_cRangeCoder.InitQSModel(); + while ( dwSrcCursor < dwSrcLength ) { + if ( callback != NULL ) { + if ( !callback( pCallbackArg, dwSrcCursor, dwSrcLength ) ) + return FALSE; + } + if ( dwDestLength - dwDestCursor <= 4 ) // バッファが足りない + return FALSE; + write_src_size = min( RANGECODER_BLOCKSIZE, dwSrcLength - dwSrcCursor ); + m_cBWTEncode.Encode( buffer, pSrcBuffer, write_src_size ); + // BWTEncode でサイズが 2 増えるので + 2 する + m_cMTFEncode.Encode( buffer, buffer, write_src_size + 2 ); + switch ( dwCompressionLevel ) { + case CMPL_STORE: + loc_store_encode: + written = write_src_size + 2; + memcpy( pDestBuffer + 4, buffer, written ); + break; + case CMPL_MAXIMUM: + if ( !m_cRangeCoder.Encode( pDestBuffer + 4, buffer, dwDestLength - dwDestCursor - 4, write_src_size + 2, &written ) ) + return FALSE; + // オーバーしたら STORE にする + if ( written >= write_src_size + 2 ) { + m_cRangeCoder.InitQSModel(); + goto loc_store_encode; + } + break; + default: + return FALSE; + } + written += 4; + // 書きすぎ + if ( written > 0xffff || written > dwDestLength - dwDestCursor ) + return FALSE; + reinterpret_cast< unsigned short * >( pDestBuffer )[0] = static_cast< unsigned short >( written ); + reinterpret_cast< unsigned short * >( pDestBuffer )[1] = static_cast< unsigned short >( write_src_size ); + + pSrcBuffer += write_src_size; dwSrcCursor += write_src_size; + pDestBuffer += written; dwDestCursor += written; + } + *pdwWritten = dwDestCursor; + if ( callback != NULL ) + if ( !callback( pCallbackArg, dwSrcCursor, dwSrcLength ) ) + return FALSE; + + return ( dwSrcCursor == dwSrcLength ); + } + */ + + // Decode + public bool Decode (Stream input, Stream output) + { + uint dwSrcLength = (uint)input.Length; + var buffer = new byte [RANGECODER_BLOCKSIZE*4+2]; + uint dwSrcCursor = 0; + var input_buffer = new byte[RANGECODER_BLOCKSIZE]; + + m_cRangeCoder.InitQSModel(); + m_cMTFEncode.InitMTFOrder(); + + using (var reader = new BinaryReader (input, Encoding.ASCII, true)) + { + while (dwSrcCursor < dwSrcLength) + { + if (dwSrcCursor + 4 >= dwSrcLength) + return false; + ushort src_block_size = reader.ReadUInt16(); + ushort dest_block_size = reader.ReadUInt16(); + ushort comp_block_size = (ushort)(src_block_size - 4); + ushort decomp_block_size = (ushort)(dest_block_size + 2); + + if (dwSrcCursor + src_block_size > dwSrcLength) + return false; + if (src_block_size <= 4 || dest_block_size == 0) + return false; + if (comp_block_size == decomp_block_size) + { + int read = input.Read (buffer, 0, comp_block_size); + m_cRangeCoder.InitQSModel(); + } + else + { + if (comp_block_size > input_buffer.Length) + input_buffer = new byte[comp_block_size]; + int read = input.Read (input_buffer, 0, comp_block_size); + if (read != comp_block_size) + return false; + uint written = m_cRangeCoder.Decode (buffer, input_buffer, decomp_block_size, comp_block_size); + if (0 == written) + break; + if (written != decomp_block_size) + return false; + } + m_cMTFEncode.Decode (buffer, buffer, decomp_block_size); + m_cBWTEncode.Decode (output, buffer, decomp_block_size); + + dwSrcCursor += src_block_size; + } + } + return dwSrcCursor == dwSrcLength; + } + } + + internal class CRangeCoder + { + byte[] m_pSrcBuffer; + byte[] m_pDestBuffer; + uint m_dwSrcLength; + uint m_dwDestLength; + uint m_dwSrcIndex; + uint m_dwDestIndex; + RangeCoder m_rc = new RangeCoder(); + QSModel m_qsm; + + const int CODE_BITS = 32; + const int SHIFT_BITS = CODE_BITS - 9; + const int EXTRA_BITS = (CODE_BITS - 2) % 8 + 1; + const uint Top_value = 1u << (CODE_BITS - 1); + const uint Bottom_value = Top_value >> 8; + + static readonly int[] RANGECODER_INITFREQ = { + 1400, 640, 320, 240, 160, 120, 80, 64, + 48, 40, 32, 24, 20, 20, 20, 20, + 16, 16, 16, 16, 12, 12, 12, 12, + 12, 12, 8, 8, 8, 8, 8, 8, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + + 3, 3, 3, 3, 3, 3, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + + 2, + }; + + public void InitQSModel() + { + m_qsm = new QSModel (257, 12, 2000, RANGECODER_INITFREQ, false); + } + + /* + public bool Encode (byte[] dest, const BYTE *src, DWORD destsize, DWORD srcsize, DWORD *pwritten ) + { + BYTE ch_byte; + int syfreq, ltfreq; + + m_dwSrcIndex = m_dwDestIndex = 0; + m_pSrcBuffer = src; + m_pDestBuffer = dest; + m_dwSrcLength = srcsize; + m_dwDestLength = destsize; + + //this->InitQSModel(); + this->StartEncoding(); + + while ( m_dwSrcIndex < m_dwSrcLength ) { + if ( !this->GetSrcByteImpl( &ch_byte ) ) + return false; + qsgetfreq( &m_qsm, ch_byte, &syfreq, <freq ); + this->EncodeShift( syfreq, ltfreq, 12 ); + qsupdate( &m_qsm, ch_byte ); + } + qsgetfreq( &m_qsm, 256, &syfreq, <freq ); + this->EncodeShift( syfreq, ltfreq, 12 ); + + this->DoneEncoding(); + *pwritten = m_dwDestIndex; // written-size + + return true; + } + */ + + public uint Decode (byte[] dest, byte[] src, uint destsize, uint srcsize) + { + int ch, ltfreq, syfreq; + + m_dwSrcIndex = m_dwDestIndex = 0; + m_pSrcBuffer = src; + m_pDestBuffer = dest; + m_dwSrcLength = srcsize; + m_dwDestLength = destsize; + + StartDecoding(); + + while (m_dwSrcIndex < m_dwSrcLength) + { + ltfreq = (int)DecodeCulshift (12); + ch = m_qsm.GetSym (ltfreq); + if (256 == ch) // check for end-of-file + break; + if (m_dwDestIndex >= m_dwDestLength) + return 0; + SetDestByteImpl ((byte)ch); + m_qsm.GetFreq (ch, out syfreq, out ltfreq); + DecodeUpdate (syfreq, ltfreq, 1 << 12); + m_qsm.Update (ch); + } + m_qsm.GetFreq (256, out syfreq, out ltfreq); + DecodeUpdate (syfreq, ltfreq, 1 << 12); + DoneDecoding(); + return m_dwDestIndex; + } +/* + // Encode -------------------------------------------------------- + void StartEncoding( char c = 0, int initlength = 0 ) + { + m_rc.low = 0; // Full code range + m_rc.range = Top_value; + m_rc.buffer = c; + m_rc.help = 0; // No bytes to follow + m_rc.bytecount = initlength; + } + void EncNormalize() + { + while ( m_rc.range <= Bottom_value ) { // do we need renormalisation? + if ( m_rc.low < (uint)0xff << SHIFT_BITS ) { // no carry possible --> output + this->SetDestByteImpl( m_rc.buffer ); + for ( ; m_rc.help; m_rc.help -- ) + this->SetDestByteImpl( 0xff ); + m_rc.buffer = (unsigned char)( m_rc.low >> SHIFT_BITS ); + } else if ( m_rc.low & Top_value ) { // carry now, no future carry + this->SetDestByteImpl( m_rc.buffer+1 ); + for ( ; m_rc.help; m_rc.help -- ) + this->SetDestByteImpl( 0 ); + m_rc.buffer = (unsigned char)( m_rc.low >> SHIFT_BITS ); + } else // passes on a potential carry + m_rc.help ++; + m_rc.range <<= 8; + m_rc.low = ( m_rc.low << 8 ) & ( Top_value - 1 ); + m_rc.bytecount ++; + } + } + void EncodeFreq (uint sy_f, uint lt_f, uint tot_f) + { + uint r, tmp; + + this->EncNormalize(); + r = m_rc.range / tot_f; + tmp = r * lt_f; + m_rc.low += tmp; + if ( lt_f + sy_f < tot_f ) + m_rc.range = r * sy_f; + else + m_rc.range -= tmp; + } + void EncodeShift (uint sy_f, uint lt_f, uint shift) + { + uint r, tmp; + + this->EncNormalize(); + r = m_rc.range >> shift; + tmp = r * lt_f; + m_rc.low += tmp; + if ( ( lt_f + sy_f ) >> shift ) + m_rc.range -= tmp; + else + m_rc.range = r * sy_f; + } + uint4 DoneEncoding() + { + uint tmp; + + this->EncNormalize(); // now we have a normalized state + m_rc.bytecount += 5; + if ( ( m_rc.low & ( Bottom_value - 1 ) ) < ( ( m_rc.bytecount & 0xffffffL ) >> 1 ) ) + tmp = m_rc.low >> SHIFT_BITS; + else + tmp = ( m_rc.low >> SHIFT_BITS ) + 1; + if ( tmp > 0xff ) { // we have a carry + this->SetDestByteImpl( m_rc.buffer + 1 ); + for ( ; m_rc.help; m_rc.help -- ) + this->SetDestByteImpl( 0 ); + } else { // no carry + this->SetDestByteImpl( m_rc.buffer ); + for ( ; m_rc.help; m_rc.help -- ) + this->SetDestByteImpl( 0xff ); + } + this->SetDestByteImpl( static_cast< BYTE >( tmp ) ); + this->SetDestByteImpl( static_cast< BYTE >( m_rc.bytecount >> 16 ) ); + this->SetDestByteImpl( static_cast< BYTE >( m_rc.bytecount >> 8 ) ); + this->SetDestByteImpl( static_cast< BYTE >( m_rc.bytecount ) ); + + return m_rc.bytecount; + } +*/ + + // Decode -------------------------------------------------------- + int StartDecoding () + { + byte c; + + if (!GetSrcByteImpl (out c)) + return -1; + if (!GetSrcByteImpl (out m_rc.buffer)) + return -1; + m_rc.low = (uint)(m_rc.buffer >> (8 - EXTRA_BITS)); + m_rc.range = (uint)1 << EXTRA_BITS; + + return c; + } + + bool DecNormalize() + { + while ( m_rc.range <= Bottom_value ) + { + m_rc.low = ( m_rc.low << 8 ) | (byte)(m_rc.buffer << EXTRA_BITS); + if (!GetSrcByteImpl (out m_rc.buffer)) + return false; + m_rc.low |= (uint)m_rc.buffer >> ( 8 - EXTRA_BITS ); + m_rc.range <<= 8; + } + return true; + } + + uint DecodeCulshift (int shift) + { + uint tmp; + + DecNormalize(); + m_rc.help = m_rc.range >> shift; + tmp = m_rc.low / m_rc.help; + return (0 != (tmp >> shift) ? (1u << shift) - 1u : tmp); + } + + void DecodeUpdate (int sy_f, int lt_f, int tot_f) + { + uint tmp = m_rc.help * (uint)lt_f; + + m_rc.low -= tmp; + if ( lt_f + sy_f < tot_f ) + m_rc.range = m_rc.help * (uint)sy_f; + else + m_rc.range -= tmp; + } + + void DoneDecoding() + { + DecNormalize(); // normalize to use up all bytes + } + + // I/O ----------------------------------------------------------- + bool GetSrcByteImpl (out byte pData) + { + if (m_dwSrcIndex >= m_dwSrcLength) + { + pData = 0; + return false; + } + pData = m_pSrcBuffer[m_dwSrcIndex++]; + return true; + } + + bool SetDestByteImpl (byte byData) + { + if (m_dwDestIndex >= m_dwDestLength) + return false; + m_pDestBuffer[m_dwDestIndex++] = byData; + return true; + } + } + + internal class RangeCoder + { + public uint low; /* low end of interval */ + public uint range; /* length of interval */ + public uint help; /* bytes_to_follow resp. intermediate value */ + public byte buffer; /* buffer for input/output */ + /* the following is used only when encoding */ + public uint bytecount; /* counter for outputed bytes */ + } + + // とりあえずこれで可逆性を概ね確認 (数タイトルの song.txt で確認) + internal class BWTEncode + { + public const ulong BWT_SORTTABLESIZE = 0x00010000; + +/* + byte[] m_pWorkTable = new byte[BWT_SORTTABLESIZE / 2]; + + void Encode( BYTE *dest, const BYTE *src, int size ) + { + int top = 0; // 初期値は不要だが、警告回避のため + BYTE *ptr; + int count[256] = { 0, }; + int count_sum[256+1]; + BYTE *sort_buffer = new BYTE[size*2]; + LPBYTE *sort_table = new LPBYTE[BWT_SORTTABLESIZE]; + + // 作業領域にコピー + memcpy( sort_buffer, src, size ); + memcpy( sort_buffer + size, sort_buffer, size ); + + // 分布数え上げソート + for ( int i = 0; i < size; i ++ ) + count[ sort_buffer[i] ]++; + count_sum[0] = 0; + for ( int i = 1; i <= 256; i ++ ) + count_sum[i] = count[i-1] + count_sum[i-1]; + for ( int i = 1; i < 256; i ++ ) + count[i] += count[i-1]; + + for ( int i = size - 1; i >= 0; i -- ) { + ptr = sort_buffer + i; + sort_table[ -- count[*ptr] ] = ptr; + } + + // 2 段階ソート + for ( int i = 1; i < 256; i ++ ) { + int j, k; + int high = count_sum[i+1]; + + for ( j = k = count_sum[i]; j < high; j ++ ) { + ptr = sort_table[j]; + if ( *ptr > *(ptr + 1) ) { + sort_table[j] = sort_table[k]; + sort_table[k ++] = ptr; + } + } + if ( high - k > 1 ) + this->MergeSort( sort_table, k, high - 1, size ); + } + // 0 は全てソート + if ( count_sum[1] > 1 ) + this->MergeSort( sort_table, 0, count_sum[1] - 1, size ); + // ソート不要部分 + for ( int i = 0; i < size; i ++ ) { + ptr = sort_table[i]; + if ( ptr == sort_buffer ) + ptr += size; + if ( *(ptr - 1) > *ptr ) + sort_table[ count_sum[*(ptr - 1)] ++ ] = ptr - 1; + } + // 出力 + for ( int i = 0; i < size; i ++ ) { + ptr = sort_table[i]; + if ( ptr == sort_buffer ) + top = i; + dest[i+2] = *(ptr + size - 1); + } + *reinterpret_cast< unsigned short * >( dest ) = static_cast< unsigned short >( top ); + // 解放 + delete[] sort_buffer; + delete[] sort_table; + } +*/ + int[] sort_table = new int[BWT_SORTTABLESIZE]; + + public void Decode (Stream dest, byte[] src, int size) + { + int[] count = new int[256]; + int top = src[0] | src[1] << 8; + + int pos = 2; + size -= 2; + // 分布数え上げソート + for (int i = 0; i < size; i++) + count[ src[pos+i] ]++; + for (short i = 1; i < 256; i++) + count[i] += count[i-1]; + for (int i = size - 1; i >= 0; i --) + { + sort_table[--count[src[pos+i]]] = i; + } + // 出力 + int ptr = sort_table[top]; + for (int i = 0; i < size; i++) + { + dest.WriteByte (src[pos+ptr]); + ptr = sort_table[ptr]; + } + } +/* + void MergeSort( BYTE *sort_table[], int low, int high, int size ) + { + int len = size - 1; + + if ( high - low <= 10 ) { + this->InsertSort( sort_table, low, high, size ); + } else { + int middle = (low + high) / 2; + int i, p, j, k; + + this->MergeSort( sort_table, low, middle, size ); + this->MergeSort( sort_table, middle + 1, high, size ); + p = 0; + i = low; + while ( i <= middle ) + m_pWorkTable[p ++] = sort_table[i ++]; + i = middle + 1; + j = 0; + k = low; + while ( i <= high && j < p ) { + if ( memcmp( m_pWorkTable[j] + 1, sort_table[i] + 1, len ) <= 0 ) + sort_table[k ++] = m_pWorkTable[j ++]; + else + sort_table[k ++] = sort_table[i ++]; + } + while ( j < p ) + sort_table[k ++] = m_pWorkTable[j ++]; + } + } + void InsertSort( BYTE *sort_table[], int low, int high, int size ) + { + int j, len = size - 1; + + for ( int i = low + 1; i <= high ; i ++ ) { + BYTE *tmp = sort_table[i]; + + for ( j = i - 1; j >= low && memcmp( tmp + 1, sort_table[j] + 1, len ) < 0; j -- ) + sort_table[j + 1] = sort_table[j]; + sort_table[j + 1] = tmp; + } + } +*/ + } + + internal class MTFEncode + { + byte[] m_MTFTable = new byte[256]; + + public void InitMTFOrder() + { + for (int i = 0; i < 256; i++) + m_MTFTable[i] = (byte)i; + } + + // MTF は当然、destsize == srcsize + public void Encode (byte[] dest, byte[] src, int size) + { + for (int i = 0; i < size; i++) + { + byte c = src[i]; + byte n = 0; + + while (m_MTFTable[n] != c) + n++; + if (n > 0) + { + Array.Copy (m_MTFTable, 0, m_MTFTable, 1, n); + m_MTFTable[0] = c; + } + dest[i] = n; + } + } + + // MTF は当然、destsize == srcsize + public void Decode (byte[] dest, byte[] src, int size) + { + for ( int i = 0; i < size; i++ ) + { + byte n = src[i]; + byte c = m_MTFTable[n]; + if (n > 0) + { + Array.Copy (m_MTFTable, 0, m_MTFTable, 1, n); + m_MTFTable[0] = c; + } + dest[i] = c; + } + } + } + + /* + Quasistatic probability model + + // 若干改変 by juicy.gt at 2008/03/27 00:00 + + (c) Michael Schindler + 1997, 1998, 2000 + http://www.compressconsult.com/ + michael@compressconsult.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. It may be that this + program violates local patents in your country, however it is + belived (NO WARRANTY!) to be patent-free here in Austria. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA. + + Qsmodel is a quasistatic probability model that periodically + (at chooseable intervals) updates probabilities of symbols; + it also allows to initialize probabilities. Updating is done more + frequent in the beginning, so it adapts very fast even without + initialisation. + + it provides function for creation, deletion, query for probabilities + and symbols and model updating. + */ + + internal class QSModel + { + public int m_n; /* number of symbols */ + public int m_left; /* symbols to next rescale */ + public int m_nextleft; /* symbols with other increment */ + public int m_rescale; /* intervals between rescales */ + public int m_targetrescale; /* should be interval between rescales */ + public int m_incr; /* increment per update */ + public int m_searchshift; /* shift for lt_freq before using as index */ + public ushort[] m_cf; /* array of cumulative frequencies */ + public ushort[] m_newf; /* array for collecting ststistics */ + public ushort[] m_search; /* structure for searching on decompression */ + + public const int TBLSHIFT = 7; + + /// + /// initialisation of qsmodel + /// + /// number of symbols in that model + /// base2 log of total frequency count + /// desired rescaling interval, should be < 1<<(lg_totf+1) + /// array of int's to be used for initialisation (NULL ok) + /// true on compression, false on decompression + public QSModel (int n, int lg_totf, int rescale, int[] init, bool compress) + { + m_n = n; + m_targetrescale = rescale; + m_searchshift = lg_totf - TBLSHIFT; + if (m_searchshift < 0) + m_searchshift = 0; + m_cf = new ushort[n+1]; + m_newf = new ushort[n+1]; + m_cf[n] = (ushort)(1 << lg_totf); + m_cf[0] = 0; + if (compress) + { + m_search = null; + } + else + { + m_search = new ushort[(1< + /// reinitialisation of qsmodel + /// + /// array of int's to be used for initialisation (NULL ok) + public void Reset (int[] init) + { + int i; + m_rescale = m_n>>4 | 2; + m_nextleft = 0; + if (init == null) + { + int initval = m_cf[m_n] / m_n; + int end = m_cf[m_n] % m_n; + for (i = 0; i < end; i++) + m_newf[i] = (ushort)(initval+1); + for (; i < m_n; i++) + m_newf[i] = (ushort)initval; + } + else + { + for (i = 0; i < m_n; i++) + m_newf[i] = (ushort)init[i]; + } + DoRescale(); + } + + void DoRescale () + { + if (0 != m_nextleft) /* we have some more before actual rescaling */ + { + m_incr++; + m_left = m_nextleft; + m_nextleft = 0; + return; + } + if (m_rescale < m_targetrescale) /* double rescale interval if needed */ + { + m_rescale <<= 1; + if (m_rescale > m_targetrescale) + m_rescale = m_targetrescale; + } + int i, cf, missing; + cf = missing = m_cf[m_n]; /* do actual rescaling */ + for (i = m_n-1; i != 0; i--) + { + int tmp = m_newf[i]; + cf -= tmp; + m_cf[i] = (ushort)cf; + tmp = tmp>>1 | 1; + missing -= tmp; + m_newf[i] = (ushort)tmp; + } + if (cf != m_newf[0]) + throw new ApplicationException ("Run-time error in QSModel.DoRescale"); + + m_newf[0] = (ushort)(m_newf[0]>>1 | 1); + missing -= m_newf[0]; + m_incr = missing / m_rescale; + m_nextleft = missing % m_rescale; + m_left = m_rescale - m_nextleft; + if (m_search != null) + { + i = m_n; + while (i != 0) + { + int end = (m_cf[i]-1) >> m_searchshift; + i--; + int start = m_cf[i] >> m_searchshift; + while (start <= end) + { + m_search[start] = (ushort)i; + start++; + } + } + } + } + + /// + /// retrieval of estimated frequencies for a symbol + /// + /// symbol for which data is desired; must be <n + /// frequency of that symbol + /// frequency of all smaller symbols together + /// the total frequency is 1<<lg_totf + public void GetFreq (int sym, out int sy_f, out int lt_f) + { + lt_f = m_cf[sym]; + sy_f = m_cf[sym+1] - lt_f; + } + + /// + /// find out symbol for a given cumulative frequency. + /// + /// cumulative frequency + public int GetSym (int lt_f) + { + int lo, hi; + int tmp = lt_f >> m_searchshift; + lo = m_search[tmp]; + hi = m_search[tmp+1] + 1; + while (lo+1 < hi) + { + int mid = (lo + hi) >> 1; + if (lt_f < m_cf[mid]) + hi = mid; + else + lo = mid; + } + return lo; + } + + /// + /// update model + /// + /// symbol that occurred (must be <n from init) + public void Update (int sym) + { + if (m_left <= 0) + DoRescale(); + m_left--; + m_newf[sym] += (ushort)m_incr; + } + } +} diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 63d703b9..5eff4542 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -216,6 +216,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Kogado game engine resource archive. + /// + public static string KogadoDescription { + get { + return ResourceManager.GetString("KogadoDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Liar-soft image archive. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index e8fc1998..7f4d8383 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -171,6 +171,9 @@ Choose appropriate encryption scheme. Enter archive encryption key or choose predefined encryption scheme. + + Kogado game engine resource archive + Liar-soft image archive