diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index d81bb8cc..07455422 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -61,6 +61,7 @@ + @@ -72,6 +73,9 @@ CreateONSWidget.xaml + + CreatePDWidget.xaml + CreateSGWidget.xaml @@ -139,6 +143,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/ArcPD.cs b/ArcFormats/ArcPD.cs new file mode 100644 index 00000000..6ba90049 --- /dev/null +++ b/ArcFormats/ArcPD.cs @@ -0,0 +1,221 @@ +//! \file ArcPD.cs +//! \date Thu Aug 14 19:10:02 2014 +//! \brief PD archive format implementation. +// +// Copyright (C) 2014 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Text; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.Fs +{ + internal class PackPlusArchive : ArcFile + { + public PackPlusArchive (ArcView arc, ArchiveFormat impl, ICollection dir) + : base (arc, impl, dir) + { + } + } + + public class PdOptions : ResourceOptions + { + public bool ScrambleContents { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class PdOpener : ArchiveFormat + { + public override string Tag { get { return "PD"; } } + public override string Description { get { return arcStrings.PDDescription; } } + public override uint Signature { get { return 0x6b636150; } } // Pack + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return true; } } + + public override ArcFile TryOpen (ArcView file) + { + uint version = file.View.ReadUInt32 (4); + if (0x796c6e4f != version && 0x73756c50 != version) // 'Only' || 'Plus' + return null; + uint count = file.View.ReadUInt32 (0x40); + if (count > 0x0fffff || count * 0x90 >= file.MaxOffset) + return null; + bool encrypted = 0x73756c50 == version; + long cur_offset = 0x48; + var dir = new List ((int)count); + for (uint i = 0; i < count; ++i) + { + string name = file.View.ReadString (cur_offset, 0x80); + var entry = FormatCatalog.Instance.CreateEntry (name); + entry.Offset = file.View.ReadInt64 (cur_offset+0x80); + entry.Size = file.View.ReadUInt32 (cur_offset+0x88); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + if (Path.GetExtension (name).Equals (".dsf", System.StringComparison.OrdinalIgnoreCase)) + entry.Type = "script"; + dir.Add (entry); + cur_offset += 0x90; + } + return encrypted ? new PackPlusArchive (file, this, dir) : new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + if (arc is PackPlusArchive) + { + MemoryStream buffer; + using (input) + { + buffer = new MemoryStream ((int)entry.Size); + input.CopyTo (buffer); + } + var data = buffer.GetBuffer(); + for (uint i = 0; i < entry.Size; ++i) + data[i] = (byte)~data[i]; + buffer.Position = 0; + input = buffer; + } + return input; + } + + public override ResourceOptions GetDefaultOptions () + { + return new PdOptions { ScrambleContents = Settings.Default.PDScrambleContents }; + } + + public override object GetCreationWidget () + { + return new GUI.CreatePDWidget(); + } + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + int file_count = list.Count(); + if (file_count > 0x4000) + throw new InvalidFormatException (arcStrings.MsgTooManyFiles); + if (null != callback) + callback (file_count+2, null, null); + int callback_count = 0; + var pd_options = GetOptions (options); + + using (var writer = new BinaryWriter (output, Encoding.ASCII, true)) + { + writer.Write (Signature); + if (pd_options.ScrambleContents) + writer.Write ((uint)0x73756c50); + else + writer.Write ((uint)0x796c6e4f); + output.Seek (0x38, SeekOrigin.Current); + writer.Write (file_count); + writer.Write ((int)0); + long dir_offset = output.Position; + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + var encoding = Encodings.cp932.WithFatalFallback(); + byte[] name_buf = new byte[0x80]; + int previous_size = 0; + + // first, write names only + foreach (var entry in list) + { + string name = Path.GetFileName (entry.Name); + try + { + int size = encoding.GetBytes (name, 0, name.Length, name_buf, 0); + for (int i = size; i < previous_size; ++i) + name_buf[i] = 0; + previous_size = size; + } + catch (EncoderFallbackException X) + { + throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); + } + catch (ArgumentException X) + { + throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong, X); + } + writer.Write (name_buf); + writer.BaseStream.Seek (16, SeekOrigin.Current); + } + + // now, write files and remember offset/sizes + long current_offset = 0x240000 + dir_offset; + output.Seek (current_offset, SeekOrigin.Begin); + foreach (var entry in list) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + entry.Offset = current_offset; + using (var input = File.OpenRead (entry.Name)) + { + var size = input.Length; + if (size > uint.MaxValue) + throw new FileSizeException(); + current_offset += size; + entry.Size = (uint)size; + if (pd_options.ScrambleContents) + CopyScrambled (input, output); + else + input.CopyTo (output); + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgUpdatingIndex); + + // at last, go back to directory and write offset/sizes + dir_offset += 0x80; + foreach (var entry in list) + { + writer.BaseStream.Position = dir_offset; + writer.Write (entry.Offset); + writer.Write ((long)entry.Size); + dir_offset += 0x90; + } + } + } + + void CopyScrambled (Stream input, Stream output) + { + byte[] buffer = new byte[81920]; + for (;;) + { + int read = input.Read (buffer, 0, buffer.Length); + if (0 == read) + break; + for (int i = 0; i < read; ++i) + buffer[i] = (byte)~buffer[i]; + output.Write (buffer, 0, read); + } + } + } +} diff --git a/ArcFormats/CreatePDWidget.xaml b/ArcFormats/CreatePDWidget.xaml new file mode 100644 index 00000000..166fee2f --- /dev/null +++ b/ArcFormats/CreatePDWidget.xaml @@ -0,0 +1,8 @@ + + + diff --git a/ArcFormats/CreatePDWidget.xaml.cs b/ArcFormats/CreatePDWidget.xaml.cs new file mode 100644 index 00000000..512f53ed --- /dev/null +++ b/ArcFormats/CreatePDWidget.xaml.cs @@ -0,0 +1,15 @@ +using System.Windows.Controls; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for CreatePDWidget.xaml + /// + public partial class CreatePDWidget : Grid + { + public CreatePDWidget () + { + InitializeComponent (); + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index a5869627..4b59afd3 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -165,5 +165,17 @@ namespace GameRes.Formats.Properties { this["AMIUseBaseArchive"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool PDScrambleContents { + get { + return ((bool)(this["PDScrambleContents"])); + } + set { + this["PDScrambleContents"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 8b465116..be5bc78a 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -38,5 +38,8 @@ False + + False + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index e11f4f96..d1e0d0db 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -261,6 +261,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Number of files exceedes archive limit.. + /// + public static string MsgTooManyFiles { + get { + return ResourceManager.GetString("MsgTooManyFiles", resourceCulture); + } + } + /// /// Looks up a localized string similar to Updating index.... /// @@ -333,6 +342,24 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Flying Shine resource archive. + /// + public static string PDDescription { + get { + return ResourceManager.GetString("PDDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Scramble contents. + /// + public static string PDScrambleContents { + get { + return ResourceManager.GetString("PDScrambleContents", resourceCulture); + } + } + /// /// Looks up a localized string similar to Amaterasu Translations Muv-Luv script file. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index aa6ee1c0..f186a541 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -186,6 +186,9 @@ predefined encryption scheme. {0}: image format not recognized. + + Number of files exceedes archive limit. + Updating index... @@ -210,6 +213,12 @@ predefined encryption scheme. None + + Flying Shine resource archive + + + Scramble contents + Amaterasu Translations Muv-Luv script file diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index 99cdcfe5..b8dfe3cd 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -171,6 +171,9 @@ {0}: не удалось распознать формат изображения. + + Количество файлов превышает ограничения архива. + Обновляется оглавление... @@ -186,6 +189,9 @@ Без компрессии + + Шифровать содержимое + Кодировка имён файлов diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 5b0274a2..c03c64d8 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -11,7 +11,7 @@ NotEncrypted - + 4294967295 @@ -35,11 +35,14 @@ None - + False + + False +