From da3403339e239282ebba1a55a951ce557b3f177e Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 6 Jun 2017 23:22:19 +0400 Subject: [PATCH] (malie): generalized encryption handling. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/Malie/ArcLIB.cs | 184 ++++---------------------- ArcFormats/Malie/ArcLIBU.cs | 4 +- ArcFormats/Malie/LibScheme.cs | 94 +++++++++++++ ArcFormats/Malie/MalieEncryption.cs | 198 ++++++++++++++++++++++++++++ 5 files changed, 319 insertions(+), 163 deletions(-) create mode 100644 ArcFormats/Malie/LibScheme.cs create mode 100644 ArcFormats/Malie/MalieEncryption.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 3219283f..9735d410 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -114,6 +114,8 @@ + + diff --git a/ArcFormats/Malie/ArcLIB.cs b/ArcFormats/Malie/ArcLIB.cs index 1025a095..9de84af3 100644 --- a/ArcFormats/Malie/ArcLIB.cs +++ b/ArcFormats/Malie/ArcLIB.cs @@ -27,8 +27,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; -using System.Text; -using GameRes.Cryptography; using GameRes.Utility; namespace GameRes.Formats.Malie @@ -115,46 +113,15 @@ namespace GameRes.Formats.Malie public class MalieArchive : ArcFile { - public readonly Camellia Encryption; + public readonly IMalieDecryptor Decryptor; - public MalieArchive (ArcView file, ArchiveFormat format, ICollection dir, Camellia encryption) + public MalieArchive (ArcView file, ArchiveFormat format, ICollection dir, IMalieDecryptor decr) : base (file, format, dir) { - Encryption = encryption; + Decryptor = decr; } } - [Serializable] - public class LibScheme - { - public uint DataAlign; - public uint[] Key; - - public LibScheme (uint[] key) : this (0x1000, key) - { - } - - public LibScheme (uint align, uint[] key) - { - DataAlign = align; - Key = key; - } - - public LibScheme (string key) : this (Camellia.GenerateKey (key)) - { - } - - public LibScheme (uint align, string key) : this (align, Camellia.GenerateKey (key)) - { - } - } - - [Serializable] - public class MalieScheme : ResourceScheme - { - public Dictionary KnownSchemes; - } - [Export(typeof(ArchiveFormat))] public class DatOpener : ArchiveFormat { @@ -167,6 +134,7 @@ namespace GameRes.Formats.Malie public DatOpener () { Extensions = new string[] { "lib", "dat" }; + Signatures = new uint[] { 0, 0x3F503FB1 }; } public override ArcFile TryOpen (ArcView file) @@ -176,19 +144,19 @@ namespace GameRes.Formats.Malie var header = new byte[0x10]; foreach (var scheme in KnownSchemes.Values) { - var encryption = new Camellia (scheme.Key); - ReadEncrypted (file.View, encryption, 0, header, 0, 0x10); + var decryptor = scheme.CreateDecryptor(); + ReadEncrypted (file.View, decryptor, 0, header, 0, 0x10); ILibIndexReader reader; if (Binary.AsciiEqual (header, 0, "LIBP")) - reader = new LibPReader (file, encryption, header, scheme); + reader = new LibPReader (file, decryptor, header, scheme); else if (Binary.AsciiEqual (header, 0, "LIBU")) - reader = LibUReader.Create (file, encryption); + reader = LibUReader.Create (file, decryptor); else continue; using (reader) { if (reader.ReadIndex()) - return new MalieArchive (file, this, reader.Dir, encryption); + return new MalieArchive (file, this, reader.Dir, decryptor); } } return null; @@ -199,25 +167,25 @@ namespace GameRes.Formats.Malie var march = arc as MalieArchive; if (null == march) return arc.File.CreateStream (entry.Offset, entry.Size); - var input = new EncryptedStream (march.File, march.Encryption); + var input = new EncryptedStream (march.File, march.Decryptor); return new StreamRegion (input, entry.Offset, entry.Size); } internal abstract class LibIndexReader : ILibIndexReader { - protected ArcView.Frame m_view; - protected readonly long m_max_offset; - protected Camellia m_enc; - protected List m_dir = new List(); - protected byte[] m_header; + protected ArcView.Frame m_view; + protected readonly long m_max_offset; + protected IMalieDecryptor m_dec; + protected List m_dir = new List(); + protected byte[] m_header; public List Dir { get { return m_dir; } } - protected LibIndexReader (ArcView file, Camellia encryption, byte[] header) + protected LibIndexReader (ArcView file, IMalieDecryptor decryptor, byte[] header) { m_view = file.View; m_max_offset = file.MaxOffset; - m_enc = encryption; + m_dec = decryptor; m_header = header; } @@ -243,8 +211,8 @@ namespace GameRes.Formats.Malie long m_data_align; uint[] m_offset_table; - public LibPReader (ArcView file, Camellia encryption, byte[] header, LibScheme scheme) - : base (file, encryption, header) + public LibPReader (ArcView file, IMalieDecryptor decryptor, byte[] header, LibScheme scheme) + : base (file, decryptor, header) { m_base_offset = 0; m_data_align = scheme.DataAlign - 1; @@ -261,10 +229,10 @@ namespace GameRes.Formats.Malie var offsets = new byte[4 * offset_count]; m_base_offset += 0x10; - if (m_index.Length != ReadEncrypted (m_view, m_enc, m_base_offset, m_index, 0, m_index.Length)) + if (m_index.Length != ReadEncrypted (m_view, m_dec, m_base_offset, m_index, 0, m_index.Length)) return false; m_base_offset += m_index.Length; - if (offsets.Length != ReadEncrypted (m_view, m_enc, m_base_offset, offsets, 0, offsets.Length)) + if (offsets.Length != ReadEncrypted (m_view, m_dec, m_base_offset, offsets, 0, offsets.Length)) return false; m_offset_table = new uint[offset_count]; Buffer.BlockCopy (offsets, 0, m_offset_table, 0, offsets.Length); @@ -306,7 +274,7 @@ namespace GameRes.Formats.Malie } } - private static int ReadEncrypted (ArcView.Frame view, Camellia enc, long offset, byte[] buffer, int index, int length) + private static int ReadEncrypted (ArcView.Frame view, IMalieDecryptor dec, long offset, byte[] buffer, int index, int length) { int offset_pad = (int)offset & 0xF; int aligned_len = (offset_pad + length + 0xF) & ~0xF; @@ -328,7 +296,7 @@ namespace GameRes.Formats.Malie for (int block_count = aligned_len / 0x10; block_count > 0; --block_count) { - enc.DecryptBlock (offset, aligned_buf, block); + dec.DecryptBlock (offset, aligned_buf, block); block += 0x10; offset += 0x10; } @@ -345,110 +313,4 @@ namespace GameRes.Formats.Malie set { KnownSchemes = ((MalieScheme)value).KnownSchemes; } } } - - internal class EncryptedStream : Stream - { - ArcView.Frame m_view; - Camellia m_enc; - long m_max_offset; - long m_position = 0; - byte[] m_current_block = new byte[BlockLength]; - int m_current_block_length = 0; - long m_current_block_position = 0; - - public const int BlockLength = 0x1000; - - public Camellia Encryption { get { return m_enc; } } - - public EncryptedStream (ArcView mmap, Camellia encryption) - { - m_view = mmap.CreateFrame(); - m_enc = encryption; - m_max_offset = mmap.MaxOffset; - } - - public override int Read (byte[] buf, int index, int count) - { - int total_read = 0; - bool refill_buffer = !(m_position >= m_current_block_position && m_position < m_current_block_position + m_current_block_length); - while (count > 0 && m_position < m_max_offset) - { - if (refill_buffer) - { - m_current_block_position = m_position & ~((long)BlockLength-1); - FillBuffer(); - } - int src_offset = (int)m_position & (BlockLength-1); - int available = Math.Min (count, m_current_block_length - src_offset); - Buffer.BlockCopy (m_current_block, src_offset, buf, index, available); - m_position += available; - total_read += available; - index += available; - count -= available; - refill_buffer = true; - } - return total_read; - } - - private void FillBuffer () - { - m_current_block_length = m_view.Read (m_current_block_position, m_current_block, 0, (uint)BlockLength); - for (int offset = 0; offset < m_current_block_length; offset += 0x10) - { - m_enc.DecryptBlock (m_current_block_position+offset, m_current_block, offset); - } - } - - #region IO.Stream methods - public override bool CanRead { get { return !m_disposed; } } - public override bool CanWrite { get { return false; } } - public override bool CanSeek { get { return !m_disposed; } } - - public override long Length { get { return m_max_offset; } } - public override long Position - { - get { return m_position; } - set { m_position = value; } - } - - public override long Seek (long pos, SeekOrigin whence) - { - if (SeekOrigin.Current == whence) - m_position += pos; - else if (SeekOrigin.End == whence) - m_position = m_max_offset + pos; - else - m_position = pos; - return m_position; - } - - public override void Write (byte[] buf, int index, int count) - { - throw new NotSupportedException(); - } - - public override void SetLength (long length) - { - throw new NotSupportedException(); - } - - public override void Flush () - { - } - #endregion - - #region IDisposable methods - bool m_disposed = false; - protected override void Dispose (bool disposing) - { - if (!m_disposed) - { - if (disposing) - m_view.Dispose(); - m_disposed = true; - base.Dispose(); - } - } - #endregion - } } diff --git a/ArcFormats/Malie/ArcLIBU.cs b/ArcFormats/Malie/ArcLIBU.cs index fba9e95b..323711b4 100644 --- a/ArcFormats/Malie/ArcLIBU.cs +++ b/ArcFormats/Malie/ArcLIBU.cs @@ -77,9 +77,9 @@ namespace GameRes.Formats.Malie return new LibUReader (input); } - public static LibUReader Create (ArcView file, Camellia encryption) + public static LibUReader Create (ArcView file, IMalieDecryptor decryptor) { - var input = new EncryptedStream (file, encryption); + var input = new EncryptedStream (file, decryptor); return new LibUReader (input); } diff --git a/ArcFormats/Malie/LibScheme.cs b/ArcFormats/Malie/LibScheme.cs new file mode 100644 index 00000000..692713e8 --- /dev/null +++ b/ArcFormats/Malie/LibScheme.cs @@ -0,0 +1,94 @@ +//! \file LibScheme.cs +//! \date Tue Jun 06 22:47:22 2017 +//! \brief Malie encryption schemes. +// +// Copyright (C) 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 +// 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 GameRes.Cryptography; + +namespace GameRes.Formats.Malie +{ + [Serializable] + public abstract class LibScheme + { + public uint DataAlign; + + public LibScheme (uint align) + { + DataAlign = align; + } + + public abstract IMalieDecryptor CreateDecryptor (); + } + + [Serializable] + public class LibCamelliaScheme : LibScheme + { + public uint[] Key { get; set; } + + public LibCamelliaScheme (uint[] key) : this (0x1000, key) + { + } + + public LibCamelliaScheme (uint align, uint[] key) : base (align) + { + Key = key; + } + + public LibCamelliaScheme (string key) : this (Camellia.GenerateKey (key)) + { + } + + public LibCamelliaScheme (uint align, string key) : this (align, Camellia.GenerateKey (key)) + { + } + + public override IMalieDecryptor CreateDecryptor () + { + return new CamelliaDecryptor (Key); + } + } + + [Serializable] + public class LibCfiScheme : LibScheme + { + public byte[] Key { get; set; } + + public LibCfiScheme (uint align, byte[] key) : base (align) + { + Key = key; + } + + public override IMalieDecryptor CreateDecryptor () + { + return new CfiDecryptor (Key); + } + } + + [Serializable] + public class MalieScheme : ResourceScheme + { + public Dictionary KnownSchemes; + } +} diff --git a/ArcFormats/Malie/MalieEncryption.cs b/ArcFormats/Malie/MalieEncryption.cs new file mode 100644 index 00000000..7c339fe6 --- /dev/null +++ b/ArcFormats/Malie/MalieEncryption.cs @@ -0,0 +1,198 @@ +//! \file MalieEncryption.cs +//! \date Tue Jun 06 20:38:57 2017 +//! \brief Malie System encryption implementation. +// +// Copyright (C) 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 +// 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 GameRes.Cryptography; +using GameRes.Utility; + +namespace GameRes.Formats.Malie +{ + public interface IMalieDecryptor + { + void DecryptBlock (long block_offset, byte[] buffer, int index); + } + + public class CamelliaDecryptor : IMalieDecryptor + { + Camellia m_enc; + + public CamelliaDecryptor (uint[] key) + { + m_enc = new Camellia (key); + } + + public void DecryptBlock (long block_offset, byte[] buffer, int index) + { + m_enc.DecryptBlock (block_offset, buffer, index); + } + } + + public class CfiDecryptor : IMalieDecryptor + { + byte[] m_key; + + public CfiDecryptor (byte[] key) + { + m_key = key; + } + + public void DecryptBlock (long block_offset, byte[] data, int index) + { + if (index < 0 || index + 0x10 > data.Length) + throw new ArgumentOutOfRangeException ("index"); + int offset = (int)block_offset; + int o = offset & 0xF; + byte first = data[index+o]; + for (int i = 0; i < 0x10; ++i) + { + if (o != i) + data[index+i] ^= first; + } + offset >>= 4; + unsafe + { + fixed (byte* data8 = &data[index]) + { + uint* data32 = (uint*)data8; + uint k = Binary.RotR (0x39653542, m_key[offset & 0x1F] ^ 0xA5); + data32[0] = Binary.RotR (data32[0] ^ k, m_key[(offset + 12) & 0x1F] ^ 0xA5); + k = Binary.RotL (0x76706367, m_key[(offset + 3) & 0x1F] ^ 0xA5); + data32[1] = Binary.RotL (data32[1] ^ k, m_key[(offset + 15) & 0x1F] ^ 0xA5); + k = Binary.RotR (0x69454462, m_key[(offset + 6) & 0x1F] ^ 0xA5); + data32[2] = Binary.RotR (data32[2] ^ k, m_key[(offset - 14) & 0x1F] ^ 0xA5); + k = Binary.RotL (0x71334334, m_key[(offset + 9) & 0x1F] ^ 0xA5); + data32[3] = Binary.RotL (data32[3] ^ k, m_key[(offset - 11) & 0x1F] ^ 0xA5); + } + } + } + } + + internal class EncryptedStream : Stream + { + ArcView.Frame m_view; + IMalieDecryptor m_dec; + long m_max_offset; + long m_position = 0; + byte[] m_current_block = new byte[BlockLength]; + int m_current_block_length = 0; + long m_current_block_position = 0; + + public const int BlockLength = 0x1000; + + public IMalieDecryptor Decryptor { get { return m_dec; } } + + public EncryptedStream (ArcView mmap, IMalieDecryptor decryptor) + { + m_view = mmap.CreateFrame(); + m_dec = decryptor; + m_max_offset = mmap.MaxOffset; + } + + public override int Read (byte[] buf, int index, int count) + { + int total_read = 0; + bool refill_buffer = !(m_position >= m_current_block_position && m_position < m_current_block_position + m_current_block_length); + while (count > 0 && m_position < m_max_offset) + { + if (refill_buffer) + { + m_current_block_position = m_position & ~((long)BlockLength-1); + FillBuffer(); + } + int src_offset = (int)m_position & (BlockLength-1); + int available = Math.Min (count, m_current_block_length - src_offset); + Buffer.BlockCopy (m_current_block, src_offset, buf, index, available); + m_position += available; + total_read += available; + index += available; + count -= available; + refill_buffer = true; + } + return total_read; + } + + private void FillBuffer () + { + m_current_block_length = m_view.Read (m_current_block_position, m_current_block, 0, (uint)BlockLength); + for (int offset = 0; offset < m_current_block_length; offset += 0x10) + { + m_dec.DecryptBlock (m_current_block_position+offset, m_current_block, offset); + } + } + + #region IO.Stream methods + public override bool CanRead { get { return !m_disposed; } } + public override bool CanWrite { get { return false; } } + public override bool CanSeek { get { return !m_disposed; } } + + public override long Length { get { return m_max_offset; } } + public override long Position + { + get { return m_position; } + set { m_position = value; } + } + + public override long Seek (long pos, SeekOrigin whence) + { + if (SeekOrigin.Current == whence) + m_position += pos; + else if (SeekOrigin.End == whence) + m_position = m_max_offset + pos; + else + m_position = pos; + return m_position; + } + + public override void Write (byte[] buf, int index, int count) + { + throw new NotSupportedException(); + } + + public override void SetLength (long length) + { + throw new NotSupportedException(); + } + + public override void Flush () + { + } + #endregion + + #region IDisposable methods + bool m_disposed = false; + protected override void Dispose (bool disposing) + { + if (!m_disposed) + { + if (disposing) + m_view.Dispose(); + m_disposed = true; + base.Dispose(); + } + } + #endregion + } +}