diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 0057045d..155e2339 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -126,6 +126,9 @@ + + WidgetDXA.xaml + @@ -1140,6 +1143,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/DxLib/ArcDX.cs b/ArcFormats/DxLib/ArcDX.cs index 12a9e263..79073490 100644 --- a/ArcFormats/DxLib/ArcDX.cs +++ b/ArcFormats/DxLib/ArcDX.cs @@ -198,7 +198,7 @@ namespace GameRes.Formats.DxLib } } - byte[] Unpack (Stream stream) + protected byte[] Unpack (Stream stream) { using (var input = new ArcView.Reader (stream)) { @@ -262,7 +262,7 @@ namespace GameRes.Formats.DxLib } } - List ReadIndex (ArcView file, int version, byte[] key) + protected List ReadIndex (ArcView file, int version, byte[] key) { DxHeader dx = null; if (version <= 4) @@ -313,12 +313,15 @@ namespace GameRes.Formats.DxLib internal static void Decrypt (byte[] data, int index, int count, long offset, byte[] key) { - int key_pos = (int)(offset % key.Length); - for (int i = 0; i < count; ++i) + if (key.Length !=0) { - data[index+i] ^= key[key_pos++]; - if (key.Length == key_pos) - key_pos = 0; + int key_pos = (int)(offset % key.Length); + for (int i = 0; i < count; ++i) + { + data[index + i] ^= key[key_pos++]; + if (key.Length == key_pos) + key_pos = 0; + } } } @@ -361,8 +364,10 @@ namespace GameRes.Formats.DxLib { if (version <= 4) return new IndexReaderV2 (header, version, input); - else if (version >= 6) + else if (version >= 6 && version < 8) return new IndexReaderV6 (header, version, input); + else if (version >=8) + return new IndexReaderV8 (header, version, input); else throw new InvalidFormatException ("Not supported DX archive version."); } @@ -545,7 +550,7 @@ namespace GameRes.Formats.DxLib : base (stream, leave_open) { m_key = key; - m_base_pos = (int)(base_position % m_key.Length); + m_base_pos = m_key.Length !=0 ?(int)(base_position % m_key.Length):0; } public override int Read (byte[] buffer, int offset, int count) @@ -559,32 +564,48 @@ namespace GameRes.Formats.DxLib public override int ReadByte () { - int key_pos = (int)((m_base_pos + Position) % m_key.Length); int b = BaseStream.ReadByte(); - if (-1 != b) + if (m_key.Length !=0) { - b ^= m_key[key_pos]; + int key_pos = (int)((m_base_pos + Position) % m_key.Length); + if (-1 != b) + { + b ^= m_key[key_pos]; + } } return b; } public override void Write (byte[] buffer, int offset, int count) { - int key_pos = (int)((m_base_pos + Position) % m_key.Length); + byte[] write_buf = new byte[count]; - for (int i = 0; i < count; ++i) + if (m_key.Length != 0) { - write_buf[i] = (byte)(buffer[offset+i] ^ m_key[key_pos++]); - if (m_key.Length == key_pos) - key_pos = 0; + int key_pos = (int)((m_base_pos + Position) % m_key.Length); + for (int i = 0; i < count; ++i) + { + write_buf[i] = (byte)(buffer[offset + i] ^ m_key[key_pos++]); + if (m_key.Length == key_pos) + key_pos = 0; + } + } else + { + write_buf = buffer; } BaseStream.Write (write_buf, 0, count); } public override void WriteByte (byte value) { - int key_pos = (int)((m_base_pos + Position) % m_key.Length); - BaseStream.WriteByte ((byte)(value ^ m_key[key_pos])); + if(m_key.Length != 0) + { + int key_pos = (int)((m_base_pos + Position) % m_key.Length); + BaseStream.WriteByte((byte)(value ^ m_key[key_pos])); + } else + { + BaseStream.WriteByte ((byte)value); + } } } } diff --git a/ArcFormats/DxLib/ArcDX8.cs b/ArcFormats/DxLib/ArcDX8.cs index 3c707813..d8e80857 100644 --- a/ArcFormats/DxLib/ArcDX8.cs +++ b/ArcFormats/DxLib/ArcDX8.cs @@ -23,14 +23,21 @@ // IN THE SOFTWARE. // +using GameRes.Formats.PkWare; +using GameRes.Formats.Strings; +using NAudio.SoundFont; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Linq; + + + namespace GameRes.Formats.DxLib { - [Export(typeof(ArchiveFormat))] + [Export(typeof(ArchiveFormat))] public class Dx8Opener : DxOpener { public override string Tag { get { return "BIN/DXLIB"; } } @@ -51,14 +58,10 @@ namespace GameRes.Formats.DxLib DxScheme DefaultScheme = new DxScheme { KnownKeys = new List() }; - internal struct DxHeaderV8 + internal class DxHeaderV8 :DxHeader { - public long BaseOffset; - public long IndexOffset; - public uint IndexSize; - public long FileTable; - public long DirTable; - public int CodePage; + new public long FileTable; + new public long DirTable; public DXA8Flags Flags; public byte HuffmanKB; //15 bytes of padding. @@ -67,7 +70,44 @@ namespace GameRes.Formats.DxLib internal enum DXA8Flags : UInt32 { DXA_FLAG_NO_KEY=1, //file is not encrypted - DXA_FLAG_NO_HEAD_PRESS=1<<1, //do not compress headers + DXA_FLAG_NO_HEAD_PRESS=1<<1, //do not compress the entire file after compressing individual entries + } + + [Serializable] + public class DXAOpts : ResourceOptions + { + public string Keyword; + } + + public override ResourceOptions GetDefaultOptions() + { + return new DXAOpts + { + Keyword = Properties.Settings.Default.DXAPassword + }; + } + + string QueryPassword(ArcView file) + { + var options = Query(arcStrings.ZIPEncryptedNotice); + return options.Keyword; + } + + public override ResourceOptions GetOptions(object widget) + { + if (widget is GUI.WidgetDXA) + { + return new DXAOpts + { + Keyword = ((GUI.WidgetDXA)widget).Password.Text + }; + } + return GetDefaultOptions(); + } + + public override object GetAccessWidget() + { + return new GUI.WidgetDXA(); } public override ArcFile TryOpen (ArcView file) @@ -84,29 +124,105 @@ namespace GameRes.Formats.DxLib }; if (dx.DirTable >= dx.IndexSize || dx.FileTable >= dx.IndexSize) return null; - //at this point we cannot proceed without user input. If NO_HEAD_PRESS is set we could maybe restore the 7-byte key - //Otherwise (assuming the archive is encrypted) we have no way to continue without user input. - - //TODO: Ask for key here. - - var key = DefaultKey; - - var index = file.View.ReadBytes(dx.IndexOffset, dx.IndexSize); - if ((dx.Flags & DXA8Flags.DXA_FLAG_NO_KEY) == 0) + DxKey8 key = null; + + //FIXME: ReadBytes sets hard cap of filesize to 4GB. + var bodyBuffer = file.View.ReadBytes(dx.BaseOffset, (uint)(file.MaxOffset-dx.BaseOffset)); + bool isencrypted = (dx.Flags & DXA8Flags.DXA_FLAG_NO_KEY) == 0; + + if (isencrypted) { - if ((dx.Flags & DXA8Flags.DXA_FLAG_NO_HEAD_PRESS) != 0) + var keyStr = Query(arcStrings.ZIPEncryptedNotice).Keyword; + key = new DxKey8(keyStr); + Decrypt(bodyBuffer, 0, bodyBuffer.Length, 0, key.Key); + + + } + //Decrypted but might be compressed + if ((dx.Flags & DXA8Flags.DXA_FLAG_NO_HEAD_PRESS) == 0) + { + //IndexSize refers to uncompressed + throw new NotImplementedException(); + } + + var readyStr = new MemoryStream(bodyBuffer); + ArcView arcView = new ArcView(readyStr, "body",(uint) bodyBuffer.LongLength); + List entries; + using (var indexStr = arcView.CreateStream(dx.IndexOffset,dx.IndexSize)) + using (var reader = IndexReader.Create(dx, 8, indexStr)) + { + entries = reader.Read(); + } + return new DxArchive(arcView, this,entries ,key, 8); + //return null; + } + } + + internal sealed class IndexReaderV8 : IndexReader + { + readonly int m_entry_size; + public IndexReaderV8(DxHeader header, int version, Stream input) : base(header, version, input) + { + m_entry_size = 0x48; + } + private class DxDirectory + { + public long DirOffset; + public long ParentDirOffset; + public int FileCount; + public long FileTable; + } + + DxDirectory ReadDirEntry() + { + var dir = new DxDirectory(); + dir.DirOffset = m_input.ReadInt64(); + dir.ParentDirOffset = m_input.ReadInt64(); + dir.FileCount = (int)m_input.ReadInt64(); + dir.FileTable = m_input.ReadInt64(); + return dir; + } + + protected override void ReadFileTable(string root, long table_offset) + { + m_input.Position = m_header.DirTable + table_offset; + var dir = ReadDirEntry(); + if (dir.DirOffset != -1 && dir.ParentDirOffset != -1) + { + m_input.Position = m_header.FileTable + dir.DirOffset; + root = Path.Combine(root, ExtractFileName(m_input.ReadInt64())); + } + long current_pos = m_header.FileTable + dir.FileTable; + for (int i = 0; i < dir.FileCount; ++i) + { + m_input.Position = current_pos; + var name_offset = m_input.ReadInt64(); + uint attr = (uint)m_input.ReadInt64(); + m_input.Seek(0x18, SeekOrigin.Current); + var offset = m_input.ReadInt64(); + if (0 != (attr & 0x10)) // FILE_ATTRIBUTE_DIRECTORY { - Decrypt(index, 0, index.Length, 0, key); + if (0 == offset || table_offset == offset) + throw new InvalidFormatException("Infinite recursion in DXA directory index"); + ReadFileTable(root, offset); } else { - //input is compressed. First by huffman then by LZ. if it's also encrypted then we're stuck. - throw new NotImplementedException(); + var size = m_input.ReadInt64(); + var packed_size = m_input.ReadInt64(); + var huffman_packed_size = m_input.ReadInt64(); + var entry = FormatCatalog.Instance.Create(Path.Combine(root, ExtractFileName(name_offset))); + entry.Offset = m_header.BaseOffset + offset; + entry.UnpackedSize = (uint)size; + entry.IsPacked = (-1 != packed_size) || -1 != huffman_packed_size; + if (entry.IsPacked) + entry.Size = (uint)(huffman_packed_size!=-1 ? huffman_packed_size:packed_size); + else + entry.Size = (uint)size; + m_dir.Add(entry); } + current_pos += m_entry_size; } - // decrypt-2 - // decompress - return null; } } } diff --git a/ArcFormats/DxLib/WidgetDXA.xaml b/ArcFormats/DxLib/WidgetDXA.xaml new file mode 100644 index 00000000..25e817e7 --- /dev/null +++ b/ArcFormats/DxLib/WidgetDXA.xaml @@ -0,0 +1,19 @@ + + diff --git a/ArcFormats/DxLib/WidgetDXA.xaml.cs b/ArcFormats/DxLib/WidgetDXA.xaml.cs new file mode 100644 index 00000000..1f40f949 --- /dev/null +++ b/ArcFormats/DxLib/WidgetDXA.xaml.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using GameRes.Formats.DxLib; + +namespace GameRes.Formats.GUI +{ + /// + /// Logika interakcji dla klasy WidgetDXA.xaml + /// + public partial class WidgetDXA : StackPanel + { + public WidgetDXA() + { + InitializeComponent(); + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 60c30c27..192f7036 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// Ten kod został wygenerowany przez narzędzie. +// Wersja wykonawcza:4.0.30319.42000 // -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. +// Zmiany w tym pliku mogą spowodować nieprawidłowe zachowanie i zostaną utracone, jeśli +// kod zostanie ponownie wygenerowany. // //------------------------------------------------------------------------------ @@ -12,7 +12,7 @@ namespace GameRes.Formats.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")] public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -813,5 +813,17 @@ namespace GameRes.Formats.Properties { this["AFAEncodingCP"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("DXARC")] + public string DXAPassword { + get { + return ((string)(this["DXAPassword"])); + } + set { + this["DXAPassword"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 4b4c6753..ebd367cb 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -200,5 +200,8 @@ 932 + + DXARC + \ No newline at end of file diff --git a/ArcFormats/app.config b/ArcFormats/app.config index dd9cbd38..e78ee4ea 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -202,6 +202,9 @@ 932 + + DXARC +