diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 5e24aaa3..56dcce63 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -92,6 +92,11 @@ + + + + WidgetNSA.xaml + @@ -354,6 +359,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/NScripter/ArcNSA.cs b/ArcFormats/NScripter/ArcNSA.cs index dd0ec41c..0f195604 100644 --- a/ArcFormats/NScripter/ArcNSA.cs +++ b/ArcFormats/NScripter/ArcNSA.cs @@ -1,8 +1,8 @@ //! \file ArcNSA.cs //! \date Sun Jul 27 11:25:46 2014 -//! \brief ONScripter NSA/SAR archives implementation. +//! \brief NScripter NSA archives implementation. // -// Copyright (C) 2014 by morkt +// Copyright (C) 2014-2015 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 @@ -27,22 +27,33 @@ using System; using System.IO; using System.Collections.Generic; using System.ComponentModel.Composition; -using GameRes.Formats.Strings; -using GameRes.Utility; -using GameRes.Formats.Properties; using System.Text; -using System.Diagnostics; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; +using GameRes.Utility; -namespace GameRes.Formats.ONScripter +namespace GameRes.Formats.NScripter { public class NsaEntry : PackedEntry { public Compression CompressionType { get; set; } } + internal class NsaEncryptedArchive : ArcFile + { + public readonly byte[] Key; + + public NsaEncryptedArchive (ArcView arc, ArchiveFormat impl, ICollection dir, byte[] key) + : base (arc, impl, dir) + { + Key = key; + } + } + public class NsaOptions : ResourceOptions { public Compression CompressionType { get; set; } + public string Password { get; set; } } public enum Compression @@ -54,138 +65,19 @@ namespace GameRes.Formats.ONScripter NBZ = 4, } - [Export(typeof(ArchiveFormat))] - public class SarOpener : ArchiveFormat - { - public override string Tag { get { return "SAR"; } } - public override string Description { get { return arcStrings.NSADescription; } } - public override uint Signature { get { return 0; } } - public override bool IsHierarchic { get { return true; } } - public override bool CanCreate { get { return true; } } - - public override ArcFile TryOpen (ArcView file) - { - int num_of_files = Binary.BigEndian (file.View.ReadInt16 (0)); - if (num_of_files <= 0) - return null; - uint base_offset = Binary.BigEndian (file.View.ReadUInt32 (2)); - if (base_offset >= file.MaxOffset || base_offset < 10 * (uint)num_of_files) - return null; - - uint cur_offset = 6; - var dir = new List(); - for (int i = 0; i < num_of_files; ++i) - { - if (base_offset - cur_offset < 10) - return null; - int name_len; - byte[] name_buffer = ReadName (file, cur_offset, base_offset-cur_offset, out name_len); - if (0 == name_len || base_offset-cur_offset == name_len) - return null; - cur_offset += (uint)(name_len + 1); - if (base_offset - cur_offset < 8) - return null; - - string name = Encodings.cp932.GetString (name_buffer, 0, name_len); - var entry = FormatCatalog.Instance.Create (name); - entry.Offset = Binary.BigEndian (file.View.ReadUInt32 (cur_offset)) + (long)base_offset; - entry.Size = Binary.BigEndian (file.View.ReadUInt32 (cur_offset+4)); - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - - cur_offset += 8; - dir.Add (entry); - } - return new ArcFile (file, this, dir); - } - - public override void Create (Stream output, IEnumerable list, ResourceOptions options, - EntryCallback callback) - { - var encoding = Encodings.cp932.WithFatalFallback(); - int callback_count = 0; - - var real_entry_list = new List(); - var used_names = new HashSet(); - int index_size = 0; - foreach (var entry in list) - { - if (!used_names.Add (entry.Name)) // duplicate name - continue; - try - { - index_size += encoding.GetByteCount (entry.Name) + 1; - } - catch (EncoderFallbackException X) - { - throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); - } - index_size += 8; - real_entry_list.Add (entry); - } - - long start_offset = output.Position; - long base_offset = 6+index_size; - output.Seek (base_offset, SeekOrigin.Current); - foreach (var entry in real_entry_list) - { - using (var input = File.OpenRead (entry.Name)) - { - var file_size = input.Length; - if (file_size > uint.MaxValue) - throw new FileSizeException(); - long file_offset = output.Position - base_offset; - if (file_offset+file_size > uint.MaxValue) - throw new FileSizeException(); - entry.Offset = file_offset; - entry.Size = (uint)file_size; - if (null != callback) - callback (callback_count++, entry, arcStrings.MsgAddingFile); - - input.CopyTo (output); - } - } - - if (null != callback) - callback (callback_count++, null, arcStrings.MsgWritingIndex); - output.Position = start_offset; - using (var writer = new BinaryWriter (output, encoding, true)) - { - writer.Write (Binary.BigEndian ((short)real_entry_list.Count)); - writer.Write (Binary.BigEndian ((uint)base_offset)); - foreach (var entry in real_entry_list) - { - writer.Write (encoding.GetBytes (entry.Name)); - writer.Write ((byte)0); - writer.Write (Binary.BigEndian ((uint)entry.Offset)); - writer.Write (Binary.BigEndian ((uint)entry.Size)); - } - } - } - - protected static byte[] ReadName (ArcView file, uint offset, uint limit, out int name_len) - { - byte[] name_buffer = new byte[40]; - for (name_len = 0; name_len < limit; ++name_len) - { - byte b = file.View.ReadByte (offset+name_len); - if (0 == b) - break; - if (name_buffer.Length == name_len) - { - Array.Resize (ref name_buffer, checked(name_len/2*3)); - } - name_buffer[name_len] = b; - } - return name_buffer; - } - } - [Export(typeof(ArchiveFormat))] public class NsaOpener : SarOpener { public override string Tag { get { return "NSA"; } } + public static readonly Dictionary KnownKeys = new Dictionary() + { + { "Kimi ga Aruji de Shitsuji ga Ore de", + "kopkl;fdsl;kl;mwekopj@pgfd[p;:kl:;,lwret;kl;kolsgfdio@pdsflkl:,rse;:l,;:lpksdfpo" }, + { "Kiss yori Amakute Fukai Mono", + "dfklmdsgkmlkmljklgfnlsdfnklsdfjkl;sdfmkldfskfsdmklsdfjklfdsjklsdfsdfl;" }, + }; + public NsaOpener () { Extensions = new string[] { "nsa", "dat" }; @@ -193,85 +85,140 @@ namespace GameRes.Formats.ONScripter public override ArcFile TryOpen (ArcView file) { - if (0 != file.View.ReadInt16 (0)) - return ReadIndex (file, 0); - else - return ReadIndex (file, 2); + List dir = null; + bool zero_signature = 0 == file.View.ReadInt16 (0); + try + { + using (var input = new ViewStream (file, true)) + { + if (zero_signature) + input.Seek (2, SeekOrigin.Begin); + dir = ReadIndex (input); + if (null != dir) + return new ArcFile (file, this, dir); + } + } + catch { /* ignore parse errors */ } + if (zero_signature || !file.Name.EndsWith (".nsa", StringComparison.InvariantCultureIgnoreCase)) + return null; + + var password = QueryPassword(); + if (string.IsNullOrEmpty (password)) + return null; + var key = Encoding.ASCII.GetBytes (password); + + using (var input = new EncryptedViewStream (file, key, true)) + { + dir = ReadIndex (input); + if (null == dir) + return null; + return new NsaEncryptedArchive (file, this, dir, key); + } } - private ArcFile ReadIndex (ArcView file, uint base_offset) + protected List ReadIndex (Stream file) { - int num_of_files = Binary.BigEndian (file.View.ReadInt16 (base_offset)); - if (num_of_files <= 0) - return null; - uint cur_offset = base_offset+6; - base_offset += Binary.BigEndian (file.View.ReadUInt32 (base_offset+2)); - if (base_offset >= file.MaxOffset || base_offset < 15 * (uint)num_of_files) - return null; - - var dir = new List(); - for (int i = 0; i < num_of_files; ++i) + long base_offset = file.Position; + using (var input = new ArcView.Reader (file)) { - if (base_offset - cur_offset < 15) + int count = Binary.BigEndian (input.ReadInt16()); + if (!IsSaneCount (count)) return null; - int name_len; - byte[] name_buffer = ReadName (file, cur_offset, base_offset-cur_offset, out name_len); - if (0 == name_len || base_offset-cur_offset == name_len) - return null; - cur_offset += (uint)(name_len + 1); - if (base_offset - cur_offset < 13) + base_offset += Binary.BigEndian (input.ReadUInt32()); + if (base_offset >= file.Length || base_offset < 15 * count) return null; - var name = Encodings.cp932.GetString (name_buffer, 0, name_len); - var entry = FormatCatalog.Instance.Create (name); - byte compression_type = file.View.ReadByte (cur_offset); - entry.Offset = Binary.BigEndian (file.View.ReadUInt32 (cur_offset+1)) + (long)base_offset; - entry.Size = Binary.BigEndian (file.View.ReadUInt32 (cur_offset+5)); - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - entry.UnpackedSize = Binary.BigEndian (file.View.ReadUInt32 (cur_offset+9)); - entry.IsPacked = compression_type != 0; - switch (compression_type) + var dir = new List(); + for (int i = 0; i < count; ++i) { - case 0: entry.CompressionType = Compression.None; break; - case 1: entry.CompressionType = Compression.SPB; break; - case 2: entry.CompressionType = Compression.LZSS; break; - case 4: entry.CompressionType = Compression.NBZ; break; - default: entry.CompressionType = Compression.Unknown; break; + if (base_offset - file.Position < 15) + return null; + var name = file.ReadCString(); + if (base_offset - file.Position < 13) + return null; + + var entry = FormatCatalog.Instance.Create (name); + byte compression_type = input.ReadByte(); + entry.Offset = Binary.BigEndian (input.ReadUInt32()) + base_offset; + entry.Size = Binary.BigEndian (input.ReadUInt32()); + if (!entry.CheckPlacement (file.Length)) + return null; + entry.UnpackedSize = Binary.BigEndian (input.ReadUInt32()); + entry.IsPacked = compression_type != 0; + switch (compression_type) + { + case 0: entry.CompressionType = Compression.None; break; + case 1: entry.CompressionType = Compression.SPB; break; + case 2: entry.CompressionType = Compression.LZSS; break; + case 4: entry.CompressionType = Compression.NBZ; break; + default: entry.CompressionType = Compression.Unknown; break; + } + dir.Add (entry); } - cur_offset += 13; - dir.Add (entry); + return dir; } - return new ArcFile (file, this, dir); } public override Stream OpenEntry (ArcFile arc, Entry entry) { - var nsa_entry = entry as NsaEntry; - if (null != nsa_entry && - (Compression.LZSS == nsa_entry.CompressionType || - Compression.SPB == nsa_entry.CompressionType)) + var nsa_arc = arc as NsaEncryptedArchive; + if (null == nsa_arc) { - using (var input = arc.File.CreateStream (nsa_entry.Offset, nsa_entry.Size)) - { - var decoder = new Unpacker (input, nsa_entry.UnpackedSize); - switch (nsa_entry.CompressionType) - { - case Compression.LZSS: return decoder.LzssDecodedStream(); - case Compression.SPB: return decoder.SpbDecodedStream(); - } - } + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return UnpackEntry (input, entry as NsaEntry); } - return arc.File.CreateStream (entry.Offset, entry.Size); + var data = new byte[entry.Size]; + using (var input = new EncryptedViewStream (arc.File, nsa_arc.Key, true)) + { + input.Position = entry.Offset; + input.Read (data, 0, data.Length); + } + return UnpackEntry (new MemoryStream (data), entry as NsaEntry); + } + + protected Stream UnpackEntry (Stream input, NsaEntry nsa_entry) + { + if (null == nsa_entry + || !(Compression.LZSS == nsa_entry.CompressionType || + Compression.SPB == nsa_entry.CompressionType)) + return input; + using (input) + { + var decoder = new Unpacker (input, nsa_entry.UnpackedSize); + if (Compression.SPB == nsa_entry.CompressionType) + return decoder.SpbDecodedStream(); + else + return decoder.LzssDecodedStream(); + } + } + + private string QueryPassword () + { + var options = Query (arcStrings.ArcEncryptedNotice); + return options.Password; } public override ResourceOptions GetDefaultOptions () { return new NsaOptions { - CompressionType = Settings.Default.ONSCompression, + CompressionType = Settings.Default.ONSCompression, + Password = Settings.Default.NSAPassword, }; } + public override ResourceOptions GetOptions (object widget) + { + var w = widget as GUI.WidgetNSA; + if (null != w) + Settings.Default.NSAPassword = w.Password.Text; + return GetDefaultOptions(); + } + + public override object GetAccessWidget () + { + return new GUI.WidgetNSA(); + } + public override object GetCreationWidget () { return new GUI.CreateONSWidget(); @@ -384,42 +331,34 @@ namespace GameRes.Formats.ONScripter public const int F = ((1 << EJ) + P); /* lookahead buffer size */ } - internal class Unpacker + internal class Unpacker : MsbBitStream { - private Stream m_input; private byte[] m_output; private byte[] m_read_buf = new byte[4096]; - public Unpacker (Stream input, uint unpacked_size) + public byte[] Output { get { return m_output; } } + + public Unpacker (Stream input, uint unpacked_size) : base (input, true) { - m_input = input; m_output = new byte[unpacked_size]; } public Stream LzssDecodedStream () { - uint size = DecodeLZSS(); - if (size != m_output.Length) - System.Diagnostics.Trace.WriteLine ("Invalid compressed data", "LzssDecoder"); - return new MemoryStream (m_output, false); + DecodeLZSS(); + return new MemoryStream (m_output); } public Stream SpbDecodedStream () { - uint size = DecodeSPB(); - return new MemoryStream (m_output, false); + DecodeSPB(); + return new MemoryStream (m_output); } - private int m_getbit_mask; - private int m_getbit_len; - private int m_getbit_count; - uint DecodeLZSS () { uint count = 0; - m_getbit_mask = 0; - m_getbit_len = m_getbit_count = 0; byte[] decomp_buffer = new byte[LZSS.N*2]; int r = LZSS.N - LZSS.F; int c; @@ -456,13 +395,10 @@ namespace GameRes.Formats.ONScripter uint DecodeSPB () { - m_getbit_mask = 0; - m_getbit_len = m_getbit_count = 0; - - uint width = (uint)(m_input.ReadByte() << 8); - width |= (uint)m_input.ReadByte(); - uint height = (uint)(m_input.ReadByte() << 8); - height |= (uint)m_input.ReadByte(); + uint width = (uint)Input.ReadByte() << 8; + width |= (uint)Input.ReadByte(); + uint height = (uint)Input.ReadByte() << 8; + height |= (uint)Input.ReadByte(); uint width_pad = (4 - width * 3 % 4) % 4; int stride = (int)(width * 3 + width_pad); @@ -475,19 +411,13 @@ namespace GameRes.Formats.ONScripter /* Write header */ m_output[0] = (byte)'B'; m_output[1] = (byte)'M'; - m_output[2] = (byte)(total_size & 0xff); - m_output[3] = (byte)((total_size >> 8) & 0xff); - m_output[4] = (byte)((total_size >> 16) & 0xff); - m_output[5] = (byte)((total_size >> 24) & 0xff); + LittleEndian.Pack (total_size, m_output, 2); m_output[10] = 54; // offset to the body m_output[14] = 40; // header size - m_output[18] = (byte)(width & 0xff); - m_output[19] = (byte)((width >> 8) & 0xff); - m_output[22] = (byte)(height & 0xff); - m_output[23] = (byte)((height >> 8) & 0xff); + LittleEndian.Pack (width, m_output, 18); + LittleEndian.Pack (height, m_output, 22); m_output[26] = 1; // the number of the plane m_output[28] = 24; // bpp -// m_output[34] = (byte)(total_size - 54); // size of the body byte[] decomp_buffer = new byte[width*height*4]; @@ -554,33 +484,6 @@ namespace GameRes.Formats.ONScripter } return total_size; } - - private int m_getbit_buf = 0; - - int GetBits (int n) - { - int x = 0; - for (int i = 0; i < n; i++) - { - if (0 == m_getbit_mask) - { - if (m_getbit_len == m_getbit_count) - { - m_getbit_len = m_input.Read (m_read_buf, 0, m_read_buf.Length); - if (0 == m_getbit_len) - return -1; - m_getbit_count = 0; - } - m_getbit_buf = m_read_buf[m_getbit_count++]; - m_getbit_mask = 128; - } - x <<= 1; - if (0 != (m_getbit_buf & m_getbit_mask)) - x |= 1; - m_getbit_mask >>= 1; - } - return x; - } } internal class Packer diff --git a/ArcFormats/NScripter/ArcSAR.cs b/ArcFormats/NScripter/ArcSAR.cs new file mode 100644 index 00000000..56e81478 --- /dev/null +++ b/ArcFormats/NScripter/ArcSAR.cs @@ -0,0 +1,160 @@ +//! \file ArcSAR.cs +//! \date Tue Sep 01 01:36:24 2015 +//! \brief NScripter SAR archives implementation. +// +// 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.IO; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Text; +using GameRes.Formats.Strings; +using GameRes.Utility; + +namespace GameRes.Formats.NScripter +{ + [Export(typeof(ArchiveFormat))] + public class SarOpener : ArchiveFormat + { + public override string Tag { get { return "SAR"; } } + public override string Description { get { return arcStrings.NSADescription; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return true; } } + + public override ArcFile TryOpen (ArcView file) + { + int num_of_files = Binary.BigEndian (file.View.ReadInt16 (0)); + if (num_of_files <= 0) + return null; + uint base_offset = Binary.BigEndian (file.View.ReadUInt32 (2)); + if (base_offset >= file.MaxOffset || base_offset < 10 * (uint)num_of_files) + return null; + + uint cur_offset = 6; + var dir = new List(); + for (int i = 0; i < num_of_files; ++i) + { + if (base_offset - cur_offset < 10) + return null; + int name_len; + byte[] name_buffer = ReadName (file, cur_offset, base_offset-cur_offset, out name_len); + if (0 == name_len || base_offset-cur_offset == name_len) + return null; + cur_offset += (uint)(name_len + 1); + if (base_offset - cur_offset < 8) + return null; + + string name = Encodings.cp932.GetString (name_buffer, 0, name_len); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = Binary.BigEndian (file.View.ReadUInt32 (cur_offset)) + (long)base_offset; + entry.Size = Binary.BigEndian (file.View.ReadUInt32 (cur_offset+4)); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + + cur_offset += 8; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var encoding = Encodings.cp932.WithFatalFallback(); + int callback_count = 0; + + var real_entry_list = new List(); + var used_names = new HashSet(); + int index_size = 0; + foreach (var entry in list) + { + if (!used_names.Add (entry.Name)) // duplicate name + continue; + try + { + index_size += encoding.GetByteCount (entry.Name) + 1; + } + catch (EncoderFallbackException X) + { + throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); + } + index_size += 8; + real_entry_list.Add (entry); + } + + long start_offset = output.Position; + long base_offset = 6+index_size; + output.Seek (base_offset, SeekOrigin.Current); + foreach (var entry in real_entry_list) + { + using (var input = File.OpenRead (entry.Name)) + { + var file_size = input.Length; + if (file_size > uint.MaxValue) + throw new FileSizeException(); + long file_offset = output.Position - base_offset; + if (file_offset+file_size > uint.MaxValue) + throw new FileSizeException(); + entry.Offset = file_offset; + entry.Size = (uint)file_size; + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + input.CopyTo (output); + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + output.Position = start_offset; + using (var writer = new BinaryWriter (output, encoding, true)) + { + writer.Write (Binary.BigEndian ((short)real_entry_list.Count)); + writer.Write (Binary.BigEndian ((uint)base_offset)); + foreach (var entry in real_entry_list) + { + writer.Write (encoding.GetBytes (entry.Name)); + writer.Write ((byte)0); + writer.Write (Binary.BigEndian ((uint)entry.Offset)); + writer.Write (Binary.BigEndian ((uint)entry.Size)); + } + } + } + + protected static byte[] ReadName (ArcView file, uint offset, uint limit, out int name_len) + { + byte[] name_buffer = new byte[40]; + for (name_len = 0; name_len < limit; ++name_len) + { + byte b = file.View.ReadByte (offset+name_len); + if (0 == b) + break; + if (name_buffer.Length == name_len) + { + Array.Resize (ref name_buffer, checked(name_len/2*3)); + } + name_buffer[name_len] = b; + } + return name_buffer; + } + } +} diff --git a/ArcFormats/NScripter/CreateONSWidget.xaml.cs b/ArcFormats/NScripter/CreateONSWidget.xaml.cs index 76361f64..8c75bd91 100644 --- a/ArcFormats/NScripter/CreateONSWidget.xaml.cs +++ b/ArcFormats/NScripter/CreateONSWidget.xaml.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Windows; using System.Windows.Controls; using System.Windows.Data; using GameRes.Formats.Strings; @@ -18,16 +17,16 @@ namespace GameRes.Formats.GUI } } - [ValueConversion (typeof (ONScripter.Compression), typeof (string))] + [ValueConversion (typeof (NScripter.Compression), typeof (string))] class CompressionToStringConverter : IValueConverter { public object Convert (object value, Type targetType, object parameter, CultureInfo culture) { - switch ((ONScripter.Compression)value) + switch ((NScripter.Compression)value) { - case ONScripter.Compression.SPB: return "SPB"; - case ONScripter.Compression.LZSS: return "LZSS"; - case ONScripter.Compression.NBZ: return "NBZ"; + case NScripter.Compression.SPB: return "SPB"; + case NScripter.Compression.LZSS: return "LZSS"; + case NScripter.Compression.NBZ: return "NBZ"; default: return arcStrings.ONSCompressionNone; } } @@ -38,13 +37,13 @@ namespace GameRes.Formats.GUI if (!string.IsNullOrEmpty (s)) { if ("SPB" == s) - return ONScripter.Compression.SPB; + return NScripter.Compression.SPB; else if ("LZSS" == s) - return ONScripter.Compression.LZSS; + return NScripter.Compression.LZSS; else if ("NBZ" == s) - return ONScripter.Compression.NBZ; + return NScripter.Compression.NBZ; } - return ONScripter.Compression.None; + return NScripter.Compression.None; } } } diff --git a/ArcFormats/NScripter/EncryptedStream.cs b/ArcFormats/NScripter/EncryptedStream.cs new file mode 100644 index 00000000..bfcd39a8 --- /dev/null +++ b/ArcFormats/NScripter/EncryptedStream.cs @@ -0,0 +1,200 @@ +//! \file ArcEncrypted.cs +//! \date Mon Aug 31 11:43:25 2015 +//! \brief Encrypted NSA archives implementation. +// +// Copyright (C) 2015 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.IO; +using System.Linq; +using System.Security.Cryptography; +using GameRes.Utility; + +namespace GameRes.Formats.NScripter +{ + public class ViewStream : Stream + { + protected ArcView m_file; + protected long m_position = 0; + bool m_should_dispose; + + public ViewStream (ArcView mmap, bool leave_open = false) + { + m_file = mmap; + m_should_dispose = !leave_open; + } + + public override int Read (byte[] buf, int index, int count) + { + if (m_position >= m_file.MaxOffset) + return 0; + int read = m_file.View.Read (m_position, buf, index, (uint)count); + m_position += read; + return read; + } + + #region IO.Stream methods + public override bool CanRead { get { return true; } } + public override bool CanWrite { get { return false; } } + public override bool CanSeek { get { return true; } } + + public override long Length { get { return m_file.MaxOffset; } } + public override long Position + { + get { return m_position; } + set { m_position = value; } + } + + public override long Seek (long pos, SeekOrigin whence) + { + if (SeekOrigin.Current == whence) + m_position += pos; + else if (SeekOrigin.End == whence) + m_position = m_file.MaxOffset + pos; + else + m_position = pos; + return m_position; + } + + public override void Write (byte[] buf, int index, int count) + { + throw new NotSupportedException(); + } + + public override void SetLength (long length) + { + throw new NotSupportedException(); + } + + public override void Flush () + { + } + #endregion + + #region IDisposable methods + bool m_disposed = false; + protected override void Dispose (bool disposing) + { + if (!m_disposed) + { + if (disposing && m_should_dispose) + m_file.Dispose(); + m_disposed = true; + base.Dispose(); + } + } + #endregion + } + + internal class EncryptedViewStream : ViewStream + { + byte[] m_key; + byte[] m_current_block = new byte[BlockLength]; + int m_current_block_length = 0; + long m_current_block_position = 0; + + static readonly HashAlgorithm MD5 = System.Security.Cryptography.MD5.Create(); + static readonly HashAlgorithm SHA1 = System.Security.Cryptography.SHA1.Create(); + + public const int BlockLength = 1024; + + public EncryptedViewStream (ArcView mmap, byte[] key, bool leave_open = false) + : base (mmap, leave_open) + { + m_key = key; + } + + public override int Read (byte[] buf, int index, int count) + { + int total_read = 0; + bool refill_buffer = !(m_position >= m_current_block_position && m_position < m_current_block_position + m_current_block_length); + while (count > 0 && m_position < m_file.MaxOffset) + { + if (refill_buffer) + { + int block_num = (int)(m_position / BlockLength); + m_current_block_position = m_position & ~((long)BlockLength-1); + m_current_block_length = m_file.View.Read (m_current_block_position, m_current_block, 0, (uint)BlockLength); + DecryptBlock (block_num); + } + int src_offset = (int)m_position & (BlockLength-1); + int available = Math.Min (count, m_current_block_length - src_offset); + Buffer.BlockCopy (m_current_block, src_offset, buf, index, available); + m_position += available; + total_read += available; + index += available; + count -= available; + refill_buffer = true; + } + return total_read; + } + + private void DecryptBlock (int block_num) + { + byte[] bn = new byte[8]; + LittleEndian.Pack (block_num, bn, 0); + + var md5_hash = MD5.ComputeHash (bn); + var sha1_hash = SHA1.ComputeHash (bn); + var hmac_key = new byte[16]; + for (int i = 0; i < 16; i++) + hmac_key[i] = (byte)(md5_hash[i] ^ sha1_hash[i]); + + var HMAC = new HMACSHA512 (hmac_key); + var hmac_hash = HMAC.ComputeHash (m_key); + + int[] map = Enumerable.Range (0, 256).ToArray(); + + byte index = 0; + int h = 0; + for (int i = 0; i < 256; i++) + { + if (hmac_hash.Length == h) + h = 0; + int tmp = map[i]; + index = (byte)(tmp + hmac_hash[h++] + index); + map[i] = map[index]; + map[index] = tmp; + } + + int i0 = 0, i1 = 0; + for (int i = 0; i < 300; i++) + { + i0 = (i0 + 1) & 0xFF; + int tmp = map[i0]; + i1 = (i1 + tmp) & 0xFF; + map[i0] = map[i1]; + map[i1] = tmp; + } + + for (int i = 0; i < m_current_block_length; i++) + { + i0 = (i0 + 1) & 0xFF; + int tmp = map[i0]; + i1 = (i1 + tmp) & 0xFF; + map[i0] = map[i1]; + map[i1] = tmp; + m_current_block[i] ^= (byte)map[(map[i0] + tmp) & 0xFF]; + } + } + } +} diff --git a/ArcFormats/NScripter/WidgetNSA.xaml b/ArcFormats/NScripter/WidgetNSA.xaml new file mode 100644 index 00000000..57cc8e85 --- /dev/null +++ b/ArcFormats/NScripter/WidgetNSA.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/ArcFormats/NScripter/WidgetNSA.xaml.cs b/ArcFormats/NScripter/WidgetNSA.xaml.cs new file mode 100644 index 00000000..e9c9a616 --- /dev/null +++ b/ArcFormats/NScripter/WidgetNSA.xaml.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Windows.Controls; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for NSAWidget.xaml + /// + public partial class WidgetNSA : Grid + { + public WidgetNSA () + { + InitializeComponent (); + this.Password.Text = Settings.Default.NSAPassword; + if (null != this.Title.SelectedItem) + { + var selected = (KeyValuePair)this.Title.SelectedItem; + if (Settings.Default.NSAPassword != selected.Value) + this.Title.SelectedIndex = -1; + } + } + + private void Title_SelectionChanged (object sender, SelectionChangedEventArgs e) + { + if (null != this.Title.SelectedItem && null != this.Password) + { + var selected = (KeyValuePair)this.Title.SelectedItem; + this.Password.Text = selected.Value; + } + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index 3809b2ec..4460365d 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.1.10.422")] -[assembly: AssemblyFileVersion ("1.1.10.422")] +[assembly: AssemblyVersion ("1.1.10.423")] +[assembly: AssemblyFileVersion ("1.1.10.423")] diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index ccb14470..fb71087f 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -133,9 +133,9 @@ namespace GameRes.Formats.Properties { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("None")] - public global::GameRes.Formats.ONScripter.Compression ONSCompression { + public global::GameRes.Formats.NScripter.Compression ONSCompression { get { - return ((global::GameRes.Formats.ONScripter.Compression)(this["ONSCompression"])); + return ((global::GameRes.Formats.NScripter.Compression)(this["ONSCompression"])); } set { this["ONSCompression"] = value; @@ -417,5 +417,29 @@ namespace GameRes.Formats.Properties { this["RCTTitle"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string NSAPassword { + get { + return ((string)(this["NSAPassword"])); + } + set { + this["NSAPassword"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string NSATitle { + get { + return ((string)(this["NSATitle"])); + } + set { + this["NSATitle"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 09608314..0f10e9af 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -29,7 +29,7 @@ shift-jis - + None @@ -101,5 +101,11 @@ + + + + + + - + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 7dbadb90..1c10853c 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -470,6 +470,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Choose title or enter a password. + /// + public static string NSAChoose { + get { + return ResourceManager.GetString("NSAChoose", resourceCulture); + } + } + /// /// Looks up a localized string similar to ONScripter game engine resource archive. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index 8d057a21..3b2e7205 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -331,4 +331,7 @@ Choose encryption scheme or enter a passphrase. Choose title or enter a password + + Choose title or enter a password + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index 885aa6d2..6e898324 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -233,6 +233,9 @@ Ключи шифрования (требуются даже если содержимое не шифруется) + + Выберите наименование или введите пароль + Тип архива diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 3182ffc0..8a384388 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -103,6 +103,12 @@ + + + + + + diff --git a/supported.html b/supported.html index 87e4d035..61066e3f 100644 --- a/supported.html +++ b/supported.html @@ -108,7 +108,7 @@ Zoku Satsuriku no Django Hanachirasu Jingai Makyou -*.nsa*.sar-YesNScripter +*.nsa*.sar-Yes1NScripter Binary Pot Tsukihime Umineko