diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 32c7c8a6..fc065a39 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -97,6 +97,7 @@
+
diff --git a/ArcFormats/Qlie/ArcQLIE.cs b/ArcFormats/Qlie/ArcQLIE.cs
index b8d241ad..d6e9c028 100644
--- a/ArcFormats/Qlie/ArcQLIE.cs
+++ b/ArcFormats/Qlie/ArcQLIE.cs
@@ -2,7 +2,7 @@
//! \date Mon Jun 15 04:03:18 2015
//! \brief QLIE engine archives implementation.
//
-// Copyright (C) 2015 by morkt
+// Copyright (C) 2015-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
@@ -32,6 +32,7 @@ using System.Linq;
using GameRes.Utility;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
+using GameRes.Formats.Borland;
namespace GameRes.Formats.Qlie
{
@@ -136,7 +137,7 @@ namespace GameRes.Formats.Qlie
use_pack_keyfile = key_file != null;
// currently, user is prompted to choose encryption scheme only if there's 'key.fkey' file found.
if (use_pack_keyfile)
- arc_key = QueryEncryption();
+ arc_key = QueryEncryption (file);
// use_pack_keyfile = null != arc_key;
var key_data = file.View.ReadBytes (file.MaxOffset-0x41C, 0x100);
@@ -423,10 +424,20 @@ namespace GameRes.Formats.Qlie
return new GUI.WidgetQLIE();
}
- byte[] QueryEncryption ()
+ byte[] QueryEncryption (ArcView file)
{
- var options = Query (arcStrings.ArcEncryptedNotice);
- return options.GameKeyData;
+ var title = FormatCatalog.Instance.LookupGame (file.Name, @"..\*.exe");
+ byte[] key = null;
+ if (!string.IsNullOrEmpty (title))
+ key = GetKeyData (title);
+ if (null == key)
+ key = GuessKeyData (file.Name);
+ if (null == key)
+ {
+ var options = Query (arcStrings.ArcEncryptedNotice);
+ key = options.GameKeyData;
+ }
+ return key;
}
static byte[] GetKeyData (string scheme)
@@ -457,5 +468,46 @@ namespace GameRes.Formats.Qlie
}
return null;
}
+
+ byte[] GuessKeyData (string arc_name)
+ {
+ if (VFS.IsVirtual)
+ return null;
+ // XXX add button to query dialog like with CatSystem?
+ var pattern = VFS.CombinePath (VFS.GetDirectoryName (arc_name), @"..\*.exe");
+ foreach (var file in VFS.GetFiles (pattern))
+ {
+ try
+ {
+ var key = GetKeyDataFromExe (file.Name);
+ if (key != null)
+ return key;
+ }
+ catch { /* ignore errors */ }
+ }
+ return null;
+ }
+
+ public static byte[] GetKeyDataFromExe (string filename)
+ {
+ using (var exe = new ExeFile.ResourceAccessor (filename))
+ {
+ var tform = exe.GetResource ("TFORM1", "#10");
+ if (null == tform || !tform.AsciiEqual (0, "TPF0"))
+ return null;
+ using (var input = new BinMemoryStream (tform))
+ {
+ var deserializer = new DelphiDeserializer (input);
+ var form = deserializer.Deserialize();
+ var image = form.Contents.FirstOrDefault (n => n.Name == "IconKeyImage");
+ if (null == image)
+ return null;
+ var icon = image.Props["Picture.Data"] as byte[];
+ if (null == icon || icon.Length < 0x106 || !icon.AsciiEqual (0, "\x05TIcon"))
+ return null;
+ return new CowArray (icon, 6, 0x100).ToArray();
+ }
+ }
+ }
}
}
diff --git a/ArcFormats/Qlie/DelphiDeserializer.cs b/ArcFormats/Qlie/DelphiDeserializer.cs
new file mode 100644
index 00000000..3cbe7983
--- /dev/null
+++ b/ArcFormats/Qlie/DelphiDeserializer.cs
@@ -0,0 +1,147 @@
+//! \file DelphiDeserializer.cs
+//! \date Wed Feb 22 15:40:33 2017
+//! \brief Borland Delphi binary data deserializer.
+//
+// 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.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+namespace GameRes.Formats.Borland
+{
+ public sealed class DelphiDeserializer
+ {
+ IBinaryStream m_input;
+
+ public Encoding Encoding { get; set; }
+
+ public DelphiDeserializer (IBinaryStream input)
+ {
+ m_input = input;
+ Encoding = Encodings.cp932;
+ }
+
+ public DelphiObject Deserialize ()
+ {
+ if (m_input.ReadUInt32() != 0x30465054) // 'TPF0'
+ return null;
+ return DeserializeNode();
+ }
+
+ DelphiObject DeserializeNode ()
+ {
+ int type_len = m_input.ReadByte();
+ if (type_len <= 0)
+ return null;
+ var node = new DelphiObject();
+ node.Type = ReadString (type_len);
+ node.Name = ReadString();
+ int key_length;
+ while ((key_length = m_input.ReadUInt8()) > 0)
+ {
+ var key = ReadString (key_length);
+ node.Props[key] = ReadValue();
+ }
+ DelphiObject child;
+ while ((child = DeserializeNode()) != null)
+ {
+ node.Contents.Add (child);
+ }
+ return node;
+ }
+
+ object ReadValue ()
+ {
+ int type = m_input.ReadUInt8();
+ switch (type)
+ {
+ case 2: return (int)m_input.ReadUInt8();
+ case 3: return (int)m_input.ReadUInt16();
+ case 5: return ReadLongDouble();
+ case 6:
+ case 7: return ReadString();
+ case 8:
+ case 9: return true;
+ case 10: return ReadByteString();
+ case 11: return ReadStringArray();
+ case 18: return ReadUnicodeString();
+ default: throw new System.NotImplementedException();
+ }
+ }
+
+ string ReadString ()
+ {
+ return ReadString (m_input.ReadUInt8());
+ }
+
+ string ReadString (int length)
+ {
+ return m_input.ReadCString (length, Encoding);
+ }
+
+ string ReadUnicodeString ()
+ {
+ int length = m_input.ReadInt32();
+ if (length < 0)
+ throw new InvalidFormatException();
+ if (0 == length)
+ return "";
+ var bytes = m_input.ReadBytes (length * 2);
+ return Encoding.Unicode.GetString (bytes);
+ }
+
+ byte[] ReadByteString ()
+ {
+ int length = m_input.ReadInt32();
+ if (length < 0)
+ throw new InvalidFormatException();
+ if (0 == length)
+ return new byte[0];
+ return m_input.ReadBytes (length);
+ }
+
+ IList ReadStringArray ()
+ {
+ var list = new List();
+ int length;
+ while ((length = m_input.ReadUInt8()) > 0)
+ {
+ list.Add (ReadString (length));
+ }
+ return list;
+ }
+
+ object ReadLongDouble ()
+ {
+ return m_input.ReadBytes (10); // long double deserialization not implemented
+ }
+ }
+
+ public class DelphiObject
+ {
+ public string Type;
+ public string Name;
+ public IDictionary Props = new Hashtable();
+ public IList Contents = new List();
+ }
+}