diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 3e0deeb0..70fcaaee 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -97,6 +97,7 @@ WidgetBELL.xaml + @@ -229,6 +230,9 @@ + + WidgetTactics.xaml + @@ -648,6 +652,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 5dc31d96..9dea8703 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -597,5 +597,29 @@ namespace GameRes.Formats.Properties { this["NPKScheme"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string TacticsArcPassword { + get { + return ((string)(this["TacticsArcPassword"])); + } + set { + this["TacticsArcPassword"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string TacticsArcTitle { + get { + return ((string)(this["TacticsArcTitle"])); + } + set { + this["TacticsArcTitle"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index a881c3cd..16349e7b 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -146,5 +146,11 @@ + + + + + + \ No newline at end of file diff --git a/ArcFormats/Tactics/ArcTactics.cs b/ArcFormats/Tactics/ArcTactics.cs index 897993a8..3b93c991 100644 --- a/ArcFormats/Tactics/ArcTactics.cs +++ b/ArcFormats/Tactics/ArcTactics.cs @@ -2,7 +2,7 @@ //! \date Thu Jul 23 16:27:55 2015 //! \brief Tactics archive file implementation. // -// Copyright (C) 2015 by morkt +// Copyright (C) 2015-2016 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 @@ -30,18 +30,29 @@ using System.IO; using System.Linq; using System.Security.Cryptography; using GameRes.Compression; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; using GameRes.Utility; namespace GameRes.Formats.Tactics { internal class TacticsArcFile : ArcFile { - public byte[] Password; + public readonly byte[] Password; + public readonly bool CustomLzss; public TacticsArcFile (ArcView file, ArchiveFormat format, ICollection dir, byte[] pass) : base (file, format, dir) { Password = pass; + CustomLzss = false; + } + + public TacticsArcFile (ArcView file, ArchiveFormat format, ICollection dir, ArcScheme scheme) + : base (file, format, dir) + { + Password = Encodings.cp932.GetBytes (scheme.Password); + CustomLzss = scheme.CustomLzss; } } @@ -74,7 +85,7 @@ namespace GameRes.Formats.Tactics public override Stream OpenEntry (ArcFile arc, Entry entry) { var tarc = arc as TacticsArcFile; - var tent = entry as PackedEntry; + var tent = (PackedEntry)entry; if (null == tarc || null == tarc.Password && !tent.IsPacked) return arc.File.CreateStream (entry.Offset, entry.Size); if (null == tarc.Password) @@ -88,10 +99,15 @@ namespace GameRes.Formats.Tactics if (p == tarc.Password.Length) p = 0; } - var input = new MemoryStream (data); - if (null == tent || !tent.IsPacked) - return input; - return new LzssStream (input); + if (tarc.CustomLzss && tent.IsPacked) + { + data = UnpackCustomLzss (data); + return new MemoryStream (data); + } + Stream input = new MemoryStream (data); + if (tent.IsPacked) + input = new LzssStream (input); + return input; } internal class IndexReader @@ -148,7 +164,8 @@ namespace GameRes.Formats.Tactics using (var proxy = new InputProxyStream (input, true)) using (var xored = new CryptoStream (proxy, new NotTransform(), CryptoStreamMode.Read)) using (var lzss = new LzssStream (xored)) - lzss.Read (m_index, 0, m_index.Length); + if (m_index.Length != lzss.Read (m_index, 0, m_index.Length)) + return false; int index_offset = Array.IndexOf (m_index, 0); if (-1 == index_offset || 0 == index_offset) @@ -161,10 +178,10 @@ namespace GameRes.Formats.Tactics var entry = new PackedEntry(); entry.Offset = LittleEndian.ToUInt32 (m_index, index_offset) + base_offset; entry.Size = LittleEndian.ToUInt32 (m_index, index_offset + 4); - entry.UnpackedSize = LittleEndian.ToUInt32 (m_index, index_offset + 8); - entry.IsPacked = entry.UnpackedSize != 0; if (!entry.CheckPlacement (m_file.MaxOffset)) return false; + entry.UnpackedSize = LittleEndian.ToUInt32 (m_index, index_offset + 8); + entry.IsPacked = entry.UnpackedSize != 0; if (!entry.IsPacked) entry.UnpackedSize = entry.Size; int name_len = LittleEndian.ToInt32 (m_index, index_offset + 0xC); @@ -186,7 +203,8 @@ namespace GameRes.Formats.Tactics return false; using (var lzss = new LzssStream (input, LzssMode.Decompress, true)) - lzss.Read (m_index, 0, m_index.Length); + if (m_index.Length != lzss.Read (m_index, 0, m_index.Length)) + return false; for (int i = 0; i < m_index.Length; ++i) { @@ -221,5 +239,193 @@ namespace GameRes.Formats.Tactics return true; } } + + static readonly ushort[] LzRefTable = { + 0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, + 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, + 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, + 0x0007, 0x080A, 0x1007, 0x2007, 0x0008, 0x080B, 0x1008, 0x2008, + 0x0009, 0x0904, 0x1009, 0x2009, 0x000A, 0x0905, 0x100A, 0x200A, + 0x000B, 0x0906, 0x100B, 0x200B, 0x000C, 0x0907, 0x100C, 0x200C, + 0x000D, 0x0908, 0x100D, 0x200D, 0x000E, 0x0909, 0x100E, 0x200E, + 0x000F, 0x090A, 0x100F, 0x200F, 0x0010, 0x090B, 0x1010, 0x2010, + 0x0011, 0x0A04, 0x1011, 0x2011, 0x0012, 0x0A05, 0x1012, 0x2012, + 0x0013, 0x0A06, 0x1013, 0x2013, 0x0014, 0x0A07, 0x1014, 0x2014, + 0x0015, 0x0A08, 0x1015, 0x2015, 0x0016, 0x0A09, 0x1016, 0x2016, + 0x0017, 0x0A0A, 0x1017, 0x2017, 0x0018, 0x0A0B, 0x1018, 0x2018, + 0x0019, 0x0B04, 0x1019, 0x2019, 0x001A, 0x0B05, 0x101A, 0x201A, + 0x001B, 0x0B06, 0x101B, 0x201B, 0x001C, 0x0B07, 0x101C, 0x201C, + 0x001D, 0x0B08, 0x101D, 0x201D, 0x001E, 0x0B09, 0x101E, 0x201E, + 0x001F, 0x0B0A, 0x101F, 0x201F, 0x0020, 0x0B0B, 0x1020, 0x2020, + 0x0021, 0x0C04, 0x1021, 0x2021, 0x0022, 0x0C05, 0x1022, 0x2022, + 0x0023, 0x0C06, 0x1023, 0x2023, 0x0024, 0x0C07, 0x1024, 0x2024, + 0x0025, 0x0C08, 0x1025, 0x2025, 0x0026, 0x0C09, 0x1026, 0x2026, + 0x0027, 0x0C0A, 0x1027, 0x2027, 0x0028, 0x0C0B, 0x1028, 0x2028, + 0x0029, 0x0D04, 0x1029, 0x2029, 0x002A, 0x0D05, 0x102A, 0x202A, + 0x002B, 0x0D06, 0x102B, 0x202B, 0x002C, 0x0D07, 0x102C, 0x202C, + 0x002D, 0x0D08, 0x102D, 0x202D, 0x002E, 0x0D09, 0x102E, 0x202E, + 0x002F, 0x0D0A, 0x102F, 0x202F, 0x0030, 0x0D0B, 0x1030, 0x2030, + 0x0031, 0x0E04, 0x1031, 0x2031, 0x0032, 0x0E05, 0x1032, 0x2032, + 0x0033, 0x0E06, 0x1033, 0x2033, 0x0034, 0x0E07, 0x1034, 0x2034, + 0x0035, 0x0E08, 0x1035, 0x2035, 0x0036, 0x0E09, 0x1036, 0x2036, + 0x0037, 0x0E0A, 0x1037, 0x2037, 0x0038, 0x0E0B, 0x1038, 0x2038, + 0x0039, 0x0F04, 0x1039, 0x2039, 0x003A, 0x0F05, 0x103A, 0x203A, + 0x003B, 0x0F06, 0x103B, 0x203B, 0x003C, 0x0F07, 0x103C, 0x203C, + 0x0801, 0x0F08, 0x103D, 0x203D, 0x1001, 0x0F09, 0x103E, 0x203E, + 0x1801, 0x0F0A, 0x103F, 0x203F, 0x2001, 0x0F0B, 0x1040, 0x2040, + }; + + internal byte[] UnpackCustomLzss (byte[] input) + { + int unpacked_size = 0; + int src = 0; + int i = 0; + byte b; + do + { + b = input[src++]; + unpacked_size |= (b & 0x7F) << i; + i += 7; + } + while (b >= 0x80); + if (unpacked_size <= 0) + throw new InvalidEncryptionScheme(); + + var output = new byte[unpacked_size]; + int dst = 0; + while (dst < unpacked_size) + { + b = input[src++]; + if (0 != (b & 3)) + { + int offset_length = (LzRefTable[b] >> 8) & ~7; + int offset = 0; + for (i = 0; i < offset_length; i += 8) + offset |= input[src++] << i; + offset += LzRefTable[b] & 0x700; + + int count = LzRefTable[b] & 0xFF; + Binary.CopyOverlapped (output, dst - offset, dst, count); + dst += count; + } + else + { + int count = (b >> 2) + 1; + if (count >= 0x3D) + { + int count_length = (count - 0x3C) * 8; + count = 0; + for (i = 0; i < count_length; i += 8) + count |= input[src++] << i; + count++; + } + Buffer.BlockCopy (input, src, output, dst, count); + src += count; + dst += count; + } + } + return output; + } + } + + [Export(typeof(ArchiveFormat))] + public class Arc2Opener : ArcOpener + { + public override string Tag { get { return "ARC/Tactics/2"; } } + public override bool CanCreate { get { return false; } } + + public Arc2Opener () + { + Extensions = new string[] { "arc" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "ICS_ARC_FILE")) + return null; + + var dir = new List(); + long offset = 0x10; + while (offset < file.MaxOffset) + { + uint size = file.View.ReadUInt32 (offset); + uint unpacked_size = file.View.ReadUInt32 (offset+4); + uint name_length = file.View.ReadUInt32 (offset+8); + if (0 == name_length) + break; + if (name_length > 0x100) + return null; + offset += 0x14; + var name = file.View.ReadString (offset, name_length); + offset += name_length; + if (offset + size > file.MaxOffset) + return null; + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = offset; + entry.Size = size; + entry.IsPacked = unpacked_size != 0; + entry.UnpackedSize = entry.IsPacked ? unpacked_size : size; + dir.Add (entry); + offset += size; + } + if (0 == dir.Count) + return null; + var scheme = QueryScheme(); + if (null == scheme) + return null; + return new TacticsArcFile (file, this, dir, scheme); + } + + ArcScheme QueryScheme () + { + var options = Query (arcStrings.ArcEncryptedNotice); + return options.Scheme; + } + + public override ResourceOptions GetDefaultOptions () + { + string title = Settings.Default.TacticsArcTitle; + ArcScheme scheme = null; + if (!KnownSchemes.TryGetValue (title, out scheme) && !string.IsNullOrEmpty (Settings.Default.TacticsArcPassword)) + scheme = new ArcScheme (Settings.Default.TacticsArcPassword); + return new TacticsOptions { Scheme = scheme }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetTactics(); + } + + public static Dictionary KnownSchemes = new Dictionary(); + + public override ResourceScheme Scheme + { + get { return new SchemeMap { KnownSchemes = KnownSchemes }; } + set { KnownSchemes = ((SchemeMap)value).KnownSchemes; } + } + } + + public class TacticsOptions : ResourceOptions + { + public ArcScheme Scheme; + } + + [Serializable] + public class ArcScheme + { + public string Password; + public bool CustomLzss; + + public ArcScheme (string password, bool custom_lzss = false) + { + Password = password; + CustomLzss = custom_lzss; + } + } + + [Serializable] + public class SchemeMap : ResourceScheme + { + public Dictionary KnownSchemes; } } diff --git a/ArcFormats/Tactics/WidgetTactics.xaml b/ArcFormats/Tactics/WidgetTactics.xaml new file mode 100644 index 00000000..288d2114 --- /dev/null +++ b/ArcFormats/Tactics/WidgetTactics.xaml @@ -0,0 +1,10 @@ + + + diff --git a/ArcFormats/Tactics/WidgetTactics.xaml.cs b/ArcFormats/Tactics/WidgetTactics.xaml.cs new file mode 100644 index 00000000..20cad0eb --- /dev/null +++ b/ArcFormats/Tactics/WidgetTactics.xaml.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows.Controls; +using GameRes.Formats.Properties; +using GameRes.Formats.Tactics; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetTactics.xaml + /// + public partial class WidgetTactics : StackPanel + { + public WidgetTactics() + { + InitializeComponent(); + Title.ItemsSource = Arc2Opener.KnownSchemes.OrderBy (x => x.Key); + } + } +} diff --git a/ArcFormats/app.config b/ArcFormats/app.config index f8cdb6fc..2a03d682 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -148,6 +148,12 @@ + + + + + +