diff --git a/ArcFormats/Primel/ArcPCF.cs b/ArcFormats/Primel/ArcPCF.cs index 5641119e..32d8dab5 100644 --- a/ArcFormats/Primel/ArcPCF.cs +++ b/ArcFormats/Primel/ArcPCF.cs @@ -2,7 +2,7 @@ //! \date Fri Sep 30 10:37:28 2016 //! \brief Primel the Adventure System resource archive. // -// Copyright (C) 2016 by morkt +// Copyright (C) 2016-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 @@ -35,8 +35,19 @@ namespace GameRes.Formats.Primel { internal class PcfEntry : PackedEntry { - public uint Flags; - public IEnumerable Key; + public uint Flags; + public byte[] Key; + } + + internal class PcfArchive : ArcFile + { + public readonly PrimelScheme Scheme; + + public PcfArchive (ArcView arc, ArchiveFormat impl, ICollection dir, PrimelScheme scheme) + : base (arc, impl, dir) + { + Scheme = scheme; + } } [Export(typeof(ArchiveFormat))] @@ -55,49 +66,23 @@ namespace GameRes.Formats.Primel int count = file.View.ReadInt32 (8); if (!IsSaneCount (count)) return null; - long data_size = file.View.ReadInt64 (0x10); - long index_offset = file.View.ReadInt64 (0x28); - if (data_size >= file.MaxOffset || index_offset >= file.MaxOffset) + var reader = new PcfIndexReader (file, count); + var dir = reader.Read(); + if (null == dir) return null; - uint index_size = file.View.ReadUInt32 (0x30); - uint flags = file.View.ReadUInt32 (0x38); - var key = file.View.ReadBytes (0x58, 8); - long base_offset = file.MaxOffset - data_size; - - using (var stream = file.CreateStream (base_offset + index_offset, index_size)) - using (var index = ReadFile (stream, key, flags)) - { - var buffer = new byte[0x80]; - var dir = new List (count); - for (int i = 0; i < count; ++i) - { - if (buffer.Length != index.Read (buffer, 0, buffer.Length)) - break; - var name = Binary.GetCString (buffer, 0, 0x50); - var entry = FormatCatalog.Instance.Create (name); - entry.Offset = LittleEndian.ToInt64 (buffer, 0x50) + base_offset; - entry.UnpackedSize = LittleEndian.ToUInt32 (buffer, 0x58); - entry.Size = LittleEndian.ToUInt32 (buffer, 0x60); - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - entry.Flags = LittleEndian.ToUInt32 (buffer, 0x68); - entry.Key = new ArraySegment (buffer, 0x78, 8).ToArray(); - entry.IsPacked = entry.UnpackedSize != entry.Size; - dir.Add (entry); - } - return new ArcFile (file, this, dir); - } + return new PcfArchive (file, this, dir, reader.Scheme); } public override Stream OpenEntry (ArcFile arc, Entry entry) { + var parc = arc as PcfArchive; var pent = entry as PcfEntry; - if (null == pent) + if (null == pent || null == parc) return base.OpenEntry (arc, entry); Stream input = arc.File.CreateStream (entry.Offset, entry.Size); try { - input = ReadFile (input, pent.Key, pent.Flags); + input = parc.Scheme.TransformStream (input, pent.Key, pent.Flags); if (pent.IsPacked) input = new LimitStream (input, pent.UnpackedSize); return input; @@ -108,8 +93,85 @@ namespace GameRes.Formats.Primel throw; } } + } - Stream ReadFile (Stream input, IEnumerable key, uint flags) + internal sealed class PcfIndexReader + { + ArcView m_file; + int m_count; + long m_base_offset; + List m_dir; + + public PrimelScheme Scheme { get; set; } + + public PcfIndexReader (ArcView file, int count) + { + m_file = file; + m_count = count; + m_dir = new List (m_count); + } + + static readonly PrimelScheme[] KnownSchemes = { + new PrimelScheme(), new PrimelSchemeV2() + }; + + public List Read () + { + long data_size = m_file.View.ReadInt64 (0x10); + long index_offset = m_file.View.ReadInt64 (0x28); + if (data_size >= m_file.MaxOffset || index_offset >= m_file.MaxOffset) + return null; + uint index_size = m_file.View.ReadUInt32 (0x30); + uint flags = m_file.View.ReadUInt32 (0x38); + var key = m_file.View.ReadBytes (0x58, 8); + m_base_offset = m_file.MaxOffset - data_size; + foreach (var scheme in KnownSchemes) + { + m_dir.Clear(); + try + { + using (var stream = m_file.CreateStream (m_base_offset + index_offset, index_size)) + using (var index = scheme.TransformStream (stream, key, flags)) + { + if (ReadIndex (index)) + { + this.Scheme = scheme; + return m_dir; + } + } + } + catch { /* invalid scheme, retry */ } + } + return null; + } + + byte[] m_buffer = new byte[0x80]; + + bool ReadIndex (Stream index) + { + for (int i = 0; i < m_count; ++i) + { + if (m_buffer.Length != index.Read (m_buffer, 0, m_buffer.Length)) + break; + var name = Binary.GetCString (m_buffer, 0, 0x50); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = LittleEndian.ToInt64 (m_buffer, 0x50) + m_base_offset; + entry.UnpackedSize = LittleEndian.ToUInt32 (m_buffer, 0x58); + entry.Size = LittleEndian.ToUInt32 (m_buffer, 0x60); + if (!entry.CheckPlacement (m_file.MaxOffset)) + return false; + entry.Flags = LittleEndian.ToUInt32 (m_buffer, 0x68); + entry.Key = new ArraySegment (m_buffer, 0x78, 8).ToArray(); + entry.IsPacked = entry.UnpackedSize != entry.Size; + m_dir.Add (entry); + } + return m_dir.Count > 0; + } + } + + internal class PrimelScheme + { + public Stream TransformStream (Stream input, byte[] key, uint flags) { var key1 = GenerateKey (key); var iv = GenerateKey (key1); @@ -168,10 +230,9 @@ namespace GameRes.Formats.Primel } } - byte[] GenerateKey (IEnumerable seed) + byte[] GenerateKey (byte[] seed) { - var sha = new Primel.SHA256(); - var hash = sha.ComputeHash (seed.ToArray()); + var hash = ComputeHash (seed); var key = new byte[0x10]; for (int i = 0; i < hash.Length; ++i) { @@ -179,5 +240,20 @@ namespace GameRes.Formats.Primel } return key; } + + protected virtual byte[] ComputeHash (byte[] seed) + { + var sha = new Primel.SHA256(); + return sha.ComputeHash (seed); + } + } + + internal class PrimelSchemeV2 : PrimelScheme + { + protected override byte[] ComputeHash (byte[] seed) + { + using (var sha = System.Security.Cryptography.SHA256.Create()) + return sha.ComputeHash (seed); + } } }