diff --git a/ArcFormats/CsWare/ArcPCS.cs b/ArcFormats/CsWare/ArcPCS.cs index ad4fd9a1..d3c9c727 100644 --- a/ArcFormats/CsWare/ArcPCS.cs +++ b/ArcFormats/CsWare/ArcPCS.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Security.Cryptography; using GameRes.Utility; namespace GameRes.Formats.CsWare @@ -34,6 +35,18 @@ namespace GameRes.Formats.CsWare internal class PcsEntry : Entry { public byte Key; + public uint NameHash; // used in archives version 6 + } + + internal class PcsArchive : ArcFile + { + public readonly int Version; + + public PcsArchive (ArcView arc, ArchiveFormat impl, ICollection dir, int version) + : base (arc, impl, dir) + { + Version = version; + } } [Export(typeof(ArchiveFormat))] @@ -48,82 +61,88 @@ namespace GameRes.Formats.CsWare public override ArcFile TryOpen (ArcView file) { int version = file.View.ReadUInt16 (4); - if (version < 1 || version > 4) + if (version < 1 || version > 6) return null; int count = file.View.ReadInt32 (8); if (!IsSaneCount (count)) return null; uint data_offset = file.View.ReadUInt32 (12); - int index_size = (int)data_offset - 0x10; - - int index_offset = 0x10; - var index_buffer = new byte[0x40]; + uint index_size = data_offset - 0x10; + var index = file.View.ReadBytes (0x10, index_size); + if (6 == version) + { + index = ShuffleBlocks (index, file.View.ReadUInt16 (6)); + } + int index_offset = 0; var dir = new List (count); for (int i = 0; i < count; ++i) { int name_length; if (version > 1) { - name_length = file.View.ReadInt32 (index_offset); + name_length = LittleEndian.ToInt32 (index, index_offset); if (0 == name_length) break; if (name_length > index_size) return null; index_offset += 5 + name_length; } - name_length = file.View.ReadInt32 (index_offset); + name_length = LittleEndian.ToInt32 (index, index_offset); if (0 == name_length) break; if (name_length > index_size) return null; - if (name_length > index_buffer.Length) - index_buffer = new byte[name_length]; - file.View.Read (index_offset+5, index_buffer, 0, (uint)name_length); - index_offset += 5 + name_length; + index_offset += 5; byte checksum; - var name = DecryptName (index_buffer, name_length, out checksum); + var name = DecryptName (index, index_offset, name_length, out checksum); Entry entry; - if (4 == version) + if (version >= 4) { - entry = FormatCatalog.Instance.Create (name); - (entry as PcsEntry).Key = checksum; + var pcs_entry = FormatCatalog.Instance.Create (name); + entry = pcs_entry; + pcs_entry.Key = checksum; + if (6 == version) + pcs_entry.NameHash = ComputeHash (index, index_offset, name_length-1); + index_offset += name_length; int c = -1 - checksum; - file.View.Read (index_offset, index_buffer, 0, 8); for (int j = 0; j < 4; ++j) { byte key = (byte)((checksum + (17 << j)) & 0x33); - index_buffer[j] = (byte)(c + key - index_buffer[j]); - index_buffer[j+4] = (byte)(c + key - index_buffer[j+4]); + index[index_offset+j] = (byte)(c + key - index[index_offset+j]); + index[index_offset+j+4] = (byte)(c + key - index[index_offset+j+4]); } - entry.Offset = LittleEndian.ToUInt32 (index_buffer, 0); - entry.Size = LittleEndian.ToUInt32 (index_buffer, 4); } else { + index_offset += name_length; entry = FormatCatalog.Instance.Create (name); - entry.Offset = file.View.ReadUInt32 (index_offset); - entry.Size = file.View.ReadUInt32 (index_offset+4); } - index_offset += 0x10; if (index_offset > data_offset) return null; - entry.Offset += data_offset; + entry.Offset = LittleEndian.ToUInt32 (index, index_offset) + data_offset; + entry.Size = LittleEndian.ToUInt32 (index, index_offset+4); if (!entry.CheckPlacement (file.MaxOffset)) return null; dir.Add (entry); + index_offset += 0x10; } if (0 == dir.Count) return null; - return new ArcFile (file, this, dir); + return new PcsArchive (file, this, dir, version); } public override Stream OpenEntry (ArcFile arc, Entry entry) { var pent = entry as PcsEntry; - if (null == pent) + var parc = arc as PcsArchive; + if (null == pent || null == parc) return base.OpenEntry (arc, entry); uint header_size = Math.Min (entry.Size, 512u); var header = arc.File.View.ReadBytes (entry.Offset, header_size); + if (6 == parc.Version) + { + header = ShuffleBlocks (header, pent.NameHash); + } for (int i = 0; i < header.Length; ++i) { header[i] = (byte)(pent.Key - header[i] - 1); @@ -134,18 +153,46 @@ namespace GameRes.Formats.CsWare return new PrefixStream (header, rest); } - string DecryptName (byte[] name_buffer, int length, out byte checksum) + string DecryptName (byte[] name_buffer, int offset, int length, out byte checksum) { int count; checksum = 0; for (count = 0; count < length; ++count) { - if (0 == name_buffer[count]) + if (0 == name_buffer[offset+count]) break; - name_buffer[count] = Binary.RotByteL (name_buffer[count], 4); - checksum += name_buffer[count]; + name_buffer[offset+count] = Binary.RotByteL (name_buffer[offset+count], 4); + checksum += name_buffer[offset+count]; } - return Encodings.cp932.GetString (name_buffer, 0, count); + return Encodings.cp932.GetString (name_buffer, offset, count); + } + + byte[] ShuffleBlocks (byte[] input, uint key) + { + int block_size = input.Length >> 5; + var output = new byte[input.Length]; + var twister = new MersenneTwister (key); + int copied_sections = 0; + for (int i = 0; i < 0x20; ++i) + { + int j = (int)(twister.Rand() & 0x1F); + while (0 != (copied_sections & (1 << j))) + j = (j + 1) & 0x1F; + copied_sections |= 1 << j; + Buffer.BlockCopy (input, j * block_size, output, i * block_size, block_size); + } + int shuffled = block_size << 5; + if (shuffled != input.Length) + Buffer.BlockCopy (input, shuffled, output, shuffled, input.Length-shuffled); + return output; + } + + static readonly Lazy SHA1 = new Lazy (() => System.Security.Cryptography.SHA1.Create()); + + uint ComputeHash (byte[] data, int offset, int length) + { + var hash = SHA1.Value.ComputeHash (data, offset, length); + return BigEndian.ToUInt32 (hash, 0); } } } diff --git a/supported.html b/supported.html index 9c98cac4..cf1ee95e 100644 --- a/supported.html +++ b/supported.html @@ -663,6 +663,7 @@ Mahokoi ~Ecchi na Mahou de Koi x Koi Shichau~
*.alpAP-2No *.anmAN00No *.pcsPCCSNoC's ware +Kuro to Kuro to Kuro no Saidan ~Kodoku~
Mikan
*.052
*.055VAFSHNoSoftpal