From 04f1e37d6acc75bf1e2ec8d317124f605b799f36 Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 8 Jan 2017 06:19:56 +0400 Subject: [PATCH] implemented EPK archives. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/Ellefin/ArcEPK.cs | 263 +++++++++++++++++++++++++++++++++++ supported.html | 5 + 3 files changed, 269 insertions(+) create mode 100644 ArcFormats/Ellefin/ArcEPK.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 560ea96c..6380b136 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -99,6 +99,7 @@ + diff --git a/ArcFormats/Ellefin/ArcEPK.cs b/ArcFormats/Ellefin/ArcEPK.cs new file mode 100644 index 00000000..163c61d6 --- /dev/null +++ b/ArcFormats/Ellefin/ArcEPK.cs @@ -0,0 +1,263 @@ +//! \file ArcEPK.cs +//! \date Sat Jan 07 11:30:02 2017 +//! \brief Ellefin Game System resource archive. +// +// 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 System.ComponentModel.Composition; +using System.IO; +using GameRes.Compression; +using GameRes.Formats.Lucifen; +using GameRes.Utility; + +/// +/// Ellefin Game System is a Lucifen predecessor. +/// +namespace GameRes.Formats.Ellefin +{ + internal class EpkEntry : PackedEntry + { + public int DirIndex; + } + + internal class EpkInfo : LpkInfo + { + public bool IndexEncrypted; + } + + [Export(typeof(ArchiveFormat))] + public class EpkOpener : ArchiveFormat + { + public override string Tag { get { return "EPK/Ellefin"; } } + public override string Description { get { return "Ellefin Game System resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public EpkOpener () + { + Signatures = new uint[] { 0x1A4B5045, 0x1E4B5045, 0 }; + } + + static readonly EncryptionScheme DefaultScheme = new EncryptionScheme + { + BaseKey = new LpkOpener.Key (0xA6BD375E, 0x375D916B), ContentXor = 0xD9, RotatePattern = 0x17236351 + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "EPK")) + return null; + int flags = file.View.ReadByte (3); + if (0 == (flags & 2)) + return null; + var scheme = DefaultScheme; + uint arc_key = scheme.BaseKey.Key1; + uint index_key = scheme.BaseKey.Key2; + var arc_info = new EpkInfo + { + AlignedOffset = 0 != (flags & 1), + Flag1 = 0 != (flags & 2), + WholeCrypt = 0 != (flags & 4), + IsEncrypted = 0 != (flags & 8), + IndexEncrypted = 0 != (flags & 0xF0), + PackedEntries = true, + }; + uint index_size = file.View.ReadUInt32 (4); + if (arc_info.IndexEncrypted) + { + var base_name = Path.GetFileNameWithoutExtension (file.Name).ToUpperInvariant(); + var name_bytes = Encodings.cp932.GetBytes (base_name); + int back = name_bytes.Length-1; + for (int i = 0; i < name_bytes.Length; ++i) + { + arc_key ^= name_bytes[back-i]; + index_key ^= name_bytes[i]; + arc_key = Binary.RotR (arc_key, 8); + index_key = Binary.RotL (index_key, 8); + } + index_size ^= index_key; + } + arc_info.Key = arc_key; + if (arc_info.AlignedOffset) + index_size <<= 11; + if (!arc_info.IndexEncrypted || arc_info.AlignedOffset) + index_size -= 8; + if (index_size >= file.MaxOffset) + return null; + var index = file.View.ReadBytes (8, index_size); + if (arc_info.IndexEncrypted) + scheme.DecryptIndex (index, index.Length, index_key); + + var reader = new EpkIndexReader (arc_info); + var dir = reader.Read (index); + if (null == dir) + return null; + if (arc_info.IndexEncrypted) + return new LuciArchive (file, this, dir, scheme, arc_info); + else + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + var epk_ent = entry as PackedEntry; + if (null == epk_ent) + return input; + if (epk_ent.IsPacked) + { + input = new LzssStream (input); + } + var epk = arc as LuciArchive; + if (null == epk || (!epk.Info.WholeCrypt && !epk.Info.IsEncrypted && null == epk.Info.Prefix)) + return input; + var data = new byte[epk_ent.UnpackedSize]; + using (input) + { + input.Read (data, 0, data.Length); + } + if (epk.Info.WholeCrypt) + { + epk.Scheme.DecryptContent (data); + } + if (epk.Info.IsEncrypted) + { + int count = Math.Min (data.Length, 0x10); + epk.Scheme.DecryptEntry (data, count, epk.Info.Key); + } + var header = epk.Info.Prefix; + if (header != null && header.Length <= data.Length) + Buffer.BlockCopy (header, 0, data, 0, header.Length); + return new BinMemoryStream (data, entry.Name); + } + } + + internal sealed class EpkIndexReader + { + IBinaryStream m_index; + EpkInfo m_info; + List m_dir; + byte[] m_name_buf; + bool m_wide_offset; + + public EpkIndexReader (EpkInfo info) + { + m_info = info; + } + + public List Read (byte[] index) + { + using (m_index = new BinMemoryStream (index)) + { + int count = m_index.ReadInt32(); + if (!ArchiveFormat.IsSaneCount (count)) + return null; + m_dir = new List (count); + if (m_info.IndexEncrypted) + ParseEncryptedIndex (count); + else + ParseRegularIndex (count); + return m_dir.Count > 0 ? m_dir : null; + } + } + + void ParseEncryptedIndex (int count) + { + int header_length = m_index.ReadUInt8(); + if (0 != header_length) + { + m_info.Prefix = m_index.ReadBytes (header_length); + } + m_wide_offset = m_index.ReadByte() != 0; + int name_tree_length = m_index.ReadInt32(); + var entry_table_offset = m_index.Position + name_tree_length; + + m_name_buf = new byte[0x110]; + TraverseIndex (m_index.Position, 0); + if (m_dir.Count != count) + throw new InvalidFormatException(); + + foreach (EpkEntry entry in m_dir) + { + m_index.Position = entry_table_offset + entry.DirIndex * 12; + ReadEntry (entry); + } + } + + void ParseRegularIndex (int count) + { + for (int i = 0; i < count; ++i) + { + int name_length = m_index.ReadByte(); + var name = m_index.ReadCString (name_length); + var entry = FormatCatalog.Instance.Create (name); + ReadEntry (entry); + m_dir.Add (entry); + } + } + + void ReadEntry (PackedEntry entry) + { + entry.Offset = m_index.ReadUInt32(); + entry.Size = m_index.ReadUInt32(); + entry.UnpackedSize = m_index.ReadUInt32(); + if (m_info.AlignedOffset) + { + entry.Offset <<= 11; + entry.Size <<= 11; // ??? + } + entry.IsPacked = entry.UnpackedSize != 0; + if (!entry.IsPacked) + entry.UnpackedSize = entry.Size; + } + + void TraverseIndex (long pos, int name_length) + { + if (name_length >= m_name_buf.Length) + throw new InvalidFormatException ("Entry filename is too long"); + m_index.Position = pos; + int count = m_index.ReadByte(); + for (int i = 0; i < count; ++i) + { + byte next_letter = m_index.ReadUInt8(); + int next_offset = m_wide_offset ? m_index.ReadInt32() : (int)m_index.ReadUInt16(); + if (0 == next_letter) + { + var name = Encodings.cp932.GetString (m_name_buf, 0, name_length); + var entry = FormatCatalog.Instance.Create (name); + entry.DirIndex = next_offset; + m_dir.Add (entry); + } + else + { + m_name_buf[name_length] = next_letter; + pos = m_index.Position; + TraverseIndex (pos + next_offset, name_length+1); + m_index.Position = pos; + } + } + } + } +} diff --git a/supported.html b/supported.html index 9627ff6d..064703e8 100644 --- a/supported.html +++ b/supported.html @@ -305,6 +305,7 @@ Zettai Karen! Ojou-sama
*.tlgTLG0.0
TLG5.0
TLG6.0No *.ypfYPFYesYU-RIS +77 (Sevens) ~And, Two Stars Meet Again~
Eroge! ~H mo Game mo Kaihatsu Zanmai~
Koi Mekuri Clover
Mamono Musume-tachi to no Rakuen ~Slime & Scylla~
@@ -1302,6 +1303,10 @@ Boku no Elf Onee-san
*.dat-NoYoukai Tamanokoshi Tsumadori +*.epkEPKNoEllefin Game System +Angelium -Tokimeki Love God-
+ +*.elgELGNo

1 Non-encrypted only