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 @@
+
+
+
+
+
+